diff --git a/NEWS b/NEWS index 30008e3440..81b5cf5ae4 100644 --- a/NEWS +++ b/NEWS @@ -1,46 +1,47 @@ ***************************************************************************************************** digiKam 5.9.0 - Release date: 2018-02-25 NEW FEATURES: General : Libraw updated to last 0.18.8. BUGFIXES FROM BUGZILLA: 001 ==> 388977 - Error during upgrading core database schema from v8 to v9. 002 ==> 388867 - problem migrating databaseschema from 8 to 9. 003 ==> 388824 - UpdateSchemaFromV7ToV9 fails due to foreign key contraint in 5.8.0-01. 004 ==> 389042 - Auto-rotation/flip Images delete face tags. 005 ==> 337243 - Tool-Tips ignoring custom font size setting. 006 ==> 388320 - Selecting print layout does not stay selected. 007 ==> 389246 - Single click confirmation of suggested face tag. 008 ==> 389208 - Request addition of "Lens" to Tool-Tips (Icon Items). 009 ==> 389342 - Tags are copied although moving of file fails because file with the same name already exists in target location. 010 ==> 389420 - Thumbnails diminishing in size after switching Views. 011 ==> 389493 - Collection browse dialog should be consistent with other browse dialogs on KDE. 012 ==> 389512 - Timeline Thumbnails Not Shown. 013 ==> 389651 - Custom font size is not applied until you enter and exit the settings menu. 014 ==> 388345 - AppImage MySQL connection issues. 015 ==> 389827 - Ignores albums selection. 016 ==> 389835 - Incorrect DateTime manipulation if timestamp is before unix epoch time. 017 ==> 390121 - Thumbnail size is too small. 018 ==> 390136 - digiKam crashes when launching scan and recognize faces. 019 ==> 390325 - Segmentation fault - QMutex::lock(). 020 ==> 390443 - FAQ entry unaccurate - Thumbnail generation fails on video files. 021 ==> 390529 - Import from scanner not enabled. 022 ==> 390683 - Batch processing creates 0 byte files on Fujifilm RAF RAW images with lens correction. 023 ==> 390688 - Show pick and color label in thumbnail list [patch]. 024 ==> 390162 - digiKam-5.9.0 appimage fail to start. 025 ==> 384853 - Ungrouping images is not working. 026 ==> 390902 - Right sidebar icons not displayed. 027 ==> 391312 - Rename with time template can add colons (:) forbidden character in Windows 10. 028 ==> 386921 - Import Sony video metadata from sidecar file. 029 ==> 377133 - Multiple tags don't display in video metadata. 030 ==> 389619 - digiKam does not read from sidecars for video/mp4. 031 ==> 375570 - XMP sidecar files are not read for videos when importing metadata. 032 ==> 374684 - Crash in Digikam when importing from SD card via reader. 033 ==> 381586 - Crash after tagging ~5 images. 034 ==> 339803 - digiKam crashes frequently on batch rename. 035 ==> 346698 - Delete Duplicate on NAS - Crash. 036 ==> 379420 - Showphoto crashed after resize. -037 ==> +037 ==> 391440 - Metadata (date) is not refreshed after a file has been modified. +038 ==> diff --git a/app/main/digikamapp.cpp b/app/main/digikamapp.cpp index cd40e30147..45590a433d 100644 --- a/app/main/digikamapp.cpp +++ b/app/main/digikamapp.cpp @@ -1,3490 +1,3492 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2002-16-10 * Description : main digiKam interface implementation * * Copyright (C) 2002-2005 by Renchi Raju * Copyright (C) 2006 by Tom Albers * Copyright (C) 2009-2012 by Andi Clemens * Copyright (C) 2013 by Michael G. Hansen * Copyright (C) 2014-2015 by Mohamed Anwer * Copyright (C) 2002-2018 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * 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. * * ============================================================ */ #include "digikamapp.h" /// @todo Order should be changed according to krazy2, but compilation fails. Try again later. MH #include "digikamapp_p.h" // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include #include #include #include #include #include #include #include #include #include #include // Local includes #include "actioncategorizedview.h" #include "drawdecoder.h" #include "dlayoutbox.h" #include "digikam_debug.h" #include "album.h" #include "coredb.h" #include "albummodel.h" #include "albumselectdialog.h" #include "albumthumbnailloader.h" #include "cameratype.h" #include "dbinfoiface.h" #include "imagegps.h" #include "importui.h" #include "cameranamehelper.h" #include "categorizeditemmodel.h" #include "collectionscanner.h" #include "collectionmanager.h" #include "componentsinfo.h" #include "coredbthumbinfoprovider.h" #include "dio.h" #include "dlogoaction.h" #include "fileactionmngr.h" #include "filterstatusbar.h" #include "iccsettings.h" #include "imageattributeswatch.h" #include "imageinfo.h" #include "imagepropertiestab.h" #include "imagewindow.h" #include "lighttablewindow.h" #include "queuemgrwindow.h" #include "loadingcache.h" #include "loadingcacheinterface.h" #include "loadsavethread.h" #include "metaengine_rotation.h" #include "scancontroller.h" #include "setup.h" #include "setupeditor.h" #include "setupicc.h" #include "thememanager.h" #include "thumbnailloadthread.h" #include "thumbnailsize.h" #include "dmetadata.h" #include "tagscache.h" #include "tagsactionmngr.h" #include "databaseserverstarter.h" #include "metadatasettings.h" #include "statusbarprogresswidget.h" #include "dbmigrationdlg.h" #include "progressmanager.h" #include "progressview.h" #include "maintenancedlg.h" #include "maintenancemngr.h" #include "newitemsfinder.h" #include "dbcleaner.h" #include "tagsmanager.h" #include "imagesortsettings.h" #include "metadatahubmngr.h" #include "metadataedit.h" #include "expoblendingmanager.h" #include "calwizard.h" #include "mailwizard.h" #include "advprintwizard.h" #include "dfiledialog.h" #include "dmediaservermngr.h" #include "dmediaserverdlg.h" #ifdef HAVE_MARBLE # include "geolocationedit.h" #endif #ifdef HAVE_HTMLGALLERY # include "htmlwizard.h" #endif #ifdef HAVE_DBUS # include "digikamadaptor.h" #endif #ifdef HAVE_PANORAMA # include "panomanager.h" #endif #ifdef HAVE_MEDIAPLAYER # include "vidslidewizard.h" #endif #ifdef HAVE_KIPI # include "kipipluginloader.h" #endif #ifdef HAVE_KFILEMETADATA # include "baloowrap.h" #endif namespace Digikam { DigikamApp* DigikamApp::m_instance = 0; DigikamApp::DigikamApp() : DXmlGuiWindow(0), d(new Private) { setObjectName(QLatin1String("Digikam")); setConfigGroupName(ApplicationSettings::instance()->generalConfigGroupName()); setFullScreenOptions(FS_ALBUMGUI); setXMLFile(QLatin1String("digikamui5.rc")); m_instance = this; d->config = KSharedConfig::openConfig(); KConfigGroup group = d->config->group(configGroupName()); #ifdef HAVE_DBUS new DigikamAdaptor(this); QDBusConnection::sessionBus().registerObject(QLatin1String("/Digikam"), this); QDBusConnection::sessionBus().registerService(QLatin1String("org.kde.digikam-") + QString::number(QCoreApplication::instance()->applicationPid())); #endif // collection scan if (!CollectionScanner::databaseInitialScanDone()) { ScanController::instance()->completeCollectionScanDeferFiles(); } if (ApplicationSettings::instance()->getShowSplashScreen() && !qApp->isSessionRestored()) { d->splashScreen = new DSplashScreen(); d->splashScreen->show(); } else { // Windows need here QCoreApplication::processEvents(). qApp->processEvents(); } if (d->splashScreen) { d->splashScreen->setMessage(i18n("Initializing...")); } // ensure creation AlbumManager::instance(); LoadingCacheInterface::initialize(); IccSettings::instance()->loadAllProfilesProperties(); MetadataSettings::instance(); DMetadataSettings::instance(); ProgressManager::instance(); ThumbnailLoadThread::setDisplayingWidget(this); DIO::instance(); // creation of the engine on first use - when drawing - // can take considerable time and cause a noticeable hang in the UI thread. QFontMetrics fm(font()); fm.width(QLatin1String("a")); connect(ApplicationSettings::instance(), SIGNAL(setupChanged()), this, SLOT(slotSetupChanged())); connect(IccSettings::instance(), SIGNAL(settingsChanged()), this, SLOT(slotColorManagementOptionsChanged())); d->cameraMenu = new QMenu(this); d->usbMediaMenu = new QMenu(this); d->cardReaderMenu = new QMenu(this); d->quickImportMenu = new QMenu(this); d->cameraList = new CameraList(this, QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/cameras.xml")); connect(d->cameraList, SIGNAL(signalCameraAdded(CameraType*)), this, SLOT(slotCameraAdded(CameraType*))); connect(d->cameraList, SIGNAL(signalCameraRemoved(QAction*)), this, SLOT(slotCameraRemoved(QAction*))); d->modelCollection = new DigikamModelCollection; // This manager must be created after collection setup and before accelerators setup. d->tagsActionManager = new TagsActionMngr(this); // First create everything, then connect. // Otherwise some items may send signals and the slots can try // to access items which were not created yet. setupView(); setupAccelerators(); setupActions(); setupStatusBar(); initGui(); setupViewConnections(); applyMainWindowSettings(group); slotColorManagementOptionsChanged(); // Check ICC profiles repository availability if (d->splashScreen) { d->splashScreen->setMessage(i18n("Checking ICC repository...")); } d->validIccPath = SetupICC::iccRepositoryIsValid(); // Read albums from database if (d->splashScreen) { d->splashScreen->setMessage(i18n("Reading database...")); } AlbumManager::instance()->startScan(); // Load Plugins. loadPlugins(); // preload additional windows preloadWindows(); readFullScreenSettings(group); #ifdef HAVE_KFILEMETADATA // Create BalooWrap object, because it need to register a listener // to update digiKam data when changes in Baloo occur BalooWrap* const baloo = BalooWrap::instance(); Q_UNUSED(baloo); #endif //HAVE_KFILEMETADATA setAutoSaveSettings(group, true); LoadSaveThread::setInfoProvider(new DatabaseLoadSaveFileInfoProvider); setupSelectToolsAction(); } DigikamApp::~DigikamApp() { ProgressManager::instance()->slotAbortAll(); ImageAttributesWatch::shutDown(); // Close and delete image editor instance. if (ImageWindow::imageWindowCreated()) { // Delete after close ImageWindow::imageWindow()->setAttribute(Qt::WA_DeleteOnClose, true); // close the window ImageWindow::imageWindow()->close(); } // Close and delete light table instance. if (LightTableWindow::lightTableWindowCreated()) { LightTableWindow::lightTableWindow()->setAttribute(Qt::WA_DeleteOnClose, true); LightTableWindow::lightTableWindow()->close(); } // Close and delete Batch Queue Manager instance. if (QueueMgrWindow::queueManagerWindowCreated()) { QueueMgrWindow::queueManagerWindow()->setAttribute(Qt::WA_DeleteOnClose, true); QueueMgrWindow::queueManagerWindow()->close(); } if (TagsManager::isCreated()) { TagsManager::instance()->close(); } if (MetadataHubMngr::isCreated()) { delete MetadataHubMngr::internalPtr; } #ifdef HAVE_KFILEMETADATA if (BalooWrap::isCreated()) { BalooWrap::internalPtr.clear(); } #endif if (ExpoBlendingManager::isCreated()) { delete ExpoBlendingManager::internalPtr; } #ifdef HAVE_PANORAMA if (PanoManager::isCreated()) { delete PanoManager::internalPtr; } #endif delete d->view; ApplicationSettings::instance()->setRecurseAlbums(d->recurseAlbumsAction->isChecked()); ApplicationSettings::instance()->setRecurseTags(d->recurseTagsAction->isChecked()); ApplicationSettings::instance()->setShowThumbbar(d->showBarAction->isChecked()); ApplicationSettings::instance()->saveSettings(); ScanController::instance()->shutDown(); AlbumManager::instance()->cleanUp(); ImageAttributesWatch::cleanUp(); ThumbnailLoadThread::cleanUp(); AlbumThumbnailLoader::instance()->cleanUp(); LoadingCacheInterface::cleanUp(); DIO::cleanUp(); DMediaServerMngr::instance()->saveAtShutdown(); // close database server if (ApplicationSettings::instance()->getDbEngineParameters().internalServer) { DatabaseServerStarter::instance()->stopServerManagerProcess(); } AlbumManager::instance()->removeFakeConnection(); m_instance = 0; delete d->modelCollection; delete d; } DigikamApp* DigikamApp::instance() { return m_instance; } DigikamView* DigikamApp::view() const { return d->view; } void DigikamApp::show() { // Remove Splashscreen. if (d->splashScreen) { d->splashScreen->finish(this); delete d->splashScreen; d->splashScreen = 0; } // Display application window. KMainWindow::show(); // Report errors from ICC repository path. if (!d->validIccPath) { QString message = i18n("

The ICC profiles folder seems to be invalid.

" "

If you want to try setting it again, choose \"Yes\" here, otherwise " "choose \"No\", and the \"Color Management\" feature " "will be disabled until you solve this issue.

"); if (QMessageBox::warning(this, qApp->applicationName(), message, QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { if (!setupICC()) { d->config->group(QLatin1String("Color Management")).writeEntry(QLatin1String("EnableCM"), false); d->config->sync(); } } else { d->config->group(QLatin1String("Color Management")).writeEntry(QLatin1String("EnableCM"), false); d->config->sync(); } } // Init album icon view zoom factor. slotThumbSizeChanged(ApplicationSettings::instance()->getDefaultIconSize()); slotZoomSliderChanged(ApplicationSettings::instance()->getDefaultIconSize()); d->autoShowZoomToolTip = true; // Enable finished the collection scan as deferred process if (ApplicationSettings::instance()->getScanAtStart() || !CollectionScanner::databaseInitialScanDone()) { NewItemsFinder* const tool = new NewItemsFinder(NewItemsFinder::ScanDeferredFiles); QTimer::singleShot(1000, tool, SLOT(start())); } if (ApplicationSettings::instance()->getCleanAtStart()) { DbCleaner* const tool = new DbCleaner(false,false); QTimer::singleShot(1000, tool, SLOT(start())); } // Start the Media Server if necessary DMediaServerMngr::instance()->loadAtStartup(); } void DigikamApp::restoreSession() { //TODO: show and restore ImageEditor, Lighttable, and Batch Queue Manager main windows if (qApp->isSessionRestored()) { int n = 1; while (KMainWindow::canBeRestored(n)) { const QString className = KMainWindow::classNameOfToplevel(n); if (className == QLatin1String(Digikam::DigikamApp::staticMetaObject.className())) { restore(n, false); break; } ++n; } } } void DigikamApp::closeEvent(QCloseEvent* e) { // may show a progress dialog to finish actions FileActionMngr::instance()->requestShutDown(); // may show a progress dialog to apply pending metadata if (MetadataHubMngr::isCreated()) MetadataHubMngr::instance()->requestShutDown(); DXmlGuiWindow::closeEvent(e); e->accept(); } void DigikamApp::autoDetect() { // Called from main if command line option is set, or via DBus if (d->splashScreen) { d->splashScreen->setMessage(i18n("Auto-Detecting Camera...")); } QTimer::singleShot(0, this, SLOT(slotCameraAutoDetect())); } void DigikamApp::downloadFrom(const QString& cameraGuiPath) { // Called from main if command line option is set, or via DBus if (!cameraGuiPath.isEmpty()) { if (d->splashScreen) { d->splashScreen->setMessage(i18n("Opening Download Dialog...")); } emit queuedOpenCameraUiFromPath(cameraGuiPath); } } void DigikamApp::downloadFromUdi(const QString& udi) { // Called from main if command line option is set, or via DBus if (!udi.isEmpty()) { if (d->splashScreen) { d->splashScreen->setMessage(i18n("Opening Download Dialog...")); } emit queuedOpenSolidDevice(udi); } } bool DigikamApp::queryClose() { bool ret = true; if (ImageWindow::imageWindowCreated()) { ret &= ImageWindow::imageWindow()->queryClose(); } if (QueueMgrWindow::queueManagerWindowCreated()) { ret &= QueueMgrWindow::queueManagerWindow()->queryClose(); } return ret; } void DigikamApp::setupView() { if (d->splashScreen) { d->splashScreen->setMessage(i18n("Initializing Main View...")); } d->view = new DigikamView(this, d->modelCollection); setCentralWidget(d->view); d->view->applySettings(); } void DigikamApp::setupViewConnections() { connect(d->view, SIGNAL(signalAlbumSelected(Album*)), this, SLOT(slotAlbumSelected(Album*))); connect(d->view, SIGNAL(signalSelectionChanged(int)), this, SLOT(slotSelectionChanged(int))); connect(d->view, SIGNAL(signalImageSelected(ImageInfoList,ImageInfoList)), this, SLOT(slotImageSelected(ImageInfoList,ImageInfoList))); connect(d->view, SIGNAL(signalSwitchedToPreview()), this, SLOT(slotSwitchedToPreview())); connect(d->view, SIGNAL(signalSwitchedToIconView()), this, SLOT(slotSwitchedToIconView())); connect(d->view, SIGNAL(signalSwitchedToMapView()), this, SLOT(slotSwitchedToMapView())); connect(d->view, SIGNAL(signalSwitchedToTableView()), this, SLOT(slotSwitchedToTableView())); connect(d->view, SIGNAL(signalSwitchedToTrashView()), this, SLOT(slotSwitchedToTrashView())); } void DigikamApp::setupStatusBar() { d->statusLabel = new DAdjustableLabel(statusBar()); d->statusLabel->setAlignment(Qt::AlignLeft|Qt::AlignVCenter); statusBar()->addWidget(d->statusLabel, 80); //------------------------------------------------------------------------------ d->metadataStatusBar = new MetadataStatusBar(statusBar()); statusBar()->addWidget(d->metadataStatusBar, 50); //------------------------------------------------------------------------------ d->filterStatusBar = new FilterStatusBar(statusBar()); statusBar()->addWidget(d->filterStatusBar, 50); d->view->connectIconViewFilter(d->filterStatusBar); //------------------------------------------------------------------------------ ProgressView* const view = new ProgressView(statusBar(), this); view->hide(); StatusbarProgressWidget* const littleProgress = new StatusbarProgressWidget(view, statusBar()); littleProgress->show(); statusBar()->addPermanentWidget(littleProgress); //------------------------------------------------------------------------------ d->zoomBar = new DZoomBar(statusBar()); d->zoomBar->setZoomToFitAction(d->zoomFitToWindowAction); d->zoomBar->setZoomTo100Action(d->zoomTo100percents); d->zoomBar->setZoomPlusAction(d->zoomPlusAction); d->zoomBar->setZoomMinusAction(d->zoomMinusAction); d->zoomBar->setBarMode(DZoomBar::ThumbsSizeCtrl); statusBar()->addPermanentWidget(d->zoomBar); //------------------------------------------------------------------------------ connect(d->zoomBar, SIGNAL(signalZoomSliderChanged(int)), this, SLOT(slotZoomSliderChanged(int))); connect(this, SIGNAL(signalWindowHasMoved()), d->zoomBar, SLOT(slotUpdateTrackerPos())); connect(d->zoomBar, SIGNAL(signalZoomValueEdited(double)), d->view, SLOT(setZoomFactor(double))); connect(d->view, SIGNAL(signalZoomChanged(double)), this, SLOT(slotZoomChanged(double))); connect(d->view, SIGNAL(signalThumbSizeChanged(int)), this, SLOT(slotThumbSizeChanged(int))); } void DigikamApp::setupAccelerators() { KActionCollection* const ac = actionCollection(); // Action are added by tag in ui.rc XML file QAction* const escapeAction = new QAction(i18n("Exit Preview Mode"), this); ac->addAction(QLatin1String("exit_preview_mode"), escapeAction); ac->setDefaultShortcut(escapeAction, Qt::Key_Escape); connect(escapeAction, SIGNAL(triggered()), this, SIGNAL(signalEscapePressed())); QAction* const nextImageAction = new QAction(i18n("Next Image"), this); nextImageAction->setIcon(QIcon::fromTheme(QLatin1String("go-next"))); ac->addAction(QLatin1String("next_image"), nextImageAction); ac->setDefaultShortcut(nextImageAction, Qt::Key_Space); connect(nextImageAction, SIGNAL(triggered()), this, SIGNAL(signalNextItem())); QAction* const previousImageAction = new QAction(i18n("Previous Image"), this); previousImageAction->setIcon(QIcon::fromTheme(QLatin1String("go-previous"))); ac->addAction(QLatin1String("previous_image"), previousImageAction); ac->setDefaultShortcuts(previousImageAction, QList() << Qt::Key_Backspace << Qt::SHIFT+Qt::Key_Space); connect(previousImageAction, SIGNAL(triggered()), this, SIGNAL(signalPrevItem())); QAction* const firstImageAction = new QAction(i18n("First Image"), this); ac->addAction(QLatin1String("first_image"), firstImageAction); ac->setDefaultShortcuts(firstImageAction, QList() << Qt::CTRL + Qt::Key_Home); connect(firstImageAction, SIGNAL(triggered()), this, SIGNAL(signalFirstItem())); QAction* const lastImageAction = new QAction(i18n("Last Image"), this); ac->addAction(QLatin1String("last_image"), lastImageAction); ac->setDefaultShortcuts(lastImageAction, QList() << Qt::CTRL + Qt::Key_End); connect(lastImageAction, SIGNAL(triggered()), this, SIGNAL(signalLastItem())); d->cutItemsAction = new QAction(i18n("Cu&t"), this); d->cutItemsAction->setIcon(QIcon::fromTheme(QLatin1String("edit-cut"))); d->cutItemsAction->setWhatsThis(i18n("Cut selection to clipboard")); ac->addAction(QLatin1String("cut_album_selection"), d->cutItemsAction); // NOTE: shift+del keyboard shortcut must not be assigned to Cut action // else the shortcut for Delete permanently collides with secondary shortcut of Cut ac->setDefaultShortcut(d->cutItemsAction, Qt::CTRL + Qt::Key_X); connect(d->cutItemsAction, SIGNAL(triggered()), this, SIGNAL(signalCutAlbumItemsSelection())); d->copyItemsAction = buildStdAction(StdCopyAction, this, SIGNAL(signalCopyAlbumItemsSelection()), this); ac->addAction(QLatin1String("copy_album_selection"), d->copyItemsAction); d->pasteItemsAction = buildStdAction(StdPasteAction, this, SIGNAL(signalPasteAlbumItemsSelection()), this); ac->addAction(QLatin1String("paste_album_selection"), d->pasteItemsAction); // Labels shortcuts must be registered here to be saved in XML GUI files if user customize it. d->tagsActionManager->registerLabelsActions(ac); QAction* const editTitles = new QAction(i18n("Edit Titles"), this); ac->addAction(QLatin1String("edit_titles"), editTitles); ac->setDefaultShortcut(editTitles, Qt::META + Qt::Key_T); connect(editTitles, SIGNAL(triggered()), d->view, SLOT(slotRightSideBarActivateTitles())); QAction* const editComments = new QAction(i18n("Edit Comments"), this); ac->addAction(QLatin1String("edit_comments"), editComments); ac->setDefaultShortcut(editComments, Qt::META + Qt::Key_C); connect(editComments, SIGNAL(triggered()), d->view, SLOT(slotRightSideBarActivateComments())); QAction* const assignedTags = new QAction(i18n("Show Assigned Tags"), this); ac->addAction(QLatin1String("assigned _tags"), assignedTags); ac->setDefaultShortcut(assignedTags, Qt::META + Qt::Key_A); connect(assignedTags, SIGNAL(triggered()), d->view, SLOT(slotRightSideBarActivateAssignedTags())); } void DigikamApp::setupActions() { KActionCollection* const ac = actionCollection(); d->solidCameraActionGroup = new QActionGroup(this); connect(d->solidCameraActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotOpenSolidCamera(QAction*))); d->solidUsmActionGroup = new QActionGroup(this); connect(d->solidUsmActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotOpenSolidUsmDevice(QAction*))); d->manualCameraActionGroup = new QActionGroup(this); connect(d->manualCameraActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotOpenManualCamera(QAction*))); // ----------------------------------------------------------------- d->backwardActionMenu = new KToolBarPopupAction(QIcon::fromTheme(QLatin1String("go-previous")), i18n("&Back"), this); d->backwardActionMenu->setEnabled(false); ac->addAction(QLatin1String("album_back"), d->backwardActionMenu); ac->setDefaultShortcut(d->backwardActionMenu, Qt::ALT+Qt::Key_Left); connect(d->backwardActionMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShowBackwardMenu())); // we are using a signal mapper to identify which of a bunch of actions was triggered d->backwardSignalMapper = new QSignalMapper(this); // connect mapper to view connect(d->backwardSignalMapper, SIGNAL(mapped(int)), d->view, SLOT(slotAlbumHistoryBack(int))); // connect action to mapper connect(d->backwardActionMenu, SIGNAL(triggered()), d->backwardSignalMapper, SLOT(map())); // inform mapper about number of steps d->backwardSignalMapper->setMapping(d->backwardActionMenu, 1); // ----------------------------------------------------------------- d->forwardActionMenu = new KToolBarPopupAction(QIcon::fromTheme(QLatin1String("go-next")), i18n("Forward"), this); d->forwardActionMenu->setEnabled(false); ac->addAction(QLatin1String("album_forward"), d->forwardActionMenu); ac->setDefaultShortcut(d->forwardActionMenu, Qt::ALT+Qt::Key_Right); connect(d->forwardActionMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(slotAboutToShowForwardMenu())); d->forwardSignalMapper = new QSignalMapper(this); connect(d->forwardSignalMapper, SIGNAL(mapped(int)), d->view, SLOT(slotAlbumHistoryForward(int))); connect(d->forwardActionMenu, SIGNAL(triggered()), d->forwardSignalMapper, SLOT(map())); d->forwardSignalMapper->setMapping(d->forwardActionMenu, 1); // ----------------------------------------------------------------- d->refreshAction = new QAction(QIcon::fromTheme(QLatin1String("view-refresh")), i18n("Refresh"), this); d->refreshAction->setWhatsThis(i18n("Refresh the current contents.")); connect(d->refreshAction, SIGNAL(triggered()), d->view, SLOT(slotRefresh())); ac->addAction(QLatin1String("view_refresh"), d->refreshAction); ac->setDefaultShortcut(d->refreshAction, Qt::Key_F5); // ----------------------------------------------------------------- QSignalMapper* const browseActionsMapper = new QSignalMapper(this); connect(browseActionsMapper, SIGNAL(mapped(QWidget*)), d->view, SLOT(slotLeftSideBarActivate(QWidget*))); foreach(SidebarWidget* const leftWidget, d->view->leftSidebarWidgets()) { QString actionName = QLatin1String("browse_") + leftWidget->objectName() .remove(QLatin1Char(' ')) .remove(QLatin1String("Sidebar")) .remove(QLatin1String("FolderView")) .remove(QLatin1String("View")).toLower(); qCDebug(DIGIKAM_GENERAL_LOG) << actionName; QAction* const action = new QAction(leftWidget->getIcon(), leftWidget->getCaption(), this); ac->addAction(actionName, action); ac->setDefaultShortcut(action, QKeySequence(leftWidget->property("Shortcut").toInt())); connect(action, SIGNAL(triggered()), browseActionsMapper, SLOT(map())); browseActionsMapper->setMapping(action, leftWidget); } // ----------------------------------------------------------------- d->newAction = new QAction(QIcon::fromTheme(QLatin1String("folder-new")), i18n("&New..."), this); d->newAction->setWhatsThis(i18n("Creates a new empty Album in the collection.")); connect(d->newAction, SIGNAL(triggered()), d->view, SLOT(slotNewAlbum())); ac->addAction(QLatin1String("album_new"), d->newAction); ac->setDefaultShortcuts(d->newAction, QList() << Qt::CTRL + Qt::Key_N); // ----------------------------------------------------------------- d->moveSelectionToAlbumAction = new QAction(QIcon::fromTheme(QLatin1String("folder-new")), i18n("&Move to Album..."), this); d->moveSelectionToAlbumAction->setWhatsThis(i18n("Move selected images into an album.")); connect(d->moveSelectionToAlbumAction, SIGNAL(triggered()), d->view, SLOT(slotMoveSelectionToAlbum())); ac->addAction(QLatin1String("move_selection_to_album"), d->moveSelectionToAlbumAction); // ----------------------------------------------------------------- d->deleteAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete Album"), this); connect(d->deleteAction, SIGNAL(triggered()), d->view, SLOT(slotDeleteAlbum())); ac->addAction(QLatin1String("album_delete"), d->deleteAction); // ----------------------------------------------------------------- d->renameAction = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Rename..."), this); connect(d->renameAction, SIGNAL(triggered()), d->view, SLOT(slotRenameAlbum())); ac->addAction(QLatin1String("album_rename"), d->renameAction); ac->setDefaultShortcut(d->renameAction, Qt::SHIFT + Qt::Key_F2); // ----------------------------------------------------------------- d->propsEditAction = new QAction(QIcon::fromTheme(QLatin1String("configure")), i18n("Properties"), this); d->propsEditAction->setWhatsThis(i18n("Edit album properties and collection information.")); connect(d->propsEditAction, SIGNAL(triggered()), d->view, SLOT(slotAlbumPropsEdit())); ac->addAction(QLatin1String("album_propsEdit"), d->propsEditAction); ac->setDefaultShortcut(d->propsEditAction, Qt::ALT + Qt::Key_Return); // ----------------------------------------------------------------- d->writeAlbumMetadataAction = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Write Metadata to Images"), this); d->writeAlbumMetadataAction->setWhatsThis(i18n("Updates metadata of images in the current " "album with the contents of digiKam database " "(image metadata will be overwritten with data from " "the database).")); connect(d->writeAlbumMetadataAction, SIGNAL(triggered()), d->view, SLOT(slotAlbumWriteMetadata())); ac->addAction(QLatin1String("album_write_metadata"), d->writeAlbumMetadataAction); // ----------------------------------------------------------------- d->readAlbumMetadataAction = new QAction(QIcon::fromTheme(QLatin1String("edit-redo")), i18n("Reread Metadata From Images"), this); d->readAlbumMetadataAction->setWhatsThis(i18n("Updates the digiKam database from the metadata " "of the files in the current album " "(information in the database will be overwritten with data from " "the files' metadata).")); connect(d->readAlbumMetadataAction, SIGNAL(triggered()), d->view, SLOT(slotAlbumReadMetadata())); ac->addAction(QLatin1String("album_read_metadata"), d->readAlbumMetadataAction); // ----------------------------------------------------------------- d->openInFileManagerAction = new QAction(QIcon::fromTheme(QLatin1String("folder-open")), i18n("Open in File Manager"), this); connect(d->openInFileManagerAction, SIGNAL(triggered()), d->view, SLOT(slotAlbumOpenInFileManager())); ac->addAction(QLatin1String("album_openinfilemanager"), d->openInFileManagerAction); // ----------------------------------------------------------- d->openTagMngrAction = new QAction(QIcon::fromTheme(QLatin1String("tag")), i18n("Tag Manager"), this); connect(d->openTagMngrAction, SIGNAL(triggered()), d->view, SLOT(slotOpenTagsManager())); ac->addAction(QLatin1String("open_tag_mngr"), d->openTagMngrAction); // ----------------------------------------------------------- d->newTagAction = new QAction(QIcon::fromTheme(QLatin1String("tag-new")), i18nc("new tag", "N&ew..."), this); connect(d->newTagAction, SIGNAL(triggered()), d->view, SLOT(slotNewTag())); ac->addAction(QLatin1String("tag_new"), d->newTagAction); // ----------------------------------------------------------- d->editTagAction = new QAction(QIcon::fromTheme(QLatin1String("tag-properties")), i18n("Properties"), this); connect(d->editTagAction, SIGNAL(triggered()), d->view, SLOT(slotEditTag())); ac->addAction(QLatin1String("tag_edit"), d->editTagAction); // ----------------------------------------------------------- d->deleteTagAction = new QAction(QIcon::fromTheme(QLatin1String("user-trash")), i18n("Delete"), this); connect(d->deleteTagAction, SIGNAL(triggered()), d->view, SLOT(slotDeleteTag())); ac->addAction(QLatin1String("tag_delete"), d->deleteTagAction); // ----------------------------------------------------------- d->assignTagAction = new QAction(QIcon::fromTheme(QLatin1String("tag-new")), i18n("Assign Tag"), this); connect(d->assignTagAction, SIGNAL(triggered()), d->view, SLOT(slotAssignTag())); ac->addAction(QLatin1String("tag_assign"), d->assignTagAction); ac->setDefaultShortcut(d->assignTagAction, Qt::Key_T); // ----------------------------------------------------------- d->imageViewSelectionAction = new KSelectAction(QIcon::fromTheme(QLatin1String("view-preview")), i18n("Views"), this); ac->addAction(QLatin1String("view_selection"), d->imageViewSelectionAction); d->imageIconViewAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-icons")), i18nc("@action Go to thumbnails (icon) view", "Thumbnails"), this); d->imageIconViewAction->setCheckable(true); ac->addAction(QLatin1String("icon_view"), d->imageIconViewAction); connect(d->imageIconViewAction, SIGNAL(triggered()), d->view, SLOT(slotIconView())); d->imageViewSelectionAction->addAction(d->imageIconViewAction); d->imagePreviewAction = new QAction(QIcon::fromTheme(QLatin1String("view-preview")), i18nc("View the selected image", "Preview"), this); d->imagePreviewAction->setCheckable(true); ac->addAction(QLatin1String("image_view"), d->imagePreviewAction); ac->setDefaultShortcut(d->imagePreviewAction, Qt::Key_F3); connect(d->imagePreviewAction, SIGNAL(triggered()), d->view, SLOT(slotImagePreview())); d->imageViewSelectionAction->addAction(d->imagePreviewAction); #ifdef HAVE_MARBLE d->imageMapViewAction = new QAction(QIcon::fromTheme(QLatin1String("globe")), i18nc("@action Switch to map view", "Map"), this); d->imageMapViewAction->setCheckable(true); ac->addAction(QLatin1String("map_view"), d->imageMapViewAction); connect(d->imageMapViewAction, SIGNAL(triggered()), d->view, SLOT(slotMapWidgetView())); d->imageViewSelectionAction->addAction(d->imageMapViewAction); #endif // HAVE_MARBLE d->imageTableViewAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-details")), i18nc("@action Switch to table view", "Table"), this); d->imageTableViewAction->setCheckable(true); ac->addAction(QLatin1String("table_view"), d->imageTableViewAction); connect(d->imageTableViewAction, SIGNAL(triggered()), d->view, SLOT(slotTableView())); d->imageViewSelectionAction->addAction(d->imageTableViewAction); // ----------------------------------------------------------- d->imageViewAction = new QAction(QIcon::fromTheme(QLatin1String("quickopen-file")), i18n("Open..."), this); d->imageViewAction->setWhatsThis(i18n("Open the selected item.")); connect(d->imageViewAction, SIGNAL(triggered()), d->view, SLOT(slotImageEdit())); ac->addAction(QLatin1String("image_edit"), d->imageViewAction); ac->setDefaultShortcut(d->imageViewAction, Qt::Key_F4); d->openWithAction = new QAction(QIcon::fromTheme(QLatin1String("preferences-desktop-filetype-association")), i18n("Open With Default Application"), this); d->openWithAction->setWhatsThis(i18n("Open the selected item with default assigned application.")); connect(d->openWithAction, SIGNAL(triggered()), d->view, SLOT(slotFileWithDefaultApplication())); ac->addAction(QLatin1String("open_with_default_application"), d->openWithAction); ac->setDefaultShortcut(d->openWithAction, Qt::META + Qt::Key_F4); d->ieAction = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Image Editor"), this); d->ieAction->setWhatsThis(i18n("Open the image editor.")); connect(d->ieAction, SIGNAL(triggered()), d->view, SLOT(slotEditor())); ac->addAction(QLatin1String("imageeditor"), d->ieAction); // ----------------------------------------------------------- d->ltAction = new QAction(QIcon::fromTheme(QLatin1String("lighttable")), i18n("Light Table"), this); connect(d->ltAction, SIGNAL(triggered()), d->view, SLOT(slotLightTable())); ac->addAction(QLatin1String("light_table"), d->ltAction); ac->setDefaultShortcut(d->ltAction, Qt::Key_L); d->imageLightTableAction = new QAction(QIcon::fromTheme(QLatin1String("lighttable")), i18n("Place onto Light Table"), this); d->imageLightTableAction->setWhatsThis(i18n("Place the selected items on the light table thumbbar.")); connect(d->imageLightTableAction, SIGNAL(triggered()), d->view, SLOT(slotImageLightTable())); ac->addAction(QLatin1String("image_lighttable"), d->imageLightTableAction); ac->setDefaultShortcut(d->imageLightTableAction, Qt::CTRL+Qt::Key_L); d->imageAddLightTableAction = new QAction(QIcon::fromTheme(QLatin1String("list-add")), i18n("Add to Light Table"), this); d->imageAddLightTableAction->setWhatsThis(i18n("Add selected items to the light table thumbbar.")); connect(d->imageAddLightTableAction, SIGNAL(triggered()), d->view, SLOT(slotImageAddToLightTable())); ac->addAction(QLatin1String("image_add_to_lighttable"), d->imageAddLightTableAction); ac->setDefaultShortcut(d->imageAddLightTableAction, Qt::SHIFT+Qt::CTRL+Qt::Key_L); // ----------------------------------------------------------- d->bqmAction = new QAction(QIcon::fromTheme(QLatin1String("run-build")), i18n("Batch Queue Manager"), this); connect(d->bqmAction, SIGNAL(triggered()), d->view, SLOT(slotQueueMgr())); ac->addAction(QLatin1String("queue_manager"), d->bqmAction); ac->setDefaultShortcut(d->bqmAction, Qt::Key_B); d->imageAddCurrentQueueAction = new QAction(QIcon::fromTheme(QLatin1String("go-up")), i18n("Add to Current Queue"), this); d->imageAddCurrentQueueAction->setWhatsThis(i18n("Add selected items to current queue from batch manager.")); connect(d->imageAddCurrentQueueAction, SIGNAL(triggered()), d->view, SLOT(slotImageAddToCurrentQueue())); ac->addAction(QLatin1String("image_add_to_current_queue"), d->imageAddCurrentQueueAction); ac->setDefaultShortcut(d->imageAddCurrentQueueAction, Qt::CTRL+Qt::Key_B); d->imageAddNewQueueAction = new QAction(QIcon::fromTheme(QLatin1String("list-add")), i18n("Add to New Queue"), this); d->imageAddNewQueueAction->setWhatsThis(i18n("Add selected items to a new queue from batch manager.")); connect(d->imageAddNewQueueAction, SIGNAL(triggered()), d->view, SLOT(slotImageAddToNewQueue())); ac->addAction(QLatin1String("image_add_to_new_queue"), d->imageAddNewQueueAction); ac->setDefaultShortcut(d->imageAddNewQueueAction, Qt::SHIFT+Qt::CTRL+Qt::Key_B); // ----------------------------------------------------------------- d->quickImportMenu->setTitle(i18nc("@action Import photos from camera", "Import")); d->quickImportMenu->setIcon(QIcon::fromTheme(QLatin1String("camera-photo"))); ac->addAction(QLatin1String("import_auto"), d->quickImportMenu->menuAction()); createKSaneAction(); // ----------------------------------------------------------------- d->imageWriteMetadataAction = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Write Metadata to Selected Images"), this); d->imageWriteMetadataAction->setWhatsThis(i18n("Updates metadata of images in the current " "album with the contents of digiKam database " "(image metadata will be overwritten with data from " "the database).")); connect(d->imageWriteMetadataAction, SIGNAL(triggered()), d->view, SLOT(slotImageWriteMetadata())); ac->addAction(QLatin1String("image_write_metadata"), d->imageWriteMetadataAction); // ----------------------------------------------------------------- d->imageReadMetadataAction = new QAction(QIcon::fromTheme(QLatin1String("edit-redo")), i18n("Reread Metadata From Selected Images"), this); d->imageReadMetadataAction->setWhatsThis(i18n("Updates the digiKam database from the metadata " "of the files in the current album " "(information in the database will be overwritten with data from " "the files' metadata).")); connect(d->imageReadMetadataAction, SIGNAL(triggered()), d->view, SLOT(slotImageReadMetadata())); ac->addAction(QLatin1String("image_read_metadata"), d->imageReadMetadataAction); // ----------------------------------------------------------- d->imageFindSimilarAction = new QAction(QIcon::fromTheme(QLatin1String("tools-wizard")), i18n("Find Similar..."), this); d->imageFindSimilarAction->setWhatsThis(i18n("Find similar images using selected item as reference.")); connect(d->imageFindSimilarAction, SIGNAL(triggered()), d->view, SLOT(slotImageFindSimilar())); ac->addAction(QLatin1String("image_find_similar"), d->imageFindSimilarAction); // ----------------------------------------------------------- d->imageRenameAction = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Rename..."), this); d->imageRenameAction->setWhatsThis(i18n("Change the filename of the currently selected item.")); connect(d->imageRenameAction, SIGNAL(triggered()), d->view, SLOT(slotImageRename())); ac->addAction(QLatin1String("image_rename"), d->imageRenameAction); ac->setDefaultShortcut(d->imageRenameAction, Qt::Key_F2); // ----------------------------------------------------------- // Pop up dialog to ask user whether to move to trash d->imageDeleteAction = new QAction(QIcon::fromTheme(QLatin1String("user-trash")), i18nc("Non-pluralized", "Move to Trash"), this); connect(d->imageDeleteAction, SIGNAL(triggered()), d->view, SLOT(slotImageDelete())); ac->addAction(QLatin1String("image_delete"), d->imageDeleteAction); ac->setDefaultShortcut(d->imageDeleteAction, Qt::Key_Delete); // ----------------------------------------------------------- // Pop up dialog to ask user whether to permanently delete // FIXME: This action is never used?? How can someone delete a album directly, without moving it to the trash first? // This is especially important when deleting from a different partition or from a net source. // Also note that we use the wrong icon for the default album delete action, which should have a trashcan icon instead // of a red cross, it confuses users. d->imageDeletePermanentlyAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete Permanently"), this); connect(d->imageDeletePermanentlyAction, SIGNAL(triggered()), d->view, SLOT(slotImageDeletePermanently())); ac->addAction(QLatin1String("image_delete_permanently"), d->imageDeletePermanentlyAction); ac->setDefaultShortcut(d->imageDeletePermanentlyAction, Qt::SHIFT+Qt::Key_Delete); // ----------------------------------------------------------- // These two actions are hidden, no menu entry, no toolbar entry, no shortcut. // Power users may add them. d->imageDeletePermanentlyDirectlyAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete permanently without confirmation"), this); connect(d->imageDeletePermanentlyDirectlyAction, SIGNAL(triggered()), d->view, SLOT(slotImageDeletePermanentlyDirectly())); ac->addAction(QLatin1String("image_delete_permanently_directly"), d->imageDeletePermanentlyDirectlyAction); // ----------------------------------------------------------- d->imageTrashDirectlyAction = new QAction(QIcon::fromTheme(QLatin1String("user-trash")), i18n("Move to trash without confirmation"), this); connect(d->imageTrashDirectlyAction, SIGNAL(triggered()), d->view, SLOT(slotImageTrashDirectly())); ac->addAction(QLatin1String("image_trash_directly"), d->imageTrashDirectlyAction); // ----------------------------------------------------------------- d->albumSortAction = new KSelectAction(i18n("&Sort Albums"), this); d->albumSortAction->setWhatsThis(i18n("Sort Albums in tree-view.")); connect(d->albumSortAction, SIGNAL(triggered(int)), d->view, SLOT(slotSortAlbums(int))); ac->addAction(QLatin1String("album_sort"), d->albumSortAction); // Use same list order as in applicationsettings enum QStringList sortActionList; sortActionList.append(i18n("By Folder")); sortActionList.append(i18n("By Category")); sortActionList.append(i18n("By Date")); d->albumSortAction->setItems(sortActionList); // ----------------------------------------------------------- d->recurseAlbumsAction = new QAction(i18n("Include Album Sub-Tree"), this); d->recurseAlbumsAction->setCheckable(true); d->recurseAlbumsAction->setWhatsThis(i18n("Activate this option to show all sub-albums below " "the current album.")); connect(d->recurseAlbumsAction, SIGNAL(toggled(bool)), this, SLOT(slotRecurseAlbums(bool))); ac->addAction(QLatin1String("albums_recursive"), d->recurseAlbumsAction); d->recurseTagsAction = new QAction(i18n("Include Tag Sub-Tree"), this); d->recurseTagsAction->setCheckable(true); d->recurseTagsAction->setWhatsThis(i18n("Activate this option to show all images marked by the given tag " "and all its sub-tags.")); connect(d->recurseTagsAction, SIGNAL(toggled(bool)), this, SLOT(slotRecurseTags(bool))); ac->addAction(QLatin1String("tags_recursive"), d->recurseTagsAction); // ----------------------------------------------------------- d->imageSortAction = new KSelectAction(i18n("&Sort Items"), this); d->imageSortAction->setWhatsThis(i18n("The value by which the images in one album are sorted in the thumbnail view")); QSignalMapper* const imageSortMapper = new QSignalMapper(this); connect(imageSortMapper, SIGNAL(mapped(int)), d->view, SLOT(slotSortImages(int))); ac->addAction(QLatin1String("image_sort"), d->imageSortAction); // map to ImageSortSettings enum QAction* const sortByNameAction = d->imageSortAction->addAction(i18n("By Name")); QAction* const sortByPathAction = d->imageSortAction->addAction(i18n("By Path")); QAction* const sortByDateAction = d->imageSortAction->addAction(i18n("By Date")); QAction* const sortByFileSizeAction = d->imageSortAction->addAction(i18n("By File Size")); QAction* const sortByRatingAction = d->imageSortAction->addAction(i18n("By Rating")); QAction* const sortByImageSizeAction = d->imageSortAction->addAction(i18n("By Image Size")); QAction* const sortByAspectRatioAction = d->imageSortAction->addAction(i18n("By Aspect Ratio")); QAction* const sortBySimilarityAction = d->imageSortAction->addAction(i18n("By Similarity")); // activate the sort by similarity if the fuzzy search sidebar is active. Deactivate at start. sortBySimilarityAction->setEnabled(false); connect(d->view, SIGNAL(signalFuzzySidebarActive(bool)), sortBySimilarityAction, SLOT(setEnabled(bool))); connect(sortByNameAction, SIGNAL(triggered()), imageSortMapper, SLOT(map())); connect(sortByPathAction, SIGNAL(triggered()), imageSortMapper, SLOT(map())); connect(sortByDateAction, SIGNAL(triggered()), imageSortMapper, SLOT(map())); connect(sortByFileSizeAction, SIGNAL(triggered()), imageSortMapper, SLOT(map())); connect(sortByRatingAction, SIGNAL(triggered()), imageSortMapper, SLOT(map())); connect(sortByImageSizeAction, SIGNAL(triggered()), imageSortMapper, SLOT(map())); connect(sortByAspectRatioAction, SIGNAL(triggered()), imageSortMapper, SLOT(map())); connect(sortBySimilarityAction, SIGNAL(triggered()), imageSortMapper, SLOT(map())); imageSortMapper->setMapping(sortByNameAction, (int)ImageSortSettings::SortByFileName); imageSortMapper->setMapping(sortByPathAction, (int)ImageSortSettings::SortByFilePath); imageSortMapper->setMapping(sortByDateAction, (int)ImageSortSettings::SortByCreationDate); imageSortMapper->setMapping(sortByFileSizeAction, (int)ImageSortSettings::SortByFileSize); imageSortMapper->setMapping(sortByRatingAction, (int)ImageSortSettings::SortByRating); imageSortMapper->setMapping(sortByImageSizeAction, (int)ImageSortSettings::SortByImageSize); imageSortMapper->setMapping(sortByAspectRatioAction, (int)ImageSortSettings::SortByAspectRatio); imageSortMapper->setMapping(sortBySimilarityAction, (int)ImageSortSettings::SortBySimilarity); // ----------------------------------------------------------- d->imageSortOrderAction = new KSelectAction(i18n("Item Sort &Order"), this); d->imageSortOrderAction->setWhatsThis(i18n("Defines whether images are sorted in ascending or descending manner.")); QSignalMapper* const imageSortOrderMapper = new QSignalMapper(this); connect(imageSortOrderMapper, SIGNAL(mapped(int)), d->view, SLOT(slotSortImagesOrder(int))); ac->addAction(QLatin1String("image_sort_order"), d->imageSortOrderAction); QAction* const sortAscendingAction = d->imageSortOrderAction->addAction(QIcon::fromTheme(QLatin1String("view-sort-ascending")), i18n("Ascending")); QAction* const sortDescendingAction = d->imageSortOrderAction->addAction(QIcon::fromTheme(QLatin1String("view-sort-descending")), i18n("Descending")); connect(sortAscendingAction, SIGNAL(triggered()), imageSortOrderMapper, SLOT(map())); connect(sortDescendingAction, SIGNAL(triggered()), imageSortOrderMapper, SLOT(map())); imageSortOrderMapper->setMapping(sortAscendingAction, (int)ImageSortSettings::AscendingOrder); imageSortOrderMapper->setMapping(sortDescendingAction, (int)ImageSortSettings::DescendingOrder); // ----------------------------------------------------------- d->imageSeparationAction = new KSelectAction(i18n("Separate Items"), this); d->imageSeparationAction->setWhatsThis(i18n("The categories in which the images in the thumbnail view are displayed")); QSignalMapper* const imageSeparationMapper = new QSignalMapper(this); connect(imageSeparationMapper, SIGNAL(mapped(int)), d->view, SLOT(slotSeparateImages(int))); ac->addAction(QLatin1String("image_separation"), d->imageSeparationAction); // map to ImageSortSettings enum QAction* const noCategoriesAction = d->imageSeparationAction->addAction(i18n("Flat List")); QAction* const separateByAlbumAction = d->imageSeparationAction->addAction(i18n("By Album")); QAction* const separateByFormatAction = d->imageSeparationAction->addAction(i18n("By Format")); connect(noCategoriesAction, SIGNAL(triggered()), imageSeparationMapper, SLOT(map())); connect(separateByAlbumAction, SIGNAL(triggered()), imageSeparationMapper, SLOT(map())); connect(separateByFormatAction, SIGNAL(triggered()), imageSeparationMapper, SLOT(map())); imageSeparationMapper->setMapping(noCategoriesAction, (int)ImageSortSettings::OneCategory); imageSeparationMapper->setMapping(separateByAlbumAction, (int)ImageSortSettings::CategoryByAlbum); imageSeparationMapper->setMapping(separateByFormatAction, (int)ImageSortSettings::CategoryByFormat); // ----------------------------------------------------------------- d->imageSeparationSortOrderAction = new KSelectAction(i18n("Item Separation Order"), this); d->imageSeparationSortOrderAction->setWhatsThis(i18n("The sort order of the groups of separated items")); QSignalMapper* const imageSeparationSortOrderMapper = new QSignalMapper(this); connect(imageSeparationSortOrderMapper, SIGNAL(mapped(int)), d->view, SLOT(slotImageSeparationSortOrder(int))); ac->addAction(QLatin1String("image_separation_sort_order"), d->imageSeparationSortOrderAction); QAction* const sortSeparationsAscending = d->imageSeparationSortOrderAction->addAction(QIcon::fromTheme(QLatin1String("view-sort-ascending")), i18n("Ascending")); QAction* const sortSeparationsDescending = d->imageSeparationSortOrderAction->addAction(QIcon::fromTheme(QLatin1String("view-sort-descending")), i18n("Descending")); connect(sortSeparationsAscending, SIGNAL(triggered()), imageSeparationSortOrderMapper, SLOT(map())); connect(sortSeparationsDescending, SIGNAL(triggered()), imageSeparationSortOrderMapper, SLOT(map())); imageSeparationSortOrderMapper->setMapping(sortSeparationsAscending, (int)ImageSortSettings::AscendingOrder); imageSeparationSortOrderMapper->setMapping(sortSeparationsDescending, (int)ImageSortSettings::DescendingOrder); // ----------------------------------------------------------------- setupImageTransformActions(); setupExifOrientationActions(); createMetadataEditAction(); createGeolocationEditAction(); // ----------------------------------------------------------------- d->selectAllAction = new QAction(i18n("Select All"), this); connect(d->selectAllAction, SIGNAL(triggered()), d->view, SLOT(slotSelectAll())); ac->addAction(QLatin1String("selectAll"), d->selectAllAction); ac->setDefaultShortcut(d->selectAllAction, Qt::CTRL+Qt::Key_A); // ----------------------------------------------------------------- d->selectNoneAction = new QAction(i18n("Select None"), this); connect(d->selectNoneAction, SIGNAL(triggered()), d->view, SLOT(slotSelectNone())); ac->addAction(QLatin1String("selectNone"), d->selectNoneAction); ac->setDefaultShortcut(d->selectNoneAction, Qt::CTRL+Qt::SHIFT+Qt::Key_A); // ----------------------------------------------------------------- d->selectInvertAction = new QAction(i18n("Invert Selection"), this); connect(d->selectInvertAction, SIGNAL(triggered()), d->view, SLOT(slotSelectInvert())); ac->addAction(QLatin1String("selectInvert"), d->selectInvertAction); ac->setDefaultShortcut(d->selectInvertAction, Qt::CTRL+Qt::Key_I); // ----------------------------------------------------------- d->showBarAction = new QAction(QIcon::fromTheme(QLatin1String("view-choose")), i18n("Show Thumbbar"), this); d->showBarAction->setCheckable(true); connect(d->showBarAction, SIGNAL(triggered()), this, SLOT(slotToggleShowBar())); ac->addAction(QLatin1String("showthumbs"), d->showBarAction); ac->setDefaultShortcut(d->showBarAction, Qt::CTRL+Qt::Key_T); // Provides a menu entry that allows showing/hiding the toolbar(s) setStandardToolBarMenuEnabled(true); // Provides a menu entry that allows showing/hiding the statusbar createStandardStatusBarAction(); // Standard 'Configure' menu actions createSettingsActions(); // ----------------------------------------------------------- d->zoomPlusAction = buildStdAction(StdZoomInAction, d->view, SLOT(slotZoomIn()), this); QKeySequence keysPlus(d->zoomPlusAction->shortcut()[0], Qt::Key_Plus); ac->addAction(QLatin1String("album_zoomin"), d->zoomPlusAction); ac->setDefaultShortcut(d->zoomPlusAction, keysPlus); // ----------------------------------------------------------- d->zoomMinusAction = buildStdAction(StdZoomOutAction, d->view, SLOT(slotZoomOut()), this); QKeySequence keysMinus(d->zoomMinusAction->shortcut()[0], Qt::Key_Minus); ac->addAction(QLatin1String("album_zoomout"), d->zoomMinusAction); ac->setDefaultShortcut(d->zoomMinusAction, keysMinus); // ----------------------------------------------------------- d->zoomTo100percents = new QAction(QIcon::fromTheme(QLatin1String("zoom-original")), i18n("Zoom to 100%"), this); connect(d->zoomTo100percents, SIGNAL(triggered()), d->view, SLOT(slotZoomTo100Percents())); ac->addAction(QLatin1String("album_zoomto100percents"), d->zoomTo100percents); ac->setDefaultShortcut(d->zoomTo100percents, Qt::CTRL + Qt::Key_Period); // ----------------------------------------------------------- d->zoomFitToWindowAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-fit-best")), i18n("Fit to &Window"), this); connect(d->zoomFitToWindowAction, SIGNAL(triggered()), d->view, SLOT(slotFitToWindow())); ac->addAction(QLatin1String("album_zoomfit2window"), d->zoomFitToWindowAction); ac->setDefaultShortcut(d->zoomFitToWindowAction, Qt::ALT + Qt::CTRL + Qt::Key_E); // ----------------------------------------------------------- createFullScreenAction(QLatin1String("full_screen")); createSidebarActions(); // ----------------------------------------------------------- d->slideShowAction = new QMenu(i18n("Slideshow"), this); d->slideShowAction->setIcon(QIcon::fromTheme(QLatin1String("view-presentation"))); ac->addAction(QLatin1String("slideshow"), d->slideShowAction->menuAction()); d->slideShowAllAction = new QAction(i18n("All"), this); connect(d->slideShowAllAction, SIGNAL(triggered()), d->view, SLOT(slotSlideShowAll())); ac->addAction(QLatin1String("slideshow_all"), d->slideShowAllAction); ac->setDefaultShortcut(d->slideShowAllAction, Qt::Key_F9); d->slideShowAction->addAction(d->slideShowAllAction); d->slideShowSelectionAction = new QAction(i18n("Selection"), this); connect(d->slideShowSelectionAction, SIGNAL(triggered()), d->view, SLOT(slotSlideShowSelection())); ac->addAction(QLatin1String("slideshow_selected"), d->slideShowSelectionAction); ac->setDefaultShortcut(d->slideShowSelectionAction, Qt::ALT+Qt::Key_F9); d->slideShowAction->addAction(d->slideShowSelectionAction); d->slideShowRecursiveAction = new QAction(i18n("With All Sub-Albums"), this); connect(d->slideShowRecursiveAction, SIGNAL(triggered()), d->view, SLOT(slotSlideShowRecursive())); ac->addAction(QLatin1String("slideshow_recursive"), d->slideShowRecursiveAction); ac->setDefaultShortcut(d->slideShowRecursiveAction, Qt::SHIFT+Qt::Key_F9); d->slideShowAction->addAction(d->slideShowRecursiveAction); createPresentationAction(); // ----------------------------------------------------------- d->viewCMViewAction = new QAction(QIcon::fromTheme(QLatin1String("video-display")), i18n("Color-Managed View"), this); d->viewCMViewAction->setCheckable(true); connect(d->viewCMViewAction, SIGNAL(triggered()), this, SLOT(slotToggleColorManagedView())); ac->addAction(QLatin1String("color_managed_view"), d->viewCMViewAction); ac->setDefaultShortcut(d->viewCMViewAction, Qt::Key_F12); // ----------------------------------------------------------- d->quitAction = buildStdAction(StdQuitAction, this, SLOT(slotExit()), this); ac->addAction(QLatin1String("app_exit"), d->quitAction); // ----------------------------------------------------------- createHelpActions(); // ----------------------------------------------------------- QAction* const findAction = new QAction(QIcon::fromTheme(QLatin1String("edit-find")), i18n("Search..."), this); connect(findAction, SIGNAL(triggered()), d->view, SLOT(slotNewKeywordSearch())); ac->addAction(QLatin1String("search_quick"), findAction); ac->setDefaultShortcut(findAction, Qt::CTRL+Qt::Key_F); // ----------------------------------------------------------- d->advSearchAction = new QAction(QIcon::fromTheme(QLatin1String("edit-find")), i18n("Advanced Search..."), this); connect(d->advSearchAction, SIGNAL(triggered()), d->view, SLOT(slotNewAdvancedSearch())); ac->addAction(QLatin1String("search_advanced"), d->advSearchAction); ac->setDefaultShortcut(d->advSearchAction, Qt::CTRL+Qt::ALT+Qt::Key_F); // ----------------------------------------------------------- QAction* const duplicatesAction = new QAction(QIcon::fromTheme(QLatin1String("tools-wizard")), i18n("Find Duplicates..."), this); connect(duplicatesAction, SIGNAL(triggered()), d->view, SLOT(slotNewDuplicatesSearch())); ac->addAction(QLatin1String("find_duplicates"), duplicatesAction); ac->setDefaultShortcut(duplicatesAction, Qt::CTRL+Qt::Key_D); // ----------------------------------------------------------- #ifdef HAVE_MYSQLSUPPORT QAction* const databaseMigrationAction = new QAction(QIcon::fromTheme(QLatin1String("network-server-database")), i18n("Database Migration..."), this); connect(databaseMigrationAction, SIGNAL(triggered()), this, SLOT(slotDatabaseMigration())); ac->addAction(QLatin1String("database_migration"), databaseMigrationAction); #endif // ----------------------------------------------------------- d->maintenanceAction = new QAction(QIcon::fromTheme(QLatin1String("run-build-prune")), i18n("Maintenance..."), this); connect(d->maintenanceAction, SIGNAL(triggered()), this, SLOT(slotMaintenance())); ac->addAction(QLatin1String("maintenance"), d->maintenanceAction); createExpoBlendingAction(); createPanoramaAction(); createHtmlGalleryAction(); createCalendarAction(); createVideoSlideshowAction(); createSendByMailAction(); createPrintCreatorAction(); createMediaServerAction(); // ----------------------------------------------------------- QAction* const cameraAction = new QAction(i18n("Add Camera Manually..."), this); connect(cameraAction, SIGNAL(triggered()), this, SLOT(slotSetupCamera())); ac->addAction(QLatin1String("camera_add"), cameraAction); // ----------------------------------------------------------- // Load Cameras -- do this before the createGUI so that the cameras // are plugged into the toolbar at startup if (d->splashScreen) { d->splashScreen->setMessage(i18n("Loading cameras...")); } loadCameras(); // Load Themes populateThemes(); createGUI(xmlFile()); cleanupActions(); // NOTE: see bug #252130 and #283281 : we need to disable these actions when BQM is running. // These connections must be done after loading color theme else theme menu cannot be plugged to Settings menu, connect(QueueMgrWindow::queueManagerWindow(), SIGNAL(signalBqmIsBusy(bool)), d->bqmAction, SLOT(setDisabled(bool))); connect(QueueMgrWindow::queueManagerWindow(), SIGNAL(signalBqmIsBusy(bool)), d->imageAddCurrentQueueAction, SLOT(setDisabled(bool))); connect(QueueMgrWindow::queueManagerWindow(), SIGNAL(signalBqmIsBusy(bool)), d->imageAddNewQueueAction, SLOT(setDisabled(bool))); } void DigikamApp::initGui() { // Initialize Actions --------------------------------------- d->deleteAction->setEnabled(false); d->renameAction->setEnabled(false); d->addImagesAction->setEnabled(false); d->propsEditAction->setEnabled(false); d->openInFileManagerAction->setEnabled(false); d->imageViewAction->setEnabled(false); d->imagePreviewAction->setEnabled(false); d->imageLightTableAction->setEnabled(false); d->imageAddLightTableAction->setEnabled(false); d->imageFindSimilarAction->setEnabled(false); d->imageRenameAction->setEnabled(false); d->imageDeleteAction->setEnabled(false); d->imageExifOrientationActionMenu->setEnabled(false); d->openWithAction->setEnabled(false); d->slideShowSelectionAction->setEnabled(false); m_metadataEditAction->setEnabled(false); d->imageAutoExifActionMenu->setEnabled(false); #ifdef HAVE_MARBLE m_geolocationEditAction->setEnabled(false); #endif d->albumSortAction->setCurrentItem((int)ApplicationSettings::instance()->getAlbumSortRole()); d->imageSortAction->setCurrentItem((int)ApplicationSettings::instance()->getImageSortOrder()); d->imageSortOrderAction->setCurrentItem((int)ApplicationSettings::instance()->getImageSorting()); d->imageSeparationAction->setCurrentItem((int)ApplicationSettings::instance()->getImageSeparationMode()-1); // no action for enum 0 d->imageSeparationSortOrderAction->setCurrentItem((int)ApplicationSettings::instance()->getImageSeparationSortOrder()); d->recurseAlbumsAction->setChecked(ApplicationSettings::instance()->getRecurseAlbums()); d->recurseTagsAction->setChecked(ApplicationSettings::instance()->getRecurseTags()); d->showBarAction->setChecked(ApplicationSettings::instance()->getShowThumbbar()); showMenuBarAction()->setChecked(!menuBar()->isHidden()); // NOTE: workaround for bug #171080 slotSwitchedToIconView(); } void DigikamApp::enableZoomPlusAction(bool val) { d->zoomPlusAction->setEnabled(val); } void DigikamApp::enableZoomMinusAction(bool val) { d->zoomMinusAction->setEnabled(val); } void DigikamApp::enableAlbumBackwardHistory(bool enable) { d->backwardActionMenu->setEnabled(enable); } void DigikamApp::enableAlbumForwardHistory(bool enable) { d->forwardActionMenu->setEnabled(enable); } void DigikamApp::slotAboutToShowBackwardMenu() { d->backwardActionMenu->menu()->clear(); QStringList titles; d->view->getBackwardHistory(titles); for (int i = 0; i < titles.size(); ++i) { QAction* const action = d->backwardActionMenu->menu()->addAction(titles.at(i), d->backwardSignalMapper, SLOT(map())); d->backwardSignalMapper->setMapping(action, i + 1); } } void DigikamApp::slotAboutToShowForwardMenu() { d->forwardActionMenu->menu()->clear(); QStringList titles; d->view->getForwardHistory(titles); for (int i = 0; i < titles.size(); ++i) { QAction* const action = d->forwardActionMenu->menu()->addAction(titles.at(i), d->forwardSignalMapper, SLOT(map())); d->forwardSignalMapper->setMapping(action, i + 1); } } void DigikamApp::slotAlbumSelected(Album* album) { if (album) { PAlbum* const palbum = dynamic_cast(album); if (album->type() != Album::PHYSICAL || !palbum) { // Rules if not Physical album. d->deleteAction->setEnabled(false); d->renameAction->setEnabled(false); d->addImagesAction->setEnabled(false); d->propsEditAction->setEnabled(false); d->openInFileManagerAction->setEnabled(false); d->newAction->setEnabled(false); d->addFoldersAction->setEnabled(false); d->writeAlbumMetadataAction->setEnabled(true); d->readAlbumMetadataAction->setEnabled(true); d->pasteItemsAction->setEnabled(!album->isRoot()); // Special case if Tag album. bool enabled = (album->type() == Album::TAG) && !album->isRoot(); d->newTagAction->setEnabled(enabled); d->deleteTagAction->setEnabled(enabled); d->editTagAction->setEnabled(enabled); } else { // Rules if Physical album. // We have either the abstract root album, // the album root album for collection base dirs, or normal albums. bool isRoot = palbum->isRoot(); bool isAlbumRoot = palbum->isAlbumRoot(); bool isNormalAlbum = !isRoot && !isAlbumRoot; d->deleteAction->setEnabled(isNormalAlbum); d->renameAction->setEnabled(isNormalAlbum); d->addImagesAction->setEnabled(isNormalAlbum || isAlbumRoot); d->propsEditAction->setEnabled(isNormalAlbum); d->openInFileManagerAction->setEnabled(isNormalAlbum || isAlbumRoot); d->newAction->setEnabled(isNormalAlbum || isAlbumRoot); d->addFoldersAction->setEnabled(isNormalAlbum || isAlbumRoot); d->writeAlbumMetadataAction->setEnabled(isNormalAlbum || isAlbumRoot); d->readAlbumMetadataAction->setEnabled(isNormalAlbum || isAlbumRoot); d->pasteItemsAction->setEnabled(isNormalAlbum || isAlbumRoot); } } else { // Rules if no current album. d->deleteAction->setEnabled(false); d->renameAction->setEnabled(false); d->addImagesAction->setEnabled(false); d->propsEditAction->setEnabled(false); d->openInFileManagerAction->setEnabled(false); d->newAction->setEnabled(false); d->addFoldersAction->setEnabled(false); d->writeAlbumMetadataAction->setEnabled(false); d->readAlbumMetadataAction->setEnabled(false); d->pasteItemsAction->setEnabled(false); d->newTagAction->setEnabled(false); d->deleteTagAction->setEnabled(false); d->editTagAction->setEnabled(false); } } void DigikamApp::slotImageSelected(const ImageInfoList& selection, const ImageInfoList& listAll) { int numImagesWithGrouped = listAll.count(); int numImagesWithoutGrouped = d->view->allUrls(false).count(); ImageInfoList selectionWithoutGrouped = d->view->selectedInfoList(true, false); QString statusBarSelectionText; QString statusBarSelectionToolTip; switch (selection.count()) { case 0: { if (numImagesWithGrouped == numImagesWithoutGrouped) { statusBarSelectionText = i18np("No item selected (%1 item)", "No item selected (%1 items)", numImagesWithoutGrouped); break; } statusBarSelectionText = i18np("No item selected (%1 [%2] item)", "No item selected (%1 [%2] items)", numImagesWithoutGrouped, numImagesWithGrouped); statusBarSelectionToolTip = i18np("No item selected (%1 item. With grouped items: %2)", "No item selected (%1 items. With grouped items: %2)", numImagesWithoutGrouped, numImagesWithGrouped); break; } default: { if (numImagesWithGrouped == numImagesWithoutGrouped) { statusBarSelectionText = i18n("%1/%2 items selected", selection.count(), numImagesWithoutGrouped); break; } if (selectionWithoutGrouped.count() > 1) { if (selection.count() == selectionWithoutGrouped.count()) { statusBarSelectionText = i18n("%1/%2 [%3] items selected", selectionWithoutGrouped.count(), numImagesWithoutGrouped, numImagesWithGrouped); statusBarSelectionToolTip = i18n("%1/%2 items selected. Total with grouped items: %3", selectionWithoutGrouped.count(), numImagesWithoutGrouped, numImagesWithGrouped); } else { statusBarSelectionText = i18n("%1/%2 [%3/%4] items selected", selectionWithoutGrouped.count(), numImagesWithoutGrouped, selection.count(), numImagesWithGrouped); statusBarSelectionToolTip = i18n("%1/%2 items selected. With grouped items: %3/%4", selectionWithoutGrouped.count(), numImagesWithoutGrouped, selection.count(), numImagesWithGrouped); } break; } // no break; is completely intentional, arriving here is equivalent to case 1: [[fallthrough]]; } case 1: { slotSetCheckedExifOrientationAction(selectionWithoutGrouped.first()); int index = listAll.indexOf(selection.first()) + 1; if (numImagesWithGrouped == numImagesWithoutGrouped) { statusBarSelectionText = selection.first().fileUrl().fileName() + i18n(" (%1 of %2)", index, numImagesWithoutGrouped); } else { int indexWithoutGrouped = d->view->allInfo(false).indexOf(selectionWithoutGrouped.first()) + 1; statusBarSelectionText = selection.first().fileUrl().fileName() + i18n(" (%1 of %2 [%3])", indexWithoutGrouped, numImagesWithoutGrouped, numImagesWithGrouped); statusBarSelectionToolTip = selection.first().fileUrl().fileName() + i18n(" (%1 of %2. Total with grouped items: %3)", indexWithoutGrouped, numImagesWithoutGrouped, numImagesWithGrouped); } break; } } d->statusLabel->setAdjustedText(statusBarSelectionText); d->statusLabel->setToolTip(statusBarSelectionToolTip); } void DigikamApp::slotSelectionChanged(int selectionCount) { // The preview can either be activated when only one image is selected, // or if multiple images are selected, but one image is the 'current image'. bool hasAtLeastCurrent =(selectionCount == 1) || ( (selectionCount > 0) && d->view->hasCurrentItem()); d->imagePreviewAction->setEnabled(hasAtLeastCurrent); d->imageViewAction->setEnabled(hasAtLeastCurrent); d->imageFindSimilarAction->setEnabled(selectionCount == 1); d->imageRenameAction->setEnabled(selectionCount > 0); d->imageLightTableAction->setEnabled(selectionCount > 0); d->imageAddLightTableAction->setEnabled(selectionCount > 0); d->imageAddCurrentQueueAction->setEnabled((selectionCount > 0) && !QueueMgrWindow::queueManagerWindow()->isBusy()); d->imageAddNewQueueAction->setEnabled((selectionCount > 0) && !QueueMgrWindow::queueManagerWindow()->isBusy()); d->imageWriteMetadataAction->setEnabled(selectionCount > 0); d->imageReadMetadataAction->setEnabled(selectionCount > 0); d->imageDeleteAction->setEnabled(selectionCount > 0); d->imageRotateActionMenu->setEnabled(selectionCount > 0); d->imageFlipActionMenu->setEnabled(selectionCount > 0); d->imageExifOrientationActionMenu->setEnabled(selectionCount > 0); d->slideShowSelectionAction->setEnabled(selectionCount > 0); d->moveSelectionToAlbumAction->setEnabled(selectionCount > 0); d->cutItemsAction->setEnabled(selectionCount > 0); d->copyItemsAction->setEnabled(selectionCount > 0); m_metadataEditAction->setEnabled(selectionCount > 0); d->openWithAction->setEnabled(selectionCount > 0); d->imageAutoExifActionMenu->setEnabled(selectionCount > 0); #ifdef HAVE_MARBLE m_geolocationEditAction->setEnabled(selectionCount > 0); #endif if (selectionCount > 0) { d->imageWriteMetadataAction->setText(i18np("Write Metadata to Image", "Write Metadata to Selected Images", selectionCount)); d->imageReadMetadataAction->setText(i18np("Reread Metadata From Image", "Reread Metadata From Selected Images", selectionCount)); slotResetExifOrientationActions(); } } void DigikamApp::slotExit() { close(); } void DigikamApp::downloadImages( const QString& folder ) { if (!folder.isNull()) { // activate window when called by media menu and DCOP if (isMinimized()) { KWindowSystem::unminimizeWindow(winId()); } KWindowSystem::activateWindow(winId()); emit queuedOpenCameraUiFromPath(folder); } } void DigikamApp::cameraAutoDetect() { // activate window when called by media menu and DCOP if (isMinimized()) { KWindowSystem::unminimizeWindow(winId()); } KWindowSystem::activateWindow(winId()); slotCameraAutoDetect(); } void DigikamApp::loadCameras() { KActionCollection *ac = actionCollection(); d->cameraMenu->setTitle(i18n("Cameras")); d->cameraMenu->setIcon(QIcon::fromTheme(QLatin1String("camera-photo"))); d->usbMediaMenu->setTitle(i18n("USB Storage Devices")); d->usbMediaMenu->setIcon(QIcon::fromTheme(QLatin1String("drive-removable-media"))); d->cardReaderMenu->setTitle(i18n("Card Readers")); d->cardReaderMenu->setIcon(QIcon::fromTheme(QLatin1String("media-flash-sd-mmc"))); ac->addAction(QLatin1String("cameras"), d->cameraMenu->menuAction()); ac->addAction(QLatin1String("usb_media"), d->usbMediaMenu->menuAction()); ac->addAction(QLatin1String("card_reader"), d->cardReaderMenu->menuAction()); // ----------------------------------------------------------------- d->addImagesAction = new QAction(QIcon::fromTheme(QLatin1String("document-import")), i18n("Add Images..."), this); d->addImagesAction->setWhatsThis(i18n("Adds new items to an Album.")); connect(d->addImagesAction, SIGNAL(triggered()), this, SLOT(slotImportAddImages())); ac->addAction(QLatin1String("import_addImages"), d->addImagesAction); ac->setDefaultShortcut(d->addImagesAction, Qt::CTRL+Qt::ALT+Qt::Key_I); // ----------------------------------------------------------------- d->addFoldersAction = new QAction(QIcon::fromTheme(QLatin1String("folder-new")), i18n("Add Folders..."), this); d->addFoldersAction->setWhatsThis(i18n("Adds new folders to Album library.")); connect(d->addFoldersAction, SIGNAL(triggered()), this, SLOT(slotImportAddFolders())); ac->addAction(QLatin1String("import_addFolders"), d->addFoldersAction); // -- fill manually added cameras ---------------------------------- d->cameraList->load(); // -- scan Solid devices ------------------------------------------- fillSolidMenus(); connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceAdded(QString)), this, SLOT(slotSolidDeviceChanged(QString))); connect(Solid::DeviceNotifier::instance(), SIGNAL(deviceRemoved(QString)), this, SLOT(slotSolidDeviceChanged(QString))); // -- queued connections ------------------------------------------- connect(this, SIGNAL(queuedOpenCameraUiFromPath(QString)), this, SLOT(slotOpenCameraUiFromPath(QString)), Qt::QueuedConnection); connect(this, SIGNAL(queuedOpenSolidDevice(QString)), this, SLOT(slotOpenSolidDevice(QString)), Qt::QueuedConnection); } void DigikamApp::slotCameraAdded(CameraType* ctype) { if (!ctype) { return; } QAction* const cAction = new QAction(QIcon::fromTheme(QLatin1String("camera-photo")), ctype->title(), d->manualCameraActionGroup); cAction->setData(ctype->title()); actionCollection()->addAction(ctype->title(), cAction); ctype->setAction(cAction); updateCameraMenu(); updateQuickImportAction(); } void DigikamApp::slotCameraRemoved(QAction* cAction) { if (cAction) { d->manualCameraActionGroup->removeAction(cAction); } updateCameraMenu(); updateQuickImportAction(); } void DigikamApp::slotCameraAutoDetect() { bool retry = false; CameraType* const ctype = d->cameraList->autoDetect(retry); if (!ctype && retry) { QTimer::singleShot(0, this, SLOT(slotCameraAutoDetect())); return; } if (ctype && ctype->action()) { ctype->action()->activate(QAction::Trigger); } } void DigikamApp::slotOpenCameraUiFromPath(const QString& path) { if (path.isEmpty()) { return; } // the ImportUI will delete itself when it has finished ImportUI* const cgui = new ImportUI(i18n("Images found in %1", path), QLatin1String("directory browse"), QLatin1String("Fixed"), path, 1); cgui->show(); connect(cgui, SIGNAL(signalLastDestination(QUrl)), d->view, SLOT(slotSelectAlbum(QUrl))); } void DigikamApp::slotOpenManualCamera(QAction* action) { CameraType* const ctype = d->cameraList->find(action->data().toString()); if (ctype) { // check not to open two dialogs for the same camera if (ctype->currentImportUI() && !ctype->currentImportUI()->isClosed()) { // show and raise dialog if (ctype->currentImportUI()->isMinimized()) { KWindowSystem::unminimizeWindow(ctype->currentImportUI()->winId()); } KWindowSystem::activateWindow(ctype->currentImportUI()->winId()); } else { // the ImportUI will delete itself when it has finished ImportUI* const cgui = new ImportUI(ctype->title(), ctype->model(), ctype->port(), ctype->path(), ctype->startingNumber()); ctype->setCurrentImportUI(cgui); cgui->show(); connect(cgui, SIGNAL(signalLastDestination(QUrl)), d->view, SLOT(slotSelectAlbum(QUrl))); } } } void DigikamApp::slotOpenSolidDevice(const QString& udi) { // Identifies device as either Camera or StorageAccess and calls methods accordingly Solid::Device device(udi); if (!device.isValid()) { QMessageBox::critical(this, qApp->applicationName(), i18n("The specified device (\"%1\") is not valid.", udi)); return; } if (device.is()) { openSolidUsmDevice(udi); } else if (device.is()) { if (!checkSolidCamera(device)) { QMessageBox::critical(this, qApp->applicationName(), i18n("The specified camera (\"%1\") is not supported.", udi)); return; } openSolidCamera(udi); } } void DigikamApp::slotOpenSolidCamera(QAction* action) { QString udi = action->data().toString(); openSolidCamera(udi, action->iconText()); } void DigikamApp::openSolidCamera(const QString& udi, const QString& cameraLabel) { // if there is already an open ImportUI for the device, show and raise it, and be done if (d->cameraUIMap.contains(udi)) { ImportUI* const ui = d->cameraUIMap.value(udi); if (ui && !ui->isClosed()) { if (ui->isMinimized()) { KWindowSystem::unminimizeWindow(ui->winId()); } KWindowSystem::activateWindow(ui->winId()); return; } } // recreate device from unambiguous UDI Solid::Device device(udi); if ( device.isValid() ) { if (cameraLabel.isNull()) { QString label = labelForSolidCamera(device); } Solid::Camera* const camera = device.as(); QList list = camera->driverHandle(QLatin1String("gphoto")).toList(); // all sanity checks have already been done when creating the action if (list.size() < 3) { return; } // NOTE: See bug #262296: With KDE 4.6, Solid API return device vendor id // and product id in hexadecimal strings. bool ok; int vendorId = list.at(1).toString().toInt(&ok, 16); int productId = list.at(2).toString().toInt(&ok, 16); QString model, port; if (CameraList::findConnectedCamera(vendorId, productId, model, port)) { qCDebug(DIGIKAM_GENERAL_LOG) << "Found camera from ids " << vendorId << " " << productId << " camera is: " << model << " at " << port; // the ImportUI will delete itself when it has finished ImportUI* const cgui = new ImportUI(cameraLabel, model, port, QLatin1String("/"), 1); d->cameraUIMap[udi] = cgui; cgui->show(); connect(cgui, SIGNAL(signalLastDestination(QUrl)), d->view, SLOT(slotSelectAlbum(QUrl))); } else { qCDebug(DIGIKAM_GENERAL_LOG) << "Failed to detect camera with GPhoto2 from Solid information"; } } } void DigikamApp::slotOpenSolidUsmDevice(QAction* action) { QString udi = action->data().toString(); openSolidUsmDevice(udi, action->iconText()); } void DigikamApp::openSolidUsmDevice(const QString& udi, const QString& givenLabel) { QString mediaLabel = givenLabel; // if there is already an open ImportUI for the device, show and raise it if (d->cameraUIMap.contains(udi)) { ImportUI* const ui = d->cameraUIMap.value(udi); if (ui && !ui->isClosed()) { if (ui->isMinimized()) { KWindowSystem::unminimizeWindow(ui->winId()); } KWindowSystem::activateWindow(ui->winId()); return; } } // recreate device from unambiguous UDI Solid::Device device(udi); if ( device.isValid() ) { Solid::StorageAccess* const access = device.as(); if (!access) { return; } if (!access->isAccessible()) { QApplication::setOverrideCursor(Qt::WaitCursor); if (!access->setup()) { return; } d->eventLoop = new QEventLoop(this); connect(access, SIGNAL(setupDone(Solid::ErrorType,QVariant,QString)), this, SLOT(slotSolidSetupDone(Solid::ErrorType,QVariant,QString))); int returnCode = d->eventLoop->exec(QEventLoop::ExcludeUserInputEvents); delete d->eventLoop; d->eventLoop = 0; QApplication::restoreOverrideCursor(); if (returnCode == 1) { QMessageBox::critical(this, qApp->applicationName(), d->solidErrorMessage); return; } } // Create Camera UI QString path = QDir::fromNativeSeparators(access->filePath()); if (mediaLabel.isNull()) { mediaLabel = path; } // the ImportUI will delete itself when it has finished ImportUI* const cgui = new ImportUI(i18n("Images on %1", mediaLabel), QLatin1String("directory browse"), QLatin1String("Fixed"), path, 1); d->cameraUIMap[udi] = cgui; cgui->show(); connect(cgui, SIGNAL(signalLastDestination(QUrl)), d->view, SLOT(slotSelectAlbum(QUrl))); } } void DigikamApp::slotSolidSetupDone(Solid::ErrorType errorType, QVariant errorData, const QString& /*udi*/) { if (!d->eventLoop) { return; } if (errorType == Solid::NoError) { d->eventLoop->exit(0); } else { d->solidErrorMessage = i18n("Cannot access the storage device.\n"); d->solidErrorMessage += errorData.toString(); d->eventLoop->exit(1); } } void DigikamApp::slotSolidDeviceChanged(const QString& udi) { Q_UNUSED(udi) fillSolidMenus(); } bool DigikamApp::checkSolidCamera(const Solid::Device& cameraDevice) { const Solid::Camera* const camera = cameraDevice.as(); if (!camera) { return false; } QStringList drivers = camera->supportedDrivers(); qCDebug(DIGIKAM_GENERAL_LOG) << "fillSolidMenus: Found Camera " << QString::fromUtf8("%1 %2").arg(cameraDevice.vendor()).arg(cameraDevice.product()) << " protocols " << camera->supportedProtocols() << " drivers " << camera->supportedDrivers(QLatin1String("ptp")); // We handle gphoto2 cameras in this loop if (! (camera->supportedDrivers().contains(QLatin1String("gphoto")) || camera->supportedProtocols().contains(QLatin1String("ptp"))) ) { return false; } QVariant driverHandle = camera->driverHandle(QLatin1String("gphoto")); if (!driverHandle.canConvert(QVariant::List)) { qCWarning(DIGIKAM_GENERAL_LOG) << "Solid returns unsupported driver handle for gphoto2"; return false; } QList driverHandleList = driverHandle.toList(); if (driverHandleList.size() < 3 || driverHandleList.at(0).toString() != QLatin1String("usb") || !driverHandleList.at(1).canConvert(QVariant::Int) || !driverHandleList.at(2).canConvert(QVariant::Int) ) { qCWarning(DIGIKAM_GENERAL_LOG) << "Solid returns unsupported driver handle for gphoto2"; return false; } return true; } QString DigikamApp::labelForSolidCamera(const Solid::Device& cameraDevice) { QString vendor = cameraDevice.vendor(); QString product = cameraDevice.product(); if (product == QLatin1String("USB Imaging Interface") || product == QLatin1String("USB Vendor Specific Interface")) { Solid::Device parentUsbDevice = cameraDevice.parent(); if (parentUsbDevice.isValid()) { vendor = parentUsbDevice.vendor(); product = parentUsbDevice.product(); if (!vendor.isEmpty() && !product.isEmpty()) { if (vendor == QLatin1String("Canon, Inc.")) { vendor = QLatin1String("Canon"); if (product.startsWith(QLatin1String("Canon "))) { product = product.mid(6); // cut off another "Canon " from product } if (product.endsWith(QLatin1String(" (ptp)"))) { product.chop(6); // cut off " (ptp)" } } else if (vendor == QLatin1String("Fuji Photo Film Co., Ltd")) { vendor = QLatin1String("Fuji"); } else if (vendor == QLatin1String("Nikon Corp.")) { vendor = QLatin1String("Nikon"); if (product.startsWith(QLatin1String("NIKON "))) { product = product.mid(6); } } } } } return vendor + QLatin1Char(' ') + product; } void DigikamApp::fillSolidMenus() { QHash newAppearanceTimes; d->usbMediaMenu->clear(); d->cardReaderMenu->clear(); // delete the actionGroups to avoid duplicate menu entries delete d->solidUsmActionGroup; delete d->solidCameraActionGroup; d->solidCameraActionGroup = new QActionGroup(this); connect(d->solidCameraActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotOpenSolidCamera(QAction*))); d->solidUsmActionGroup = new QActionGroup(this); connect(d->solidUsmActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(slotOpenSolidUsmDevice(QAction*))); // -------------------------------------------------------- QList cameraDevices = Solid::Device::listFromType(Solid::DeviceInterface::Camera); foreach(const Solid::Device& cameraDevice, cameraDevices) { // USM camera: will be handled below if (cameraDevice.is()) { continue; } if (!checkSolidCamera(cameraDevice)) { continue; } // -------------------------------------------------------- QString l = labelForSolidCamera(cameraDevice); QString label = CameraNameHelper::cameraNameAutoDetected(l.trimmed()); // -------------------------------------------------------- QString iconName = cameraDevice.icon(); if (iconName.isEmpty()) { iconName = QLatin1String("camera-photo"); } QAction* const action = new QAction(label, d->solidCameraActionGroup); action->setIcon(QIcon::fromTheme(iconName)); // set data to identify device in action slot slotSolidSetupDevice action->setData(cameraDevice.udi()); newAppearanceTimes[cameraDevice.udi()] = d->cameraAppearanceTimes.contains(cameraDevice.udi()) ? d->cameraAppearanceTimes.value(cameraDevice.udi()) : QDateTime::currentDateTime(); d->cameraMenu->addAction(action); } QList storageDevices = Solid::Device::listFromType(Solid::DeviceInterface::StorageAccess); foreach(const Solid::Device& accessDevice, storageDevices) { // check for StorageAccess if (!accessDevice.is()) { continue; } // check for StorageDrive Solid::Device driveDevice; for (Solid::Device currentDevice = accessDevice; currentDevice.isValid(); currentDevice = currentDevice.parent()) { if (currentDevice.is()) { driveDevice = currentDevice; break; } } if (!driveDevice.isValid()) { continue; } const Solid::StorageDrive* const drive = driveDevice.as(); QString driveType; bool isHarddisk = false; switch (drive->driveType()) { // skip these case Solid::StorageDrive::CdromDrive: case Solid::StorageDrive::Floppy: case Solid::StorageDrive::Tape: default: continue; // accept card readers case Solid::StorageDrive::CompactFlash: driveType = i18n("CompactFlash Card Reader"); break; case Solid::StorageDrive::MemoryStick: driveType = i18n("Memory Stick Reader"); break; case Solid::StorageDrive::SmartMedia: driveType = i18n("SmartMedia Card Reader"); break; case Solid::StorageDrive::SdMmc: driveType = i18n("SD / MMC Card Reader"); break; case Solid::StorageDrive::Xd: driveType = i18n("xD Card Reader"); break; case Solid::StorageDrive::HardDisk: // We don't want to list HardDisk partitions, but USB Mass Storage devices. // Don't know what is the exact difference between removable and hotpluggable. if (drive->isRemovable() || drive->isHotpluggable()) { isHarddisk = true; if (drive->bus() == Solid::StorageDrive::Usb) { driveType = i18n("USB Disk"); } else { driveType = i18nc("non-USB removable storage device", "Disk"); } break; } else { continue; } } // check for StorageVolume Solid::Device volumeDevice; for (Solid::Device currentDevice = accessDevice; currentDevice.isValid(); currentDevice = currentDevice.parent()) { if (currentDevice.is()) { volumeDevice = currentDevice; break; } } if (!volumeDevice.isValid()) { continue; } bool isCamera = accessDevice.is(); const Solid::StorageAccess* const access = accessDevice.as(); const Solid::StorageVolume* const volume = volumeDevice.as(); if (volume->isIgnored()) { continue; } QString label; if (isCamera) { label = accessDevice.vendor() + QLatin1Char(' ') + accessDevice.product(); } else { QString labelOrProduct; if (!volume->label().isEmpty()) { labelOrProduct = volume->label(); } else if (!volumeDevice.product().isEmpty()) { labelOrProduct = volumeDevice.product(); } else if (!volumeDevice.vendor().isEmpty()) { labelOrProduct = volumeDevice.vendor(); } else if (!driveDevice.product().isEmpty()) { labelOrProduct = driveDevice.product(); } if (!labelOrProduct.isNull()) { if (!access->filePath().isEmpty()) { label += i18nc(" \"\" at ", "%1 \"%2\" at %3", driveType, labelOrProduct, QDir::toNativeSeparators(access->filePath())); } else { label += i18nc(" \"\"", "%1 \"%2\"", driveType, labelOrProduct); } } else { if (!access->filePath().isEmpty()) { label += i18nc(" at ", "%1 at %2", driveType, QDir::toNativeSeparators(access->filePath())); } else { label += driveType; } } if (volume->size()) { label += i18nc("device label etc... ()", " (%1)", ImagePropertiesTab::humanReadableBytesCount(volume->size())); } } QString iconName; if (!driveDevice.icon().isEmpty()) { iconName = driveDevice.icon(); } else if (!accessDevice.icon().isEmpty()) { iconName = accessDevice.icon(); } else if (!volumeDevice.icon().isEmpty()) { iconName = volumeDevice.icon(); } QAction* const action = new QAction(label, d->solidUsmActionGroup); if (!iconName.isEmpty()) { action->setIcon(QIcon::fromTheme(iconName)); } // set data to identify device in action slot slotSolidSetupDevice action->setData(accessDevice.udi()); newAppearanceTimes[accessDevice.udi()] = d->cameraAppearanceTimes.contains(accessDevice.udi()) ? d->cameraAppearanceTimes.value(accessDevice.udi()) : QDateTime::currentDateTime(); if (isCamera) { d->cameraMenu->addAction(action); } if (isHarddisk) { d->usbMediaMenu->addAction(action); } else { d->cardReaderMenu->addAction(action); } } /* //TODO: Find best usable solution when no devices are connected: One entry, hide, or disable? // Add one entry telling that no device is available if (d->cameraSolidMenu->isEmpty()) { QAction* const action = d->cameraSolidMenu->addAction(i18n("No Camera Connected")); action->setEnabled(false); } if (d->usbMediaMenu->isEmpty()) { QAction* const action = d->usbMediaMenu->addAction(i18n("No Storage Devices Found")); action->setEnabled(false); } if (d->cardReaderMenu->isEmpty()) { QAction* const action = d->cardReaderMenu->addAction(i18n("No Card Readers Available")); action->setEnabled(false); } // hide empty menus d->cameraSolidMenu->menuAction()->setVisible(!d->cameraSolidMenu->isEmpty()); d->usbMediaMenu->menuAction()->setVisible(!d->usbMediaMenu->isEmpty()); d->cardReaderMenu->menuAction()->setVisible(!d->cardReaderMenu->isEmpty()); */ d->cameraAppearanceTimes = newAppearanceTimes; // disable empty menus d->usbMediaMenu->setEnabled(!d->usbMediaMenu->isEmpty()); d->cardReaderMenu->setEnabled(!d->cardReaderMenu->isEmpty()); updateCameraMenu(); updateQuickImportAction(); } void DigikamApp::slotSetup() { setup(); } bool DigikamApp::setup() { return Setup::execDialog(this, Setup::LastPageUsed); } bool DigikamApp::setupICC() { return Setup::execSinglePage(this, Setup::ICCPage); } void DigikamApp::slotSetupCamera() { Setup::execSinglePage(this, Setup::CameraPage); } void DigikamApp::slotSetupChanged() { // raw loading options might have changed LoadingCacheInterface::cleanCache(); // TODO: clear history when location changed //if(ApplicationSettings::instance()->getAlbumLibraryPath() != AlbumManager::instance()->getLibraryPath()) // d->view->clearHistory(); const DbEngineParameters prm = ApplicationSettings::instance()->getDbEngineParameters(); if (!AlbumManager::instance()->databaseEqual(prm)) { AlbumManager::instance()->changeDatabase(ApplicationSettings::instance()->getDbEngineParameters()); } if (ApplicationSettings::instance()->getShowFolderTreeViewItemsCount()) { AlbumManager::instance()->prepareItemCounts(); } // Load full-screen options KConfigGroup group = KSharedConfig::openConfig()->group(configGroupName()); readFullScreenSettings(group); d->view->applySettings(); AlbumThumbnailLoader::instance()->setThumbnailSize(ApplicationSettings::instance()->getTreeViewIconSize()); if (LightTableWindow::lightTableWindowCreated()) { LightTableWindow::lightTableWindow()->applySettings(); } if (QueueMgrWindow::queueManagerWindowCreated()) { QueueMgrWindow::queueManagerWindow()->applySettings(); } d->config->sync(); } void DigikamApp::slotEditKeys() { #ifdef HAVE_KIPI editKeyboardShortcuts(KipiPluginLoader::instance()->pluginsActionCollection(), i18nc("KIPI-Plugins keyboard shortcuts", "KIPI-Plugins")); #else editKeyboardShortcuts(); #endif /* HAVE_KIPI */ } void DigikamApp::slotShowKipiHelp() { DXmlGuiWindow::openHandbook(); } void DigikamApp::slotDBStat() { showDigikamDatabaseStat(); } void DigikamApp::loadPlugins() { #ifdef HAVE_KIPI // Load KIPI plugins new KipiPluginLoader(this, d->splashScreen); #endif /* HAVE_KIPI */ // Setting the initial menu options after all plugins have been loaded QList albumList = AlbumManager::instance()->currentAlbums(); d->view->slotAlbumSelected(albumList); } void DigikamApp::populateThemes() { if (d->splashScreen) { d->splashScreen->setMessage(i18n("Loading themes...")); } ThemeManager::instance()->setThemeMenuAction(new QMenu(i18n("&Themes"), this)); ThemeManager::instance()->registerThemeActions(this); ThemeManager::instance()->setCurrentTheme(ApplicationSettings::instance()->getCurrentTheme()); connect(ThemeManager::instance(), SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); } void DigikamApp::slotThemeChanged() { ApplicationSettings::instance()->setCurrentTheme(ThemeManager::instance()->currentThemeName()); } void DigikamApp::preloadWindows() { if (d->splashScreen) { d->splashScreen->setMessage(i18n("Loading tools...")); } QueueMgrWindow::queueManagerWindow(); ImageWindow::imageWindow(); LightTableWindow::lightTableWindow(); d->tagsActionManager->registerTagsActionCollections(); } void DigikamApp::slotDatabaseMigration() { DatabaseMigrationDialog dlg(this); dlg.exec(); } void DigikamApp::slotMaintenance() { MaintenanceDlg* const dlg = new MaintenanceDlg(this); if (dlg->exec() == QDialog::Accepted) { d->maintenanceAction->setEnabled(false); MaintenanceMngr* const mngr = new MaintenanceMngr(this); connect(mngr, SIGNAL(signalComplete()), this, SLOT(slotMaintenanceDone())); mngr->setSettings(dlg->settings()); } } void DigikamApp::slotMaintenanceDone() { d->maintenanceAction->setEnabled(true); d->view->refreshView(); if (LightTableWindow::lightTableWindowCreated()) { LightTableWindow::lightTableWindow()->refreshView(); } if (QueueMgrWindow::queueManagerWindowCreated()) { QueueMgrWindow::queueManagerWindow()->refreshView(); } } void DigikamApp::slotExpoBlending() { ExpoBlendingManager::instance()->checkBinaries(); ExpoBlendingManager::instance()->setItemsList(view()->selectedUrls(ApplicationSettings::Tools)); ExpoBlendingManager::instance()->run(); } void DigikamApp::slotPanorama() { #ifdef HAVE_PANORAMA PanoManager::instance()->checkBinaries(); PanoManager::instance()->setItemsList(view()->selectedUrls(ApplicationSettings::Tools)); PanoManager::instance()->run(); #endif } void DigikamApp::slotVideoSlideshow() { #ifdef HAVE_MEDIAPLAYER VidSlideWizard w(this, new DBInfoIface(this, QList(), ApplicationSettings::Tools)); w.exec(); #endif } void DigikamApp::slotHtmlGallery() { #ifdef HAVE_HTMLGALLERY HTMLWizard w(this, new DBInfoIface(this, QList(), ApplicationSettings::Tools)); w.exec(); #endif } void DigikamApp::slotCalendar() { CalWizard w(view()->selectedUrls(ApplicationSettings::Tools), this); w.exec(); } void DigikamApp::slotSendByMail() { MailWizard w(this, new DBInfoIface(this, QList(), ApplicationSettings::Tools)); w.exec(); } void DigikamApp::slotPrintCreator() { AdvPrintWizard w(this, new DBInfoIface(this, QList(), ApplicationSettings::Tools)); w.exec(); } void DigikamApp::slotMediaServer() { DBInfoIface* const iface = new DBInfoIface(this, QList(), ApplicationSettings::Tools); // NOTE: We overwrite the default albums chooser object name for load save check items state between sessions. // The goal is not mix these settings with other export tools. iface->setObjectName(QLatin1String("SetupMediaServerIface")); DMediaServerDlg w(this, iface); w.exec(); } void DigikamApp::slotRecurseAlbums(bool checked) { d->view->setRecurseAlbums(checked); } void DigikamApp::slotRecurseTags(bool checked) { d->view->setRecurseTags(checked); } void DigikamApp::slotZoomSliderChanged(int size) { d->view->setThumbSize(size); } void DigikamApp::slotThumbSizeChanged(int size) { d->zoomBar->setThumbsSize(size); if (!fullScreenIsActive() && d->autoShowZoomToolTip) { d->zoomBar->triggerZoomTrackerToolTip(); } } void DigikamApp::slotZoomChanged(double zoom) { double zmin = d->view->zoomMin(); double zmax = d->view->zoomMax(); d->zoomBar->setZoom(zoom, zmin, zmax); if (!fullScreenIsActive() && d->autoShowZoomToolTip) { d->zoomBar->triggerZoomTrackerToolTip(); } } void DigikamApp::slotImportAddImages() { QString startingPath = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); QUrl url = DFileDialog::getExistingDirectoryUrl(this, i18n("Select folder to parse"), QUrl::fromLocalFile(startingPath)); if (url.isEmpty() || !url.isLocalFile()) { return; } // The folder contents will be parsed by Camera interface in "Directory Browse" mode. downloadFrom(url.toLocalFile()); } void DigikamApp::slotImportAddFolders() { // NOTE: QFileDialog don't have an option to permit multiple selection of directories. // This work around is inspired from http://www.qtcentre.org/threads/34226-QFileDialog-select-multiple-directories // Check Later Qt 5.4 if a new native Qt way have been introduced. QPointer dlg = new DFileDialog(this); dlg->setWindowTitle(i18n("Select folders to import into album")); dlg->setFileMode(DFileDialog::DirectoryOnly); QListView* const l = dlg->findChild(QLatin1String("listView")); if (l) { l->setSelectionMode(QAbstractItemView::MultiSelection); } QTreeView* const t = dlg->findChild(); if (t) { t->setSelectionMode(QAbstractItemView::MultiSelection); } if (dlg->exec() != QDialog::Accepted) { delete dlg; return; } QList urls = dlg->selectedUrls(); delete dlg; if (urls.empty()) { return; } QList albumList = AlbumManager::instance()->currentAlbums(); Album* album = 0; if (!albumList.isEmpty()) { album = albumList.first(); } if (album && album->type() != Album::PHYSICAL) { album = 0; } QString header(i18n("

Please select the destination album from the digiKam library to " "import folders into.

")); album = AlbumSelectDialog::selectAlbum(this, (PAlbum*)album, header); if (!album) { return; } PAlbum* const pAlbum = dynamic_cast(album); if (!pAlbum) { return; } DIO::copy(urls, pAlbum); } void DigikamApp::slotToggleShowBar() { d->view->toggleShowBar(d->showBarAction->isChecked()); } void DigikamApp::moveEvent(QMoveEvent*) { emit signalWindowHasMoved(); } void DigikamApp::updateCameraMenu() { d->cameraMenu->clear(); foreach(QAction* const action, d->solidCameraActionGroup->actions()) { d->cameraMenu->addAction(action); } d->cameraMenu->addSeparator(); foreach(QAction* const action, d->manualCameraActionGroup->actions()) { // remove duplicate entries, prefer manually added cameras foreach(QAction* const actionSolid, d->solidCameraActionGroup->actions()) { if (CameraNameHelper::sameDevices(actionSolid->iconText(), action->iconText())) { d->cameraMenu->removeAction(actionSolid); d->solidCameraActionGroup->removeAction(actionSolid); } } d->cameraMenu->addAction(action); } d->cameraMenu->addSeparator(); d->cameraMenu->addAction(actionCollection()->action(QLatin1String("camera_add"))); } void DigikamApp::updateQuickImportAction() { d->quickImportMenu->clear(); foreach(QAction* const action, d->solidCameraActionGroup->actions()) { d->quickImportMenu->addAction(action); } foreach(QAction* const action, d->solidUsmActionGroup->actions()) { d->quickImportMenu->addAction(action); } foreach(QAction* const action, d->manualCameraActionGroup->actions()) { d->quickImportMenu->addAction(action); } if (d->quickImportMenu->actions().isEmpty()) { d->quickImportMenu->setEnabled(false); } else { disconnect(d->quickImportMenu->menuAction(), SIGNAL(triggered()), 0, 0); QAction* primaryAction = 0; QDateTime latest; foreach(QAction* const action, d->quickImportMenu->actions()) { QDateTime appearanceTime = d->cameraAppearanceTimes.value(action->data().toString()); if (latest.isNull() || appearanceTime > latest) { primaryAction = action; latest = appearanceTime; } } if (!primaryAction) { primaryAction = d->quickImportMenu->actions().first(); } connect(d->quickImportMenu->menuAction(), SIGNAL(triggered()), primaryAction, SLOT(trigger())); d->quickImportMenu->setEnabled(true); } } void DigikamApp::setupExifOrientationActions() { KActionCollection* const ac = actionCollection(); QSignalMapper* const exifOrientationMapper = new QSignalMapper(d->view); connect(exifOrientationMapper, SIGNAL(mapped(int)), d->view, SLOT(slotImageExifOrientation(int))); d->imageExifOrientationActionMenu = new QMenu(i18n("Adjust Exif Orientation Tag"), this); ac->addAction(QLatin1String("image_set_exif_orientation"), d->imageExifOrientationActionMenu->menuAction()); d->imageSetExifOrientation1Action = new QAction(i18nc("normal exif orientation", "Normal"), this); d->imageSetExifOrientation1Action->setCheckable(true); d->imageSetExifOrientation2Action = new QAction(i18n("Flipped Horizontally"), this); d->imageSetExifOrientation2Action->setCheckable(true); d->imageSetExifOrientation3Action = new QAction(i18n("Rotated Upside Down"), this); d->imageSetExifOrientation3Action->setCheckable(true); d->imageSetExifOrientation4Action = new QAction(i18n("Flipped Vertically"), this); d->imageSetExifOrientation4Action->setCheckable(true); d->imageSetExifOrientation5Action = new QAction(i18n("Rotated Right / Horiz. Flipped"), this); d->imageSetExifOrientation5Action->setCheckable(true); d->imageSetExifOrientation6Action = new QAction(i18n("Rotated Right"), this); d->imageSetExifOrientation6Action->setCheckable(true); d->imageSetExifOrientation7Action = new QAction(i18n("Rotated Right / Vert. Flipped"), this); d->imageSetExifOrientation7Action->setCheckable(true); d->imageSetExifOrientation8Action = new QAction(i18n("Rotated Left"), this); d->imageSetExifOrientation8Action->setCheckable(true); d->exifOrientationActionGroup = new QActionGroup(d->imageExifOrientationActionMenu); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation1Action); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation2Action); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation3Action); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation4Action); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation5Action); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation6Action); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation7Action); d->exifOrientationActionGroup->addAction(d->imageSetExifOrientation8Action); d->imageSetExifOrientation1Action->setChecked(true); ac->addAction(QLatin1String("image_set_exif_orientation_normal"), d->imageSetExifOrientation1Action); ac->addAction(QLatin1String("image_set_exif_orientation_flipped_horizontal"), d->imageSetExifOrientation2Action); ac->addAction(QLatin1String("image_set_exif_orientation_rotated_upside_down"), d->imageSetExifOrientation3Action); ac->addAction(QLatin1String("image_set_exif_orientation_flipped_vertically"), d->imageSetExifOrientation4Action); ac->addAction(QLatin1String("image_set_exif_orientation_rotated_right_hor_flipped"), d->imageSetExifOrientation5Action); ac->addAction(QLatin1String("image_set_exif_orientation_rotated_right"), d->imageSetExifOrientation6Action); ac->addAction(QLatin1String("image_set_exif_orientation_rotated_right_ver_flipped"), d->imageSetExifOrientation7Action); ac->addAction(QLatin1String("image_set_exif_orientation_rotated_left"), d->imageSetExifOrientation8Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation1Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation2Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation3Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation4Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation5Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation6Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation7Action); d->imageExifOrientationActionMenu->addAction(d->imageSetExifOrientation8Action); connect(d->imageSetExifOrientation1Action, SIGNAL(triggered()), exifOrientationMapper, SLOT(map())); connect(d->imageSetExifOrientation2Action, SIGNAL(triggered()), exifOrientationMapper, SLOT(map())); connect(d->imageSetExifOrientation3Action, SIGNAL(triggered()), exifOrientationMapper, SLOT(map())); connect(d->imageSetExifOrientation4Action, SIGNAL(triggered()), exifOrientationMapper, SLOT(map())); connect(d->imageSetExifOrientation5Action, SIGNAL(triggered()), exifOrientationMapper, SLOT(map())); connect(d->imageSetExifOrientation6Action, SIGNAL(triggered()), exifOrientationMapper, SLOT(map())); connect(d->imageSetExifOrientation7Action, SIGNAL(triggered()), exifOrientationMapper, SLOT(map())); connect(d->imageSetExifOrientation8Action, SIGNAL(triggered()), exifOrientationMapper, SLOT(map())); exifOrientationMapper->setMapping(d->imageSetExifOrientation1Action, 1); exifOrientationMapper->setMapping(d->imageSetExifOrientation2Action, 2); exifOrientationMapper->setMapping(d->imageSetExifOrientation3Action, 3); exifOrientationMapper->setMapping(d->imageSetExifOrientation4Action, 4); exifOrientationMapper->setMapping(d->imageSetExifOrientation5Action, 5); exifOrientationMapper->setMapping(d->imageSetExifOrientation6Action, 6); exifOrientationMapper->setMapping(d->imageSetExifOrientation7Action, 7); exifOrientationMapper->setMapping(d->imageSetExifOrientation8Action, 8); } void DigikamApp::slotResetExifOrientationActions() { d->imageSetExifOrientation1Action->setChecked(false); d->imageSetExifOrientation2Action->setChecked(false); d->imageSetExifOrientation3Action->setChecked(false); d->imageSetExifOrientation4Action->setChecked(false); d->imageSetExifOrientation5Action->setChecked(false); d->imageSetExifOrientation6Action->setChecked(false); d->imageSetExifOrientation7Action->setChecked(false); d->imageSetExifOrientation8Action->setChecked(false); } void DigikamApp::slotSetCheckedExifOrientationAction(const ImageInfo& info) { //DMetadata meta(info.fileUrl().toLocalFile()); //int orientation = (meta.isEmpty()) ? 0 : meta.getImageOrientation(); int orientation = info.orientation(); switch (orientation) { case 1: d->imageSetExifOrientation1Action->setChecked(true); break; case 2: d->imageSetExifOrientation2Action->setChecked(true); break; case 3: d->imageSetExifOrientation3Action->setChecked(true); break; case 4: d->imageSetExifOrientation4Action->setChecked(true); break; case 5: d->imageSetExifOrientation5Action->setChecked(true); break; case 6: d->imageSetExifOrientation6Action->setChecked(true); break; case 7: d->imageSetExifOrientation7Action->setChecked(true); break; case 8: d->imageSetExifOrientation8Action->setChecked(true); break; default: slotResetExifOrientationActions(); break; } } void DigikamApp::setupImageTransformActions() { KActionCollection* const ac = actionCollection(); d->imageRotateActionMenu = new QMenu(i18n("Rotate"), this); d->imageRotateActionMenu->setIcon(QIcon::fromTheme(QLatin1String("object-rotate-right"))); QAction* const left = ac->addAction(QLatin1String("rotate_ccw")); left->setText(i18nc("rotate image left", "Left")); ac->setDefaultShortcut(left, Qt::SHIFT+Qt::CTRL+Qt::Key_Left); connect(left, SIGNAL(triggered(bool)), this, SLOT(slotTransformAction())); d->imageRotateActionMenu->addAction(left); QAction* const right = ac->addAction(QLatin1String("rotate_cw")); right->setText(i18nc("rotate image right", "Right")); ac->setDefaultShortcut(right, Qt::SHIFT+Qt::CTRL+Qt::Key_Right); connect(right, SIGNAL(triggered(bool)), this, SLOT(slotTransformAction())); d->imageRotateActionMenu->addAction(right); ac->addAction(QLatin1String("image_rotate"), d->imageRotateActionMenu->menuAction()); // ----------------------------------------------------------------------------------- d->imageFlipActionMenu = new QMenu(i18n("Flip"), this); d->imageFlipActionMenu->setIcon(QIcon::fromTheme(QLatin1String("flip-horizontal"))); QAction* const hori = ac->addAction(QLatin1String("flip_horizontal")); hori->setText(i18n("Horizontally")); ac->setDefaultShortcut(hori, Qt::CTRL+Qt::Key_Asterisk); connect(hori, SIGNAL(triggered(bool)), this, SLOT(slotTransformAction())); d->imageFlipActionMenu->addAction(hori); QAction* const verti = ac->addAction(QLatin1String("flip_vertical")); verti->setText(i18n("Vertically")); ac->setDefaultShortcut(verti, Qt::CTRL+Qt::Key_Slash); connect(verti, SIGNAL(triggered(bool)), this, SLOT(slotTransformAction())); d->imageFlipActionMenu->addAction(verti); ac->addAction(QLatin1String("image_flip"), d->imageFlipActionMenu->menuAction()); // ----------------------------------------------------------------------------------- d->imageAutoExifActionMenu = new QAction(i18n("Auto Rotate/Flip Using Exif Information"), this); connect(d->imageAutoExifActionMenu, SIGNAL(triggered(bool)), this, SLOT(slotTransformAction())); ac->addAction(QLatin1String("image_transform_exif"), d->imageAutoExifActionMenu); } void DigikamApp::slotTransformAction() { if (sender()->objectName() == QLatin1String("rotate_ccw")) { d->view->imageTransform(MetaEngineRotation::Rotate270); } else if (sender()->objectName() == QLatin1String("rotate_cw")) { d->view->imageTransform(MetaEngineRotation::Rotate90); } else if (sender()->objectName() == QLatin1String("flip_horizontal")) { d->view->imageTransform(MetaEngineRotation::FlipHorizontal); } else if (sender()->objectName() == QLatin1String("flip_vertical")) { d->view->imageTransform(MetaEngineRotation::FlipVertical); } else if (sender()->objectName() == QLatin1String("image_transform_exif")) { // special value for FileActionMngr d->view->imageTransform(MetaEngineRotation::NoTransformation); } } QMenu* DigikamApp::slideShowMenu() const { return d->slideShowAction; } void DigikamApp::rebuild() { QString file = xmlFile(); if (!file.isEmpty()) { setXMLGUIBuildDocument(QDomDocument()); loadStandardsXmlFile(); setXMLFile(file, true); } } void DigikamApp::showSideBars(bool visible) { visible ? d->view->showSideBars() : d->view->hideSideBars(); } void DigikamApp::slotToggleLeftSideBar() { d->view->toggleLeftSidebar(); } void DigikamApp::slotToggleRightSideBar() { d->view->toggleRightSidebar(); } void DigikamApp::slotPreviousLeftSideBarTab() { d->view->previousLeftSideBarTab(); } void DigikamApp::slotNextLeftSideBarTab() { d->view->nextLeftSideBarTab(); } void DigikamApp::slotNextRightSideBarTab() { d->view->nextRightSideBarTab(); } void DigikamApp::slotPreviousRightSideBarTab() { d->view->previousRightSideBarTab(); } void DigikamApp::showThumbBar(bool visible) { view()->toggleShowBar(visible); } bool DigikamApp::thumbbarVisibility() const { return d->showBarAction->isChecked(); } void DigikamApp::slotSwitchedToPreview() { d->imagePreviewAction->setChecked(true); d->zoomBar->setBarMode(DZoomBar::PreviewZoomCtrl); toggleShowBar(); } void DigikamApp::slotSwitchedToIconView() { d->zoomBar->setBarMode(DZoomBar::ThumbsSizeCtrl); d->imageIconViewAction->setChecked(true); toggleShowBar(); } void DigikamApp::slotSwitchedToMapView() { //TODO: Link to map view's zoom actions d->zoomBar->setBarMode(DZoomBar::ThumbsSizeCtrl); #ifdef HAVE_MARBLE d->imageMapViewAction->setChecked(true); #endif // HAVE_MARBLE toggleShowBar(); } void DigikamApp::slotSwitchedToTableView() { d->zoomBar->setBarMode(DZoomBar::ThumbsSizeCtrl); d->imageTableViewAction->setChecked(true); toggleShowBar(); } void DigikamApp::slotSwitchedToTrashView() { d->zoomBar->setBarMode(DZoomBar::ThumbsSizeCtrl); // TODO: disable all other views toggleShowBar(); } void DigikamApp::customizedFullScreenMode(bool set) { toolBarMenuAction()->setEnabled(!set); showMenuBarAction()->setEnabled(!set); showStatusBarAction()->setEnabled(!set); set ? d->showBarAction->setEnabled(false) : toggleShowBar(); d->view->toggleFullScreen(set); } void DigikamApp::toggleShowBar() { switch (d->view->viewMode()) { case StackedView::PreviewImageMode: case StackedView::MediaPlayerMode: d->showBarAction->setEnabled(true); break; default: d->showBarAction->setEnabled(false); break; } } void DigikamApp::slotComponentsInfo() { showDigikamComponentsInfo(); } void DigikamApp::slotToggleColorManagedView() { if (!IccSettings::instance()->isEnabled()) { return; } bool cmv = !IccSettings::instance()->settings().useManagedPreviews; IccSettings::instance()->setUseManagedPreviews(cmv); } void DigikamApp::slotColorManagementOptionsChanged() { ICCSettingsContainer settings = IccSettings::instance()->settings(); d->viewCMViewAction->blockSignals(true); d->viewCMViewAction->setEnabled(settings.enableCM); d->viewCMViewAction->setChecked(settings.useManagedPreviews); d->viewCMViewAction->blockSignals(false); } QString DigikamApp::scannerTargetPlace() { QString place = QDir::homePath(); QStringList pics = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); if (!pics.isEmpty()) place = pics.first(); Album* const album = AlbumManager::instance()->currentAlbums().first(); if (album->type() == Album::PHYSICAL) { PAlbum* const p = dynamic_cast(album); if (p) { place = p->folderPath(); } } else { QStringList cols = CollectionManager::instance()->allAvailableAlbumRootPaths(); if (!cols.isEmpty()) place = cols.first(); } return place; } void DigikamApp::slotEditGeolocation() { #ifdef HAVE_MARBLE ImageInfoList infos = d->view->selectedInfoList(ApplicationSettings::Metadata); if (infos.isEmpty()) return; TagModel* const tagModel = new TagModel(AbstractAlbumModel::IgnoreRootAlbum, this); TagPropertiesFilterModel* const filterModel = new TagPropertiesFilterModel(this); filterModel->setSourceAlbumModel(tagModel); filterModel->sort(0); QPointer dialog = new GeolocationEdit(filterModel, new DBInfoIface(this, QList(), ApplicationSettings::Tools), QApplication::activeWindow()); dialog->setItems(ImageGPS::infosToItems(infos)); dialog->exec(); delete dialog; // Refresh Database with new metadata from files. foreach(const ImageInfo& inf, infos) { ScanController::instance()->scannedInfo(inf.fileUrl().toLocalFile()); } #endif } void DigikamApp::slotEditMetadata() { QList urls = view()->selectedUrls(ApplicationSettings::Metadata); if (urls.isEmpty()) return; QPointer dialog = new MetadataEditDialog(QApplication::activeWindow(), urls); dialog->exec(); delete dialog; // Refresh Database with new metadata from files. - foreach(const QUrl& u, urls) + CollectionScanner scanner; + + foreach(const QUrl& url, urls) { - ScanController::instance()->scannedInfo(u.toLocalFile()); + scanner.scanFile(url.toLocalFile(), CollectionScanner::Rescan); } } void DigikamApp::slotImportFromScanner() { #ifdef HAVE_KSANE m_ksaneAction->activate(scannerTargetPlace(), configGroupName()); #endif } void DigikamApp::setupSelectToolsAction() { // Create action model ActionItemModel* const actionModel = new ActionItemModel(this); actionModel->setMode(ActionItemModel::ToplevelMenuCategory | ActionItemModel::SortCategoriesByInsertionOrder); // Builtin actions QString mainCategory = i18nc("@title Main Tools", "Main Tools"); actionModel->addAction(d->ieAction, mainCategory); actionModel->addAction(d->openTagMngrAction, mainCategory); actionModel->addAction(d->bqmAction, mainCategory); actionModel->addAction(d->maintenanceAction, mainCategory); actionModel->addAction(d->ltAction, mainCategory); actionModel->addAction(d->advSearchAction, mainCategory); QString postCategory = i18nc("@title Post Processing Tools", "Post-Processing"); actionModel->addAction(m_expoBlendingAction, postCategory); actionModel->addAction(m_calendarAction, postCategory); actionModel->addAction(m_metadataEditAction, postCategory); actionModel->addAction(m_presentationAction, postCategory); actionModel->addAction(m_sendByMailAction, postCategory); actionModel->addAction(m_printCreatorAction, postCategory); actionModel->addAction(m_mediaServerAction, postCategory); #ifdef HAVE_PANORAMA actionModel->addAction(m_panoramaAction, postCategory); #endif #ifdef HAVE_MEDIAPLAYER actionModel->addAction(m_videoslideshowAction, postCategory); #endif #ifdef HAVE_HTMLGALLERY actionModel->addAction(m_htmlGalleryAction, postCategory); #endif #ifdef HAVE_MARBLE actionModel->addAction(m_geolocationEditAction, postCategory); #endif QString importCategory = i18nc("@title Import Tools", "Import"); #ifdef HAVE_KIPI foreach(QAction* const ac, KipiPluginLoader::instance()->kipiActionsByCategory(KIPI::ToolsPlugin)) { actionModel->addAction(ac, postCategory); } foreach(QAction* const ac, KipiPluginLoader::instance()->kipiActionsByCategory(KIPI::ImagesPlugin)) { actionModel->addAction(ac, postCategory); } QString exportCategory = i18nc("@title Export Tools", "Export"); foreach(QAction* const ac, KipiPluginLoader::instance()->kipiActionsByCategory(KIPI::ExportPlugin)) { actionModel->addAction(ac, exportCategory); } foreach(QAction* const ac, KipiPluginLoader::instance()->kipiActionsByCategory(KIPI::ImportPlugin)) { actionModel->addAction(ac, importCategory); } #endif #ifdef HAVE_KSANE actionModel->addAction(m_ksaneAction, importCategory); #endif // setup categorized view DCategorizedSortFilterProxyModel* const filterModel = actionModel->createFilterModel(); ActionCategorizedView* const selectToolsActionView = new ActionCategorizedView; selectToolsActionView->setupIconMode(); selectToolsActionView->setModel(filterModel); selectToolsActionView->adjustGridSize(); connect(selectToolsActionView, SIGNAL(clicked(QModelIndex)), actionModel, SLOT(trigger(QModelIndex))); d->view->setToolsIconView(selectToolsActionView); } void DigikamApp::slotPresentation() { d->view->presentation(); } } // namespace Digikam diff --git a/utilities/imageeditor/main/imagewindow.cpp b/utilities/imageeditor/main/imagewindow.cpp index 930fc11833..eccafac082 100644 --- a/utilities/imageeditor/main/imagewindow.cpp +++ b/utilities/imageeditor/main/imagewindow.cpp @@ -1,1869 +1,1871 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2004-02-12 * Description : digiKam image editor GUI * * Copyright (C) 2004-2005 by Renchi Raju * Copyright (C) 2004-2018 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * 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. * * ============================================================ */ #include "imagewindow.h" // C++ includes #include // Qt includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE includes #include #include #include // Local includes #include "dlayoutbox.h" #include "album.h" #include "coredb.h" #include "albummanager.h" #include "albummodel.h" #include "albumfiltermodel.h" #include "applicationsettings.h" #include "canvas.h" #include "collectionlocation.h" #include "collectionmanager.h" +#include "collectionscanner.h" #include "componentsinfo.h" #include "coredbaccess.h" #include "coredbwatch.h" #include "coredbchangesets.h" #include "ddragobjects.h" #include "deletedialog.h" #include "dimg.h" #include "editorcore.h" #include "dimagehistory.h" #include "digikamapp.h" #include "dio.h" #include "dmetadata.h" #include "editorstackview.h" #include "fileactionmngr.h" #include "dfileoperations.h" #include "digikam_globals.h" #include "iccsettingscontainer.h" #include "imageattributeswatch.h" #include "imagefiltermodel.h" #include "imagedragdrop.h" #include "imagedescedittab.h" #include "imageinfo.h" #include "imagegps.h" #include "imagelistmodel.h" #include "imagepropertiessidebardb.h" #include "imagepropertiesversionstab.h" #include "imagescanner.h" #include "imagethumbnailbar.h" #include "iofilesettings.h" #include "dnotificationwrapper.h" #include "loadingcacheinterface.h" #include "metadatahub.h" #include "metadatasettings.h" #include "metadataedit.h" #include "colorlabelwidget.h" #include "picklabelwidget.h" #include "presentationmngr.h" #include "ratingwidget.h" #include "savingcontext.h" #include "scancontroller.h" #include "setup.h" #include "slideshow.h" #include "statusprogressbar.h" #include "syncjob.h" #include "tagsactionmngr.h" #include "tagscache.h" #include "tagspopupmenu.h" #include "thememanager.h" #include "thumbbardock.h" #include "thumbnailloadthread.h" #include "undostate.h" #include "imagewindow_p.h" #include "digikam_debug.h" #include "dexpanderbox.h" #include "dbinfoiface.h" #include "calwizard.h" #include "expoblendingmanager.h" #include "mailwizard.h" #include "advprintwizard.h" #include "dmediaserverdlg.h" #ifdef HAVE_MARBLE # include "geolocationedit.h" #endif #ifdef HAVE_HTMLGALLERY # include "htmlwizard.h" #endif #ifdef HAVE_PANORAMA # include "panomanager.h" #endif #ifdef HAVE_MEDIAPLAYER # include "vidslidewizard.h" #endif namespace Digikam { ImageWindow* ImageWindow::m_instance = 0; ImageWindow* ImageWindow::imageWindow() { if (!m_instance) { new ImageWindow(); } return m_instance; } bool ImageWindow::imageWindowCreated() { return m_instance; } ImageWindow::ImageWindow() : EditorWindow(QLatin1String("Image Editor")), d(new Private) { setXMLFile(QLatin1String("imageeditorui5.rc")); m_instance = this; // We don't want to be deleted on close setAttribute(Qt::WA_DeleteOnClose, false); setAcceptDrops(true); // -- Build the GUI ------------------------------- setupUserArea(); setupActions(); setupStatusBar(); createGUI(xmlFile()); cleanupActions(); showMenuBarAction()->setChecked(!menuBar()->isHidden()); // NOTE: workaround for bug #171080 // Create tool selection view setupSelectToolsAction(); // Create context menu. setupContextMenu(); // Make signals/slots connections setupConnections(); // -- Read settings -------------------------------- readSettings(); KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroupName()); applyMainWindowSettings(group); d->thumbBarDock->setShouldBeVisible(group.readEntry(d->configShowThumbbarEntry, false)); setAutoSaveSettings(configGroupName(), true); d->viewContainer->setAutoSaveSettings(QLatin1String("ImageViewer Thumbbar"), true); //------------------------------------------------------------- d->rightSideBar->setConfigGroup(KConfigGroup(&group, QLatin1String("Right Sidebar"))); d->rightSideBar->loadState(); d->rightSideBar->populateTags(); slotSetupChanged(); } ImageWindow::~ImageWindow() { m_instance = 0; delete d->rightSideBar; delete d->thumbBar; delete d; } void ImageWindow::closeEvent(QCloseEvent* e) { if (!queryClose()) { e->ignore(); return; } // put right side bar in a defined state emit signalNoCurrentItem(); m_canvas->resetImage(); // There is one nasty habit with the thumbnail bar if it is floating: it // doesn't close when the parent window does, so it needs to be manually // closed. If the light table is opened again, its original state needs to // be restored. // This only needs to be done when closing a visible window and not when // destroying a closed window, since the latter case will always report that // the thumbnail bar isn't visible. if (isVisible()) { thumbBar()->hide(); } KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroupName()); saveMainWindowSettings(group); saveSettings(); d->rightSideBar->setConfigGroup(KConfigGroup(&group, "Right Sidebar")); d->rightSideBar->saveState(); DXmlGuiWindow::closeEvent(e); e->accept(); } void ImageWindow::showEvent(QShowEvent*) { // Restore the visibility of the thumbbar and start autosaving again. thumbBar()->restoreVisibility(); } bool ImageWindow::queryClose() { // Note: we re-implement closeEvent above for this window. // Additionally, queryClose is called from DigikamApp. // wait if a save operation is currently running if (!waitForSavingToComplete()) { return false; } return promptUserSave(d->currentUrl()); } void ImageWindow::setupUserArea() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroupName()); QWidget* const widget = new QWidget(this); QHBoxLayout* const hlay = new QHBoxLayout(widget); m_splitter = new SidebarSplitter(widget); d->viewContainer = new KMainWindow(widget, Qt::Widget); m_splitter->addWidget(d->viewContainer); m_stackView = new EditorStackView(d->viewContainer); m_canvas = new Canvas(m_stackView); d->viewContainer->setCentralWidget(m_stackView); m_splitter->setFrameStyle(QFrame::NoFrame); m_splitter->setFrameShape(QFrame::NoFrame); m_splitter->setFrameShadow(QFrame::Plain); m_splitter->setStretchFactor(0, 10); // set Canvas default size to max. m_splitter->setOpaqueResize(false); m_canvas->makeDefaultEditingCanvas(); m_stackView->setCanvas(m_canvas); m_stackView->setViewMode(EditorStackView::CanvasMode); d->rightSideBar = new ImagePropertiesSideBarDB(widget, m_splitter, Qt::RightEdge, true); d->rightSideBar->setObjectName(QLatin1String("ImageEditor Right Sidebar")); d->rightSideBar->getFiltersHistoryTab()->addOpenImageAction(); hlay->addWidget(m_splitter); hlay->addWidget(d->rightSideBar); hlay->setContentsMargins(QMargins()); hlay->setSpacing(0); // Code to check for the now depreciated HorizontalThumbar directive. It // is found, it is honored and deleted. The state will from than on be saved // by d->viewContainers built-in mechanism. Qt::DockWidgetArea dockArea = Qt::LeftDockWidgetArea; if (group.hasKey(d->configHorizontalThumbbarEntry)) { if (group.readEntry(d->configHorizontalThumbbarEntry, true)) { // Horizontal thumbbar layout dockArea = Qt::TopDockWidgetArea; } group.deleteEntry(d->configHorizontalThumbbarEntry); } d->imageInfoModel = new ImageListModel(this); d->imageFilterModel = new ImageFilterModel(this); d->imageFilterModel->setSourceImageModel(d->imageInfoModel); d->imageInfoModel->setWatchFlags(d->imageFilterModel->suggestedWatchFlags()); d->imageInfoModel->setThumbnailLoadThread(ThumbnailLoadThread::defaultIconViewThread()); d->imageFilterModel->setCategorizationMode(ImageSortSettings::NoCategories); d->imageFilterModel->setStringTypeNatural(ApplicationSettings::instance()->isStringTypeNatural()); d->imageFilterModel->setSortRole((ImageSortSettings::SortRole)ApplicationSettings::instance()->getImageSortOrder()); d->imageFilterModel->setSortOrder((ImageSortSettings::SortOrder)ApplicationSettings::instance()->getImageSorting()); d->imageFilterModel->setAllGroupsOpen(true); // disable filtering out by group, see bug #283847 d->imageFilterModel->sort(0); // an initial sorting is necessary d->dragDropHandler = new ImageDragDropHandler(d->imageInfoModel); d->dragDropHandler->setReadOnlyDrop(true); d->imageInfoModel->setDragDropHandler(d->dragDropHandler); // The thumb bar is placed in a detachable/dockable widget. d->thumbBarDock = new ThumbBarDock(d->viewContainer, Qt::Tool); d->thumbBarDock->setObjectName(QLatin1String("editor_thumbbar")); d->thumbBar = new ImageThumbnailBar(d->thumbBarDock); d->thumbBar->setModels(d->imageInfoModel, d->imageFilterModel); d->thumbBarDock->setWidget(d->thumbBar); d->viewContainer->addDockWidget(dockArea, d->thumbBarDock); d->thumbBarDock->setFloating(false); //d->thumbBar->slotDockLocationChanged(dockArea); setCentralWidget(widget); } void ImageWindow::setupConnections() { setupStandardConnections(); connect(this, SIGNAL(loadCurrentLater()), this, SLOT(slotLoadCurrent()), Qt::QueuedConnection); // To toggle properly keyboards shortcuts from comments & tags side bar tab. connect(d->rightSideBar, SIGNAL(signalNextItem()), this, SLOT(slotForward())); connect(d->rightSideBar, SIGNAL(signalPrevItem()), this, SLOT(slotBackward())); connect(d->rightSideBar->getFiltersHistoryTab(), SIGNAL(actionTriggered(ImageInfo)), this, SLOT(openImage(ImageInfo))); connect(this, SIGNAL(signalSelectionChanged(QRect)), d->rightSideBar, SLOT(slotImageSelectionChanged(QRect))); connect(this, SIGNAL(signalNoCurrentItem()), d->rightSideBar, SLOT(slotNoCurrentItem())); ImageAttributesWatch* watch = ImageAttributesWatch::instance(); connect(watch, SIGNAL(signalFileMetadataChanged(QUrl)), this, SLOT(slotFileMetadataChanged(QUrl))); /* connect(CoreDbAccess::databaseWatch(), SIGNAL(collectionImageChange(CollectionImageChangeset)), this, SLOT(slotCollectionImageChange(CollectionImageChangeset)), Qt::QueuedConnection); connect(d->imageFilterModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(slotRowsAboutToBeRemoved(QModelIndex,int,int))); */ connect(d->thumbBar, SIGNAL(currentChanged(ImageInfo)), this, SLOT(slotThumbBarImageSelected(ImageInfo))); connect(d->dragDropHandler, SIGNAL(imageInfosDropped(QList)), this, SLOT(slotDroppedOnThumbbar(QList))); connect(d->thumbBarDock, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), d->thumbBar, SLOT(slotDockLocationChanged(Qt::DockWidgetArea))); connect(d->imageInfoModel, SIGNAL(allRefreshingFinished()), this, SLOT(slotThumbBarModelReady())); connect(ApplicationSettings::instance(), SIGNAL(setupChanged()), this, SLOT(slotSetupChanged())); } void ImageWindow::setupActions() { setupStandardActions(); KActionCollection* const ac = actionCollection(); d->toMainWindowAction = new QAction(QIcon::fromTheme(QLatin1String("view-list-icons")), i18nc("@action Finish editing, close editor, back to main window", "Close Editor"), this); connect(d->toMainWindowAction, SIGNAL(triggered()), this, SLOT(slotToMainWindow())); ac->addAction(QLatin1String("imageview_tomainwindow"), d->toMainWindowAction); // -- Special Delete actions --------------------------------------------------------------- // Pop up dialog to ask user whether to permanently delete d->fileDeletePermanentlyAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete File Permanently"), this); connect(d->fileDeletePermanentlyAction, SIGNAL(triggered()), this, SLOT(slotDeleteCurrentItemPermanently())); ac->addAction(QLatin1String("image_delete_permanently"), d->fileDeletePermanentlyAction); ac->setDefaultShortcut(d->fileDeletePermanentlyAction, Qt::SHIFT + Qt::Key_Delete); // These two actions are hidden, no menu entry, no toolbar entry, no shortcut. // Power users may add them. d->fileDeletePermanentlyDirectlyAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete Permanently without Confirmation"), this); connect(d->fileDeletePermanentlyDirectlyAction, SIGNAL(triggered()), this, SLOT(slotDeleteCurrentItemPermanentlyDirectly())); ac->addAction(QLatin1String("image_delete_permanently_directly"), d->fileDeletePermanentlyDirectlyAction); d->fileTrashDirectlyAction = new QAction(QIcon::fromTheme(QLatin1String("user-trash")), i18n("Move to Trash without Confirmation"), this); connect(d->fileTrashDirectlyAction, SIGNAL(triggered()), this, SLOT(slotTrashCurrentItemDirectly())); ac->addAction(QLatin1String("image_trash_directly"), d->fileTrashDirectlyAction); // --------------------------------------------------------------------------------- createHelpActions(); // Labels shortcuts must be registered here to be saved in XML GUI files if user customize it. TagsActionMngr::defaultManager()->registerLabelsActions(ac); QAction* const editTitles = new QAction(i18n("Edit Titles"), this); ac->addAction(QLatin1String("edit_titles"), editTitles); ac->setDefaultShortcut(editTitles, Qt::META + Qt::Key_T); connect(editTitles, SIGNAL(triggered()), this, SLOT(slotRightSideBarActivateTitles())); QAction* const editComments = new QAction(i18n("Edit Comments"), this); ac->addAction(QLatin1String("edit_comments"), editComments); ac->setDefaultShortcut(editComments, Qt::META + Qt::Key_C); connect(editComments, SIGNAL(triggered()), this, SLOT(slotRightSideBarActivateComments())); QAction* const assignedTags = new QAction(i18n("Show Assigned Tags"), this); ac->addAction(QLatin1String("assigned _tags"), assignedTags); ac->setDefaultShortcut(assignedTags, Qt::META + Qt::Key_A); connect(assignedTags, SIGNAL(triggered()), this, SLOT(slotRightSideBarActivateAssignedTags())); } void ImageWindow::slotSetupChanged() { applyStandardSettings(); VersionManagerSettings versionSettings = ApplicationSettings::instance()->getVersionManagerSettings(); d->versionManager.setSettings(versionSettings); m_nonDestructive = versionSettings.enabled; toggleNonDestructiveActions(); d->imageFilterModel->setStringTypeNatural(ApplicationSettings::instance()->isStringTypeNatural()); d->imageFilterModel->setSortRole((ImageSortSettings::SortRole)ApplicationSettings::instance()->getImageSortOrder()); d->imageFilterModel->setSortOrder((ImageSortSettings::SortOrder)ApplicationSettings::instance()->getImageSorting()); d->rightSideBar->setStyle(ApplicationSettings::instance()->getSidebarTitleStyle()); } void ImageWindow::loadImageInfos(const ImageInfoList& imageInfoList, const ImageInfo& imageInfoCurrent, const QString& caption) { // Very first thing is to check for changes, user may choose to cancel operation if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } d->currentImageInfo = ImageInfo(); d->currentImageInfo = imageInfoCurrent; // Note: Addition is asynchronous, indexes not yet available // We enable thumbbar as soon as indexes are available // If not, we load imageInfoCurrent, then the index 0, then again imageInfoCurrent d->thumbBar->setEnabled(false); d->imageInfoModel->setImageInfos(imageInfoList); d->setThumbBarToCurrent(); if (!caption.isEmpty()) { setCaption(i18n("Image Editor - %1", caption)); } else { setCaption(i18n("Image Editor")); } // it can slightly improve the responsiveness when inserting an event loop run here QTimer::singleShot(0, this, SLOT(slotLoadImageInfosStage2())); } void ImageWindow::slotLoadImageInfosStage2() { // if window is minimized, show it if (isMinimized()) { KWindowSystem::unminimizeWindow(winId()); } slotLoadCurrent(); } void ImageWindow::slotThumbBarModelReady() { d->thumbBar->setEnabled(true); } void ImageWindow::openImage(const ImageInfo& info) { if (d->currentImageInfo == info) { return; } d->currentImageInfo = info; d->ensureModelContains(d->currentImageInfo); slotLoadCurrent(); } void ImageWindow::slotLoadCurrent() { if (!d->currentIsValid()) { return; } m_canvas->load(d->currentImageInfo.filePath(), m_IOFileSettings); QModelIndex next = d->nextIndex(); if (next.isValid()) { m_canvas->preload(d->imageInfo(next).filePath()); } d->setThumbBarToCurrent(); // Do this _after_ the canvas->load(), so that the main view histogram does not load // a smaller version if a raw image, and after that the EditorCore loads the full version. // So first let EditorCore create its loading task, only then any external objects. setViewToURL(d->currentUrl()); } void ImageWindow::setViewToURL(const QUrl& url) { emit signalURLChanged(url); } void ImageWindow::slotThumbBarImageSelected(const ImageInfo& info) { if (d->currentImageInfo == info || !d->thumbBar->isEnabled()) { return; } if (!promptUserSave(d->currentUrl(), AskIfNeeded, false)) { return; } d->currentImageInfo = info; slotLoadCurrent(); } void ImageWindow::slotDroppedOnThumbbar(const QList& infos) { // Check whether dropped image list is empty if (infos.isEmpty()) { return; } // Create new list and images that are not present currently in the thumbbar QList toAdd; foreach(const ImageInfo& it, infos) { QModelIndex index(d->imageFilterModel->indexForImageInfo(it)); if( !index.isValid() ) { toAdd.append(it); } } // Loading images if new images are dropped if (!toAdd.isEmpty()) { loadImageInfos(toAdd, toAdd.first(), QString()); } } void ImageWindow::slotFileOriginChanged(const QString& filePath) { // By redo or undo, we have virtually switched to a new image. // So we do _not_ load anything! ImageInfo newCurrent = ImageInfo::fromLocalFile(filePath); if (newCurrent.isNull() || !d->imageInfoModel->hasImage(newCurrent)) { return; } d->currentImageInfo = newCurrent; d->setThumbBarToCurrent(); setViewToURL(d->currentUrl()); } void ImageWindow::loadIndex(const QModelIndex& index) { if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } if (!index.isValid()) { return; } d->currentImageInfo = d->imageFilterModel->imageInfo(index); slotLoadCurrent(); } void ImageWindow::slotForward() { loadIndex(d->nextIndex()); } void ImageWindow::slotBackward() { loadIndex(d->previousIndex()); } void ImageWindow::slotFirst() { loadIndex(d->firstIndex()); } void ImageWindow::slotLast() { loadIndex(d->lastIndex()); } void ImageWindow::slotContextMenu() { if (m_contextMenu) { m_contextMenu->addSeparator(); addServicesMenu(); m_contextMenu->addSeparator(); TagsPopupMenu* assignTagsMenu = 0; TagsPopupMenu* removeTagsMenu = 0; // Bulk assignment/removal of tags -------------------------- QList idList; idList << d->currentImageInfo.id(); assignTagsMenu = new TagsPopupMenu(idList, TagsPopupMenu::RECENTLYASSIGNED, this); removeTagsMenu = new TagsPopupMenu(idList, TagsPopupMenu::REMOVE, this); assignTagsMenu->menuAction()->setText(i18n("Assign Tag")); removeTagsMenu->menuAction()->setText(i18n("Remove Tag")); m_contextMenu->addSeparator(); m_contextMenu->addMenu(assignTagsMenu); m_contextMenu->addMenu(removeTagsMenu); connect(assignTagsMenu, SIGNAL(signalTagActivated(int)), this, SLOT(slotAssignTag(int))); connect(removeTagsMenu, SIGNAL(signalTagActivated(int)), this, SLOT(slotRemoveTag(int))); connect(assignTagsMenu, SIGNAL(signalPopupTagsView()), d->rightSideBar, SLOT(slotPopupTagsView())); if (!CoreDbAccess().db()->hasTags(idList)) { m_contextMenu->menuAction()->setEnabled(false); } m_contextMenu->addSeparator(); // Assign Labels ------------------------------------------- QMenu* const menuLabels = new QMenu(i18n("Assign Labels"), m_contextMenu); PickLabelMenuAction* const pmenu = new PickLabelMenuAction(m_contextMenu); ColorLabelMenuAction* const cmenu = new ColorLabelMenuAction(m_contextMenu); RatingMenuAction* const rmenu = new RatingMenuAction(m_contextMenu); menuLabels->addAction(pmenu->menuAction()); menuLabels->addAction(cmenu->menuAction()); menuLabels->addAction(rmenu->menuAction()); m_contextMenu->addMenu(menuLabels); connect(pmenu, SIGNAL(signalPickLabelChanged(int)), this, SLOT(slotAssignPickLabel(int))); connect(cmenu, SIGNAL(signalColorLabelChanged(int)), this, SLOT(slotAssignColorLabel(int))); connect(rmenu, SIGNAL(signalRatingChanged(int)), this, SLOT(slotAssignRating(int))); // -------------------------------------------------------------- m_contextMenu->exec(QCursor::pos()); delete assignTagsMenu; delete removeTagsMenu; delete cmenu; delete pmenu; delete rmenu; delete menuLabels; } } void ImageWindow::slotChanged() { QString mpixels; QSize dims(m_canvas->imageWidth(), m_canvas->imageHeight()); mpixels.setNum(dims.width()*dims.height() / 1000000.0, 'f', 2); QString str = (!dims.isValid()) ? i18n("Unknown") : i18n("%1x%2 (%3Mpx)", dims.width(), dims.height(), mpixels); m_resLabel->setAdjustedText(str); if (!d->currentIsValid()) { return; } DImg* const img = m_canvas->interface()->getImg(); DImageHistory history = m_canvas->interface()->getImageHistory(); DImageHistory redoHistory = m_canvas->interface()->getImageHistoryOfFullRedo(); d->rightSideBar->itemChanged(d->currentImageInfo, m_canvas->getSelectedArea(), img, redoHistory); // Filters for redo will be greyed out d->rightSideBar->getFiltersHistoryTab()->setEnabledHistorySteps(history.actionCount()); /* if (!d->currentImageInfo.isNull()) { } else { d->rightSideBar->itemChanged(d->currentUrl(), m_canvas->getSelectedArea(), img); } */ } void ImageWindow::slotToggleTag(const QUrl& url, int tagID) { toggleTag(ImageInfo::fromUrl(url), tagID); } void ImageWindow::toggleTag(int tagID) { toggleTag(d->currentImageInfo, tagID); } void ImageWindow::toggleTag(const ImageInfo& info, int tagID) { if (!info.isNull()) { if (info.tagIds().contains(tagID)) { FileActionMngr::instance()->removeTag(info, tagID); } else { FileActionMngr::instance()->assignTag(info, tagID); } } } void ImageWindow::slotAssignTag(int tagID) { if (!d->currentImageInfo.isNull()) { FileActionMngr::instance()->assignTag(d->currentImageInfo, tagID); } } void ImageWindow::slotRemoveTag(int tagID) { if (!d->currentImageInfo.isNull()) { FileActionMngr::instance()->removeTag(d->currentImageInfo, tagID); } } void ImageWindow::slotAssignPickLabel(int pickId) { assignPickLabel(d->currentImageInfo, pickId); } void ImageWindow::slotAssignColorLabel(int colorId) { assignColorLabel(d->currentImageInfo, colorId); } void ImageWindow::assignPickLabel(const ImageInfo& info, int pickId) { if (!info.isNull()) { FileActionMngr::instance()->assignPickLabel(info, pickId); } } void ImageWindow::assignColorLabel(const ImageInfo& info, int colorId) { if (!info.isNull()) { FileActionMngr::instance()->assignColorLabel(info, colorId); } } void ImageWindow::slotAssignRating(int rating) { assignRating(d->currentImageInfo, rating); } void ImageWindow::assignRating(const ImageInfo& info, int rating) { rating = qMin(RatingMax, qMax(RatingMin, rating)); if (!info.isNull()) { FileActionMngr::instance()->assignRating(info, rating); } } void ImageWindow::slotRatingChanged(const QUrl& url, int rating) { assignRating(ImageInfo::fromUrl(url), rating); } void ImageWindow::slotColorLabelChanged(const QUrl& url, int color) { assignColorLabel(ImageInfo::fromUrl(url), color); } void ImageWindow::slotPickLabelChanged(const QUrl& url, int pick) { assignPickLabel(ImageInfo::fromUrl(url), pick); } void ImageWindow::slotUpdateItemInfo() { QString text = i18nc(" ( of )", "%1 (%2 of %3)", d->currentImageInfo.name(), QString::number(d->currentIndex().row() + 1), QString::number(d->imageFilterModel->rowCount())); m_nameLabel->setText(text); if (!m_actionEnabledState) { return; } if (d->imageInfoModel->rowCount() == 1) { m_backwardAction->setEnabled(false); m_forwardAction->setEnabled(false); m_firstAction->setEnabled(false); m_lastAction->setEnabled(false); } else { m_backwardAction->setEnabled(true); m_forwardAction->setEnabled(true); m_firstAction->setEnabled(true); m_lastAction->setEnabled(true); } if (d->currentIndex() == d->firstIndex()) { m_backwardAction->setEnabled(false); m_firstAction->setEnabled(false); } if (d->currentIndex() == d->lastIndex()) { m_forwardAction->setEnabled(false); m_lastAction->setEnabled(false); } /* // Disable some menu actions if the current root image URL // is not include in the digiKam Albums library database. // This is necessary when ImageEditor is opened from cameraclient. QUrl u(d->currentUrl().directory()); PAlbum* palbum = AlbumManager::instance()->findPAlbum(u); if (!palbum) { m_fileDeleteAction->setEnabled(false); } else { m_fileDeleteAction->setEnabled(true); } */ } void ImageWindow::slotSetup() { Setup::execDialog(this); } void ImageWindow::slotSetupICC() { Setup::execSinglePage(this, Setup::ICCPage); } void ImageWindow::slotToMainWindow() { close(); } void ImageWindow::saveIsComplete() { // With save(), we do not reload the image but just continue using the data. // This means that a saving operation does not lead to quality loss for // subsequent editing operations. // put image in cache, the LoadingCacheInterface cares for the details LoadingCacheInterface::putImage(m_savingContext.destinationURL.toLocalFile(), m_canvas->currentImage()); ScanController::instance()->scannedInfo(m_savingContext.destinationURL.toLocalFile()); // reset the orientation flag in the database DMetadata meta(m_canvas->currentImage().getMetadata()); d->currentImageInfo.setOrientation(meta.getImageOrientation()); // Pop-up a message to bring user when save is done. DNotificationWrapper(QLatin1String("editorsavefilecompleted"), i18n("Image saved successfully"), this, windowTitle()); resetOrigin(); QModelIndex next = d->nextIndex(); if (next.isValid()) { m_canvas->preload(d->imageInfo(next).filePath()); } slotUpdateItemInfo(); setViewToURL(d->currentImageInfo.fileUrl()); } void ImageWindow::saveVersionIsComplete() { qCDebug(DIGIKAM_GENERAL_LOG) << "save version done"; saveAsIsComplete(); } void ImageWindow::saveAsIsComplete() { qCDebug(DIGIKAM_GENERAL_LOG) << "Saved" << m_savingContext.srcURL << "to" << m_savingContext.destinationURL; // Nothing to be done if operating without database if (d->currentImageInfo.isNull()) { return; } if (CollectionManager::instance()->albumRootPath(m_savingContext.srcURL).isNull() || CollectionManager::instance()->albumRootPath(m_savingContext.destinationURL).isNull()) { // not in-collection operation - nothing to do return; } // copy the metadata of the original file to the new file qCDebug(DIGIKAM_GENERAL_LOG) << "was versioned" << (m_savingContext.executedOperation == SavingContext::SavingStateVersion) << "current" << d->currentImageInfo.id() << d->currentImageInfo.name() << "destinations" << m_savingContext.versionFileOperation.allFilePaths(); ImageInfo sourceInfo = d->currentImageInfo; // Set new current index. Employ synchronous scanning for this main file. d->currentImageInfo = ScanController::instance()->scannedInfo(m_savingContext.destinationURL.toLocalFile()); if (m_savingContext.destinationExisted) { // reset the orientation flag in the database DMetadata meta(m_canvas->currentImage().getMetadata()); d->currentImageInfo.setOrientation(meta.getImageOrientation()); } QStringList derivedFilePaths; if (m_savingContext.executedOperation == SavingContext::SavingStateVersion) { derivedFilePaths = m_savingContext.versionFileOperation.allFilePaths(); } else { derivedFilePaths << m_savingContext.destinationURL.toLocalFile(); } // Will ensure files are scanned, and then copy attributes in a thread FileActionMngr::instance()->copyAttributes(sourceInfo, derivedFilePaths); // The model updates asynchronously, so we need to force addition of the main entry d->ensureModelContains(d->currentImageInfo); // set origin of EditorCore: "As if" the last saved image was loaded directly resetOriginSwitchFile(); // If the DImg is put in the cache under the new name, this means the new file will not be reloaded. // This may irritate users who want to check for quality loss in lossy formats. // In any case, only do that if the format did not change - too many assumptions otherwise (see bug #138949). if (m_savingContext.originalFormat == m_savingContext.format) { LoadingCacheInterface::putImage(m_savingContext.destinationURL.toLocalFile(), m_canvas->currentImage()); } // all that is done in slotLoadCurrent, except for loading d->thumbBar->setCurrentIndex(d->currentIndex()); QModelIndex next = d->nextIndex(); if (next.isValid()) { m_canvas->preload(d->imageInfo(next).filePath()); } setViewToURL(d->currentImageInfo.fileUrl()); slotUpdateItemInfo(); // Pop-up a message to bring user when save is done. DNotificationWrapper(QLatin1String("editorsavefilecompleted"), i18n("Image saved successfully"), this, windowTitle()); } void ImageWindow::prepareImageToSave() { if (!d->currentImageInfo.isNull()) { // Write metadata from database to DImg MetadataHub hub; hub.load(d->currentImageInfo); DImg image(m_canvas->currentImage()); hub.write(image, MetadataHub::WRITE_ALL); // Ensure there is a UUID for the source image in the database, // even if not in the source image's metadata if (d->currentImageInfo.uuid().isNull()) { QString uuid = m_canvas->interface()->ensureHasCurrentUuid(); d->currentImageInfo.setUuid(uuid); } else { m_canvas->interface()->provideCurrentUuid(d->currentImageInfo.uuid()); } } } VersionManager* ImageWindow::versionManager() const { return &d->versionManager; } bool ImageWindow::save() { prepareImageToSave(); startingSave(d->currentUrl()); return true; } bool ImageWindow::saveAs() { prepareImageToSave(); return startingSaveAs(d->currentUrl()); } bool ImageWindow::saveNewVersion() { prepareImageToSave(); return startingSaveNewVersion(d->currentUrl()); } bool ImageWindow::saveCurrentVersion() { prepareImageToSave(); return startingSaveCurrentVersion(d->currentUrl()); } bool ImageWindow::saveNewVersionAs() { prepareImageToSave(); return startingSaveNewVersionAs(d->currentUrl()); } bool ImageWindow::saveNewVersionInFormat(const QString& format) { prepareImageToSave(); return startingSaveNewVersionInFormat(d->currentUrl(), format); } QUrl ImageWindow::saveDestinationUrl() { return d->currentUrl(); } void ImageWindow::slotDeleteCurrentItem() { deleteCurrentItem(true, false); } void ImageWindow::slotDeleteCurrentItemPermanently() { deleteCurrentItem(true, true); } void ImageWindow::slotDeleteCurrentItemPermanentlyDirectly() { deleteCurrentItem(false, true); } void ImageWindow::slotTrashCurrentItemDirectly() { deleteCurrentItem(false, false); } void ImageWindow::deleteCurrentItem(bool ask, bool permanently) { // This function implements all four of the above slots. // The meaning of permanently differs depending on the value of ask if (d->currentImageInfo.isNull()) { return; } if (!promptUserDelete(d->currentUrl())) { return; } bool useTrash; if (ask) { bool preselectDeletePermanently = permanently; DeleteDialog dialog(this); QList urlList; urlList << d->currentUrl(); if (!dialog.confirmDeleteList(urlList, DeleteDialogMode::Files, preselectDeletePermanently ? DeleteDialogMode::NoChoiceDeletePermanently : DeleteDialogMode::NoChoiceTrash)) { return; } useTrash = !dialog.shouldDelete(); } else { useTrash = !permanently; } DIO::del(d->currentImageInfo, useTrash); // bring all (sidebar) to a defined state without letting them sit on the deleted file emit signalNoCurrentItem(); // We have database information, which means information will get through // everywhere. Just do it asynchronously. removeCurrent(); } void ImageWindow::removeCurrent() { if (!d->currentIsValid()) { return; } if (m_canvas->interface()->undoState().hasChanges) { m_canvas->slotRestore(); } d->imageInfoModel->removeImageInfo(d->currentImageInfo); if (d->imageInfoModel->isEmpty()) { // No image in the current Album -> Quit ImageEditor... QMessageBox::information(this, i18n("No Image in Current Album"), i18n("There is no image to show in the current album.\n" "The image editor will be closed.")); close(); } } void ImageWindow::slotFileMetadataChanged(const QUrl& url) { if (url == d->currentUrl()) { m_canvas->interface()->readMetadataFromFile(url.toLocalFile()); } } /* * Should all be done by ItemViewCategorized * void ImageWindow::slotRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { // ignore when closed if (!isVisible() || !d->currentIsValid()) { return; } QModelIndex currentIndex = d->currentIndex(); if (currentIndex.row() >= start && currentIndex.row() <= end) { promptUserSave(d->currentUrl(), AlwaysNewVersion, false); // ensure selection int totalToRemove = end - start + 1; if (d->imageFilterModel->rowCount(parent) > totalToRemove) { if (end == d->imageFilterModel->rowCount(parent) - 1) { loadIndex(d->imageFilterModel->index(start - 1, 0)); // last remaining, no next one left } else { loadIndex(d->imageFilterModel->index(end + 1, 0)); // next remaining } } } }*/ /* void ImageWindow::slotCollectionImageChange(const CollectionImageChangeset& changeset) { bool needLoadCurrent = false; switch (changeset.operation()) { case CollectionImageChangeset::Removed: for (int i=0; iimageInfoList.size(); ++i) { if (changeset.containsImage(d->imageInfoList[i].id())) { if (d->currentImageInfo == d->imageInfoList[i]) { promptUserSave(d->currentUrl(), AlwaysNewVersion, false); if (removeItem(i)) { needLoadCurrent = true; } } else { removeItem(i); } --i; } } break; case CollectionImageChangeset::RemovedAll: for (int i=0; iimageInfoList.size(); ++i) { if (changeset.containsAlbum(d->imageInfoList[i].albumId())) { if (d->currentImageInfo == d->imageInfoList[i]) { promptUserSave(d->currentUrl(), AlwaysNewVersion, false); if (removeItem(i)) { needLoadCurrent = true; } } else { removeItem(i); } --i; } } break; default: break; } if (needLoadCurrent) { QTimer::singleShot(0, this, SLOT(slotLoadCurrent())); } } */ void ImageWindow::slotFilePrint() { printImage(d->currentUrl()); } void ImageWindow::slotPresentation() { PresentationMngr* const mngr = new PresentationMngr(this); foreach(const ImageInfo& info, d->imageInfoModel->imageInfos()) { mngr->addFile(info.fileUrl(), info.comment()); qApp->processEvents(); } mngr->showConfigDialog(); } void ImageWindow::slideShow(SlideShowSettings& settings) { m_cancelSlideShow = false; settings.exifRotate = MetadataSettings::instance()->settings().exifRotate; if (!d->imageInfoModel->isEmpty()) { // We have started image editor from Album GUI. we get picture comments from database. m_nameLabel->setProgressBarMode(StatusProgressBar::CancelProgressBarMode, i18n("Preparing slideshow. Please wait...")); float cnt = (float)d->imageInfoModel->rowCount(); int i = 0; foreach(const ImageInfo& info, d->imageInfoModel->imageInfos()) { SlidePictureInfo pictInfo; pictInfo.comment = info.comment(); pictInfo.rating = info.rating(); pictInfo.colorLabel = info.colorLabel(); pictInfo.pickLabel = info.pickLabel(); pictInfo.photoInfo = info.photoInfoContainer(); settings.pictInfoMap.insert(info.fileUrl(), pictInfo); settings.fileList << info.fileUrl(); m_nameLabel->setProgressValue((int)((i++ / cnt) * 100.0)); qApp->processEvents(); } } /* else { // We have started image editor from Camera GUI. we get picture comments from metadata. m_nameLabel->setProgressBarMode(StatusProgressBar::CancelProgressBarMode, i18n("Preparing slideshow. Please wait...")); cnt = (float)d->urlList.count(); DMetadata meta; settings.fileList = d->urlList; for (QList::Iterator it = d->urlList.begin() ; !m_cancelSlideShow && (it != d->urlList.end()) ; ++it) { SlidePictureInfo pictInfo; meta.load((*it).toLocalFile()); pictInfo.comment = meta.getImageComments()[QString("x-default")].caption; pictInfo.photoInfo = meta.getPhotographInformation(); settings.pictInfoMap.insert(*it, pictInfo); m_nameLabel->setProgressValue((int)((i++/cnt)*100.0)); qApp->processEvents(); } } */ m_nameLabel->setProgressBarMode(StatusProgressBar::TextMode, QString()); if (!m_cancelSlideShow) { SlideShow* const slide = new SlideShow(settings); TagsActionMngr::defaultManager()->registerActionsToWidget(slide); if (settings.startWithCurrent) { slide->setCurrentItem(d->currentUrl()); } connect(slide, SIGNAL(signalRatingChanged(QUrl,int)), this, SLOT(slotRatingChanged(QUrl,int))); connect(slide, SIGNAL(signalColorLabelChanged(QUrl,int)), this, SLOT(slotColorLabelChanged(QUrl,int))); connect(slide, SIGNAL(signalPickLabelChanged(QUrl,int)), this, SLOT(slotPickLabelChanged(QUrl,int))); connect(slide, SIGNAL(signalToggleTag(QUrl,int)), this, SLOT(slotToggleTag(QUrl,int))); slide->show(); } } void ImageWindow::dragMoveEvent(QDragMoveEvent* e) { int albumID; QList albumIDs; QList imageIDs; QList urls; QList kioURLs; if (DItemDrag::decode(e->mimeData(), urls, kioURLs, albumIDs, imageIDs) || DAlbumDrag::decode(e->mimeData(), urls, albumID) || DTagListDrag::canDecode(e->mimeData())) { e->accept(); return; } e->ignore(); } void ImageWindow::dropEvent(QDropEvent* e) { int albumID; QList albumIDs; QList imageIDs; QList urls; QList kioURLs; if (DItemDrag::decode(e->mimeData(), urls, kioURLs, albumIDs, imageIDs)) { ImageInfoList imageInfoList(imageIDs); if (imageInfoList.isEmpty()) { e->ignore(); return; } QString ATitle; AlbumManager* const man = AlbumManager::instance(); PAlbum* const palbum = man->findPAlbum(albumIDs.first()); if (palbum) { ATitle = palbum->title(); } loadImageInfos(imageInfoList, imageInfoList.first(), i18n("Album \"%1\"", ATitle)); e->accept(); } else if (DAlbumDrag::decode(e->mimeData(), urls, albumID)) { AlbumManager* const man = AlbumManager::instance(); QList itemIDs = CoreDbAccess().db()->getItemIDsInAlbum(albumID); ImageInfoList imageInfoList(itemIDs); if (imageInfoList.isEmpty()) { e->ignore(); return; } QString ATitle; PAlbum* const palbum = man->findPAlbum(albumIDs.first()); if (palbum) { ATitle = palbum->title(); } loadImageInfos(imageInfoList, imageInfoList.first(), i18n("Album \"%1\"", ATitle)); e->accept(); } else if (DTagListDrag::canDecode(e->mimeData())) { QList tagIDs; if (!DTagListDrag::decode(e->mimeData(), tagIDs)) { return; } AlbumManager* const man = AlbumManager::instance(); QList itemIDs = CoreDbAccess().db()->getItemIDsInTag(tagIDs.first(), true); ImageInfoList imageInfoList(itemIDs); if (imageInfoList.isEmpty()) { e->ignore(); return; } QString ATitle; TAlbum* const talbum = man->findTAlbum(tagIDs.first()); if (talbum) { ATitle = talbum->title(); } loadImageInfos(imageInfoList, imageInfoList.first(), i18n("Album \"%1\"", ATitle)); e->accept(); } else { e->ignore(); } } void ImageWindow::slotRevert() { if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } if (m_canvas->interface()->undoState().hasChanges) { m_canvas->slotRestore(); } } void ImageWindow::slotOpenOriginal() { if (!hasOriginalToRestore()) { return; } if (!promptUserSave(d->currentUrl(), AskIfNeeded)) { return; } // this time, with mustBeAvailable = true DImageHistory availableResolved = ImageScanner::resolvedImageHistory(m_canvas->interface()->getImageHistory(), true); QList originals = availableResolved.referredImagesOfType(HistoryImageId::Original); HistoryImageId originalId = m_canvas->interface()->getImageHistory().originalReferredImage(); if (originals.isEmpty()) { //TODO: point to remote collection QMessageBox::warning(this, i18nc("@title", "File Not Available"), i18nc("@info", "The original file (%1) is currently not available", originalId.m_fileName)); return; } QList imageInfos; foreach(const HistoryImageId& id, originals) { QUrl url = QUrl::fromLocalFile(id.m_filePath); url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + (id.m_fileName)); imageInfos << ImageInfo::fromUrl(url); } ImageScanner::sortByProximity(imageInfos, d->currentImageInfo); if (!imageInfos.isEmpty() && !imageInfos.first().isNull()) { openImage(imageInfos.first()); } } bool ImageWindow::hasOriginalToRestore() { // not implemented for db-less situation, so check for ImageInfo return !d->currentImageInfo.isNull() && EditorWindow::hasOriginalToRestore(); } DImageHistory ImageWindow::resolvedImageHistory(const DImageHistory& history) { return ImageScanner::resolvedImageHistory(history); } ThumbBarDock* ImageWindow::thumbBar() const { return d->thumbBarDock; } Sidebar* ImageWindow::rightSideBar() const { return (dynamic_cast(d->rightSideBar)); } void ImageWindow::slotComponentsInfo() { showDigikamComponentsInfo(); } void ImageWindow::slotDBStat() { showDigikamDatabaseStat(); } void ImageWindow::slotAddedDropedItems(QDropEvent* e) { int albumID; QList albumIDs; QList imageIDs; QList urls, kioURLs; ImageInfoList imgList; if (DItemDrag::decode(e->mimeData(), urls, kioURLs, albumIDs, imageIDs)) { imgList = ImageInfoList(imageIDs); } else if (DAlbumDrag::decode(e->mimeData(), urls, albumID)) { QList itemIDs = CoreDbAccess().db()->getItemIDsInAlbum(albumID); imgList = ImageInfoList(itemIDs); } else if (DTagListDrag::canDecode(e->mimeData())) { QList tagIDs; if (!DTagListDrag::decode(e->mimeData(), tagIDs)) { return; } QList itemIDs = CoreDbAccess().db()->getItemIDsInTag(tagIDs.first(), true); imgList = ImageInfoList(itemIDs); } e->accept(); if (!imgList.isEmpty()) { loadImageInfos(imgList, imgList.first(), QString()); } } void ImageWindow::slotFileWithDefaultApplication() { DFileOperations::openFilesWithDefaultApplication(QList() << d->currentUrl()); } void ImageWindow::addServicesMenu() { addServicesMenuForUrl(d->currentUrl()); } void ImageWindow::slotOpenWith(QAction* action) { openWith(d->currentUrl(), action); } void ImageWindow::slotRightSideBarActivateTitles() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->setFocusToTitlesEdit(); } void ImageWindow::slotRightSideBarActivateComments() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->setFocusToCommentsEdit(); } void ImageWindow::slotRightSideBarActivateAssignedTags() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->activateAssignedTagsButton(); } void ImageWindow::slotImportFromScanner() { #ifdef HAVE_KSANE m_ksaneAction->activate(DigikamApp::instance()->scannerTargetPlace(), configGroupName()); connect(m_ksaneAction, SIGNAL(signalImportedImage(QUrl)), this, SLOT(slotImportedImagefromScanner(QUrl))); #endif } void ImageWindow::slotImportedImagefromScanner(const QUrl& url) { ImageInfo info = ScanController::instance()->scannedInfo(url.toLocalFile()); openImage(info); } void ImageWindow::slotEditGeolocation() { #ifdef HAVE_MARBLE ImageInfoList infos = d->thumbBar->allImageInfos(); if (infos.isEmpty()) { return; } TagModel* const tagModel = new TagModel(AbstractAlbumModel::IgnoreRootAlbum, this); TagPropertiesFilterModel* const filterModel = new TagPropertiesFilterModel(this); filterModel->setSourceAlbumModel(tagModel); filterModel->sort(0); QPointer dialog = new GeolocationEdit(filterModel, new DBInfoIface(this, d->thumbBar->allUrls()), QApplication::activeWindow()); dialog->setItems(ImageGPS::infosToItems(infos)); dialog->exec(); delete dialog; // Refresh Database with new metadata from files. foreach(const ImageInfo& inf, infos) { ScanController::instance()->scannedInfo(inf.fileUrl().toLocalFile()); } #endif } void ImageWindow::slotEditMetadata() { if (d->currentImageInfo.isNull()) return; QUrl url = d->currentImageInfo.fileUrl(); QPointer dialog = new MetadataEditDialog(QApplication::activeWindow(), QList() << url); dialog->exec(); delete dialog; // Refresh Database with new metadata from file. - ScanController::instance()->scannedInfo(url.toLocalFile()); + CollectionScanner scanner; + scanner.scanFile(url.toLocalFile(), CollectionScanner::Rescan); } void ImageWindow::slotHtmlGallery() { #ifdef HAVE_HTMLGALLERY HTMLWizard w(this, new DBInfoIface(this, d->thumbBar->allUrls())); w.exec(); #endif } void ImageWindow::slotCalendar() { CalWizard w(d->thumbBar->allUrls(), this); w.exec(); } void ImageWindow::slotPanorama() { #ifdef HAVE_PANORAMA PanoManager::instance()->checkBinaries(); PanoManager::instance()->setItemsList(d->thumbBar->allUrls()); PanoManager::instance()->run(); #endif } void ImageWindow::slotVideoSlideshow() { #ifdef HAVE_MEDIAPLAYER VidSlideWizard w(this, new DBInfoIface(this, d->thumbBar->allUrls())); w.exec(); #endif } void ImageWindow::slotExpoBlending() { ExpoBlendingManager::instance()->checkBinaries(); ExpoBlendingManager::instance()->setItemsList(d->thumbBar->allUrls()); ExpoBlendingManager::instance()->run(); } void ImageWindow::slotSendByMail() { MailWizard w(this, new DBInfoIface(this, d->thumbBar->allUrls())); w.exec(); } void ImageWindow::slotPrintCreator() { AdvPrintWizard w(this, new DBInfoIface(this, d->thumbBar->allUrls())); w.exec(); } void ImageWindow::slotMediaServer() { DBInfoIface* const iface = new DBInfoIface(this, QList(), ApplicationSettings::Tools); // NOTE: We overwrite the default albums chooser object name for load save check items state between sessions. // The goal is not mix these settings with other export tools. iface->setObjectName(QLatin1String("SetupMediaServerIface")); DMediaServerDlg w(this, iface); w.exec(); } } // namespace Digikam diff --git a/utilities/lighttable/lighttablewindow.cpp b/utilities/lighttable/lighttablewindow.cpp index 939ff49b42..1b9f617b87 100644 --- a/utilities/lighttable/lighttablewindow.cpp +++ b/utilities/lighttable/lighttablewindow.cpp @@ -1,1906 +1,1908 @@ /* ============================================================ * * This file is a part of digiKam project * http://www.digikam.org * * Date : 2007-03-05 * Description : digiKam light table GUI * * Copyright (C) 2007-2018 by Gilles Caulier * * This program is free software; you can redistribute it * and/or modify it under the terms of the GNU General * Public License as published by the Free Software Foundation; * either version 2, or (at your option) * any later version. * * 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. * * ============================================================ */ #include "lighttablewindow.h" #include "lighttablewindow_p.h" // Qt includes #include #include #include #include #include #include #include #include // KDE includes #include #include // Local includes #include "drawdecoder.h" #include "digikam_debug.h" #include "componentsinfo.h" #include "digikamapp.h" #include "thememanager.h" #include "dimg.h" #include "dio.h" #include "dmetadata.h" #include "dfileoperations.h" #include "metadatasettings.h" #include "metadataedit.h" #include "applicationsettings.h" #include "albummanager.h" #include "loadingcacheinterface.h" #include "deletedialog.h" #include "iccsettings.h" #include "imagewindow.h" #include "imagegps.h" #include "imagedescedittab.h" #include "presentationmngr.h" #include "slideshowbuilder.h" #include "slideshow.h" #include "setup.h" #include "syncjob.h" #include "lighttablepreview.h" #include "albummodel.h" #include "albumfiltermodel.h" #include "coredbchangesets.h" +#include "collectionscanner.h" #include "scancontroller.h" #include "tagsactionmngr.h" #include "thumbbardock.h" #include "thumbnailsize.h" #include "thumbnailloadthread.h" #include "dexpanderbox.h" #include "dbinfoiface.h" #include "calwizard.h" #include "expoblendingmanager.h" #include "mailwizard.h" #include "advprintwizard.h" #include "dmediaserverdlg.h" #ifdef HAVE_MARBLE # include "geolocationedit.h" #endif #ifdef HAVE_HTMLGALLERY # include "htmlwizard.h" #endif #ifdef HAVE_PANORAMA # include "panomanager.h" #endif #ifdef HAVE_MEDIAPLAYER # include "vidslidewizard.h" #endif namespace Digikam { LightTableWindow* LightTableWindow::m_instance = 0; LightTableWindow* LightTableWindow::lightTableWindow() { if (!m_instance) { new LightTableWindow(); } return m_instance; } bool LightTableWindow::lightTableWindowCreated() { return m_instance; } LightTableWindow::LightTableWindow() : DXmlGuiWindow(0), d(new Private) { setConfigGroupName(QLatin1String("LightTable Settings")); setXMLFile(QLatin1String("lighttablewindowui5.rc")); m_instance = this; setWindowFlags(Qt::Window); setCaption(i18n("Light Table")); // We don't want to be deleted on close setAttribute(Qt::WA_DeleteOnClose, false); setFullScreenOptions(FS_LIGHTTABLE); // -- Build the GUI ------------------------------- setupUserArea(); setupActions(); setupStatusBar(); // ------------------------------------------------ setupConnections(); slotColorManagementOptionsChanged(); readSettings(); d->leftSideBar->populateTags(); d->rightSideBar->populateTags(); applySettings(); setAutoSaveSettings(configGroupName(), true); } LightTableWindow::~LightTableWindow() { m_instance = 0; delete d->thumbView; delete d->rightSideBar; delete d->leftSideBar; delete d; } void LightTableWindow::readSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroupName()); d->hSplitter->restoreState(group, QLatin1String("Horizontal Splitter State")); d->barViewDock->setShouldBeVisible(group.readEntry(QLatin1String("Show Thumbbar"), true)); d->navigateByPairAction->setChecked(group.readEntry(QLatin1String("Navigate By Pair"), false)); slotToggleNavigateByPair(); d->leftSideBar->setConfigGroup(KConfigGroup(&group, QLatin1String("Left Sidebar"))); d->leftSideBar->loadState(); d->rightSideBar->setConfigGroup(KConfigGroup(&group, QLatin1String("Right Sidebar"))); d->rightSideBar->loadState(); readFullScreenSettings(group); } void LightTableWindow::writeSettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroupName()); d->hSplitter->saveState(group, QLatin1String("Horizontal Splitter State")); group.writeEntry(QLatin1String("Show Thumbbar"), d->barViewDock->shouldBeVisible()); group.writeEntry(QLatin1String("Navigate By Pair"), d->navigateByPairAction->isChecked()); group.writeEntry(QLatin1String("Clear On Close"), d->clearOnCloseAction->isChecked()); d->leftSideBar->setConfigGroup(KConfigGroup(&group, QLatin1String("Left Sidebar"))); d->leftSideBar->saveState(); d->rightSideBar->setConfigGroup(KConfigGroup(&group, QLatin1String("Right Sidebar"))); d->rightSideBar->saveState(); config->sync(); } void LightTableWindow::applySettings() { KSharedConfig::Ptr config = KSharedConfig::openConfig(); KConfigGroup group = config->group(configGroupName()); d->autoLoadOnRightPanel = group.readEntry(QLatin1String("Auto Load Right Panel"), true); d->autoSyncPreview = group.readEntry(QLatin1String("Auto Sync Preview"), true); d->clearOnCloseAction->setChecked(group.readEntry(QLatin1String("Clear On Close"), false)); slotApplicationSettingsChanged(); // Restore full screen Mode readFullScreenSettings(group); // NOTE: Image orientation settings in thumbbar is managed by image model. refreshView(); } void LightTableWindow::refreshView() { d->leftSideBar->refreshTagsView(); d->rightSideBar->refreshTagsView(); } void LightTableWindow::closeEvent(QCloseEvent* e) { if (!e) { return; } if (d->clearOnCloseAction->isChecked()) { slotClearItemsList(); } // There is one nasty habit with the thumbnail bar if it is floating: it // doesn't close when the parent window does, so it needs to be manually // closed. If the light table is opened again, its original state needs to // be restored. // This only needs to be done when closing a visible window and not when // destroying a closed window, since the latter case will always report that // the thumbnail bar isn't visible. if (isVisible()) { d->barViewDock->hide(); } writeSettings(); DXmlGuiWindow::closeEvent(e); e->accept(); } void LightTableWindow::showEvent(QShowEvent*) { // Restore the visibility of the thumbbar and start autosaving again. d->barViewDock->restoreVisibility(); } void LightTableWindow::setupUserArea() { QWidget* const mainW = new QWidget(this); d->hSplitter = new SidebarSplitter(Qt::Horizontal, mainW); QHBoxLayout* const hlay = new QHBoxLayout(mainW); // The left sidebar d->leftSideBar = new ImagePropertiesSideBarDB(mainW, d->hSplitter, Qt::LeftEdge, true); // The central preview is wrapped in a KMainWindow so that the thumbnail // bar can float around it. KMainWindow* const viewContainer = new KMainWindow(mainW, Qt::Widget); d->hSplitter->addWidget(viewContainer); d->previewView = new LightTableView(viewContainer); viewContainer->setCentralWidget(d->previewView); // The right sidebar. d->rightSideBar = new ImagePropertiesSideBarDB(mainW, d->hSplitter, Qt::RightEdge, true); hlay->addWidget(d->leftSideBar); hlay->addWidget(d->hSplitter); hlay->addWidget(d->rightSideBar); hlay->setSpacing(0); hlay->setContentsMargins(QMargins()); hlay->setStretchFactor(d->hSplitter, 10); d->hSplitter->setFrameStyle(QFrame::NoFrame); d->hSplitter->setFrameShadow(QFrame::Plain); d->hSplitter->setFrameShape(QFrame::NoFrame); d->hSplitter->setOpaqueResize(false); d->hSplitter->setStretchFactor(1, 10); // set previewview+thumbbar container default size to max. // The thumb bar is placed in a detachable/dockable widget. d->barViewDock = new ThumbBarDock(viewContainer, Qt::Tool); d->barViewDock->setObjectName(QLatin1String("lighttable_thumbbar")); d->thumbView = new LightTableThumbBar(d->barViewDock); d->barViewDock->setWidget(d->thumbView); viewContainer->addDockWidget(Qt::TopDockWidgetArea, d->barViewDock); d->barViewDock->setFloating(false); // Restore the previous state. This doesn't emit the proper signals to the // dock widget, so it has to be manually reinitialized. viewContainer->setAutoSaveSettings(QLatin1String("LightTable Thumbbar"), true); connect(d->barViewDock, SIGNAL(dockLocationChanged(Qt::DockWidgetArea)), d->thumbView, SLOT(slotDockLocationChanged(Qt::DockWidgetArea))); d->barViewDock->reInitialize(); setCentralWidget(mainW); } void LightTableWindow::setupStatusBar() { d->leftZoomBar = new DZoomBar(statusBar()); d->leftZoomBar->setZoomToFitAction(d->leftZoomFitToWindowAction); d->leftZoomBar->setZoomTo100Action(d->leftZoomTo100percents); d->leftZoomBar->setZoomPlusAction(d->leftZoomPlusAction); d->leftZoomBar->setZoomMinusAction(d->leftZoomMinusAction); d->leftZoomBar->setBarMode(DZoomBar::PreviewZoomCtrl); d->leftZoomBar->setEnabled(false); statusBar()->addWidget(d->leftZoomBar, 1); d->leftFileName = new DAdjustableLabel(statusBar()); d->leftFileName->setAlignment(Qt::AlignLeft | Qt::AlignVCenter); statusBar()->addWidget(d->leftFileName, 10); d->statusProgressBar = new StatusProgressBar(statusBar()); d->statusProgressBar->setAlignment(Qt::AlignCenter); statusBar()->addWidget(d->statusProgressBar, 10); d->rightFileName = new DAdjustableLabel(statusBar()); d->rightFileName->setAlignment(Qt::AlignRight | Qt::AlignVCenter); statusBar()->addWidget(d->rightFileName, 10); d->rightZoomBar = new DZoomBar(statusBar()); d->rightZoomBar->setZoomToFitAction(d->rightZoomFitToWindowAction); d->rightZoomBar->setZoomTo100Action(d->rightZoomTo100percents); d->rightZoomBar->setZoomPlusAction(d->rightZoomPlusAction); d->rightZoomBar->setZoomMinusAction(d->rightZoomMinusAction); d->rightZoomBar->setBarMode(DZoomBar::PreviewZoomCtrl); d->rightZoomBar->setEnabled(false); statusBar()->addWidget(d->rightZoomBar, 1); } void LightTableWindow::setupConnections() { connect(ApplicationSettings::instance(), SIGNAL(setupChanged()), this, SLOT(slotApplicationSettingsChanged())); connect(ThemeManager::instance(), SIGNAL(signalThemeChanged()), this, SLOT(slotThemeChanged())); connect(IccSettings::instance(), SIGNAL(settingsChanged()), this, SLOT(slotColorManagementOptionsChanged())); // Thumbs bar connections --------------------------------------- connect(d->thumbView, SIGNAL(signalSetItemOnLeftPanel(ImageInfo)), this, SLOT(slotSetItemOnLeftPanel(ImageInfo))); connect(d->thumbView, SIGNAL(signalSetItemOnRightPanel(ImageInfo)), this, SLOT(slotSetItemOnRightPanel(ImageInfo))); connect(d->thumbView, SIGNAL(signalRemoveItem(ImageInfo)), this, SLOT(slotRemoveItem(ImageInfo))); connect(d->thumbView, SIGNAL(signalEditItem(ImageInfo)), this, SLOT(slotEditItem(ImageInfo))); connect(d->thumbView, SIGNAL(signalClearAll()), this, SLOT(slotClearItemsList())); connect(d->thumbView, SIGNAL(signalDroppedItems(QList)), this, SLOT(slotThumbbarDroppedItems(QList))); connect(d->thumbView, SIGNAL(currentChanged(ImageInfo)), this, SLOT(slotItemSelected(ImageInfo))); connect(d->thumbView, SIGNAL(signalContentChanged()), this, SLOT(slotRefreshStatusBar())); // Zoom bars connections ----------------------------------------- connect(d->leftZoomBar, SIGNAL(signalZoomSliderChanged(int)), d->previewView, SLOT(slotLeftZoomSliderChanged(int))); connect(d->leftZoomBar, SIGNAL(signalZoomValueEdited(double)), d->previewView, SLOT(setLeftZoomFactor(double))); connect(d->rightZoomBar, SIGNAL(signalZoomSliderChanged(int)), d->previewView, SLOT(slotRightZoomSliderChanged(int))); connect(d->rightZoomBar, SIGNAL(signalZoomValueEdited(double)), d->previewView, SLOT(setRightZoomFactor(double))); // View connections --------------------------------------------- connect(d->previewView, SIGNAL(signalLeftPopupTagsView()), d->leftSideBar, SLOT(slotPopupTagsView())); connect(d->previewView, SIGNAL(signalRightPopupTagsView()), d->rightSideBar, SLOT(slotPopupTagsView())); connect(d->previewView, SIGNAL(signalLeftZoomFactorChanged(double)), this, SLOT(slotLeftZoomFactorChanged(double))); connect(d->previewView, SIGNAL(signalRightZoomFactorChanged(double)), this, SLOT(slotRightZoomFactorChanged(double))); connect(d->previewView, SIGNAL(signalEditItem(ImageInfo)), this, SLOT(slotEditItem(ImageInfo))); connect(d->previewView, SIGNAL(signalDeleteItem(ImageInfo)), this, SLOT(slotDeleteItem(ImageInfo))); connect(d->previewView, SIGNAL(signalSlideShow()), this, SLOT(slotSlideShowAll())); connect(d->previewView, SIGNAL(signalLeftSlideShowCurrent()), this, SLOT(slotLeftSlideShowManualFromCurrent())); connect(d->previewView, SIGNAL(signalRightSlideShowCurrent()), this, SLOT(slotRightSlideShowManualFromCurrent())); connect(d->previewView, SIGNAL(signalLeftDroppedItems(ImageInfoList)), this, SLOT(slotLeftDroppedItems(ImageInfoList))); connect(d->previewView, SIGNAL(signalRightDroppedItems(ImageInfoList)), this, SLOT(slotRightDroppedItems(ImageInfoList))); connect(d->previewView, SIGNAL(signalToggleOnSyncPreview(bool)), this, SLOT(slotToggleOnSyncPreview(bool))); connect(d->previewView, SIGNAL(signalLeftPreviewLoaded(bool)), this, SLOT(slotLeftPreviewLoaded(bool))); connect(d->previewView, SIGNAL(signalRightPreviewLoaded(bool)), this, SLOT(slotRightPreviewLoaded(bool))); connect(d->previewView, SIGNAL(signalLeftPanelLeftButtonClicked()), this, SLOT(slotLeftPanelLeftButtonClicked())); connect(d->previewView, SIGNAL(signalRightPanelLeftButtonClicked()), this, SLOT(slotRightPanelLeftButtonClicked())); connect(this, SIGNAL(signalWindowHasMoved()), d->leftZoomBar, SLOT(slotUpdateTrackerPos())); connect(this, SIGNAL(signalWindowHasMoved()), d->rightZoomBar, SLOT(slotUpdateTrackerPos())); // -- FileWatch connections ------------------------------ LoadingCacheInterface::connectToSignalFileChanged(this, SLOT(slotFileChanged(QString))); } void LightTableWindow::setupActions() { // -- Standard 'File' menu actions --------------------------------------------- KActionCollection* const ac = actionCollection(); d->backwardAction = buildStdAction(StdBackAction, this, SLOT(slotBackward()), this); ac->addAction(QLatin1String("lighttable_backward"), d->backwardAction); ac->setDefaultShortcuts(d->backwardAction, QList() << Qt::Key_PageUp << Qt::Key_Backspace); d->forwardAction = buildStdAction(StdForwardAction, this, SLOT(slotForward()), this); ac->addAction(QLatin1String("lighttable_forward"), d->forwardAction); ac->setDefaultShortcuts(d->forwardAction, QList() << Qt::Key_PageDown << Qt::Key_Space); d->forwardAction->setEnabled(false); d->firstAction = new QAction(QIcon::fromTheme(QLatin1String("go-first")), i18n("&First"), this); d->firstAction->setEnabled(false); connect(d->firstAction, SIGNAL(triggered()), this, SLOT(slotFirst())); ac->addAction(QLatin1String("lighttable_first"), d->firstAction); ac->setDefaultShortcuts(d->firstAction, QList() << Qt::CTRL + Qt::Key_Home); d->lastAction = new QAction(QIcon::fromTheme(QLatin1String("go-last")), i18n("&Last"), this); d->lastAction->setEnabled(false); connect(d->lastAction, SIGNAL(triggered()), this, SLOT(slotLast())); ac->addAction(QLatin1String("lighttable_last"), d->lastAction); ac->setDefaultShortcuts(d->lastAction, QList() << Qt::CTRL + Qt::Key_End); d->setItemLeftAction = new QAction(QIcon::fromTheme(QLatin1String("go-previous")), i18n("On left"), this); d->setItemLeftAction->setEnabled(false); d->setItemLeftAction->setWhatsThis(i18n("Show item on left panel")); connect(d->setItemLeftAction, SIGNAL(triggered()), this, SLOT(slotSetItemLeft())); ac->addAction(QLatin1String("lighttable_setitemleft"), d->setItemLeftAction); ac->setDefaultShortcut(d->setItemLeftAction, Qt::CTRL + Qt::Key_L); d->setItemRightAction = new QAction(QIcon::fromTheme(QLatin1String("go-next")), i18n("On right"), this); d->setItemRightAction->setEnabled(false); d->setItemRightAction->setWhatsThis(i18n("Show item on right panel")); connect(d->setItemRightAction, SIGNAL(triggered()), this, SLOT(slotSetItemRight())); ac->addAction(QLatin1String("lighttable_setitemright"), d->setItemRightAction); ac->setDefaultShortcut(d->setItemRightAction, Qt::CTRL + Qt::Key_R); d->editItemAction = new QAction(QIcon::fromTheme(QLatin1String("document-edit")), i18n("Edit"), this); d->editItemAction->setEnabled(false); connect(d->editItemAction, SIGNAL(triggered()), this, SLOT(slotEditItem())); ac->addAction(QLatin1String("lighttable_edititem"), d->editItemAction); ac->setDefaultShortcut(d->editItemAction, Qt::Key_F4); QAction* const openWithAction = new QAction(QIcon::fromTheme(QLatin1String("preferences-desktop-filetype-association")), i18n("Open With Default Application"), this); openWithAction->setWhatsThis(i18n("Open the item with default assigned application.")); connect(openWithAction, SIGNAL(triggered()), this, SLOT(slotFileWithDefaultApplication())); ac->addAction(QLatin1String("open_with_default_application"), openWithAction); ac->setDefaultShortcut(openWithAction, Qt::META + Qt::Key_F4); d->removeItemAction = new QAction(QIcon::fromTheme(QLatin1String("list-remove")), i18n("Remove item from LightTable"), this); d->removeItemAction->setEnabled(false); connect(d->removeItemAction, SIGNAL(triggered()), this, SLOT(slotRemoveItem())); ac->addAction(QLatin1String("lighttable_removeitem"), d->removeItemAction); ac->setDefaultShortcut(d->removeItemAction, Qt::CTRL + Qt::Key_K); d->clearListAction = new QAction(QIcon::fromTheme(QLatin1String("edit-clear")), i18n("Remove all items from LightTable"), this); d->clearListAction->setEnabled(false); connect(d->clearListAction, SIGNAL(triggered()), this, SLOT(slotClearItemsList())); ac->addAction(QLatin1String("lighttable_clearlist"), d->clearListAction); ac->setDefaultShortcut(d->clearListAction, Qt::CTRL + Qt::SHIFT + Qt::Key_K); d->fileDeleteAction = new QAction(QIcon::fromTheme(QLatin1String("user-trash")), i18nc("Non-pluralized", "Move to Trash"), this); d->fileDeleteAction->setEnabled(false); connect(d->fileDeleteAction, SIGNAL(triggered()), this, SLOT(slotDeleteItem())); ac->addAction(QLatin1String("lighttable_filedelete"), d->fileDeleteAction); ac->setDefaultShortcut(d->fileDeleteAction, Qt::Key_Delete); d->fileDeleteFinalAction = new QAction(QIcon::fromTheme(QLatin1String("edit-delete")), i18n("Delete immediately"), this); d->fileDeleteFinalAction->setEnabled(false); connect(d->fileDeleteFinalAction, SIGNAL(triggered()), this, SLOT(slotDeleteFinalItem())); ac->addAction(QLatin1String("lighttable_filefinaldelete"), d->fileDeleteFinalAction); ac->setDefaultShortcut(d->fileDeleteFinalAction, Qt::SHIFT + Qt::Key_Delete); QAction* const closeAction = buildStdAction(StdCloseAction, this, SLOT(close()), this); ac->addAction(QLatin1String("lighttable_close"), closeAction); // -- Standard 'View' menu actions --------------------------------------------- d->syncPreviewAction = new QAction(QIcon::fromTheme(QLatin1String("view-split-left-right")), i18n("Synchronize"), this); d->syncPreviewAction->setEnabled(false); d->syncPreviewAction->setCheckable(true); d->syncPreviewAction->setWhatsThis(i18n("Synchronize preview from left and right panels")); connect(d->syncPreviewAction, SIGNAL(triggered()), this, SLOT(slotToggleSyncPreview())); ac->addAction(QLatin1String("lighttable_syncpreview"), d->syncPreviewAction); ac->setDefaultShortcut(d->syncPreviewAction, Qt::CTRL + Qt::SHIFT + Qt::Key_Y); d->navigateByPairAction = new QAction(QIcon::fromTheme(QLatin1String("system-run")), i18n("By Pair"), this); d->navigateByPairAction->setEnabled(false); d->navigateByPairAction->setCheckable(true); d->navigateByPairAction->setWhatsThis(i18n("Navigate by pairs with all items")); connect(d->navigateByPairAction, SIGNAL(triggered()), this, SLOT(slotToggleNavigateByPair())); ac->addAction(QLatin1String("lighttable_navigatebypair"), d->navigateByPairAction); ac->setDefaultShortcut(d->navigateByPairAction, Qt::CTRL + Qt::SHIFT + Qt::Key_P); d->clearOnCloseAction = new QAction(QIcon::fromTheme(QLatin1String("edit-clear")), i18n("Clear On Close"), this); d->clearOnCloseAction->setEnabled(true); d->clearOnCloseAction->setCheckable(true); d->clearOnCloseAction->setToolTip(i18n("Clear light table when it is closed")); d->clearOnCloseAction->setWhatsThis(i18n("Remove all images from the light table when it is closed")); ac->addAction(QLatin1String("lighttable_clearonclose"), d->clearOnCloseAction); ac->setDefaultShortcut(d->clearOnCloseAction, Qt::CTRL + Qt::SHIFT + Qt::Key_C); d->showBarAction = d->barViewDock->getToggleAction(this); ac->addAction(QLatin1String("lighttable_showthumbbar"), d->showBarAction); ac->setDefaultShortcut(d->showBarAction, Qt::CTRL + Qt::Key_T); createFullScreenAction(QLatin1String("lighttable_fullscreen")); createSidebarActions(); d->slideShowAction = new QAction(QIcon::fromTheme(QLatin1String("view-presentation")), i18n("Slideshow"), this); connect(d->slideShowAction, SIGNAL(triggered()), this, SLOT(slotSlideShowAll())); ac->addAction(QLatin1String("lighttable_slideshow"), d->slideShowAction); ac->setDefaultShortcut(d->slideShowAction, Qt::Key_F9); createPresentationAction(); // -- Standard 'Tools' menu actions ------------------------ createKSaneAction(); createMetadataEditAction(); createGeolocationEditAction(); createHtmlGalleryAction(); createPanoramaAction(); createExpoBlendingAction(); createCalendarAction(); createVideoSlideshowAction(); createSendByMailAction(); createPrintCreatorAction(); createMediaServerAction(); // Left Panel Zoom Actions d->leftZoomPlusAction = buildStdAction(StdZoomInAction, d->previewView, SLOT(slotIncreaseLeftZoom()), this); d->leftZoomPlusAction->setEnabled(false); QKeySequence leftKeysPlus(d->leftZoomPlusAction->shortcut()[0], Qt::Key_Plus); ac->addAction(QLatin1String("lighttable_zoomplus_left"), d->leftZoomPlusAction); ac->setDefaultShortcut(d->leftZoomPlusAction, leftKeysPlus); d->leftZoomMinusAction = buildStdAction(StdZoomOutAction, d->previewView, SLOT(slotDecreaseLeftZoom()), this); d->leftZoomMinusAction->setEnabled(false); QKeySequence leftKeysMinus(d->leftZoomMinusAction->shortcut()[0], Qt::Key_Minus); ac->addAction(QLatin1String("lighttable_zoomminus_left"), d->leftZoomMinusAction); ac->setDefaultShortcut(d->leftZoomMinusAction, leftKeysMinus); d->leftZoomTo100percents = new QAction(QIcon::fromTheme(QLatin1String("zoom-original")), i18n("Zoom to 100%"), this); connect(d->leftZoomTo100percents, SIGNAL(triggered()), d->previewView, SLOT(slotLeftZoomTo100())); ac->addAction(QLatin1String("lighttable_zoomto100percents_left"), d->leftZoomTo100percents); ac->setDefaultShortcut(d->leftZoomTo100percents, Qt::CTRL + Qt::Key_Period); d->leftZoomFitToWindowAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-fit-best")), i18n("Fit to &Window"), this); connect(d->leftZoomFitToWindowAction, SIGNAL(triggered()), d->previewView, SLOT(slotLeftFitToWindow())); ac->addAction(QLatin1String("lighttable_zoomfit2window_left"), d->leftZoomFitToWindowAction); ac->setDefaultShortcut(d->leftZoomFitToWindowAction, Qt::ALT + Qt::CTRL + Qt::Key_E); // Right Panel Zoom Actions d->rightZoomPlusAction = buildStdAction(StdZoomInAction, d->previewView, SLOT(slotIncreaseRightZoom()), this); d->rightZoomPlusAction->setEnabled(false); QKeySequence rightKeysPlus(d->rightZoomPlusAction->shortcut()[0], Qt::SHIFT + Qt::CTRL + Qt::Key_Plus, Qt::SHIFT + Qt::Key_Plus); ac->addAction(QLatin1String("lighttable_zoomplus_right"), d->rightZoomPlusAction); ac->setDefaultShortcut(d->rightZoomPlusAction, rightKeysPlus); d->rightZoomMinusAction = buildStdAction(StdZoomOutAction, d->previewView, SLOT(slotDecreaseRightZoom()), this); d->rightZoomMinusAction->setEnabled(false); QKeySequence rightKeysMinus(d->rightZoomMinusAction->shortcut()[0], Qt::SHIFT + Qt::CTRL + Qt::Key_Minus, Qt::SHIFT + Qt::Key_Minus); ac->addAction(QLatin1String("lighttable_zoomminus_right"), d->rightZoomMinusAction); ac->setDefaultShortcut(d->rightZoomMinusAction, rightKeysMinus); d->rightZoomTo100percents = new QAction(QIcon::fromTheme(QLatin1String("zoom-original")), i18n("Zoom to 100%"), this); connect(d->rightZoomTo100percents, SIGNAL(triggered()), d->previewView, SLOT(slotRightZoomTo100())); ac->addAction(QLatin1String("lighttable_zoomto100percents_right"), d->rightZoomTo100percents); ac->setDefaultShortcut(d->rightZoomTo100percents, Qt::SHIFT + Qt::CTRL + Qt::Key_Period); d->rightZoomFitToWindowAction = new QAction(QIcon::fromTheme(QLatin1String("zoom-fit-best")), i18n("Fit to &Window"), this); connect(d->rightZoomFitToWindowAction, SIGNAL(triggered()), d->previewView, SLOT(slotRightFitToWindow())); ac->addAction(QLatin1String("lighttable_zoomfit2window_right"), d->rightZoomFitToWindowAction); ac->setDefaultShortcut(d->rightZoomFitToWindowAction, Qt::SHIFT + Qt::CTRL + Qt::Key_E); // ----------------------------------------------------------- d->viewCMViewAction = new QAction(QIcon::fromTheme(QLatin1String("video-display")), i18n("Color-Managed View"), this); d->viewCMViewAction->setCheckable(true); connect(d->viewCMViewAction, SIGNAL(triggered()), this, SLOT(slotToggleColorManagedView())); ac->addAction(QLatin1String("color_managed_view"), d->viewCMViewAction); ac->setDefaultShortcut(d->viewCMViewAction, Qt::Key_F12); // ----------------------------------------------------------------------------- ThemeManager::instance()->registerThemeActions(this); // Standard 'Help' menu actions createHelpActions(); // Provides a menu entry that allows showing/hiding the toolbar(s) setStandardToolBarMenuEnabled(true); // Provides a menu entry that allows showing/hiding the statusbar createStandardStatusBarAction(); // Standard 'Configure' menu actions createSettingsActions(); // -- Keyboard-only actions ---------------------------------------------------- d->addPageUpDownActions(this, this); QAction* const altBackwardAction = new QAction(i18n("Previous Image"), this); ac->addAction(QLatin1String("lighttable_backward_shift_space"), altBackwardAction); ac->setDefaultShortcut(altBackwardAction, Qt::SHIFT + Qt::Key_Space); connect(altBackwardAction, SIGNAL(triggered()), this, SLOT(slotBackward())); // Labels shortcuts must be registered here to be saved in XML GUI files if user customize it. TagsActionMngr::defaultManager()->registerLabelsActions(ac); QAction* const editTitlesRight = new QAction(i18n("Edit Titles on the Right"), this); ac->addAction(QLatin1String("edit_titles_right"), editTitlesRight); ac->setDefaultShortcut(editTitlesRight, Qt::META + Qt::Key_T); connect(editTitlesRight, SIGNAL(triggered()), this, SLOT(slotRightSideBarActivateTitles())); QAction* const editCommentsRight = new QAction(i18n("Edit Comments on the Right"), this); ac->addAction(QLatin1String("edit_comments_right"), editCommentsRight); ac->setDefaultShortcut(editCommentsRight, Qt::META + Qt::Key_C); connect(editCommentsRight, SIGNAL(triggered()), this, SLOT(slotRightSideBarActivateComments())); QAction* const editTitlesLeft = new QAction(i18n("Edit Titles on the Left"), this); ac->addAction(QLatin1String("edit_titles_left"), editTitlesLeft); ac->setDefaultShortcut(editTitlesLeft, Qt::SHIFT + Qt::META + Qt::Key_T); connect(editTitlesLeft, SIGNAL(triggered()), this, SLOT(slotLeftSideBarActivateTitles())); QAction* const editCommentsLeft = new QAction(i18n("Edit Comments on the Left"), this); ac->addAction(QLatin1String("edit_comments_left"), editCommentsLeft); ac->setDefaultShortcut(editCommentsLeft, Qt::SHIFT + Qt::META + Qt::Key_C); connect(editCommentsLeft, SIGNAL(triggered()), this, SLOT(slotLeftSideBarActivateComments())); QAction* const assignedTagsRight = new QAction(i18n("Show Assigned Tags on the Right"), this); ac->addAction(QLatin1String("assigned _tags_right"), assignedTagsRight); ac->setDefaultShortcut(assignedTagsRight, Qt::META + Qt::Key_A); connect(assignedTagsRight, SIGNAL(triggered()), this, SLOT(slotRightSideBarActivateAssignedTags())); QAction* const assignedTagsLeft = new QAction(i18n("Show Assigned Tags on the Left"), this); ac->addAction(QLatin1String("assigned _tags_left"), assignedTagsLeft); ac->setDefaultShortcut(assignedTagsLeft, Qt::SHIFT + Qt::META + Qt::Key_A); connect(assignedTagsLeft, SIGNAL(triggered()), this, SLOT(slotLeftSideBarActivateAssignedTags())); // --------------------------------------------------------------------------------- createGUI(xmlFile()); cleanupActions(); showMenuBarAction()->setChecked(!menuBar()->isHidden()); // NOTE: workaround for bug #171080 } // Deal with items dropped onto the thumbbar (e.g. from the Album view) void LightTableWindow::slotThumbbarDroppedItems(const QList& list) { // Setting the third parameter of loadImageInfos to true // means that the images are added to the presently available images. loadImageInfos(ImageInfoList() << list, ImageInfo(), true); } // We get here either // - via CTRL+L (from the albumview) // a) digikamapp.cpp: CTRL+key_L leads to slotImageLightTable()) // b) digikamview.cpp: void DigikamView::slotImageLightTable() // calls d->iconView->insertToLightTable(list, info); // c) albumiconview.cpp: AlbumIconView::insertToLightTable // calls ltview->loadImageInfos(list, current); // - via drag&drop, i.e. calls issued by the ...Dropped... routines void LightTableWindow::loadImageInfos(const ImageInfoList& list, const ImageInfo& givenImageInfoCurrent, bool addTo) { // Clear all items before adding new images to the light table. qCDebug(DIGIKAM_GENERAL_LOG) << "Clearing LT" << (!addTo); if (!addTo) { slotClearItemsList(); } ImageInfoList l = list; ImageInfo imageInfoCurrent = givenImageInfoCurrent; if (imageInfoCurrent.isNull() && !l.isEmpty()) { imageInfoCurrent = l.first(); } d->thumbView->setItems(l); QModelIndex index = d->thumbView->findItemByInfo(imageInfoCurrent); if (index.isValid()) { d->thumbView->setCurrentIndex(index); } else { d->thumbView->setCurrentWhenAvailable(imageInfoCurrent.id()); } } bool LightTableWindow::isEmpty() const { return (d->thumbView->countItems() == 0); } void LightTableWindow::slotRefreshStatusBar() { d->statusProgressBar->setProgressBarMode(StatusProgressBar::TextMode, i18np("%1 item on Light Table", "%1 items on Light Table", d->thumbView->countItems())); } void LightTableWindow::slotFileChanged(const QString& path) { QUrl url = QUrl::fromLocalFile(path); // NOTE: Thumbbar handle change through ImageCategorizedView if (!d->previewView->leftImageInfo().isNull()) { if (d->previewView->leftImageInfo().fileUrl() == url) { d->previewView->leftReload(); d->leftSideBar->itemChanged(d->previewView->leftImageInfo()); } } if (!d->previewView->rightImageInfo().isNull()) { if (d->previewView->rightImageInfo().fileUrl() == url) { d->previewView->rightReload(); d->rightSideBar->itemChanged(d->previewView->rightImageInfo()); } } } void LightTableWindow::slotLeftPanelLeftButtonClicked() { if (d->navigateByPairAction->isChecked()) { return; } d->thumbView->setCurrentInfo(d->previewView->leftImageInfo()); } void LightTableWindow::slotRightPanelLeftButtonClicked() { // With navigate by pair option, only the left panel can be selected. if (d->navigateByPairAction->isChecked()) { return; } d->thumbView->setCurrentInfo(d->previewView->rightImageInfo()); } void LightTableWindow::slotLeftPreviewLoaded(bool b) { d->leftZoomBar->setEnabled(b); d->leftFileName->setAdjustedText(); if (b) { d->leftFileName->setAdjustedText(d->previewView->leftImageInfo().name()); d->previewView->checkForSelection(d->thumbView->currentInfo()); d->thumbView->setOnLeftPanel(d->previewView->leftImageInfo()); QModelIndex index = d->thumbView->findItemByInfo(d->previewView->leftImageInfo()); if (d->navigateByPairAction->isChecked() && index.isValid()) { QModelIndex next = d->thumbView->nextIndex(index); if (next.isValid()) { d->thumbView->setOnRightPanel(d->thumbView->findItemByIndex(next)); slotSetItemOnRightPanel(d->thumbView->findItemByIndex(next)); } else { QModelIndex first = d->thumbView->firstIndex(); slotSetItemOnRightPanel(first.isValid() ? d->thumbView->findItemByIndex(first) : ImageInfo()); } } } } void LightTableWindow::slotRightPreviewLoaded(bool b) { d->rightZoomBar->setEnabled(b); d->rightFileName->setAdjustedText(); if (b) { d->rightFileName->setAdjustedText(d->previewView->rightImageInfo().name()); d->previewView->checkForSelection(d->thumbView->currentInfo()); d->thumbView->setOnRightPanel(d->previewView->rightImageInfo()); QModelIndex index = d->thumbView->findItemByInfo(d->previewView->rightImageInfo()); if (index.isValid()) { d->thumbView->setOnRightPanel(d->thumbView->findItemByIndex(index)); } } } void LightTableWindow::slotItemSelected(const ImageInfo& info) { bool hasInfo = !info.isNull(); d->setItemLeftAction->setEnabled(hasInfo); d->setItemRightAction->setEnabled(hasInfo); d->editItemAction->setEnabled(hasInfo); d->removeItemAction->setEnabled(hasInfo); d->clearListAction->setEnabled(hasInfo); d->fileDeleteAction->setEnabled(hasInfo); d->fileDeleteFinalAction->setEnabled(hasInfo); d->backwardAction->setEnabled(hasInfo); d->forwardAction->setEnabled(hasInfo); d->firstAction->setEnabled(hasInfo); d->lastAction->setEnabled(hasInfo); d->syncPreviewAction->setEnabled(hasInfo); d->navigateByPairAction->setEnabled(hasInfo); d->slideShowAction->setEnabled(hasInfo); if (hasInfo) { QModelIndex curr = d->thumbView->findItemByInfo(info); if (curr.isValid()) { if (!d->thumbView->previousIndex(curr).isValid()) { d->firstAction->setEnabled(false); } if (!d->thumbView->nextIndex(curr).isValid()) { d->lastAction->setEnabled(false); } if (d->navigateByPairAction->isChecked()) { d->setItemLeftAction->setEnabled(false); d->setItemRightAction->setEnabled(false); d->thumbView->setOnLeftPanel(info); slotSetItemOnLeftPanel(info); } else if (d->autoLoadOnRightPanel && !d->thumbView->isOnLeftPanel(info)) { d->thumbView->setOnRightPanel(info); slotSetItemOnRightPanel(info); } } } d->previewView->checkForSelection(info); } // Deal with one (or more) items dropped onto the left panel void LightTableWindow::slotLeftDroppedItems(const ImageInfoList& list) { ImageInfo info = list.first(); // add the image to the existing images loadImageInfos(list, info, true); // We will check if first item from list is already stored in thumbbar // Note that the thumbbar stores all ImageInfo reference // in memory for preview object. QModelIndex index = d->thumbView->findItemByInfo(info); if (index.isValid()) { slotSetItemOnLeftPanel(info); } } // Deal with one (or more) items dropped onto the right panel void LightTableWindow::slotRightDroppedItems(const ImageInfoList& list) { ImageInfo info = list.first(); // add the image to the existing images loadImageInfos(list, info, true); // We will check if first item from list is already stored in thumbbar // Note that the thumbbar stores all ImageInfo reference // in memory for preview object. QModelIndex index = d->thumbView->findItemByInfo(info); if (index.isValid()) { slotSetItemOnRightPanel(info); // Make this item the current one. d->thumbView->setCurrentInfo(info); } } // Set the images for the left and right panel. void LightTableWindow::setLeftRightItems(const ImageInfoList& list, bool addTo) { ImageInfoList l = list; if (l.count() == 0) { return; } ImageInfo info = l.first(); QModelIndex index = d->thumbView->findItemByInfo(info); if (l.count() == 1 && !addTo) { // Just one item; this is used for the left panel. d->thumbView->setOnLeftPanel(info); slotSetItemOnLeftPanel(info); d->thumbView->setCurrentInfo(info); return; } if (index.isValid()) { // The first item is used for the left panel. if (!addTo) { d->thumbView->setOnLeftPanel(info); slotSetItemOnLeftPanel(info); } // The subsequent item is used for the right panel. QModelIndex next = d->thumbView->nextIndex(index); if (next.isValid() && !addTo) { ImageInfo nextInf = d->thumbView->findItemByIndex(next); d->thumbView->setOnRightPanel(nextInf); slotSetItemOnRightPanel(nextInf); if (!d->navigateByPairAction->isChecked()) { d->thumbView->setCurrentInfo(nextInf); } } // If navigate by pairs is active, the left panel item is selected. // (Fixes parts of bug #150296) if (d->navigateByPairAction->isChecked()) { d->thumbView->setCurrentInfo(info); } } } void LightTableWindow::slotSetItemLeft() { if (!d->thumbView->currentInfo().isNull()) { slotSetItemOnLeftPanel(d->thumbView->currentInfo()); } } void LightTableWindow::slotSetItemRight() { if (!d->thumbView->currentInfo().isNull()) { slotSetItemOnRightPanel(d->thumbView->currentInfo()); } } void LightTableWindow::slotSetItemOnLeftPanel(const ImageInfo& info) { d->previewView->setLeftImageInfo(info); if (!info.isNull()) { d->leftSideBar->itemChanged(info); } else { d->leftSideBar->slotNoCurrentItem(); } } void LightTableWindow::slotSetItemOnRightPanel(const ImageInfo& info) { d->previewView->setRightImageInfo(info); if (!info.isNull()) { d->rightSideBar->itemChanged(info); } else { d->rightSideBar->slotNoCurrentItem(); } } void LightTableWindow::slotClearItemsList() { if (!d->previewView->leftImageInfo().isNull()) { d->previewView->setLeftImageInfo(); d->leftSideBar->slotNoCurrentItem(); } if (!d->previewView->rightImageInfo().isNull()) { d->previewView->setRightImageInfo(); d->rightSideBar->slotNoCurrentItem(); } d->thumbView->clear(); } void LightTableWindow::slotDeleteItem() { deleteItem(false); } void LightTableWindow::slotDeleteItem(const ImageInfo& info) { deleteItem(info, false); } void LightTableWindow::slotDeleteFinalItem() { deleteItem(true); } void LightTableWindow::slotDeleteFinalItem(const ImageInfo& info) { deleteItem(info, true); } void LightTableWindow::deleteItem(bool permanently) { if (!d->thumbView->currentInfo().isNull()) { deleteItem(d->thumbView->currentInfo(), permanently); } } void LightTableWindow::deleteItem(const ImageInfo& info, bool permanently) { QUrl u = info.fileUrl(); PAlbum* const palbum = AlbumManager::instance()->findPAlbum(u.adjusted(QUrl::RemoveFilename)); if (!palbum) { return; } qCDebug(DIGIKAM_GENERAL_LOG) << "Item to delete: " << u; bool useTrash; bool preselectDeletePermanently = permanently; DeleteDialog dialog(this); QList urlList; urlList.append(u); if (!dialog.confirmDeleteList(urlList, DeleteDialogMode::Files, preselectDeletePermanently ? DeleteDialogMode::NoChoiceDeletePermanently : DeleteDialogMode::NoChoiceTrash)) { return; } useTrash = !dialog.shouldDelete(); DIO::del(info, useTrash); } void LightTableWindow::slotRemoveItem() { if (!d->thumbView->currentInfo().isNull()) { slotRemoveItem(d->thumbView->currentInfo()); } } void LightTableWindow::slotRemoveItem(const ImageInfo& info) { /* if (!d->previewView->leftImageInfo().isNull()) { if (d->previewView->leftImageInfo() == info) { d->previewView->setLeftImageInfo(); d->leftSideBar->slotNoCurrentItem(); } } if (!d->previewView->rightImageInfo().isNull()) { if (d->previewView->rightImageInfo() == info) { d->previewView->setRightImageInfo(); d->rightSideBar->slotNoCurrentItem(); } } d->thumbView->removeItemByInfo(info); d->thumbView->setSelected(d->thumbView->currentItem()); */ // When either the image from the left or right panel is removed, // there are various situations to account for. // To describe them, 4 images A B C D are used // and the subscript _L and _ R mark the currently // active item on the left and right panel ImageInfo new_linfo; ImageInfo new_rinfo; bool leftPanelActive = false; ImageInfo curr_linfo = d->previewView->leftImageInfo(); ImageInfo curr_rinfo = d->previewView->rightImageInfo(); qint64 infoId = info.id(); // First determine the next images to the current left and right image: ImageInfo next_linfo; ImageInfo next_rinfo; if (!curr_linfo.isNull()) { QModelIndex index = d->thumbView->findItemByInfo(curr_linfo); if (index.isValid()) { QModelIndex next = d->thumbView->nextIndex(index); if (next.isValid()) { next_linfo = d->thumbView->findItemByIndex(next); } } } if (!curr_rinfo.isNull()) { QModelIndex index = d->thumbView->findItemByInfo(curr_rinfo); if (index.isValid()) { QModelIndex next = d->thumbView->nextIndex(index); if (next.isValid()) { next_rinfo = d->thumbView->findItemByIndex(next); } } } d->thumbView->removeItemByInfo(info); // Make sure that next_linfo and next_rinfo are still available: if (!d->thumbView->findItemByInfo(next_linfo).isValid()) { next_linfo = ImageInfo(); } if (!d->thumbView->findItemByInfo(next_rinfo).isValid()) { next_rinfo = ImageInfo(); } // removal of the left panel item? if (!curr_linfo.isNull()) { if (curr_linfo.id() == infoId) { leftPanelActive = true; // Delete the item A_L of the left panel: // 1) A_L B_R C D -> B_L C_R D // 2) A_L B C_R D -> B C_L D_R // 3) A_L B C D_R -> B_R C D_L // 4) A_L B_R -> A_L // some more corner cases: // 5) A B_L C_R D -> A C_L D_R // 6) A B_L C_R -> A_R C_L // 7) A_LR B C D -> B_L C_R D (does not yet work) // I.e. in 3) we wrap around circularly. // When removing the left panel image, // put the right panel image into the left panel. // Check if this one is not the same (i.e. also removed). if (!curr_rinfo.isNull()) { if (curr_rinfo.id() != infoId) { new_linfo = curr_rinfo; // Set the right panel to the next image: new_rinfo = next_rinfo; // set the right panel active, but not in pair mode if (!d->navigateByPairAction->isChecked()) { leftPanelActive = false; } } } } } // removal of the right panel item? if (!curr_rinfo.isNull()) { if (curr_rinfo.id() == infoId) { // Leave the left panel as the current one new_linfo = curr_linfo; // Set the right panel to the next image new_rinfo = next_rinfo; } } // Now we deal with the corner cases, where no left or right item exists. // If the right panel would be set, but not the left-one, then swap if (new_linfo.isNull() && !new_rinfo.isNull()) { new_linfo = new_rinfo; new_rinfo = ImageInfo(); leftPanelActive = true; } if (new_linfo.isNull()) { if (d->thumbView->countItems() > 0) { QModelIndex first = d->thumbView->firstIndex(); new_linfo = d->thumbView->findItemByIndex(first); } } // Make sure that new_linfo and new_rinfo exist. // This addresses a crash occurring if the last image is removed // in the navigate by pairs mode. if (!d->thumbView->findItemByInfo(new_linfo).isValid()) { new_linfo = ImageInfo(); } if (!d->thumbView->findItemByInfo(new_rinfo).isValid()) { new_rinfo = ImageInfo(); } // no right item defined? if (new_rinfo.isNull()) { // If there are at least two items, we can find reasonable right image. if (d->thumbView->countItems() > 1) { // See if there is an item next to the left one: QModelIndex index = d->thumbView->findItemByInfo(new_linfo); QModelIndex next; if (index.isValid()) { next = d->thumbView->nextIndex(index); } if (next.isValid()) { new_rinfo = d->thumbView->findItemByIndex(next); } else { // If there is no item to the right of new_linfo // then we can choose the first item for new_rinfo // (as we made sure that there are at least two items) QModelIndex first = d->thumbView->firstIndex(); new_rinfo = d->thumbView->findItemByIndex(first); } } } // Check if left and right are set to the same if (!new_linfo.isNull() && !new_rinfo.isNull()) { if (new_linfo.id() == new_rinfo.id()) { // Only keep the left one new_rinfo = ImageInfo(); } } // If the right panel would be set, but not the left-one, then swap // (note that this has to be done here again!) if (new_linfo.isNull() && !new_rinfo.isNull()) { new_linfo = new_rinfo; new_rinfo = ImageInfo(); leftPanelActive = true; } // set the image for the left panel if (!new_linfo.isNull()) { d->thumbView->setOnLeftPanel(new_linfo); slotSetItemOnLeftPanel(new_linfo); // make this the selected item if the left was active before if (leftPanelActive) { d->thumbView->setCurrentInfo(new_linfo); } } else { d->previewView->setLeftImageInfo(); d->leftSideBar->slotNoCurrentItem(); } // set the image for the right panel if (!new_rinfo.isNull()) { d->thumbView->setOnRightPanel(new_rinfo); slotSetItemOnRightPanel(new_rinfo); // make this the selected item if the left was active before if (!leftPanelActive) { d->thumbView->setCurrentInfo(new_rinfo); } } else { d->previewView->setRightImageInfo(); d->rightSideBar->slotNoCurrentItem(); } } void LightTableWindow::slotEditItem() { if (!d->thumbView->currentInfo().isNull()) { slotEditItem(d->thumbView->currentInfo()); } } void LightTableWindow::slotEditItem(const ImageInfo& info) { ImageWindow* const im = ImageWindow::imageWindow(); ImageInfoList list = d->thumbView->allImageInfos(); im->loadImageInfos(list, info, i18n("Light Table")); if (im->isHidden()) { im->show(); } else { im->raise(); } im->setFocus(); } void LightTableWindow::slotPresentation() { PresentationMngr* const mngr = new PresentationMngr(this); foreach(const ImageInfo& info, d->thumbView->allImageInfos()) { mngr->addFile(info.fileUrl(), info.comment()); qApp->processEvents(); } mngr->showConfigDialog(); } void LightTableWindow::slotSlideShowAll() { SlideShowBuilder* const builder = new SlideShowBuilder(d->thumbView->allImageInfos()); d->statusProgressBar->setProgressBarMode(StatusProgressBar::TextMode, i18n("Preparing slideshow. Please wait...")); connect(builder, SIGNAL(signalComplete(SlideShowSettings)), this, SLOT(slotSlideShowBuilderComplete(SlideShowSettings))); builder->run(); } void LightTableWindow::slotLeftSlideShowManualFromCurrent() { slotSlideShowManualFrom(d->previewView->leftImageInfo()); d->fromLeftPreview = true; } void LightTableWindow::slotRightSlideShowManualFromCurrent() { slotSlideShowManualFrom(d->previewView->rightImageInfo()); d->fromLeftPreview = false; } void LightTableWindow::slotSlideShowManualFrom(const ImageInfo& info) { SlideShowBuilder* const builder = new SlideShowBuilder(d->thumbView->allImageInfos()); builder->setOverrideStartFrom(info); builder->setAutoPlayEnabled(false); d->statusProgressBar->setProgressBarMode(StatusProgressBar::TextMode, i18n("Preparing slideshow. Please wait...")); connect(builder, SIGNAL(signalComplete(SlideShowSettings)), this, SLOT(slotSlideShowBuilderComplete(SlideShowSettings))); builder->run(); } void LightTableWindow::slotSlideShowBuilderComplete(const SlideShowSettings& settings) { SlideShow* const slide = new SlideShow(settings); TagsActionMngr::defaultManager()->registerActionsToWidget(slide); d->statusProgressBar->setProgressBarMode(StatusProgressBar::TextMode, QString()); slotRefreshStatusBar(); if (settings.imageUrl.isValid()) { slide->setCurrentItem(settings.imageUrl); } else if (settings.startWithCurrent) { slide->setCurrentItem(d->thumbView->currentInfo().fileUrl()); } connect(slide, SIGNAL(signalRatingChanged(QUrl,int)), d->thumbView, SLOT(slotRatingChanged(QUrl,int))); connect(slide, SIGNAL(signalColorLabelChanged(QUrl,int)), d->thumbView, SLOT(slotColorLabelChanged(QUrl,int))); connect(slide, SIGNAL(signalPickLabelChanged(QUrl,int)), d->thumbView, SLOT(slotPickLabelChanged(QUrl,int))); connect(slide, SIGNAL(signalToggleTag(QUrl,int)), d->thumbView, SLOT(slotToggleTag(QUrl,int))); connect(slide, SIGNAL(signalLastItemUrl(QUrl)), this, SLOT(slotSlideShowLastItemUrl(QUrl))); slide->show(); } void LightTableWindow::slotSlideShowLastItemUrl(const QUrl& url) { if (d->fromLeftPreview && !d->navigateByPairAction->isChecked()) { d->thumbView->blockSignals(true); d->thumbView->setCurrentUrl(url); d->thumbView->blockSignals(false); slotSetItemLeft(); } else { d->thumbView->setCurrentUrl(url); } } void LightTableWindow::slotSetup() { Setup::execDialog(this); } void LightTableWindow::slotLeftZoomFactorChanged(double zoom) { double zmin = d->previewView->leftZoomMin(); double zmax = d->previewView->leftZoomMax(); d->leftZoomBar->setZoom(zoom, zmin, zmax); d->leftZoomPlusAction->setEnabled(!d->previewView->leftMaxZoom()); d->leftZoomMinusAction->setEnabled(!d->previewView->leftMinZoom()); } void LightTableWindow::slotRightZoomFactorChanged(double zoom) { double zmin = d->previewView->rightZoomMin(); double zmax = d->previewView->rightZoomMax(); d->rightZoomBar->setZoom(zoom, zmin, zmax); d->rightZoomPlusAction->setEnabled(!d->previewView->rightMaxZoom()); d->rightZoomMinusAction->setEnabled(!d->previewView->rightMinZoom()); } void LightTableWindow::slotToggleSyncPreview() { d->previewView->setSyncPreview(d->syncPreviewAction->isChecked()); } void LightTableWindow::slotToggleOnSyncPreview(bool t) { d->syncPreviewAction->setEnabled(t); if (!t) { d->syncPreviewAction->setChecked(false); } else { if (d->autoSyncPreview) { d->syncPreviewAction->setChecked(true); } } } void LightTableWindow::slotBackward() { d->thumbView->toPreviousIndex(); } void LightTableWindow::slotForward() { d->thumbView->toNextIndex(); } void LightTableWindow::slotFirst() { d->thumbView->toFirstIndex(); } void LightTableWindow::slotLast() { d->thumbView->toLastIndex(); } void LightTableWindow::slotToggleNavigateByPair() { d->thumbView->setNavigateByPair(d->navigateByPairAction->isChecked()); d->previewView->setNavigateByPair(d->navigateByPairAction->isChecked()); slotItemSelected(d->thumbView->currentInfo()); } void LightTableWindow::slotComponentsInfo() { showDigikamComponentsInfo(); } void LightTableWindow::slotDBStat() { showDigikamDatabaseStat(); } void LightTableWindow::slotApplicationSettingsChanged() { d->leftSideBar->setStyle(ApplicationSettings::instance()->getSidebarTitleStyle()); d->rightSideBar->setStyle(ApplicationSettings::instance()->getSidebarTitleStyle()); /// @todo Which part of the settings has to be reloaded? // d->rightSideBar->applySettings(); d->previewView->setPreviewSettings(ApplicationSettings::instance()->getPreviewSettings()); } void LightTableWindow::moveEvent(QMoveEvent* e) { Q_UNUSED(e) emit signalWindowHasMoved(); } void LightTableWindow::toggleTag(int tagID) { d->thumbView->toggleTag(tagID); } void LightTableWindow::slotAssignPickLabel(int pickId) { d->thumbView->slotAssignPickLabel(pickId); } void LightTableWindow::slotAssignColorLabel(int colorId) { d->thumbView->slotAssignColorLabel(colorId); } void LightTableWindow::slotAssignRating(int rating) { d->thumbView->slotAssignRating(rating); } void LightTableWindow::slotThemeChanged() { d->previewView->checkForSelection(d->previewView->leftImageInfo()); d->previewView->checkForSelection(d->previewView->rightImageInfo()); } void LightTableWindow::showSideBars(bool visible) { if (visible) { d->leftSideBar->restore(); d->rightSideBar->restore(); } else { d->leftSideBar->backup(); d->rightSideBar->backup(); } } void LightTableWindow::slotToggleLeftSideBar() { d->leftSideBar->isExpanded() ? d->leftSideBar->shrink() : d->leftSideBar->expand(); } void LightTableWindow::slotToggleRightSideBar() { d->rightSideBar->isExpanded() ? d->rightSideBar->shrink() : d->rightSideBar->expand(); } void LightTableWindow::slotPreviousLeftSideBarTab() { d->leftSideBar->activePreviousTab(); } void LightTableWindow::slotNextLeftSideBarTab() { d->leftSideBar->activeNextTab(); } void LightTableWindow::slotPreviousRightSideBarTab() { d->rightSideBar->activePreviousTab(); } void LightTableWindow::slotNextRightSideBarTab() { d->rightSideBar->activeNextTab(); } void LightTableWindow::customizedFullScreenMode(bool set) { showStatusBarAction()->setEnabled(!set); toolBarMenuAction()->setEnabled(!set); showMenuBarAction()->setEnabled(!set); d->showBarAction->setEnabled(!set); d->previewView->toggleFullScreen(set); } void LightTableWindow::slotFileWithDefaultApplication() { if (!d->thumbView->currentInfo().isNull()) { DFileOperations::openFilesWithDefaultApplication(QList() << d->thumbView->currentInfo().fileUrl()); } } void LightTableWindow::slotRightSideBarActivateTitles() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->setFocusToTitlesEdit(); } void LightTableWindow::slotRightSideBarActivateComments() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->setFocusToCommentsEdit(); } void LightTableWindow::slotRightSideBarActivateAssignedTags() { d->rightSideBar->setActiveTab(d->rightSideBar->imageDescEditTab()); d->rightSideBar->imageDescEditTab()->activateAssignedTagsButton(); } void LightTableWindow::slotLeftSideBarActivateTitles() { d->leftSideBar->setActiveTab(d->leftSideBar->imageDescEditTab()); d->leftSideBar->imageDescEditTab()->setFocusToTitlesEdit(); } void LightTableWindow::slotLeftSideBarActivateComments() { d->leftSideBar->setActiveTab(d->leftSideBar->imageDescEditTab()); d->leftSideBar->imageDescEditTab()->setFocusToCommentsEdit(); } void LightTableWindow::slotLeftSideBarActivateAssignedTags() { d->leftSideBar->setActiveTab(d->leftSideBar->imageDescEditTab()); d->leftSideBar->imageDescEditTab()->activateAssignedTagsButton(); } void LightTableWindow::slotToggleColorManagedView() { if (!IccSettings::instance()->isEnabled()) { return; } bool cmv = !IccSettings::instance()->settings().useManagedPreviews; IccSettings::instance()->setUseManagedPreviews(cmv); } void LightTableWindow::slotColorManagementOptionsChanged() { ICCSettingsContainer settings = IccSettings::instance()->settings(); d->viewCMViewAction->blockSignals(true); d->viewCMViewAction->setEnabled(settings.enableCM); d->viewCMViewAction->setChecked(settings.useManagedPreviews); d->viewCMViewAction->blockSignals(false); } void LightTableWindow::slotEditGeolocation() { #ifdef HAVE_MARBLE ImageInfoList infos = d->thumbView->allImageInfos(); if (infos.isEmpty()) { return; } TagModel* const tagModel = new TagModel(AbstractAlbumModel::IgnoreRootAlbum, this); TagPropertiesFilterModel* const filterModel = new TagPropertiesFilterModel(this); filterModel->setSourceAlbumModel(tagModel); filterModel->sort(0); QPointer dialog = new GeolocationEdit(filterModel, new DBInfoIface(this, d->thumbView->allUrls()), QApplication::activeWindow()); dialog->setItems(ImageGPS::infosToItems(infos)); dialog->exec(); delete dialog; // Refresh Database with new metadata from files. foreach(const ImageInfo& inf, infos) { ScanController::instance()->scannedInfo(inf.fileUrl().toLocalFile()); } #endif } void LightTableWindow::slotEditMetadata() { if (d->thumbView->currentInfo().isNull()) { return; } QUrl url = d->thumbView->currentInfo().fileUrl(); QPointer dialog = new MetadataEditDialog(QApplication::activeWindow(), QList() << url); dialog->exec(); delete dialog; // Refresh Database with new metadata from file. - ScanController::instance()->scannedInfo(url.toLocalFile()); + CollectionScanner scanner; + scanner.scanFile(url.toLocalFile(), CollectionScanner::Rescan); } void LightTableWindow::slotImportFromScanner() { #ifdef HAVE_KSANE m_ksaneAction->activate(DigikamApp::instance()->scannerTargetPlace(), configGroupName()); connect(m_ksaneAction, SIGNAL(signalImportedImage(QUrl)), this, SLOT(slotImportedImagefromScanner(QUrl))); #endif } void LightTableWindow::slotImportedImagefromScanner(const QUrl& url) { ImageInfo info = ScanController::instance()->scannedInfo(url.toLocalFile()); loadImageInfos(ImageInfoList() << info, info, true); } void LightTableWindow::slotHtmlGallery() { #ifdef HAVE_HTMLGALLERY HTMLWizard w(this, new DBInfoIface(this, d->thumbView->allUrls())); w.exec(); #endif } void LightTableWindow::slotCalendar() { CalWizard w(d->thumbView->allUrls(), this); w.exec(); } void LightTableWindow::slotPanorama() { #ifdef HAVE_PANORAMA PanoManager::instance()->checkBinaries(); PanoManager::instance()->setItemsList(d->thumbView->allUrls()); PanoManager::instance()->run(); #endif } void LightTableWindow::slotExpoBlending() { ExpoBlendingManager::instance()->checkBinaries(); ExpoBlendingManager::instance()->setItemsList(d->thumbView->allUrls()); ExpoBlendingManager::instance()->run(); } void LightTableWindow::slotVideoSlideshow() { #ifdef HAVE_MEDIAPLAYER VidSlideWizard w(this, new DBInfoIface(this, d->thumbView->allUrls())); w.exec(); #endif } void LightTableWindow::slotSendByMail() { MailWizard w(this, new DBInfoIface(this, d->thumbView->allUrls())); w.exec(); } void LightTableWindow::slotPrintCreator() { AdvPrintWizard w(this, new DBInfoIface(this, d->thumbView->allUrls())); w.exec(); } void LightTableWindow::slotMediaServer() { DBInfoIface* const iface = new DBInfoIface(this, QList(), ApplicationSettings::Tools); // NOTE: We overwrite the default albums chooser object name for load save check items state between sessions. // The goal is not mix these settings with other export tools. iface->setObjectName(QLatin1String("SetupMediaServerIface")); DMediaServerDlg w(this, iface); w.exec(); } } // namespace Digikam