diff --git a/app/kooka.cpp b/app/kooka.cpp index ac02990..75b0f13 100644 --- a/app/kooka.cpp +++ b/app/kooka.cpp @@ -1,629 +1,621 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2000-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "kooka.h" #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef KDE4 #include #endif #include "scanglobal.h" #include "imagecanvas.h" #include "previewer.h" #include "scangallery.h" #include "kookapref.h" #include "kookasettings.h" #include "kookaview.h" #include "imagetransform.h" Kooka::Kooka(const QByteArray &deviceToUse) : KXmlGuiWindow(nullptr), #ifndef KDE4 m_printer(0), #endif m_prefDialogIndex(0) { setObjectName("Kooka"); // Set up the status bar StatusBarManager *sbm = new StatusBarManager(this); /* Start to create the main view framework */ m_view = new KookaView(this, deviceToUse); setCentralWidget(m_view); setAcceptDrops(false); // Waba: Not (yet?) supported readProperties(KSharedConfig::openConfig()->group(autoSaveGroup())); // then, setup our actions setupActions(); setupGUI(KXmlGuiWindow::Default, "kookaui.rc"); setAutoSaveSettings(); // default group, do save // Allow the view to change the status bar and caption connect(m_view, SIGNAL(changeStatus(QString,StatusBarManager::Item)), sbm, SLOT(setStatus(QString,StatusBarManager::Item))); connect(m_view, SIGNAL(clearStatus(StatusBarManager::Item)), sbm, SLOT(clearStatus(StatusBarManager::Item))); connect(m_view, SIGNAL(signalChangeCaption(QString)), SLOT(setCaption(QString))); connect(m_view, SIGNAL(signalScannerChanged(bool)), SLOT(slotUpdateScannerActions(bool))); connect(m_view, SIGNAL(signalRectangleChanged(bool)), SLOT(slotUpdateRectangleActions(bool))); connect(m_view, SIGNAL(signalViewSelectionState(KookaView::StateFlags)), SLOT(slotUpdateViewActions(KookaView::StateFlags))); connect(m_view, SIGNAL(signalOcrResultAvailable(bool)), SLOT(slotUpdateOcrResultActions(bool))); connect(m_view, SIGNAL(signalOcrPrefs()), SLOT(optionsOcrPreferences())); connect(m_view->imageViewer(), SIGNAL(imageReadOnly(bool)), SLOT(slotUpdateReadOnlyActions(bool))); connect(m_view->previewer(), SIGNAL(autoSelectStateChanged(bool,bool)), SLOT(slotUpdateAutoSelectActions(bool,bool))); connect(m_view->previewer(), SIGNAL(previewFileSizeChanged(long)), sbm, SLOT(setFileSize(long))); setCaption(i18n("KDE Scanning")); slotUpdateScannerActions(m_view->isScannerConnected()); slotUpdateRectangleActions(false); slotUpdateViewActions(KookaView::GalleryShown | KookaView::IsDirectory | KookaView::RootSelected); slotUpdateOcrResultActions(false); slotUpdateReadOnlyActions(true); } Kooka::~Kooka() { m_view->closeScanDevice(); delete m_view; // ensure its config saved #ifndef KDE4 delete m_printer; #endif } void Kooka::startup() { if (m_view == nullptr) { return; } //qDebug(); m_view->gallery()->openRoots(); m_view->loadStartupImage(); } // TODO: use KStandardShortcut wherever available void Kooka::setupActions() { printImageAction = KStandardAction::print(this, SLOT(filePrint()), actionCollection()); KStandardAction::quit(this, SLOT(close()), actionCollection()); KStandardAction::preferences(this, SLOT(optionsPreferences()), actionCollection()); // Image Viewer - QSignalMapper *mapper = new QSignalMapper(this); - connect(mapper, SIGNAL(mapped(int)), m_view, SLOT(slotImageViewerAction(int))); - scaleToWidthAction = new QAction(QIcon::fromTheme("zoom-fit-width"), i18n("Scale to Width"), this); actionCollection()->addAction("scaleToWidth", scaleToWidthAction); actionCollection()->setDefaultShortcut(scaleToWidthAction, Qt::CTRL + Qt::Key_I); - connect(scaleToWidthAction, SIGNAL(triggered()), mapper, SLOT(map())); + connect(scaleToWidthAction, &QAction::triggered, [=]() { m_view->imageViewerAction(ImageCanvas::UserActionFitWidth); }); m_view->connectViewerAction(scaleToWidthAction); m_view->connectPreviewAction(scaleToWidthAction); - mapper->setMapping(scaleToWidthAction, ImageCanvas::UserActionFitWidth); scaleToHeightAction = new QAction(QIcon::fromTheme("zoom-fit-height"), i18n("Scale to Height"), this); actionCollection()->addAction("scaleToHeight", scaleToHeightAction); actionCollection()->setDefaultShortcut(scaleToHeightAction, Qt::CTRL + Qt::Key_H); - connect(scaleToHeightAction, SIGNAL(triggered()), mapper, SLOT(map())); + connect(scaleToHeightAction, &QAction::triggered, [=]() { m_view->imageViewerAction(ImageCanvas::UserActionFitHeight); }); m_view->connectViewerAction(scaleToHeightAction); m_view->connectPreviewAction(scaleToHeightAction); - mapper->setMapping(scaleToHeightAction, ImageCanvas::UserActionFitHeight); scaleToOriginalAction = new QAction(QIcon::fromTheme("zoom-original"), i18n("Original Size"), this); actionCollection()->addAction("scaleOriginal", scaleToOriginalAction); actionCollection()->setDefaultShortcut(scaleToOriginalAction, Qt::CTRL + Qt::Key_1); - connect(scaleToOriginalAction, SIGNAL(triggered()), mapper, SLOT(map())); + connect(scaleToOriginalAction, &QAction::triggered, [=]() { m_view->imageViewerAction(ImageCanvas::UserActionOrigSize); }); m_view->connectViewerAction(scaleToOriginalAction); - mapper->setMapping(scaleToOriginalAction, ImageCanvas::UserActionOrigSize); scaleToZoomAction = new QAction(QIcon::fromTheme("page-zoom"), i18n("Set Zoom..."), this); actionCollection()->addAction("showZoomDialog", scaleToZoomAction); // No shortcut. There wasn't a standard shortcut for "Zoom" in KDE4, // and there is no KStandardShortcut::Zoom in KF5. - connect(scaleToZoomAction, SIGNAL(triggered()), mapper, SLOT(map())); + connect(scaleToZoomAction, &QAction::triggered, [=]() { m_view->imageViewerAction(ImageCanvas::UserActionZoom); }); m_view->connectViewerAction(scaleToZoomAction); - mapper->setMapping(scaleToZoomAction, ImageCanvas::UserActionZoom); keepZoomAction = new KToggleAction(QIcon::fromTheme("lockzoom"), i18n("Keep Zoom Setting"), this); actionCollection()->addAction("keepZoom", keepZoomAction); actionCollection()->setDefaultShortcut(keepZoomAction, Qt::CTRL + Qt::Key_Z); - connect(keepZoomAction, SIGNAL(toggled(bool)), m_view->imageViewer(), SLOT(setKeepZoom(bool))); + connect(keepZoomAction, &KToggleAction::toggled, m_view->imageViewer(), &ImageCanvas::setKeepZoom); m_view->connectViewerAction(keepZoomAction); // Thumb view and gallery actions newFromSelectionAction = new QAction(QIcon::fromTheme("transform-crop"), i18n("New Image From Selection"), this); actionCollection()->addAction("createFromSelection", newFromSelectionAction); actionCollection()->setDefaultShortcut(newFromSelectionAction, Qt::CTRL + Qt::Key_N); connect(newFromSelectionAction, SIGNAL(triggered()), m_view, SLOT(slotCreateNewImgFromSelection())); mirrorVerticallyAction = new QAction(QIcon::fromTheme("object-flip-vertical"), i18n("Mirror Vertically"), this); mirrorVerticallyAction->setData(ImageTransform::MirrorVertical); actionCollection()->addAction("mirrorVertical", mirrorVerticallyAction); actionCollection()->setDefaultShortcut(mirrorVerticallyAction, Qt::CTRL + Qt::Key_V); connect(mirrorVerticallyAction, SIGNAL(triggered()), m_view, SLOT(slotTransformImage())); m_view->connectViewerAction(mirrorVerticallyAction, true); mirrorHorizontallyAction = new QAction(QIcon::fromTheme("object-flip-horizontal"), i18n("Mirror Horizontally"), this); mirrorHorizontallyAction->setData(ImageTransform::MirrorHorizontal); actionCollection()->addAction("mirrorHorizontal", mirrorHorizontallyAction); actionCollection()->setDefaultShortcut(mirrorHorizontallyAction, Qt::CTRL + Qt::Key_M); connect(mirrorHorizontallyAction, SIGNAL(triggered()), m_view, SLOT(slotTransformImage())); m_view->connectViewerAction(mirrorHorizontallyAction); // Standard KDE has icons for 'object-rotate-right' and 'object-rotate-left', // but not for rotate by 180 degrees. The 3 used here are copies of the 22x22 // icons from the old kdeclassic theme. rotateAcwAction = new QAction(QIcon::fromTheme("rotate-acw"), i18n("Rotate Counter-Clockwise"), this); rotateAcwAction->setData(ImageTransform::Rotate270); actionCollection()->addAction("rotateCounterClockwise", rotateAcwAction); actionCollection()->setDefaultShortcut(rotateAcwAction, Qt::CTRL + Qt::Key_7); connect(rotateAcwAction, SIGNAL(triggered()), m_view, SLOT(slotTransformImage())); m_view->connectViewerAction(rotateAcwAction, true); rotateCwAction = new QAction(QIcon::fromTheme("rotate-cw"), i18n("Rotate Clockwise"), this); rotateCwAction->setData(ImageTransform::Rotate90); actionCollection()->addAction("rotateClockwise", rotateCwAction); actionCollection()->setDefaultShortcut(rotateCwAction, Qt::CTRL + Qt::Key_9); connect(rotateCwAction, SIGNAL(triggered()), m_view, SLOT(slotTransformImage())); m_view->connectViewerAction(rotateCwAction); rotate180Action = new QAction(QIcon::fromTheme("rotate-180"), i18n("Rotate 180 Degrees"), this); rotate180Action->setData(ImageTransform::Rotate180); actionCollection()->addAction("upsitedown", rotate180Action); actionCollection()->setDefaultShortcut(rotate180Action, Qt::CTRL + Qt::Key_8); connect(rotate180Action, SIGNAL(triggered()), m_view, SLOT(slotTransformImage())); m_view->connectViewerAction(rotate180Action); // Gallery actions createFolderAction = new QAction(QIcon::fromTheme("folder-new"), i18n("New Folder..."), this); actionCollection()->addAction("foldernew", createFolderAction); connect(createFolderAction, SIGNAL(triggered()), m_view->gallery(), SLOT(slotCreateFolder())); m_view->connectGalleryAction(createFolderAction); openWithMenu = new KActionMenu(QIcon::fromTheme("document-open"), i18n("Open With"), this); actionCollection()->addAction("openWidth", openWithMenu); connect(openWithMenu->menu(), SIGNAL(aboutToShow()), SLOT(slotOpenWithMenu())); m_view->connectGalleryAction(openWithMenu, true); m_view->connectThumbnailAction(openWithMenu); saveImageAction = new QAction(QIcon::fromTheme("document-save"), i18n("Save Image..."), this); actionCollection()->addAction("saveImage", saveImageAction); actionCollection()->setDefaultShortcuts(saveImageAction, KStandardShortcut::save()); connect(saveImageAction, SIGNAL(triggered()), m_view->gallery(), SLOT(slotExportFile())); m_view->connectGalleryAction(saveImageAction); m_view->connectThumbnailAction(saveImageAction); importImageAction = new QAction(QIcon::fromTheme("document-import"), i18n("Import Image..."), this); actionCollection()->addAction("importImage", importImageAction); connect(importImageAction, SIGNAL(triggered()), m_view->gallery(), SLOT(slotImportFile())); m_view->connectGalleryAction(importImageAction); deleteImageAction = new QAction(QIcon::fromTheme("edit-delete"), i18n("Delete Image"), this); actionCollection()->addAction("deleteImage", deleteImageAction); actionCollection()->setDefaultShortcut(deleteImageAction, Qt::SHIFT + Qt::Key_Delete); connect(deleteImageAction, SIGNAL(triggered()), m_view->gallery(), SLOT(slotDeleteItems())); m_view->connectGalleryAction(deleteImageAction); m_view->connectThumbnailAction(deleteImageAction); renameImageAction = new QAction(QIcon::fromTheme("edit-rename"), i18n("Rename Image"), this); actionCollection()->addAction("renameImage", renameImageAction); actionCollection()->setDefaultShortcut(renameImageAction, Qt::Key_F2); connect(renameImageAction, SIGNAL(triggered()), m_view->gallery(), SLOT(slotRenameItems())); m_view->connectGalleryAction(renameImageAction); unloadImageAction = new QAction(QIcon::fromTheme("document-close"), i18n("Unload Image"), this); actionCollection()->addAction("unloadImage", unloadImageAction); actionCollection()->setDefaultShortcut(unloadImageAction, Qt::CTRL + Qt::SHIFT + Qt::Key_U); connect(unloadImageAction, SIGNAL(triggered()), m_view->gallery(), SLOT(slotUnloadItems())); m_view->connectGalleryAction(unloadImageAction); m_view->connectThumbnailAction(unloadImageAction); propsImageAction = new QAction(QIcon::fromTheme("document-properties"), i18n("Properties..."), this); actionCollection()->addAction("propsImage", propsImageAction); actionCollection()->setDefaultShortcut(propsImageAction, Qt::ALT + Qt::Key_Return); connect(propsImageAction, SIGNAL(triggered()), m_view->gallery(), SLOT(slotItemProperties())); m_view->connectGalleryAction(propsImageAction, true); m_view->connectThumbnailAction(propsImageAction); // "Settings" menu selectDeviceAction = new QAction(QIcon::fromTheme("scanselect"), i18n("Select Scan Device..."), this); actionCollection()->addAction("selectsource", selectDeviceAction); connect(selectDeviceAction, SIGNAL(triggered()), m_view, SLOT(slotSelectDevice())); addDeviceAction = new QAction(QIcon::fromTheme("scanadd"), i18n("Add Scan Device..."), this); actionCollection()->addAction("addsource", addDeviceAction); connect(addDeviceAction, SIGNAL(triggered()), m_view, SLOT(slotAddDevice())); // Scanning functions previewAction = new QAction(QIcon::fromTheme("preview"), i18n("Preview"), this); actionCollection()->addAction("startPreview", previewAction); actionCollection()->setDefaultShortcut(previewAction, Qt::Key_F3); connect(previewAction, SIGNAL(triggered()), m_view, SLOT(slotStartPreview())); scanAction = new QAction(QIcon::fromTheme("scan"), i18n("Start Scan"), this); actionCollection()->addAction("startScan", scanAction); actionCollection()->setDefaultShortcut(scanAction, Qt::Key_F4); connect(scanAction, SIGNAL(triggered()), m_view, SLOT(slotStartFinalScan())); photocopyAction = new QAction(QIcon::fromTheme("photocopy"), i18n("Photocopy..."), this); actionCollection()->addAction("startPhotoCopy", photocopyAction); actionCollection()->setDefaultShortcut(photocopyAction, Qt::CTRL + Qt::Key_F); connect(photocopyAction, SIGNAL(triggered()), m_view, SLOT(slotStartPhotoCopy())); paramsAction = new QAction(QIcon::fromTheme("bookmark-new"), i18n("Scan Parameters..."), this); actionCollection()->addAction("scanparam", paramsAction); actionCollection()->setDefaultShortcut(paramsAction, Qt::CTRL + Qt::SHIFT + Qt::Key_S); connect(paramsAction, SIGNAL(triggered()), m_view, SLOT(slotScanParams())); autoselAction = new KToggleAction(QIcon::fromTheme("autoselect"), i18n("Auto Select"), this); actionCollection()->addAction("autoselect", autoselAction); actionCollection()->setDefaultShortcut(autoselAction, Qt::CTRL + Qt::Key_A); connect(autoselAction, SIGNAL(toggled(bool)), m_view, SLOT(slotAutoSelect(bool))); m_view->connectPreviewAction(autoselAction); // OCR functions ocrAction = new QAction(QIcon::fromTheme("ocr"), i18n("OCR Image..."), this); actionCollection()->addAction("ocrImage", ocrAction); connect(ocrAction, SIGNAL(triggered()), m_view, SLOT(slotStartOcr())); ocrSelectAction = new QAction(QIcon::fromTheme("ocr-select"), i18n("OCR Selection..."), this); actionCollection()->addAction("ocrImageSelect", ocrSelectAction); connect(ocrSelectAction, SIGNAL(triggered()), m_view, SLOT(slotStartOcrSelection())); m_saveOCRTextAction = new QAction(QIcon::fromTheme("document-save-as"), i18n("Save OCR Result Text..."), this); actionCollection()->addAction("saveOCRResult", m_saveOCRTextAction); actionCollection()->setDefaultShortcut(m_saveOCRTextAction, Qt::CTRL + Qt::Key_U); connect(m_saveOCRTextAction, SIGNAL(triggered()), m_view, SLOT(slotSaveOcrResult())); ocrSpellAction = new QAction(QIcon::fromTheme("tools-check-spelling"), i18n("Spell Check OCR Result..."), this); actionCollection()->addAction("ocrSpellCheck", ocrSpellAction); connect(ocrSpellAction, SIGNAL(triggered()), m_view, SLOT(slotOcrSpellCheck())); } void Kooka::closeEvent(QCloseEvent *ev) { KConfigGroup grp = KSharedConfig::openConfig()->group(autoSaveGroup()); saveProperties(grp); if (autoSaveSettings()) { saveAutoSaveSettings(); m_view->saveWindowSettings(grp); } } void Kooka::saveProperties(KConfigGroup &grp) { //qDebug() << "to group" << grp.name(); // The group object points to the session managed // config file. Anything you write here will be available // later when this app is restored. KookaSettings::setPreferencesTab(m_prefDialogIndex); //FIXME crash KookaSettings::setStartupSelectedImage(m_view->gallery()->currentImageFileName()); KookaSettings::self()->save(); } void Kooka::applyMainWindowSettings(const KConfigGroup &grp) { //qDebug() << "from group" << grp.name(); KXmlGuiWindow::applyMainWindowSettings(grp); m_view->applyWindowSettings(grp); } void Kooka::readProperties(const KConfigGroup &grp) { //qDebug() << "from group" << grp.name(); // The group object points to the session managed // config file. This function is automatically called whenever // the app is being restored. Read in here whatever you wrote // in 'saveProperties'. m_prefDialogIndex = KookaSettings::preferencesTab(); } void Kooka::dragEnterEvent(QDragEnterEvent *ev) { if (ev->mimeData()->hasUrls()) ev->accept(); // accept URI drops only } void Kooka::filePrint() { // this slot is called whenever the File->Print menu is selected, // the Print shortcut is pressed (usually CTRL+P) or the Print toolbar // button is clicked m_view->print(); } void Kooka::optionsPreferences() { KookaPref dlg; dlg.showPageIndex(m_prefDialogIndex); connect(&dlg, SIGNAL(dataSaved()), m_view, SLOT(slotApplySettings())); if (dlg.exec()) { m_prefDialogIndex = dlg.currentPageIndex(); } } void Kooka::optionsOcrPreferences() { m_prefDialogIndex = 4; // go to OCR page optionsPreferences(); } void Kooka::slotUpdateScannerActions(bool haveConnection) { //qDebug() << "haveConnection" << haveConnection; scanAction->setEnabled(haveConnection); previewAction->setEnabled(haveConnection); photocopyAction->setEnabled(haveConnection); paramsAction->setEnabled(haveConnection); if (!ScanGlobal::self()->available()) { selectDeviceAction->setEnabled(false); addDeviceAction->setEnabled(false); } setCaption(m_view->scannerName()); } void Kooka::slotUpdateRectangleActions(bool haveSelection) { //qDebug() << "haveSelection" << haveSelection; ocrSelectAction->setEnabled(haveSelection); newFromSelectionAction->setEnabled(haveSelection); } // This is the most complex action update, depending on the operation // mode (i.e. the tab selected), the gallery selection, and whether the // (two) image viewers have an image loaded. The actions controlled // here and the conditions for enabling them are: // // +========================+==================+======================+ // | Action/Operation | Scan mode | Gallery/OCR mode | // +========================+==================+======================+ // | scale width/height | preview valid | image loaded | // +------------------------+------------------+----------------------+ // | (!GalleryShown && PreviewValid) || (GalleryShown && ImageValid) | // +========================+==================+======================+ // | scale original/zoom | no | image loaded | // +------------------------+------------------+----------------------+ // | GalleryShown && ImageValid | // +========================+==================+======================+ // | keep zoom | no | yes | // +------------------------+------------------+----------------------+ // | GalleryShown | // +========================+==================+======================+ // | mirror/rotate | no | 1 image | // +------------------------+------------------+----------------------+ // | GalleryShown && FileSelected && !IsSubImage && !HasSubImages | // +========================+==================+======================+ // | create folder | directory | directory | // +------------------------+------------------+----------------------+ // | IsDirectory | // +========================+==================+======================+ // | import | no | directory | // +------------------------+------------------+----------------------+ // | GalleryShown && IsDirectory | // +========================+==================+======================+ // | OCR/print | 1 | 1 | // +------------------------+------------------+----------------------+ // | FileSelected && !HasSubImages | // +========================+==================+======================+ // | Save | 1 | 1 | // +------------------------+------------------+----------------------+ // | FileSelected && !IsSubImage | // +========================+==================+======================+ // | unload (dir) | yes | yes | // | unload (not dir) | image loaded | image loaded | // +------------------------+------------------+----------------------+ // | IsDirectory | HasSubImages | | // | ((FileSelected | ManySelected) & ImageValid) | // +========================+==================+======================+ // | delete (dir) | 1 not root | 1 not root | // | delete (not dir) | >1 | >1 | // +------------------------+------------------+----------------------+ // | (IsDirectory & !RootSelected) | ((FileSelected | ManySelected) | // | & !IsSubImage | // +========================+==================+======================+ // | rename (dir) | not root | not root | // | rename (not dir) | 1 image | 1 image | // +------------------------+------------------+----------------------+ // | (IsDirectory & !RootSelected) | (FileSelected & !IsSubImage) | // +========================+==================+======================+ // | props (dir) | yes | yes | // | props (not dir) | 1 image | 1 image | // +------------------------+------------------+----------------------+ // | IsDirectory | FileSelected | // +========================+==================+======================+ // | Open with | yes | not subimage | // +------------------------+------------------+----------------------+ // | !IsSubImage | // +==================================================================+ // // It is assumed that only one directory may be selected at a time in // the gallery, but multiple files/images may be. Currently, though, // the Kooka gallery allows single selection only. // // The source of the 'state' is KookaView::updateSelectionState(). void Kooka::slotUpdateViewActions(KookaView::StateFlags state) { //qDebug() << "state" << hex << state; bool e; e = (!(state & KookaView::GalleryShown) && (state & KookaView::PreviewValid)) || ((state & KookaView::GalleryShown) && (state & KookaView::ImageValid)); scaleToWidthAction->setEnabled(e); scaleToHeightAction->setEnabled(e); e = (state & KookaView::GalleryShown) && (state & KookaView::ImageValid); scaleToOriginalAction->setEnabled(e); scaleToZoomAction->setEnabled(e); e = (state & KookaView::GalleryShown); keepZoomAction->setEnabled(e); e = (state & KookaView::GalleryShown) && (state & KookaView::FileSelected) && !(state & (KookaView::IsSubImage|KookaView::HasSubImages)); m_imageChangeAllowed = e; mirrorVerticallyAction->setEnabled(e); mirrorHorizontallyAction->setEnabled(e); rotateCwAction->setEnabled(e); rotateAcwAction->setEnabled(e); rotate180Action->setEnabled(e); e = (state & KookaView::IsDirectory); createFolderAction->setEnabled(e); e = (state & KookaView::GalleryShown) && (state & KookaView::IsDirectory); importImageAction->setEnabled(e); e = (state & KookaView::FileSelected) && !(state & KookaView::HasSubImages); ocrAction->setEnabled(e); printImageAction->setEnabled(e); // TODO: allow this for sub-images (page extraction) e = (state & KookaView::FileSelected) && !(state & KookaView::IsSubImage); saveImageAction->setEnabled(e); if (state & KookaView::IsDirectory) { unloadImageAction->setText(i18n("Unload Folder")); deleteImageAction->setText(i18n("Delete Folder")); renameImageAction->setText(i18n("Rename Folder")); unloadImageAction->setEnabled(true); e = !(state & KookaView::RootSelected); deleteImageAction->setEnabled(e); renameImageAction->setEnabled(e); propsImageAction->setEnabled(true); } else { unloadImageAction->setText(i18n("Unload Image")); deleteImageAction->setText(i18n("Delete Image")); renameImageAction->setText(i18n("Rename Image")); e = ((state & (KookaView::FileSelected | KookaView::ManySelected)) && (state & KookaView::ImageValid)) || (state & KookaView::HasSubImages); unloadImageAction->setEnabled(e); e = (state & (KookaView::FileSelected | KookaView::ManySelected)) && !(state & KookaView::IsSubImage); deleteImageAction->setEnabled(e); e = (state & KookaView::FileSelected) && !(state & KookaView::IsSubImage); renameImageAction->setEnabled(e); propsImageAction->setEnabled(e); } // TODO: allow this for sub-images openWithMenu->setEnabled(!(state & KookaView::IsSubImage)); if (!(state & (KookaView::IsDirectory | KookaView::FileSelected | KookaView::ManySelected))) { slotUpdateRectangleActions(false); } } void Kooka::slotUpdateOcrResultActions(bool haveText) { //qDebug() << "haveText" << haveText; m_saveOCRTextAction->setEnabled(haveText); ocrSpellAction->setEnabled(haveText); } void Kooka::slotUpdateReadOnlyActions(bool ro) { bool enable = (m_imageChangeAllowed && !ro); // also check gallery state mirrorVerticallyAction->setEnabled(enable); mirrorHorizontallyAction->setEnabled(enable); rotateCwAction->setEnabled(enable); rotateAcwAction->setEnabled(enable); rotate180Action->setEnabled(enable); } void Kooka::slotUpdateAutoSelectActions(bool isAvailable, bool isOn) { //qDebug() << "available" << isAvailable << "on" << isOn; autoselAction->setEnabled(isAvailable); autoselAction->setChecked(isOn); } void Kooka::slotOpenWithMenu() { m_view->showOpenWithMenu(openWithMenu); } diff --git a/app/kookaview.cpp b/app/kookaview.cpp index 67fb2a0..6a006b5 100644 --- a/app/kookaview.cpp +++ b/app/kookaview.cpp @@ -1,1263 +1,1255 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 1999-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "kookaview.h" #include #include #include #include #include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "scandevices.h" #include "kscandevice.h" #include "imagemetainfo.h" #include "deviceselector.h" #include "adddevicedialog.h" #include "previewer.h" #include "imagecanvas.h" #include "kookapref.h" #include "galleryhistory.h" #include "thumbview.h" #include "kookaimage.h" #include "abstractocrengine.h" #include "pluginmanager.h" #include "ocrresedit.h" #include "scanparamsdialog.h" #include "kookagallery.h" #include "kookascanparams.h" #include "scangallery.h" #include "imagetransform.h" #include "statusbarmanager.h" #include "kookasettings.h" #include "imgprintdialog.h" #include "kookaprint.h" #ifndef KDE4 #include "photocopyprintdialogpage.h" #endif // --------------------------------------------------------------------------- // Some of the UI panels (the gallery and the image viewer) are common // to more that one of the main task tabs. This means that they can't simply // be added to the tabs/splitters in the normal way, as a widget can only be // a child of one parent at a time. // // This WidgetSite acts as a layout placeholder for such reassignable widgets. // It is assigned a new child widget when tabs are switched. // // It also defines the frame style for the panels. So, in order to maintain a // consistent look, all of those panels should derive from QWidget (or, if // from QFrame, do not set a frame style) and should set the margin of any // internal layout to 0. (KHBox/KVBox do this automatically, but any other // layout needs the margin explicitly set). class WidgetSite : public QFrame { public: WidgetSite(QWidget *parent, QWidget *widget = nullptr); void setWidget(QWidget *widget); private: static int sCount; }; int WidgetSite::sCount = 0; WidgetSite::WidgetSite(QWidget *parent, QWidget *widget) : QFrame(parent) { QString name = QString("WidgetSite-#%1").arg(++sCount); setObjectName(name.toLocal8Bit()); setFrameStyle(QFrame::Panel | QFrame::Raised); // from "scanparams.cpp" setLineWidth(1); QGridLayout *lay = new QGridLayout(this); lay->setRowStretch(0, 1); lay->setColumnStretch(0, 1); if (widget == nullptr) { QLabel *l = new QLabel(name, this); l->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); widget = l; } ////qDebug() << name // << "widget is a" << widget->metaObject()->className() // << "parent is a" << widget->parent()->metaObject()->className(); lay->addWidget(widget, 0, 0); widget->show(); } void WidgetSite::setWidget(QWidget *widget) { QGridLayout *lay = static_cast(layout()); QObjectList childs = children(); for (QObjectList::iterator it = childs.begin(); it != childs.end(); ++it) { QObject *ch = (*it); if (ch->isWidgetType()) { QWidget *w = static_cast(ch); w->hide(); lay->removeWidget(w); } } ////qDebug() << objectName() // << "widget is a" << widget->metaObject()->className() // << "parent is a" << widget->parent()->metaObject()->className(); lay->addWidget(widget, 0, 0); widget->show(); } // --------------------------------------------------------------------------- // Convenience class for layout splitter. class WidgetSplitter : public QSplitter { public: WidgetSplitter(Qt::Orientation orientation, QWidget *parent = nullptr); }; WidgetSplitter::WidgetSplitter(Qt::Orientation orientation, QWidget *parent) : QSplitter(orientation, parent) { setChildrenCollapsible(false); setContentsMargins(0, 0, 0, 0); setStretchFactor(1, 1); } // --------------------------------------------------------------------------- KookaView::KookaView(KMainWindow *parent, const QByteArray &deviceToUse) : QTabWidget(parent) { setObjectName("KookaView"); mMainWindow = parent; mOcrResultImg = nullptr; mScanParams = nullptr; // mOcrEngine = nullptr; mCurrentTab = KookaView::TabNone; mIsPhotoCopyMode = false; - mOpenWithMapper = nullptr; - /** Image Viewer **/ mImageCanvas = new ImageCanvas(this); mImageCanvas->setMinimumSize(100, 200); QMenu *ctxtmenu = mImageCanvas->contextMenu(); if (ctxtmenu != nullptr) { ctxtmenu->addSection(i18n("Image View")); } // Connections ImageCanvas --> myself connect(mImageCanvas, SIGNAL(newRect(QRect)), SLOT(slotSelectionChanged(QRect))); /** Thumbnail View **/ mThumbView = new ThumbView(this); /** Scan Gallery **/ mGallery = new KookaGallery(this); ScanGallery *packager = mGallery->galleryTree(); // Connections ScanGallery --> myself connect(packager, SIGNAL(itemHighlighted(QUrl,bool)), SLOT(slotGallerySelectionChanged())); connect(packager, SIGNAL(showImage(const KookaImage*,bool)), SLOT(slotShowAImage(const KookaImage*,bool))); connect(packager, SIGNAL(aboutToShowImage(QUrl)), SLOT(slotStartLoading(QUrl))); connect(packager, SIGNAL(unloadImage(const KookaImage*)), SLOT(slotUnloadAImage(const KookaImage*))); // Connections ScanGallery --> ThumbView connect(packager, SIGNAL(itemHighlighted(QUrl,bool)), mThumbView, SLOT(slotHighlightItem(QUrl,bool))); connect(packager, SIGNAL(imageChanged(const KFileItem*)), mThumbView, SLOT(slotImageChanged(const KFileItem*))); connect(packager, SIGNAL(fileRenamed(const KFileItem*,QString)), mThumbView, SLOT(slotImageRenamed(const KFileItem*,QString))); // Connections ThumbView --> ScanGallery connect(mThumbView, SIGNAL(itemHighlighted(QUrl)), packager, SLOT(slotHighlightItem(QUrl))); connect(mThumbView, SIGNAL(itemActivated(QUrl)), packager, SLOT(slotActivateItem(QUrl))); GalleryHistory *recentFolder = mGallery->galleryRecent(); // Connections ScanGallery <--> recent folder history connect(packager, SIGNAL(galleryPathChanged(const FileTreeBranch*,QString)), recentFolder, SLOT(slotPathChanged(const FileTreeBranch*,QString))); connect(packager, SIGNAL(galleryDirectoryRemoved(const FileTreeBranch*,QString)), recentFolder, SLOT(slotPathRemoved(const FileTreeBranch*,QString))); connect(recentFolder, SIGNAL(pathSelected(QString,QString)), packager, SLOT(slotSelectDirectory(QString,QString))); /** Scanner Settings **/ if (!deviceToUse.isEmpty() && deviceToUse != "gallery") { ScanDevices::self()->addUserSpecifiedDevice(deviceToUse, "on command line", "", true); } /** Scanner Device **/ mScanDevice = new KScanDevice(this); // Connections KScanDevice --> myself connect(mScanDevice, SIGNAL(sigScanFinished(KScanDevice::Status)), SLOT(slotScanFinished(KScanDevice::Status))); connect(mScanDevice, SIGNAL(sigScanFinished(KScanDevice::Status)), SLOT(slotPhotoCopyScan(KScanDevice::Status))); /* New image created after scanning now call save dialog*/ connect(mScanDevice, SIGNAL(sigNewImage(const QImage*,const ImageMetaInfo*)), SLOT(slotNewImageScanned(const QImage*,const ImageMetaInfo*))); /* New image created after scanning now print it*/ connect(mScanDevice, SIGNAL(sigNewImage(const QImage*,const ImageMetaInfo*)), SLOT(slotPhotoCopyPrint(const QImage*,const ImageMetaInfo*))); /* New preview image */ connect(mScanDevice, SIGNAL(sigNewPreview(const QImage*,const ImageMetaInfo*)), SLOT(slotNewPreview(const QImage*,const ImageMetaInfo*))); connect(mScanDevice, SIGNAL(sigScanStart(const ImageMetaInfo*)), SLOT(slotScanStart(const ImageMetaInfo*))); connect(mScanDevice, SIGNAL(sigAcquireStart()), SLOT(slotAcquireStart())); /** Scan Preview **/ mPreviewCanvas = new Previewer(this); mPreviewCanvas->setMinimumSize(100, 100); ctxtmenu = mPreviewCanvas->getImageCanvas()->contextMenu(); if (ctxtmenu != nullptr) { ctxtmenu->addSection(i18n("Scan Preview")); } // Connections Previewer --> myself connect(mPreviewCanvas, SIGNAL(previewDimsChanged(QString)), SLOT(slotPreviewDimsChanged(QString))); /** Ocr Result Text **/ mOcrResEdit = new OcrResEdit(this); mOcrResEdit->setAcceptRichText(false); mOcrResEdit->setWordWrapMode(QTextOption::NoWrap); mOcrResEdit->setPlaceholderText(i18n("OCR results will appear here")); connect(mOcrResEdit, SIGNAL(spellCheckStatus(QString)), this, SIGNAL(changeStatus(QString))); /** Tabs **/ // TODO: not sure which tab position is best, make this configurable setTabPosition(QTabWidget::West); setTabsClosable(false); mScanPage = new WidgetSplitter(Qt::Horizontal, this); addTab(mScanPage, QIcon::fromTheme("scan"), i18n("Scan")); mGalleryPage = new WidgetSplitter(Qt::Horizontal, this); addTab(mGalleryPage, QIcon::fromTheme("image-x-generic"), i18n("Gallery")); mOcrPage = new WidgetSplitter(Qt::Vertical, this); addTab(mOcrPage, QIcon::fromTheme("ocr"), i18n("OCR")); connect(this, SIGNAL(currentChanged(int)), SLOT(slotTabChanged(int))); // TODO: make the splitter layouts and sizes configurable mParamsSite = new WidgetSite(this); mScanGallerySite = new WidgetSite(this); mGalleryGallerySite = new WidgetSite(this); mOcrGallerySite = new WidgetSite(this); mGalleryImgviewSite = new WidgetSite(this); mOcrImgviewSite = new WidgetSite(this); // Even widgets that are not shared between tabs are placed in a WidgetSite, // to keep a consistent frame appearance. // "Scan" page: gallery top left, scan parameters bottom left, preview right mScanSubSplitter = new WidgetSplitter(Qt::Vertical, mScanPage); mScanSubSplitter->addWidget(mScanGallerySite); // TL mScanSubSplitter->addWidget(mParamsSite); // BL mScanPage->addWidget(new WidgetSite(this, mPreviewCanvas)); // R // "Gallery" page: gallery left, viewer top right, thumbnails bottom right mGalleryPage->addWidget(mGalleryGallerySite); // L mGallerySubSplitter = new WidgetSplitter(Qt::Vertical, mGalleryPage); mGallerySubSplitter->addWidget(mGalleryImgviewSite); // TR mGallerySubSplitter->addWidget(new WidgetSite(this, mThumbView)); // BR // "OCR" page: gallery top left, viewer top right, results bottom mOcrSubSplitter = new WidgetSplitter(Qt::Horizontal, mOcrPage); mOcrSubSplitter->addWidget(mOcrGallerySite); // TL mOcrSubSplitter->addWidget(mOcrImgviewSite); // TR mOcrPage->addWidget(new WidgetSite(this, mOcrResEdit)); // B if (slotSelectDevice(deviceToUse, false)) { slotTabChanged(KookaView::TabScan); } else { setCurrentIndex(KookaView::TabGallery); } } KookaView::~KookaView() { delete mScanDevice; //qDebug(); } // this gets called via Kooka::closeEvent() at shutdown void KookaView::saveWindowSettings(KConfigGroup &grp) { qDebug() << "to group" << grp.name(); KookaSettings::setLayoutTabIndex(currentIndex()); KookaSettings::setLayoutScan1(mScanPage->saveState().toBase64()); KookaSettings::setLayoutScan2(mScanSubSplitter->saveState().toBase64()); KookaSettings::setLayoutGallery1(mGalleryPage->saveState().toBase64()); KookaSettings::setLayoutGallery2(mGallerySubSplitter->saveState().toBase64()); KookaSettings::setLayoutOcr1(mOcrPage->saveState().toBase64()); KookaSettings::setLayoutOcr2(mOcrSubSplitter->saveState().toBase64()); saveGalleryState(); // for the current tab KookaSettings::self()->save(); } // this gets called by Kooka::applyMainWindowSettings() at startup void KookaView::applyWindowSettings(const KConfigGroup &grp) { qDebug() << "from group" << grp.name(); QString set = KookaSettings::layoutScan1(); if (!set.isEmpty()) mScanPage->restoreState(QByteArray::fromBase64(set.toLocal8Bit())); set = KookaSettings::layoutScan2(); if (!set.isEmpty()) mScanSubSplitter->restoreState(QByteArray::fromBase64(set.toLocal8Bit())); set = KookaSettings::layoutGallery1(); if (!set.isEmpty()) mGalleryPage->restoreState(QByteArray::fromBase64(set.toLocal8Bit())); set = KookaSettings::layoutGallery2(); if (!set.isEmpty()) mGallerySubSplitter->restoreState(QByteArray::fromBase64(set.toLocal8Bit())); set = KookaSettings::layoutOcr1(); if (!set.isEmpty()) mOcrPage->restoreState(QByteArray::fromBase64(set.toLocal8Bit())); set = KookaSettings::layoutOcr2(); if (!set.isEmpty()) mOcrSubSplitter->restoreState(QByteArray::fromBase64(set.toLocal8Bit())); } void KookaView::saveGalleryState(int index) const { if (index == -1) index = currentIndex(); gallery()->saveHeaderState(index); } void KookaView::restoreGalleryState(int index) { if (index == -1) index = currentIndex(); gallery()->restoreHeaderState(index); } void KookaView::slotTabChanged(int index) { //qDebug() << index; if (mCurrentTab != KookaView::TabNone) { saveGalleryState(mCurrentTab); } // save state of previous tab switch (index) { case KookaView::TabScan: // Scan mScanGallerySite->setWidget(mGallery); mGalleryImgviewSite->setWidget(mImageCanvas); // somewhere to park it emit clearStatus(StatusBarManager::ImageDims); break; case KookaView::TabGallery: // Gallery mGalleryGallerySite->setWidget(mGallery); mGalleryImgviewSite->setWidget(mImageCanvas); emit clearStatus(StatusBarManager::PreviewDims); break; case KookaView::TabOcr: // OCR mOcrGallerySite->setWidget(mGallery); mOcrImgviewSite->setWidget(mImageCanvas); emit clearStatus(StatusBarManager::PreviewDims); break; } restoreGalleryState(index); // restore state of new tab mCurrentTab = static_cast(index); // note for next tab change updateSelectionState(); // update image action states } bool KookaView::slotSelectDevice(const QByteArray &useDevice, bool alwaysAsk) { //qDebug() << "use device" << useDevice << "ask" << alwaysAsk; bool haveConnection = false; bool gallery_mode = (useDevice == "gallery"); QByteArray selDevice; /* in case useDevice is the term 'gallery', the user does not want to * connect to a scanner, but only work in gallery mode. Otherwise, try * to read the device to use from config or from a user dialog */ if (!gallery_mode) { selDevice = useDevice; if (selDevice.isEmpty()) { selDevice = userDeviceSelection(alwaysAsk); } if (selDevice.isEmpty()) { // dialogue cancelled if (mScanParams != nullptr) { return (false); // have setup, do nothing } gallery_mode = true; } } if (mScanParams != nullptr) { closeScanDevice(); // remove existing GUI object } mScanParams = new KookaScanParams(this); // and create a new one connect(mScanParams, SIGNAL(actionSelectScanner()), SLOT(slotSelectDevice())); connect(mScanParams, SIGNAL(actionAddScanner()), SLOT(slotAddDevice())); mParamsSite->setWidget(mScanParams); if (!selDevice.isEmpty()) { // connect to the selected scanner while (!haveConnection) { //qDebug() << "Opening device" << selDevice; KScanDevice::Status stat = mScanDevice->openDevice(selDevice); if (stat == KScanDevice::Ok) { haveConnection = true; } else { QString msg = xi18nc("@info", "There was a problem opening the scanner device." "" "Check that the scanner is connected and switched on, " "and that SANE support for it is correctly configured." "" "Trying to use scanner device: %3" "libkookascan reported error: %2" "SANE reported error: %1", mScanDevice->lastSaneErrorMessage(), KScanDevice::statusMessage(stat), selDevice.constData()); int tryAgain = KMessageBox::warningContinueCancel(mMainWindow, msg, QString(), KGuiItem("Retry")); if (tryAgain == KMessageBox::Cancel) { break; } } } if (haveConnection) { // scanner connected OK QSize s = mScanDevice->getMaxScanSize(); // fix for 160148 //qDebug() << "scanner max size" << s; mPreviewCanvas->setScannerBedSize(s.width(), s.height()); // Connections ScanParams --> Previewer connect(mScanParams, SIGNAL(scanResolutionChanged(int,int)), mPreviewCanvas, SLOT(slotNewScanResolutions(int,int))); connect(mScanParams, SIGNAL(scanModeChanged(int)), mPreviewCanvas, SLOT(slotNewScanMode(int))); connect(mScanParams, SIGNAL(newCustomScanSize(QRect)), mPreviewCanvas, SLOT(slotNewCustomScanSize(QRect))); // Connections Previewer --> ScanParams connect(mPreviewCanvas, SIGNAL(newPreviewRect(QRect)), mScanParams, SLOT(slotNewPreviewRect(QRect))); mScanParams->connectDevice(mScanDevice); // create scanning options mPreviewCanvas->setPreviewImage(mScanDevice->loadPreviewImage()); // load saved preview image mPreviewCanvas->connectScanner(mScanDevice); // load its autosel options } } if (!haveConnection) { // no scanner device available, // or starting in gallery mode if (mScanParams != nullptr) { mScanParams->connectDevice(nullptr, gallery_mode); } } emit signalScannerChanged(haveConnection); return (haveConnection); } void KookaView::slotAddDevice() { AddDeviceDialog d(mMainWindow, i18n("Add Scan Device")); if (d.exec()) { QByteArray dev = d.getDevice(); QString dsc = d.getDescription(); QByteArray type = d.getType(); //qDebug() << "dev" << dev << "type" << type << "desc" << dsc; ScanDevices::self()->addUserSpecifiedDevice(dev, dsc, type); } } QByteArray KookaView::userDeviceSelection(bool alwaysAsk) { /* a list of backends the scan backend knows */ QList backends = ScanDevices::self()->allDevices(); if (backends.count() == 0) { if (KMessageBox::warningContinueCancel(mMainWindow, xi18nc("@info", "No scanner devices are available." "" "If your scanner is a type that can be auto-detected by SANE, " "check that it is connected, switched on and configured correctly." "" "If the scanner cannot be auto-detected by SANE (this includes some network scanners), " "you need to specify the device to use. " "Use the Add Scan Device option to enter the backend name and parameters, " "or see that dialog for more information."), QString(), KGuiItem(i18n("Add Scan Device..."))) != KMessageBox::Continue) { return (""); } slotAddDevice(); backends = ScanDevices::self()->allDevices(); // refresh the list if (backends.count() == 0) { return (""); // give up this time } } QByteArray selDevice; DeviceSelector ds(mMainWindow, backends, (alwaysAsk ? KGuiItem() : KGuiItem(i18n("Gallery"), QIcon::fromTheme("image-x-generic")))); if (!alwaysAsk) { selDevice = ds.getDeviceFromConfig(); } if (selDevice.isEmpty()) { //qDebug() << "no selDevice, starting selector"; if (ds.exec() == QDialog::Accepted) { selDevice = ds.getSelectedDevice(); } //qDebug() << "selector returned device" << selDevice; } return (selDevice); } QString KookaView::scannerName() const { if (mScanDevice->scannerBackendName().isEmpty()) { return (i18n("Gallery")); } if (!mScanDevice->isScannerConnected()) { return (i18n("No scanner connected")); } return (mScanDevice->scannerDescription()); } bool KookaView::isScannerConnected() const { return (mScanDevice->isScannerConnected()); } void KookaView::slotSelectionChanged(const QRect &newSelection) { emit signalRectangleChanged(newSelection.isValid()); } // The 'state' generated here is used by Kooka::slotUpdateViewActions(). void KookaView::updateSelectionState() { KookaView::StateFlags state = KookaView::StateFlags(); const FileTreeViewItem *ftvi = mGallery->galleryTree()->highlightedFileTreeViewItem(); const KFileItem *fi = (ftvi != nullptr ? ftvi->fileItem() : nullptr); const bool scanmode = (mCurrentTab == KookaView::TabScan); if (scanmode) // Scan mode { if (mPreviewCanvas->getImageCanvas()->hasImage()) state |= KookaView::PreviewValid; } else // Gallery/OCR mode { state |= KookaView::GalleryShown; } if (fi != nullptr && !fi->isNull()) // have a gallery selection { if (fi->isDir()) state |= KookaView::IsDirectory; else { state |= KookaView::FileSelected; if (fi->url().hasFragment()) state |= KookaView::IsSubImage; if (ftvi->childCount()>0) state |= KookaView::HasSubImages; } } if (ftvi != nullptr && ftvi->isRoot()) state |= KookaView::RootSelected; if (imageViewer()->hasImage()) state |= KookaView::ImageValid; ////qDebug() << "state" << state; emit signalViewSelectionState(state); } void KookaView::slotGallerySelectionChanged() { const FileTreeViewItem *ftvi = mGallery->galleryTree()->highlightedFileTreeViewItem(); const KFileItem *fi = (ftvi != nullptr ? ftvi->fileItem() : nullptr); const bool scanmode = (mCurrentTab == KookaView::TabScan); if (fi == nullptr || fi->isNull()) { // no gallery selection if (!scanmode) { emit changeStatus(i18n("No selection")); } } else { // have a gallery selection if (!scanmode) { KLocalizedString str = fi->isDir() ? ki18n("Gallery folder %1") : ki18n("Gallery image %1"); emit changeStatus(str.subs(fi->url().url(QUrl::PreferLocalFile)).toString()); } } updateSelectionState(); } void KookaView::loadStartupImage() { const bool wantReadOnStart = KookaSettings::startupReadImage(); if (wantReadOnStart) { QString startup = KookaSettings::startupSelectedImage(); //qDebug() << "load startup image" << startup; if (!startup.isEmpty()) gallery()->slotSelectImage(QUrl::fromLocalFile(startup)); } else { //qDebug() << "do not load startup image"; } } void KookaView::print() { const KookaImage *img = gallery()->getCurrImage(true); if (img==nullptr) return; // load image if necessary // create a KookaPrint (subclass of a QPrinter) KookaPrint printer; printer.setImage(img); QPrintDialog d(&printer, this); d.setWindowTitle(i18nc("@title:window", "Print Image")); d.setOptions(QAbstractPrintDialog::PrintToFile|QAbstractPrintDialog::PrintShowPageSize); // TODO (investigate): even with the options set as above, the options below still // appear in the print dialogue. Is this as intended by Qt? // d.setOption(QAbstractPrintDialog::PrintSelection, false); // d.setOption(QAbstractPrintDialog::PrintPageRange, false); ImgPrintDialog imgTab(img, &printer); // create tab for our options d.setOptionTabs(QList() << &imgTab); // add tab to print dialogue if (!d.exec()) return; // open the dialogue QString msg = imgTab.checkValid(); // check that settings are valid if (!msg.isEmpty()) // if not, display error message { KMessageBox::sorry(this, i18nc("@info", "Invalid print options were specified:\n\n%1", msg), i18nc("@title:window", "Cannot Print")); return; } imgTab.updatePrintParameters(); // set final printer options printer.printImage(); // print the image } void KookaView::slotNewPreview(const QImage *newimg, const ImageMetaInfo *info) { if (newimg == nullptr) { return; } //qDebug() << "new preview image, size" << newimg->size() //<< "res [" << info->getXResolution() << "x" << info->getYResolution() << "]"; mPreviewCanvas->newImage(newimg); // set new image and size updateSelectionState(); } void KookaView::slotStartOcrSelection() { emit changeStatus(i18n("Starting OCR on selection")); startOCR(KookaImage(mImageCanvas->selectedImage())); emit clearStatus(); } void KookaView::slotStartOcr() { emit changeStatus(i18n("Starting OCR on the image")); startOCR(*gallery()->getCurrImage(true)); emit clearStatus(); } void KookaView::slotSetOcrSpellConfig(const QString &configFile) { #ifndef KF5 //qDebug() << configFile; if (mOcrResEdit!=nullptr) mOcrResEdit->setSpellCheckingConfigFileName(configFile); #endif } void KookaView::slotOcrSpellCheck(bool interactive, bool background) { if (!interactive && !background) // not doing anything { emit changeStatus(i18n("OCR finished")); return; } if (mOcrResEdit->document()->isEmpty()) { KMessageBox::sorry(mMainWindow, i18n("There is no OCR result text to spell check."), i18n("OCR Spell Check not possible")); return; } setCurrentIndex(KookaView::TabOcr); emit changeStatus(i18n("OCR Spell Check")); if (background) mOcrResEdit->setCheckSpellingEnabled(true); if (interactive) mOcrResEdit->checkSpelling(); } void KookaView::startOCR(const KookaImage img) { if (img.isNull()) return; // no image to OCR setCurrentIndex(KookaView::TabOcr); const QString engineName = KookaSettings::ocrEngineName(); if (engineName.isEmpty()) { int result = KMessageBox::warningContinueCancel(mMainWindow, i18n("No OCR engine is configured.\n" "Please select and configure one in order to perform OCR."), i18n("OCR Not Configured"), KGuiItem(i18n("Configure OCR..."))); if (result==KMessageBox::Continue) emit signalOcrPrefs(); return; } AbstractOcrEngine *engine = qobject_cast(PluginManager::self()->loadPlugin(PluginManager::OcrPlugin, engineName)); if (engine==nullptr) { KMessageBox::error(mMainWindow, i18n("Cannot load OCR plugin '%1'", engineName)); return; } // We don't know whether the plugin object has been used before. // So disconnect all of its existing signals so that they do not // get double connected. engine->disconnect(); // Connections OCR Engine --> myself connect(engine, &AbstractOcrEngine::newOCRResultText, this, &KookaView::slotOcrResultAvailable); connect(engine, &AbstractOcrEngine::setSpellCheckConfig, this, &KookaView::slotSetOcrSpellConfig); connect(engine, &AbstractOcrEngine::startSpellCheck, this, &KookaView::slotOcrSpellCheck); connect(engine, &AbstractOcrEngine::openOcrPrefs, this, &KookaView::signalOcrPrefs); // Connections OCR Results --> OCR Engine connect(mOcrResEdit, &OcrResEdit::highlightWord, engine, &AbstractOcrEngine::slotHighlightWord); connect(mOcrResEdit, &OcrResEdit::scrollToWord, engine, &AbstractOcrEngine::slotScrollToWord); // Connections OCR Engine --> OCR Results connect(engine, &AbstractOcrEngine::readOnlyEditor, mOcrResEdit, &OcrResEdit::slotSetReadOnly); connect(engine, &AbstractOcrEngine::selectWord, mOcrResEdit, &OcrResEdit::slotSelectWord); engine->setImageCanvas(mImageCanvas); engine->setTextDocument(mOcrResEdit->document()); engine->setImage(img); engine->openOcrDialogue(this); } void KookaView::slotOcrResultAvailable() { emit signalOcrResultAvailable(mOcrResEdit->document()->characterCount()>0); } void KookaView::slotScanStart(const ImageMetaInfo *info) { //qDebug() << "Scan starts..."; if (KookaSettings::saverAskBeforeScan()) // ask for filename first? { if (info != nullptr) // if we have initial image info { //qDebug() << "imgtype" << info->getImageType(); if (!gallery()->prepareToSave(info)) // get ready to save { // user cancelled file prompt mScanDevice->slotStopScanning(); // abort the scan now return; } } } if (mScanParams != nullptr) { mScanParams->setEnabled(false); KLed *led = mScanParams->operationLED(); if (led != nullptr) { led->setColor(Qt::red); // scanner warming up led->setState(KLed::On); qApp->processEvents(); // let the change show } mScanParams->setScanDestination(gallery()->saveURL().url(QUrl::PreferLocalFile)); } } void KookaView::slotAcquireStart() { //qDebug() << "Acquire starts"; if (mScanParams != nullptr) { KLed *led = mScanParams->operationLED(); if (led != nullptr) { led->setColor(Qt::green); // scanning active qApp->processEvents(); // let the change show } } } void KookaView::slotNewImageScanned(const QImage *img, const ImageMetaInfo *info) { if (mIsPhotoCopyMode) { return; } gallery()->addImage(img, info); } void KookaView::slotScanFinished(KScanDevice::Status stat) { //qDebug() << "Scan finished with status" << stat; if (stat != KScanDevice::Ok && stat != KScanDevice::Cancelled) { QString msg = xi18nc("@info", "There was a problem during preview or scanning." "" "Check that the scanner is still connected and switched on, " "" "and that media is loaded if required." "" "Trying to use scanner device: %3" "libkookascan reported error: %2" "SANE reported error: %1", mScanDevice->lastSaneErrorMessage(), KScanDevice::statusMessage(stat), mScanDevice->scannerBackendName().constData()); KMessageBox::error(mMainWindow, msg); } if (mScanParams != nullptr) { mScanParams->setEnabled(true); KLed *led = mScanParams->operationLED(); if (led != nullptr) { led->setColor(Qt::green); led->setState(KLed::Off); } } } void KookaView::closeScanDevice() { //qDebug() << "Scanner Device closes down"; if (mScanParams != nullptr) { delete mScanParams; mScanParams = nullptr; } mScanDevice->closeDevice(); } void KookaView::slotCreateNewImgFromSelection() { emit changeStatus(i18n("Create new image from selection")); QImage img = mImageCanvas->selectedImage(); if (!img.isNull()) { gallery()->addImage(&img); } emit clearStatus(); } void KookaView::slotTransformImage() { if (imageViewer()->isReadOnly()) { emit changeStatus(i18n("Cannot transform, image is read-only")); return; } // Get operation code from action that was triggered QAction *act = static_cast(sender()); if (act == nullptr) { return; } ImageTransform::Operation op = static_cast(act->data().toInt()); // This may appear to be a waste, since we are immediately unloading the image. // But we need a copy of the image anyway, so there will still be a load needed. const KookaImage *loadedImage = gallery()->getCurrImage(true); if (loadedImage == nullptr) { return; } const QImage img = *loadedImage; // get a copy of the image QString imageFile = gallery()->currentImageFileName(); if (imageFile.isEmpty()) { return; // get file to save back to } gallery()->slotUnloadItems(); // unload original from memory QThread *transformer = new ImageTransform(img, op, imageFile, this); connect(transformer, SIGNAL(done(QUrl)), gallery(), SLOT(slotUpdatedItem(QUrl))); connect(transformer, SIGNAL(statusMessage(QString)), SIGNAL(changeStatus(QString))); connect(transformer, SIGNAL(finished()), transformer, SLOT(deleteLater())); transformer->start(); } void KookaView::slotSaveOcrResult() { if (mOcrResEdit != nullptr) { mOcrResEdit->slotSaveText(); } } void KookaView::slotScanParams() { if (mScanDevice == nullptr) { return; // must have a scanner device } ScanParamsDialog d(this, mScanDevice); d.exec(); } void KookaView::slotShowAImage(const KookaImage *img, bool isDir) { if (mImageCanvas != nullptr) { // load into image viewer mImageCanvas->newImage(img); mImageCanvas->setReadOnly(false); } if (mImageCanvas != nullptr) { emit changeStatus(mImageCanvas->imageInfoString(), StatusBarManager::ImageDims); } if (img != nullptr) { emit changeStatus(i18n("Loaded image %1", img->url().url(QUrl::PreferLocalFile))); } else if (!isDir) { emit changeStatus(i18n("Unloaded image")); } updateSelectionState(); } void KookaView::slotUnloadAImage(const KookaImage *img) { //qDebug() << "Unloading Image"; if (mImageCanvas != nullptr) { mImageCanvas->newImage(nullptr); } updateSelectionState(); } /* this slot is called when the user clicks on an image in the packager * and loading of the image starts */ void KookaView::slotStartLoading(const QUrl &url) { emit changeStatus(i18n("Loading %1...", url.url(QUrl::PreferLocalFile))); } void KookaView::connectViewerAction(QAction *action, bool sepBefore) { if (action == nullptr) { return; } QMenu *popup = mImageCanvas->contextMenu(); if (popup == nullptr) { return; } if (sepBefore) { popup->addSeparator(); } popup->addAction(action); } void KookaView::connectGalleryAction(QAction *action, bool sepBefore) { if (action == nullptr) { return; } QMenu *popup = gallery()->contextMenu(); if (popup == nullptr) { return; } if (sepBefore) { popup->addSeparator(); } popup->addAction(action); } void KookaView::connectThumbnailAction(QAction *action) { if (action == nullptr) { return; } QMenu *popup = mThumbView->contextMenu(); if (popup != nullptr) { popup->addAction(action); } } void KookaView::connectPreviewAction(QAction *action) { if (action == nullptr) { return; } QMenu *popup = mPreviewCanvas->getImageCanvas()->contextMenu(); if (popup != nullptr) { popup->addAction(action); } } void KookaView::slotApplySettings() { if (mThumbView != nullptr) { mThumbView->readSettings(); // size and background } if (mGallery != nullptr) { mGallery->readSettings(); // layout and rename } } // Starting a scan or preview, switch tab and tell the scan device void KookaView::slotStartPreview() { if (mScanParams == nullptr) { return; } setCurrentIndex(KookaView::TabScan); qApp->processEvents(); // let the tab appear mScanParams->slotAcquirePreview(); } void KookaView::slotStartFinalScan() { if (mScanParams == nullptr) { return; } setCurrentIndex(KookaView::TabScan); qApp->processEvents(); // let the tab appear mScanParams->slotStartScan(); } void KookaView::slotAutoSelect(bool on) { if (mPreviewCanvas == nullptr) { return; } setCurrentIndex(KookaView::TabScan); qApp->processEvents(); // let the tab appear mPreviewCanvas->slotAutoSelToggled(on); } /* Slot called to start copying to printer */ void KookaView::slotStartPhotoCopy() { //qDebug(); #ifndef KDE4 if (mScanParams == 0) { return; } mIsPhotoCopyMode = true; mPhotoCopyPrinter = new KPrinter(true, QPrinter::HighResolution); // mPhotoCopyPrinter->removeStandardPage( KPrinter::CopiesPage ); mPhotoCopyPrinter->setUsePrinterResolution(true); mPhotoCopyPrinter->setOption(OPT_SCALING, "scan"); mPhotoCopyPrinter->setFullPage(true); slotPhotoCopyScan(KScanDevice::Ok); #endif } void KookaView::slotPhotoCopyPrint(const QImage *img, const ImageMetaInfo *info) { //qDebug(); #ifndef KDE4 if (! mIsPhotoCopyMode) { return; } KookaImage kooka_img = KookaImage(*img); KookaPrint kookaprint(mPhotoCopyPrinter); kookaprint.printImage(&kooka_img , 0); #endif } void KookaView::slotPhotoCopyScan(KScanDevice::Status status) { //qDebug(); #ifndef KDE4 if (! mIsPhotoCopyMode) { return; } // mPhotoCopyPrinter->addDialogPage( new PhotoCopyPrintDialogPage( mScanDevice ) ); KScanOption res(SANE_NAME_SCAN_RESOLUTION); mPhotoCopyPrinter->setOption(OPT_SCAN_RES, res.get()); mPhotoCopyPrinter->setMargins(QSize(0, 0)); //qDebug() << "Resolution" << res.get(); // mPhotoCopyPrinter->addDialogPage( new ImgPrintDialog( 0 ) ); if (mPhotoCopyPrinter->setup(0, "Photocopy")) { Q_CHECK_PTR(mScanDevice); mScanParams->slotStartScan(); } else { mIsPhotoCopyMode = false; delete mPhotoCopyPrinter; } #endif } ScanGallery *KookaView::gallery() const { return (mGallery->galleryTree()); } ImageCanvas *KookaView::imageViewer() const { return (mImageCanvas); } Previewer *KookaView::previewer() const { return (mPreviewCanvas); } -void KookaView::slotImageViewerAction(int act) +void KookaView::imageViewerAction(ImageCanvas::UserAction act) { - if (mCurrentTab == KookaView::TabScan) { // Scan - mPreviewCanvas->getImageCanvas()->performUserAction(static_cast(act)); - } else { // Gallery or OCR - mImageCanvas->performUserAction(static_cast(act)); + if (mCurrentTab == KookaView::TabScan) { // Scan + mPreviewCanvas->getImageCanvas()->performUserAction(act); + } else { // Gallery or OCR + mImageCanvas->performUserAction(act); } } void KookaView::showOpenWithMenu(KActionMenu *menu) { FileTreeViewItem *curr = gallery()->highlightedFileTreeViewItem(); QString mimeType = curr->fileItem()->mimetype(); //qDebug() << "Trying to open" << curr->url() << "which is" << mimeType; - if (mOpenWithMapper == nullptr) { - mOpenWithMapper = new QSignalMapper(this); - connect(mOpenWithMapper, SIGNAL(mapped(int)), SLOT(slotOpenWith(int))); - } - menu->menu()->clear(); int i = 0; mOpenWithOffers = KMimeTypeTrader::self()->query(mimeType); for (KService::List::ConstIterator it = mOpenWithOffers.constBegin(); it != mOpenWithOffers.constEnd(); ++it, ++i) { KService::Ptr service = (*it); //qDebug() << "> offer:" << (*it)->name(); QString actionName((*it)->name().replace("&", "&&")); QAction *act = new QAction(QIcon::fromTheme((*it)->icon()), actionName, this); - connect(act, SIGNAL(triggered()), mOpenWithMapper, SLOT(map())); - mOpenWithMapper->setMapping(act, i); + connect(act, &QAction::triggered, this, [=]() { slotOpenWith(i); }); menu->addAction(act); } menu->menu()->addSeparator(); QAction *act = new QAction(i18n("Other..."), this); - connect(act, SIGNAL(triggered()), mOpenWithMapper, SLOT(map())); - mOpenWithMapper->setMapping(act, i); + connect(act, &QAction::triggered, this, [=]() { slotOpenWith(-1); }); menu->addAction(act); } void KookaView::slotOpenWith(int idx) { //qDebug() << "idx" << idx; FileTreeViewItem *ftvi = gallery()->highlightedFileTreeViewItem(); if (ftvi == nullptr) { return; } QList urllist; urllist.append(ftvi->url()); - if (idx < mOpenWithOffers.count()) { // application from the menu + if (idx!=-1) // application from the menu + { + Q_ASSERT(idx and KDE Frameworks . * * * * Copyright (C) 2000-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #ifndef KOOKAVIEW_H #define KOOKAVIEW_H #include #include #include "statusbarmanager.h" +#include "imagecanvas.h" #include "kscandevice.h" class QSplitter; -class QSignalMapper; class QUrl; class KPrinter; class QAction; class KMainWindow; class KActionMenu; class ThumbView; class KookaImage; class KookaGallery; class OcrResEdit; class ScanGallery; class KookaScanParams; class ImageMetaInfo; class Previewer; class KScanDevice; class ImageCanvas; class WidgetSite; /** * This is the main view class for Kooka. Most of the non-menu, * non-toolbar, and non-statusbar (e.g., non frame) GUI code should go * here. * * @short Main view * @author Klaas Freitag * @version 0.1 */ class KookaView : public QTabWidget { Q_OBJECT public: enum MirrorType { MirrorVertical, MirrorHorizontal, MirrorBoth }; enum TabPage { TabScan = 0, TabGallery = 1, TabOcr = 2, TabNone = 0xFF }; /** * To avoid a proliferation of boolean parameters, these flags are * used to indicate the state of the gallery and image viewers. */ enum StateFlag { GalleryShown = 0x001, // in Gallery/OCR mode PreviewValid = 0x002, // scan preview valid ImageValid = 0x004, // viewer image loaded IsDirectory = 0x008, // directory selected FileSelected = 0x010, // 1 file selected ManySelected = 0x020, // multiple selection RootSelected = 0x040, // root is selected IsSubImage = 0x080, // a subimage selected HasSubImages = 0x100 // contains subimages }; Q_DECLARE_FLAGS(StateFlags, StateFlag) /** * Default constructor */ explicit KookaView(KMainWindow *parent, const QByteArray &deviceToUse); /** * Destructor */ virtual ~KookaView(); /** * Print this view to any medium -- paper or not */ void print(); void loadStartupImage(); ScanGallery *gallery() const; ImageCanvas *imageViewer() const; Previewer *previewer() const; bool isScannerConnected() const; QString scannerName() const; void closeScanDevice(); void connectViewerAction(QAction *action, bool sepBefore = false); void connectGalleryAction(QAction *action, bool sepBefore = false); void connectThumbnailAction(QAction *action); void connectPreviewAction(QAction *action); + void imageViewerAction(ImageCanvas::UserAction act); void saveWindowSettings(KConfigGroup &grp); void applyWindowSettings(const KConfigGroup &grp); public slots: void slotStartOcr(); void slotStartOcrSelection(); void slotSetOcrSpellConfig(const QString &configFile); void slotOcrSpellCheck(bool interactive = true, bool background = false); void slotSaveOcrResult(); void slotStartPreview(); void slotNewPreview(const QImage *newimg, const ImageMetaInfo *info); void slotStartFinalScan(); void slotAutoSelect(bool on); void slotCreateNewImgFromSelection(); void slotTransformImage(); void slotScanParams(); void slotApplySettings(); /** * slot to select the scanner device. Does all the work with selection * of scanner, disconnection of the old device and connecting the new. */ bool slotSelectDevice(const QByteArray &useDevice = "", bool alwaysAsk = true); void slotAddDevice(); void slotScanStart(const ImageMetaInfo *info); void slotScanFinished(KScanDevice::Status stat); void slotAcquireStart(); void showOpenWithMenu(KActionMenu *menu); protected slots: void slotStartPhotoCopy(); void slotPhotoCopyPrint(const QImage *img, const ImageMetaInfo *info); void slotPhotoCopyScan(KScanDevice::Status); void slotShowAImage(const KookaImage *img, bool isDir); void slotUnloadAImage(const KookaImage *img); /** * called from the scandevice if a new Image was successfully scanned. * Needs to convert the one-page-QImage to a KookaImage */ void slotNewImageScanned(const QImage *img, const ImageMetaInfo *info); void slotSelectionChanged(const QRect &newSelection); void slotGallerySelectionChanged(); void slotOcrResultAvailable(); void slotTabChanged(int index); - void slotImageViewerAction(int act); signals: /** * Change the content of the statusbar */ void changeStatus(const QString &text, StatusBarManager::Item item = StatusBarManager::Message); /** * Clean up the statusbar */ void clearStatus(StatusBarManager::Item item = StatusBarManager::Message); /** * Use this signal to change the content of the caption */ void signalChangeCaption(const QString &text); void signalViewSelectionState(KookaView::StateFlags state); void signalScannerChanged(bool haveConnection); void signalRectangleChanged(bool haveSelection); void signalOcrResultAvailable(bool haveText); void signalOcrPrefs(); private: void startOCR(const KookaImage img); QByteArray userDeviceSelection(bool alwaysAsk); void saveGalleryState(int index = -1) const; void restoreGalleryState(int index = -1); void updateSelectionState(); private slots: void slotStartLoading(const QUrl &url); void slotOpenWith(int idx); void slotPreviewDimsChanged(const QString &dims); private: KMainWindow *mMainWindow; ImageCanvas *mImageCanvas; ThumbView *mThumbView; Previewer *mPreviewCanvas; KookaGallery *mGallery; KookaScanParams *mScanParams; KScanDevice *mScanDevice; QImage *mOcrResultImg; OcrResEdit *mOcrResEdit; bool mIsPhotoCopyMode; KPrinter *mPhotoCopyPrinter; KookaView::TabPage mCurrentTab; QSplitter *mScanPage; QSplitter *mScanSubSplitter; QSplitter *mGalleryPage; QSplitter *mGallerySubSplitter; QSplitter *mOcrPage; QSplitter *mOcrSubSplitter; WidgetSite *mParamsSite; WidgetSite *mScanGallerySite; WidgetSite *mGalleryGallerySite; WidgetSite *mOcrGallerySite; WidgetSite *mGalleryImgviewSite; WidgetSite *mOcrImgviewSite; KService::List mOpenWithOffers; - QSignalMapper *mOpenWithMapper; }; Q_DECLARE_OPERATORS_FOR_FLAGS(KookaView::StateFlags) #endif // KOOKAVIEW_H diff --git a/app/thumbview.cpp b/app/thumbview.cpp index 4ead504..c54f251 100644 --- a/app/thumbview.cpp +++ b/app/thumbview.cpp @@ -1,361 +1,336 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2002-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #include "thumbview.h" -#include #include #include #include #include #include #include +#include #include #include #include #include #include #include "kookapref.h" #include "kookasettings.h" +void ThumbView::createActionForSize(KIconLoader::StdSizes size) +{ + KToggleAction *act = new KToggleAction(sizeName(size), this); + connect(act, &QAction::triggered, this, [=]() { slotSetSize(size); }); + m_sizeMap[size] = act; + m_sizeMenu->addAction(act); +} + + ThumbView::ThumbView(QWidget *parent) : KDirOperator(QUrl(), parent) { setObjectName("ThumbView"); m_thumbSize = KIconLoader::SizeHuge; m_firstMenu = true; // There seems to be no way to set the maximum preview file size or to // ignore it directly, the preview job with that setting is private to // KFilePreviewGenerator. But we can set the size limit in our application's // configuration - this also has a useful side effect that the user can // change the setting there if they so wish. // See PreviewJob::startPreview() in kio/src/widgets/previewjob.cpp qDebug() << "Maximum preview file size is" << KookaSettings::previewMaximumSize(); setUrl(QUrl::fromUserInput(KookaPref::galleryRoot()), true); // initial location setPreviewWidget(nullptr); // no preview at side setMode(KFile::File); // implies single selection mode setInlinePreviewShown(true); // show file previews setView(KFile::Simple); // simple icon view dirLister()->setMimeExcludeFilter(QStringList("inode/directory")); // only files, not directories connect(this, SIGNAL(fileSelected(KFileItem)), SLOT(slotFileSelected(KFileItem))); connect(this, SIGNAL(fileHighlighted(KFileItem)), SLOT(slotFileHighlighted(KFileItem))); connect(this, SIGNAL(finishedLoading()), SLOT(slotFinishedLoading())); // We want to provide our own context menu, not the one that // KDirOperator has built in. disconnect(view(), SIGNAL(customContextMenuRequested(QPoint)), nullptr, nullptr); connect(view(), SIGNAL(customContextMenuRequested(QPoint)), SLOT(slotContextMenu(QPoint))); mContextMenu = new QMenu(this); mContextMenu->addSection(i18n("Thumbnails")); // Scrolling to the selected item only works after the KDirOperator's // KDirModel has received this signal. connect(dirLister(), SIGNAL(refreshItems(QList >)), SLOT(slotEnsureVisible())); m_lastSelected = url(); m_toSelect = QUrl(); m_toChangeTo = QUrl(); readSettings(); m_sizeMenu = new KActionMenu(i18n("Preview Size"), this); - QSignalMapper *mapper = new QSignalMapper(this); - KToggleAction *act; - - act = new KToggleAction(sizeName(KIconLoader::SizeEnormous), this); - connect(act, SIGNAL(triggered()), mapper, SLOT(map())); - mapper->setMapping(act, KIconLoader::SizeEnormous); - m_sizeMap[KIconLoader::SizeEnormous] = act; - m_sizeMenu->addAction(act); - - act = new KToggleAction(sizeName(KIconLoader::SizeHuge), this); - connect(act, SIGNAL(triggered()), mapper, SLOT(map())); - mapper->setMapping(act, KIconLoader::SizeHuge); - m_sizeMap[KIconLoader::SizeHuge] = act; - m_sizeMenu->addAction(act); + createActionForSize(KIconLoader::SizeEnormous); + createActionForSize(KIconLoader::SizeHuge); + createActionForSize(KIconLoader::SizeLarge); + createActionForSize(KIconLoader::SizeMedium); + createActionForSize(KIconLoader::SizeSmallMedium); - act = new KToggleAction(sizeName(KIconLoader::SizeLarge), this); - connect(act, SIGNAL(triggered()), mapper, SLOT(map())); - mapper->setMapping(act, KIconLoader::SizeLarge); - m_sizeMap[KIconLoader::SizeLarge] = act; - m_sizeMenu->addAction(act); - - act = new KToggleAction(sizeName(KIconLoader::SizeMedium), this); - connect(act, SIGNAL(triggered()), mapper, SLOT(map())); - mapper->setMapping(act, KIconLoader::SizeMedium); - m_sizeMap[KIconLoader::SizeMedium] = act; - m_sizeMenu->addAction(act); - - act = new KToggleAction(sizeName(KIconLoader::SizeSmallMedium), this); - connect(act, SIGNAL(triggered()), mapper, SLOT(map())); - mapper->setMapping(act, KIconLoader::SizeSmallMedium); - m_sizeMap[KIconLoader::SizeSmallMedium] = act; - m_sizeMenu->addAction(act); - - connect(mapper, SIGNAL(mapped(int)), SLOT(slotSetSize(int))); - - setMinimumSize(64, 64); // sensible minimum size + setMinimumSize(64, 64); // sensible minimum size } ThumbView::~ThumbView() { saveConfig(); } void ThumbView::slotHighlightItem(const QUrl &url, bool isDir) { QUrl cur = this->url(); // directory currently showing QUrl urlToShow = url; // new URL to show QUrl dirToShow = urlToShow; // directory part of that if (!isDir) dirToShow = dirToShow.adjusted(QUrl::RemoveFilename); if (cur.adjusted(QUrl::StripTrailingSlash).path() != dirToShow.adjusted(QUrl::StripTrailingSlash).path()) { // see if changing path if (!isDir) m_toSelect = urlToShow; // select that when loading finished // Bug 216928: Need to check whether the KDirOperator's KDirLister is // currently busy. If it is, then trying to set the KDirOperator to a // new directory at this point is accepted but fails soon afterwards // with an assertion such as: // // kooka(7283)/kio (KDirModel): Items emitted in directory // KUrl("file:///home/jjm4/Documents/KookaGallery/a") // but that directory isn't in KDirModel! // Root directory: KUrl("file:///home/jjm4/Documents/KookaGallery/a/a") // ASSERT: "result" in file /ws/trunk/kdelibs/kio/kio/kdirmodel.cpp, line 372 // // To fix this, if the KDirLister is busy we delay changing to the new // directory until the it has finished, the finishedLoading() signal // will then call our slotFinishedLoading() and do the setUrl() there. // // There are two possible (but extremely unlikely) race conditions here. #ifdef WORKAROUND_216928 - if (dirLister()->isFinished()) { // idle, can do this now + if (dirLister()->isFinished()) { // idle, can do this now //qDebug() << "lister idle, changing dir to" << dirToShow; - setUrl(dirToShow, true); // change path and reload + setUrl(dirToShow, true); // change path and reload } else { //qDebug() << "lister busy, deferring change to" << dirToShow; - m_toChangeTo = dirToShow; // note to do later + m_toChangeTo = dirToShow; // note to do later } #else //qDebug() << "changing dir to" << dirToShow; - setUrl(dirToShow, true); // change path and reload + setUrl(dirToShow, true); // change path and reload #endif return; } KFileItemList selItems = selectedItems(); - if (!selItems.isEmpty()) { // the current selection + if (!selItems.isEmpty()) { // the current selection KFileItem curItem = selItems.first(); if (curItem.url() == urlToShow) { return; // already selected } } - // TODO: use QSignalBlocker - bool b = blockSignals(true); // avoid signal loop + QSignalBlocker block(this); setCurrentItem(isDir ? QUrl() : urlToShow); - blockSignals(b); } void ThumbView::slotContextMenu(const QPoint &pos) { - if (m_firstMenu) { // first time menu activated + if (m_firstMenu) { // first time menu activated mContextMenu->addSeparator(); - mContextMenu->addAction(m_sizeMenu); // append size menu at end - m_firstMenu = false; // note this has been done + mContextMenu->addAction(m_sizeMenu); // append size menu at end + m_firstMenu = false; // note this has been done } for (QMap::const_iterator it = m_sizeMap.constBegin(); - it != m_sizeMap.constEnd(); ++it) { // tick applicable size entry + it != m_sizeMap.constEnd(); ++it) { // tick applicable size entry (*it)->setChecked(m_thumbSize == it.key()); } mContextMenu->exec(QCursor::pos()); } -void ThumbView::slotSetSize(int size) +void ThumbView::slotSetSize(KIconLoader::StdSizes size) { m_thumbSize = size; // see KDirOperator::setIconsZoom() in kio/src/kfilewidgets/kdiroperator.cpp - int val = ((size - KIconLoader::SizeSmall) * 100) / (KIconLoader::SizeEnormous - KIconLoader::SizeSmall); - + const int val = ((size-KIconLoader::SizeSmall)*100) / (KIconLoader::SizeEnormous-KIconLoader::SizeSmall); //qDebug() << "size" << size << "-> val" << val; setIconsZoom(val); } void ThumbView::slotFinishedLoading() { - if (m_toChangeTo.isValid()) { // see if change deferred + if (m_toChangeTo.isValid()) { // see if change deferred //qDebug() << "setting dirop url to" << m_toChangeTo; - setUrl(m_toChangeTo, true); // change path and reload - m_toChangeTo = QUrl(); // have dealt with this now + setUrl(m_toChangeTo, true); // change path and reload + m_toChangeTo = QUrl(); // have dealt with this now return; } - if (m_toSelect.isValid()) { // see if something to select + if (m_toSelect.isValid()) { // see if something to select //qDebug() << "selecting" << m_toSelect; - // TODO: use QSignalBlocker - bool blk = blockSignals(true); // avoid signal loop + QSignalBlocker block(this); setCurrentItem(m_toSelect); - blockSignals(blk); - m_toSelect = QUrl(); // have dealt with this now + m_toSelect = QUrl(); // have dealt with this now } } void ThumbView::slotEnsureVisible() { QListView *v = qobject_cast(view()); if (v == nullptr) { return; } // Ensure that the currently selected item is visible, // from KDirOperator::Private::_k_assureVisibleSelection() // in kdelibs/kfile/kdiroperator.cpp QItemSelectionModel *selModel = v->selectionModel(); if (selModel->hasSelection()) { const QModelIndex index = selModel->currentIndex(); //qDebug() << "ensuring visible" << index; v->scrollTo(index, QAbstractItemView::EnsureVisible); } } void ThumbView::slotFileSelected(const KFileItem &kfi) { QUrl u = (!kfi.isNull() ? kfi.url() : url()); //qDebug() << u; if (u != m_lastSelected) { m_lastSelected = u; emit itemActivated(u); } } void ThumbView::slotFileHighlighted(const KFileItem &kfi) { QUrl u = (!kfi.isNull() ? kfi.url() : url()); //qDebug() << u; emit itemHighlighted(u); } void ThumbView::readSettings() { - slotSetSize(KookaSettings::thumbnailPreviewSize()); + slotSetSize(static_cast(KookaSettings::thumbnailPreviewSize())); setBackground(); } void ThumbView::saveConfig() { // Do nothing, preview size set by menu is for this session only. // Set the default size in Kooka Preferences. } void ThumbView::setBackground() { QPixmap bgPix; QWidget *iv = view()->viewport(); // go down to the icon view QPalette pal = iv->palette(); QString newBgImg = KookaSettings::thumbnailBackgroundPath(); bool newCustomBg = KookaSettings::thumbnailCustomBackground(); //qDebug() << "custom" << newCustomBg << "img" << newBgImg; if (newCustomBg && !newBgImg.isEmpty()) { // can set custom background if (bgPix.load(newBgImg)) { pal.setBrush(iv->backgroundRole(), QBrush(bgPix)); } else { qWarning() << "Failed to load background image" << newBgImg; } } else { // reset to default KColorScheme sch(QPalette::Normal); pal.setBrush(iv->backgroundRole(), sch.background()); } iv->setPalette(pal); } void ThumbView::slotImageChanged(const KFileItem *kfi) { //qDebug() << kfi->url(); // TODO: is there an equivalent? - //m_fileview->updateView(kfi); // update that view item + //m_fileview->updateView(kfi); // update that view item } void ThumbView::slotImageRenamed(const KFileItem *item, const QString &newName) { //qDebug() << item->url() << "->" << newName; // Nothing to do here. // KDirLister::refreshItems signal -> slotEnsureVisible() // will scroll to the selected item. } void ThumbView::slotImageDeleted(const KFileItem *kfi) { // No need to do anything here. } // TODO: code directly in settings QString ThumbView::standardBackground() { return (QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/thumbviewtile.png")); } QString ThumbView::sizeName(KIconLoader::StdSizes size) { switch (size) { case KIconLoader::SizeEnormous: return (i18n("Very Large")); case KIconLoader::SizeHuge: return (i18n("Large")); case KIconLoader::SizeLarge: return (i18n("Medium")); case KIconLoader::SizeMedium: return (i18n("Small")); case KIconLoader::SizeSmallMedium: return (i18n("Very Small")); case KIconLoader::SizeSmall: return (i18n("Tiny")); default: return ("?"); } } diff --git a/app/thumbview.h b/app/thumbview.h index 527d687..8ba6b17 100644 --- a/app/thumbview.h +++ b/app/thumbview.h @@ -1,103 +1,102 @@ /************************************************************************ * * * This file is part of Kooka, a scanning/OCR application using * * Qt and KDE Frameworks . * * * * Copyright (C) 2002-2016 Klaas Freitag * * Jonathan Marten * * * * Kooka is free software; you can redistribute it and/or modify it * * under the terms of the GNU Library General Public License as * * published by the Free Software Foundation and appearing in the * * file COPYING included in the packaging of this file; either * * version 2 of the License, or (at your option) any later version. * * * * As a special exception, permission is given to link this program * * with any version of the KADMOS OCR/ICR engine (a product of * * reRecognition GmbH, Kreuzlingen), and distribute the resulting * * executable without including the source code for KADMOS in the * * source distribution. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public * * License along with this program; see the file COPYING. If * * not, see . * * * ************************************************************************/ #ifndef THUMBVIEW_H #define THUMBVIEW_H #include #include #include #include class QMenu; class KFileItem; class KActionMenu; class KToggleAction; class ThumbView : public KDirOperator { Q_OBJECT public: explicit ThumbView(QWidget *parent); ~ThumbView(); QMenu *contextMenu() const { return (mContextMenu); } void readSettings(); static QString standardBackground(); static QString sizeName(KIconLoader::StdSizes size); public slots: void slotImageDeleted(const KFileItem *kfi); void slotImageChanged(const KFileItem *kfi); void slotImageRenamed(const KFileItem *kfi, const QString &newName); void slotHighlightItem(const QUrl &url, bool isDir); protected: void saveConfig(); protected slots: void slotContextMenu(const QPoint &pos); void slotFileSelected(const KFileItem &kfi); void slotFileHighlighted(const KFileItem &kfi); void slotFinishedLoading(); void slotEnsureVisible(); - void slotSetSize(int size); + void slotSetSize(KIconLoader::StdSizes size); signals: void itemHighlighted(const QUrl &url); void itemActivated(const QUrl &url); private: void setBackground(); + void createActionForSize(KIconLoader::StdSizes size); QMenu *mContextMenu; bool m_firstMenu; KActionMenu *m_sizeMenu; QMap m_sizeMap; - // This is really a KIconLoader::StdSizes, but it has to be an int because - // of the signal from QSignalMapper. - int m_thumbSize; + KIconLoader::StdSizes m_thumbSize; QUrl m_lastSelected; QUrl m_toSelect; QUrl m_toChangeTo; }; #endif // THUMBVIEW_H