diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index 61660ea0..c8646465 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -1,1682 +1,1683 @@ /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau 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 of the License, 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mainwindow.h" #include #include "dialogguard.h" // Qt #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_OSX #include #endif // KDE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Local #include "configdialog.h" #include "documentinfoprovider.h" #include "viewmainpage.h" #include "fileopscontextmanageritem.h" #include "folderviewcontextmanageritem.h" #include "fullscreencontent.h" #include "gvcore.h" #include "imageopscontextmanageritem.h" #include "infocontextmanageritem.h" #ifdef KIPI_FOUND #include "kipiexportaction.h" #include "kipiinterface.h" #endif #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE #include "semanticinfocontextmanageritem.h" #endif #include "preloader.h" #include "savebar.h" #include "sidebar.h" #include "splitter.h" #include "startmainpage.h" #include "thumbnailviewhelper.h" #include "browsemainpage.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_QTDBUS #include #endif #include #include #include #include #include #include #include #include namespace Gwenview { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) qDebug() << x #else #define LOG(x) ; #endif static const int BROWSE_PRELOAD_DELAY = 1000; static const int VIEW_PRELOAD_DELAY = 100; static const char* SESSION_CURRENT_PAGE_KEY = "Page"; static const char* SESSION_URL_KEY = "Url"; enum MainPageId { StartMainPageId, BrowseMainPageId, ViewMainPageId }; struct MainWindowState { bool mToolBarVisible; }; /* Layout of the main window looks like this: .-mCentralSplitter-----------------------------. |.-mSideBar--. .-mContentWidget---------------.| || | |.-mSaveBar-------------------.|| || | || ||| || | |'----------------------------'|| || | |.-mViewStackedWidget---------.|| || | || ||| || | || ||| || | || ||| || | || ||| || | |'----------------------------'|| |'-----------' '------------------------------'| '----------------------------------------------' */ struct MainWindow::Private { GvCore* mGvCore; MainWindow* q; QSplitter* mCentralSplitter; QWidget* mContentWidget; ViewMainPage* mViewMainPage; KUrlNavigator* mUrlNavigator; ThumbnailView* mThumbnailView; ThumbnailView* mActiveThumbnailView; DocumentInfoProvider* mDocumentInfoProvider; ThumbnailViewHelper* mThumbnailViewHelper; QPointer mThumbnailProvider; BrowseMainPage* mBrowseMainPage; StartMainPage* mStartMainPage; SideBar* mSideBar; QStackedWidget* mViewStackedWidget; FullScreenContent* mFullScreenContent; SaveBar* mSaveBar; bool mStartSlideShowWhenDirListerCompleted; SlideShow* mSlideShow; #ifdef HAVE_QTDBUS Mpris2Service* mMpris2Service; #endif Preloader* mPreloader; bool mPreloadDirectionIsForward; #ifdef KIPI_FOUND KIPIInterface* mKIPIInterface; #endif QActionGroup* mViewModeActionGroup; KRecentFilesAction* mFileOpenRecentAction; QAction * mBrowseAction; QAction * mViewAction; QAction * mGoUpAction; QAction * mGoToPreviousAction; QAction * mGoToNextAction; QAction * mGoToFirstAction; QAction * mGoToLastAction; KToggleAction* mToggleSideBarAction; QAction* mFullScreenAction; QAction * mToggleSlideShowAction; KToggleAction* mShowMenuBarAction; KToggleAction* mShowStatusBarAction; #ifdef KIPI_FOUND KIPIExportAction* mKIPIExportAction; #endif SortedDirModel* mDirModel; DocumentOnlyProxyModel* mThumbnailBarModel; KLinkItemSelectionModel* mThumbnailBarSelectionModel; ContextManager* mContextManager; MainWindowState mStateBeforeFullScreen; QString mCaption; MainPageId mCurrentMainPageId; QDateTime mFullScreenLeftAt; KNotificationRestrictions* mNotificationRestrictions; void setupContextManager() { mContextManager = new ContextManager(mDirModel, q); connect(mContextManager, SIGNAL(selectionChanged()), q, SLOT(slotSelectionChanged())); connect(mContextManager, SIGNAL(currentDirUrlChanged(QUrl)), q, SLOT(slotCurrentDirUrlChanged(QUrl))); } void setupWidgets() { mFullScreenContent = new FullScreenContent(q, mGvCore); connect(mContextManager, SIGNAL(currentUrlChanged(QUrl)), mFullScreenContent, SLOT(setCurrentUrl(QUrl))); mCentralSplitter = new Splitter(Qt::Horizontal, q); q->setCentralWidget(mCentralSplitter); // Left side of splitter mSideBar = new SideBar(mCentralSplitter); // Right side of splitter mContentWidget = new QWidget(mCentralSplitter); mSaveBar = new SaveBar(mContentWidget, q->actionCollection()); connect(mContextManager, SIGNAL(currentUrlChanged(QUrl)), mSaveBar, SLOT(setCurrentUrl(QUrl))); mViewStackedWidget = new QStackedWidget(mContentWidget); QVBoxLayout* layout = new QVBoxLayout(mContentWidget); layout->addWidget(mSaveBar); layout->addWidget(mViewStackedWidget); layout->setMargin(0); layout->setSpacing(0); //// mStartSlideShowWhenDirListerCompleted = false; mSlideShow = new SlideShow(q); connect(mContextManager, SIGNAL(currentUrlChanged(QUrl)), mSlideShow, SLOT(setCurrentUrl(QUrl))); setupThumbnailView(mViewStackedWidget); setupViewMainPage(mViewStackedWidget); setupStartMainPage(mViewStackedWidget); mViewStackedWidget->addWidget(mBrowseMainPage); mViewStackedWidget->addWidget(mViewMainPage); mViewStackedWidget->addWidget(mStartMainPage); mViewStackedWidget->setCurrentWidget(mBrowseMainPage); mCentralSplitter->setStretchFactor(0, 0); mCentralSplitter->setStretchFactor(1, 1); mThumbnailView->setFocus(); connect(mSaveBar, SIGNAL(requestSaveAll()), mGvCore, SLOT(saveAll())); connect(mSaveBar, SIGNAL(goToUrl(QUrl)), q, SLOT(goToUrl(QUrl))); connect(mSlideShow, SIGNAL(goToUrl(QUrl)), q, SLOT(goToUrl(QUrl))); } void setupThumbnailView(QWidget* parent) { Q_ASSERT(mContextManager); mBrowseMainPage = new BrowseMainPage(parent, q->actionCollection(), mGvCore); mThumbnailView = mBrowseMainPage->thumbnailView(); mThumbnailView->setSelectionModel(mContextManager->selectionModel()); mUrlNavigator = mBrowseMainPage->urlNavigator(); mDocumentInfoProvider = new DocumentInfoProvider(mDirModel); mThumbnailView->setDocumentInfoProvider(mDocumentInfoProvider); mThumbnailViewHelper = new ThumbnailViewHelper(mDirModel, q->actionCollection()); connect(mContextManager, SIGNAL(currentDirUrlChanged(QUrl)), mThumbnailViewHelper, SLOT(setCurrentDirUrl(QUrl))); mThumbnailView->setThumbnailViewHelper(mThumbnailViewHelper); mThumbnailBarSelectionModel = new KLinkItemSelectionModel(mThumbnailBarModel, mContextManager->selectionModel(), q); // Connect thumbnail view connect(mThumbnailView, SIGNAL(indexActivated(QModelIndex)), q, SLOT(slotThumbnailViewIndexActivated(QModelIndex))); // Connect delegate QAbstractItemDelegate* delegate = mThumbnailView->itemDelegate(); connect(delegate, SIGNAL(saveDocumentRequested(QUrl)), mGvCore, SLOT(save(QUrl))); connect(delegate, SIGNAL(rotateDocumentLeftRequested(QUrl)), mGvCore, SLOT(rotateLeft(QUrl))); connect(delegate, SIGNAL(rotateDocumentRightRequested(QUrl)), mGvCore, SLOT(rotateRight(QUrl))); connect(delegate, SIGNAL(showDocumentInFullScreenRequested(QUrl)), q, SLOT(showDocumentInFullScreen(QUrl))); connect(delegate, SIGNAL(setDocumentRatingRequested(QUrl,int)), mGvCore, SLOT(setRating(QUrl,int))); // Connect url navigator connect(mUrlNavigator, SIGNAL(urlChanged(QUrl)), q, SLOT(openDirUrl(QUrl))); } void setupViewMainPage(QWidget* parent) { mViewMainPage = new ViewMainPage(parent, mSlideShow, q->actionCollection(), mGvCore); connect(mViewMainPage, SIGNAL(captionUpdateRequested(QString)), q, SLOT(slotUpdateCaption(QString))); connect(mViewMainPage, SIGNAL(completed()), q, SLOT(slotPartCompleted())); connect(mViewMainPage, SIGNAL(previousImageRequested()), q, SLOT(goToPrevious())); connect(mViewMainPage, SIGNAL(nextImageRequested()), q, SLOT(goToNext())); setupThumbnailBar(mViewMainPage->thumbnailBar()); } void setupThumbnailBar(ThumbnailView* bar) { Q_ASSERT(mThumbnailBarModel); Q_ASSERT(mThumbnailBarSelectionModel); Q_ASSERT(mDocumentInfoProvider); Q_ASSERT(mThumbnailViewHelper); bar->setModel(mThumbnailBarModel); bar->setSelectionModel(mThumbnailBarSelectionModel); bar->setDocumentInfoProvider(mDocumentInfoProvider); bar->setThumbnailViewHelper(mThumbnailViewHelper); } void setupStartMainPage(QWidget* parent) { mStartMainPage = new StartMainPage(parent, mGvCore); connect(mStartMainPage, SIGNAL(urlSelected(QUrl)), q, SLOT(slotStartMainPageUrlSelected(QUrl))); connect(mStartMainPage, &StartMainPage::recentFileRemoved, [this](const QUrl& url) { mFileOpenRecentAction->removeUrl(url); }); connect(mStartMainPage, &StartMainPage::recentFilesCleared, [this]() { mFileOpenRecentAction->clear(); }); } void installDisabledActionShortcutMonitor(QAction* action, const char* slot) { DisabledActionShortcutMonitor* monitor = new DisabledActionShortcutMonitor(action, q); connect(monitor, SIGNAL(activated()), q, slot); } void setupActions() { KActionCollection* actionCollection = q->actionCollection(); KActionCategory* file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection); KActionCategory* view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection); file->addAction(KStandardAction::Save, q, SLOT(saveCurrent())); file->addAction(KStandardAction::SaveAs, q, SLOT(saveCurrentAs())); file->addAction(KStandardAction::Open, q, SLOT(openFile())); mFileOpenRecentAction = KStandardAction::openRecent(q, SLOT(openUrl(QUrl)), q); connect(mFileOpenRecentAction, &KRecentFilesAction::recentListCleared, mGvCore, &GvCore::clearRecentFilesAndFolders); QAction* clearAction = mFileOpenRecentAction->menu()->findChild("clear_action"); if (clearAction) { clearAction->setText(i18nc("@action Open Recent menu", "Forget All Files && Folders")); } file->addAction("file_open_recent", mFileOpenRecentAction); file->addAction(KStandardAction::Print, q, SLOT(print())); file->addAction(KStandardAction::Quit, qApp, SLOT(closeAllWindows())); QAction * action = file->addAction("reload", q, SLOT(reload())); action->setText(i18nc("@action reload the currently viewed image", "Reload")); action->setIcon(QIcon::fromTheme("view-refresh")); actionCollection->setDefaultShortcuts(action, KStandardShortcut::reload()); mBrowseAction = view->addAction("browse"); mBrowseAction->setText(i18nc("@action:intoolbar Switch to file list", "Browse")); mBrowseAction->setToolTip(i18nc("@info:tooltip", "Browse folders for images")); mBrowseAction->setCheckable(true); mBrowseAction->setIcon(QIcon::fromTheme("view-list-icons")); actionCollection->setDefaultShortcut(mBrowseAction, Qt::Key_Escape); connect(mViewMainPage, SIGNAL(goToBrowseModeRequested()), mBrowseAction, SLOT(trigger())); mViewAction = view->addAction("view"); mViewAction->setText(i18nc("@action:intoolbar Switch to image view", "View")); mViewAction->setToolTip(i18nc("@info:tooltip", "View selected images")); mViewAction->setIcon(QIcon::fromTheme("view-preview")); mViewAction->setCheckable(true); mViewModeActionGroup = new QActionGroup(q); mViewModeActionGroup->addAction(mBrowseAction); mViewModeActionGroup->addAction(mViewAction); connect(mViewModeActionGroup, SIGNAL(triggered(QAction*)), q, SLOT(setActiveViewModeAction(QAction*))); mFullScreenAction = KStandardAction::fullScreen(q, &MainWindow::toggleFullScreen, q, actionCollection); QList shortcuts = mFullScreenAction->shortcuts(); shortcuts.append(QKeySequence(Qt::Key_F11)); actionCollection->setDefaultShortcuts(mFullScreenAction, shortcuts); connect(mViewMainPage, SIGNAL(toggleFullScreenRequested()), mFullScreenAction, SLOT(trigger())); QAction * leaveFullScreenAction = view->addAction("leave_fullscreen", q, SLOT(leaveFullScreen())); leaveFullScreenAction->setIcon(QIcon::fromTheme("view-restore")); leaveFullScreenAction->setPriority(QAction::LowPriority); leaveFullScreenAction->setText(i18nc("@action", "Leave Fullscreen Mode")); mGoToPreviousAction = view->addAction("go_previous", q, SLOT(goToPrevious())); mGoToPreviousAction->setPriority(QAction::LowPriority); mGoToPreviousAction->setIcon(QIcon::fromTheme("go-previous-view")); mGoToPreviousAction->setText(i18nc("@action Go to previous image", "Previous")); mGoToPreviousAction->setToolTip(i18nc("@info:tooltip", "Go to previous image")); actionCollection->setDefaultShortcut(mGoToPreviousAction, Qt::Key_Backspace); installDisabledActionShortcutMonitor(mGoToPreviousAction, SLOT(showFirstDocumentReached())); mGoToNextAction = view->addAction("go_next", q, SLOT(goToNext())); mGoToNextAction->setPriority(QAction::LowPriority); mGoToNextAction->setIcon(QIcon::fromTheme("go-next-view")); mGoToNextAction->setText(i18nc("@action Go to next image", "Next")); mGoToNextAction->setToolTip(i18nc("@info:tooltip", "Go to next image")); actionCollection->setDefaultShortcut(mGoToNextAction, Qt::Key_Space); installDisabledActionShortcutMonitor(mGoToNextAction, SLOT(showLastDocumentReached())); mGoToFirstAction = view->addAction("go_first", q, SLOT(goToFirst())); mGoToFirstAction->setPriority(QAction::LowPriority); mGoToFirstAction->setIcon(QIcon::fromTheme("go-first-view")); mGoToFirstAction->setText(i18nc("@action Go to first image", "First")); mGoToFirstAction->setToolTip(i18nc("@info:tooltip", "Go to first image")); actionCollection->setDefaultShortcut(mGoToFirstAction, Qt::Key_Home); mGoToLastAction = view->addAction("go_last", q, SLOT(goToLast())); mGoToLastAction->setPriority(QAction::LowPriority); mGoToLastAction->setIcon(QIcon::fromTheme("go-last-view")); mGoToLastAction->setText(i18nc("@action Go to last image", "Last")); mGoToLastAction->setToolTip(i18nc("@info:tooltip", "Go to last image")); actionCollection->setDefaultShortcut(mGoToLastAction, Qt::Key_End); mPreloadDirectionIsForward = true; mGoUpAction = view->addAction(KStandardAction::Up, q, SLOT(goUp())); action = view->addAction("go_start_page", q, SLOT(showStartMainPage())); action->setPriority(QAction::LowPriority); action->setIcon(QIcon::fromTheme("go-home")); action->setText(i18nc("@action", "Start Page")); action->setToolTip(i18nc("@info:tooltip", "Open the start page")); mToggleSideBarAction = view->add("toggle_sidebar"); connect(mToggleSideBarAction, &KToggleAction::triggered, q, &MainWindow::toggleSideBar); mToggleSideBarAction->setIcon(QIcon::fromTheme("view-sidetree")); actionCollection->setDefaultShortcut(mToggleSideBarAction, Qt::Key_F4); mToggleSideBarAction->setText(i18nc("@action", "Sidebar")); connect(mBrowseMainPage->toggleSideBarButton(), SIGNAL(clicked()), mToggleSideBarAction, SLOT(trigger())); connect(mViewMainPage->toggleSideBarButton(), SIGNAL(clicked()), mToggleSideBarAction, SLOT(trigger())); mToggleSlideShowAction = view->addAction("toggle_slideshow", q, SLOT(toggleSlideShow())); q->updateSlideShowAction(); connect(mSlideShow, SIGNAL(stateChanged(bool)), q, SLOT(updateSlideShowAction())); q->setStandardToolBarMenuEnabled(true); mShowMenuBarAction = static_cast(view->addAction(KStandardAction::ShowMenubar, q, SLOT(toggleMenuBar()))); mShowStatusBarAction = static_cast(view->addAction(KStandardAction::ShowStatusbar, q, SLOT(toggleStatusBar(bool)))); actionCollection->setDefaultShortcut(mShowStatusBarAction, Qt::Key_F3); view->addAction(KStandardAction::KeyBindings, q->guiFactory(), SLOT(configureShortcuts())); view->addAction(KStandardAction::Preferences, q, SLOT(showConfigDialog())); view->addAction(KStandardAction::ConfigureToolbars, q, SLOT(configureToolbars())); #ifdef KIPI_FOUND mKIPIExportAction = new KIPIExportAction(q); actionCollection->addAction("kipi_export", mKIPIExportAction); #endif } void setupUndoActions() { // There is no KUndoGroup similar to KUndoStack. This code basically // does the same as KUndoStack, but for the KUndoGroup actions. QUndoGroup* undoGroup = DocumentFactory::instance()->undoGroup(); QAction* action; KActionCollection* actionCollection = q->actionCollection(); KActionCategory* edit = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "Edit"), actionCollection); action = undoGroup->createRedoAction(actionCollection); action->setObjectName(KStandardAction::name(KStandardAction::Redo)); action->setIcon(QIcon::fromTheme("edit-redo")); action->setIconText(i18n("Redo")); actionCollection->setDefaultShortcuts(action, KStandardShortcut::redo()); edit->addAction(action->objectName(), action); action = undoGroup->createUndoAction(actionCollection); action->setObjectName(KStandardAction::name(KStandardAction::Undo)); action->setIcon(QIcon::fromTheme("edit-undo")); action->setIconText(i18n("Undo")); actionCollection->setDefaultShortcuts(action, KStandardShortcut::undo()); edit->addAction(action->objectName(), action); } void setupContextManagerItems() { Q_ASSERT(mContextManager); KActionCollection* actionCollection = q->actionCollection(); // Create context manager items FolderViewContextManagerItem* folderViewItem = new FolderViewContextManagerItem(mContextManager); connect(folderViewItem, &FolderViewContextManagerItem::urlChanged, q, &MainWindow::folderViewUrlChanged); InfoContextManagerItem* infoItem = new InfoContextManagerItem(mContextManager); #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE SemanticInfoContextManagerItem* semanticInfoItem = 0; semanticInfoItem = new SemanticInfoContextManagerItem(mContextManager, actionCollection, mViewMainPage); #endif ImageOpsContextManagerItem* imageOpsItem = new ImageOpsContextManagerItem(mContextManager, q); FileOpsContextManagerItem* fileOpsItem = new FileOpsContextManagerItem(mContextManager, mThumbnailView, actionCollection, q); // Fill sidebar SideBarPage* page; page = new SideBarPage(i18n("Folders")); page->setObjectName(QLatin1String("folders")); page->addWidget(folderViewItem->widget()); page->layout()->setMargin(0); mSideBar->addPage(page); page = new SideBarPage(i18n("Information")); page->setObjectName(QLatin1String("information")); page->addWidget(infoItem->widget()); #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE if (semanticInfoItem) { page->addWidget(semanticInfoItem->widget()); } #endif mSideBar->addPage(page); page = new SideBarPage(i18n("Operations")); page->setObjectName(QLatin1String("operations")); page->addWidget(imageOpsItem->widget()); page->addWidget(fileOpsItem->widget()); page->addStretch(); mSideBar->addPage(page); } void initDirModel() { mDirModel->setKindFilter( MimeTypeUtils::KIND_DIR | MimeTypeUtils::KIND_ARCHIVE | MimeTypeUtils::KIND_RASTER_IMAGE | MimeTypeUtils::KIND_SVG_IMAGE | MimeTypeUtils::KIND_VIDEO); connect(mDirModel, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(slotDirModelNewItems())); connect(mDirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), q, SLOT(updatePreviousNextActions())); connect(mDirModel, SIGNAL(modelReset()), q, SLOT(updatePreviousNextActions())); connect(mDirModel->dirLister(), SIGNAL(completed()), q, SLOT(slotDirListerCompleted())); } void setupThumbnailBarModel() { mThumbnailBarModel = new DocumentOnlyProxyModel(q); mThumbnailBarModel->setSourceModel(mDirModel); } bool indexIsDirOrArchive(const QModelIndex& index) const { Q_ASSERT(index.isValid()); KFileItem item = mDirModel->itemForIndex(index); return ArchiveUtils::fileItemIsDirOrArchive(item); } void goTo(const QModelIndex& index) { if (!index.isValid()) { return; } mThumbnailView->setCurrentIndex(index); mThumbnailView->scrollTo(index); } void goTo(int offset) { mPreloadDirectionIsForward = offset > 0; QModelIndex index = mContextManager->selectionModel()->currentIndex(); index = mDirModel->index(index.row() + offset, 0); if (index.isValid() && !indexIsDirOrArchive(index)) { goTo(index); } } void goToFirstDocument() { QModelIndex index; for (int row = 0;; ++row) { index = mDirModel->index(row, 0); if (!index.isValid()) { return; } if (!indexIsDirOrArchive(index)) { break; } } goTo(index); } void goToLastDocument() { QModelIndex index = mDirModel->index(mDirModel->rowCount() - 1, 0); goTo(index); } void setupFullScreenContent() { mFullScreenContent->init(q->actionCollection(), mViewMainPage, mSlideShow); setupThumbnailBar(mFullScreenContent->thumbnailBar()); } inline void setActionEnabled(const char* name, bool enabled) { QAction* action = q->actionCollection()->action(name); if (action) { action->setEnabled(enabled); } else { qWarning() << "Action" << name << "not found"; } } void setActionsDisabledOnStartMainPageEnabled(bool enabled) { mBrowseAction->setEnabled(enabled); mViewAction->setEnabled(enabled); mToggleSideBarAction->setEnabled(enabled); mShowStatusBarAction->setEnabled(enabled); mFullScreenAction->setEnabled(enabled); mToggleSlideShowAction->setEnabled(enabled); setActionEnabled("reload", enabled); setActionEnabled("go_start_page", enabled); setActionEnabled("add_folder_to_places", enabled); } void updateActions() { bool isRasterImage = false; bool canSave = false; bool isModified = false; const QUrl url = mContextManager->currentUrl(); if (url.isValid()) { isRasterImage = mContextManager->currentUrlIsRasterImage(); canSave = isRasterImage; isModified = DocumentFactory::instance()->load(url)->isModified(); if (mCurrentMainPageId != ViewMainPageId && mContextManager->selectedFileItemList().count() != 1) { // Saving only makes sense if exactly one image is selected canSave = false; } } KActionCollection* actionCollection = q->actionCollection(); actionCollection->action("file_save")->setEnabled(canSave && isModified); actionCollection->action("file_save_as")->setEnabled(canSave); actionCollection->action("file_print")->setEnabled(isRasterImage); } bool sideBarVisibility() const { switch (mCurrentMainPageId) { case StartMainPageId: GV_WARN_AND_RETURN_VALUE(false, "Sidebar not implemented on start page"); break; case BrowseMainPageId: return GwenviewConfig::sideBarVisibleBrowseMode(); break; case ViewMainPageId: return q->isFullScreen() ? GwenviewConfig::sideBarVisibleViewModeFullScreen() : GwenviewConfig::sideBarVisibleViewMode(); break; } return false; } void saveSideBarVisibility(const bool visible) { switch (mCurrentMainPageId) { case StartMainPageId: GV_WARN_AND_RETURN("Sidebar not implemented on start page"); break; case BrowseMainPageId: GwenviewConfig::setSideBarVisibleBrowseMode(visible); break; case ViewMainPageId: q->isFullScreen() ? GwenviewConfig::setSideBarVisibleViewModeFullScreen(visible) : GwenviewConfig::setSideBarVisibleViewMode(visible); break; } } bool statusBarVisibility() const { switch (mCurrentMainPageId) { case StartMainPageId: GV_WARN_AND_RETURN_VALUE(false, "Statusbar not implemented on start page"); break; case BrowseMainPageId: return GwenviewConfig::statusBarVisibleBrowseMode(); break; case ViewMainPageId: return q->isFullScreen() ? GwenviewConfig::statusBarVisibleViewModeFullScreen() : GwenviewConfig::statusBarVisibleViewMode(); break; } return false; } void saveStatusBarVisibility(const bool visible) { switch (mCurrentMainPageId) { case StartMainPageId: GV_WARN_AND_RETURN("Statusbar not implemented on start page"); break; case BrowseMainPageId: GwenviewConfig::setStatusBarVisibleBrowseMode(visible); break; case ViewMainPageId: q->isFullScreen() ? GwenviewConfig::setStatusBarVisibleViewModeFullScreen(visible) : GwenviewConfig::setStatusBarVisibleViewMode(visible); break; } } void setScreenSaverEnabled(bool enabled) { // Always delete mNotificationRestrictions, it does not hurt delete mNotificationRestrictions; if (!enabled) { mNotificationRestrictions = new KNotificationRestrictions(KNotificationRestrictions::ScreenSaver, q); } else { mNotificationRestrictions = 0; } } void assignThumbnailProviderToThumbnailView(ThumbnailView* thumbnailView) { GV_RETURN_IF_FAIL(thumbnailView); if (mActiveThumbnailView) { mActiveThumbnailView->setThumbnailProvider(0); } thumbnailView->setThumbnailProvider(mThumbnailProvider); mActiveThumbnailView = thumbnailView; if (mActiveThumbnailView->isVisible()) { mThumbnailProvider->stop(); mActiveThumbnailView->generateThumbnailsForItems(); } } void autoAssignThumbnailProvider() { if (mCurrentMainPageId == ViewMainPageId) { if (q->windowState() & Qt::WindowFullScreen) { assignThumbnailProviderToThumbnailView(mFullScreenContent->thumbnailBar()); } else { assignThumbnailProviderToThumbnailView(mViewMainPage->thumbnailBar()); } } else if (mCurrentMainPageId == BrowseMainPageId) { assignThumbnailProviderToThumbnailView(mThumbnailView); } else if (mCurrentMainPageId == StartMainPageId) { assignThumbnailProviderToThumbnailView(mStartMainPage->recentFoldersView()); } } }; MainWindow::MainWindow() : KXmlGuiWindow(), d(new MainWindow::Private) { d->q = this; d->mCurrentMainPageId = StartMainPageId; d->mDirModel = new SortedDirModel(this); d->setupContextManager(); d->setupThumbnailBarModel(); d->mGvCore = new GvCore(this, d->mDirModel); d->mPreloader = new Preloader(this); d->mNotificationRestrictions = 0; d->mThumbnailProvider = new ThumbnailProvider(); d->mActiveThumbnailView = 0; d->initDirModel(); d->setupWidgets(); d->setupActions(); d->setupUndoActions(); d->setupContextManagerItems(); d->setupFullScreenContent(); #ifdef HAVE_QTDBUS d->mMpris2Service = new Mpris2Service(d->mSlideShow, d->mContextManager, d->mToggleSlideShowAction, d->mFullScreenAction, d->mGoToPreviousAction, d->mGoToNextAction, this); #endif d->updateActions(); updatePreviousNextActions(); d->mSaveBar->initActionDependentWidgets(); createGUI(); loadConfig(); connect(DocumentFactory::instance(), SIGNAL(modifiedDocumentListChanged()), SLOT(slotModifiedDocumentListChanged())); #ifdef KIPI_FOUND d->mKIPIInterface = new KIPIInterface(this); d->mKIPIExportAction->setKIPIInterface(d->mKIPIInterface); #endif setAutoSaveSettings(); #ifdef Q_OS_OSX qApp->installEventFilter(this); #endif } MainWindow::~MainWindow() { if (GwenviewConfig::deleteThumbnailCacheOnExit()) { QDir dir(ThumbnailProvider::thumbnailBaseDir()); if (dir.exists()) { dir.removeRecursively(); } } delete d->mThumbnailProvider; delete d; } ContextManager* MainWindow::contextManager() const { return d->mContextManager; } ViewMainPage* MainWindow::viewMainPage() const { return d->mViewMainPage; } void MainWindow::setCaption(const QString& caption) { // Keep a trace of caption to use it in slotModifiedDocumentListChanged() d->mCaption = caption; KXmlGuiWindow::setCaption(caption); } void MainWindow::setCaption(const QString& caption, bool modified) { d->mCaption = caption; KXmlGuiWindow::setCaption(caption, modified); } void MainWindow::slotUpdateCaption(const QString& caption) { const QUrl url = d->mContextManager->currentUrl(); const QList list = DocumentFactory::instance()->modifiedDocumentList(); setCaption(caption, list.contains(url)); } void MainWindow::slotModifiedDocumentListChanged() { d->updateActions(); slotUpdateCaption(d->mCaption); } void MainWindow::setInitialUrl(const QUrl &_url) { Q_ASSERT(_url.isValid()); QUrl url = UrlUtils::fixUserEnteredUrl(_url); if (UrlUtils::urlIsDirectory(url)) { d->mBrowseAction->trigger(); openDirUrl(url); } else { openUrl(url); } } void MainWindow::startSlideShow() { d->mViewAction->trigger(); // We need to wait until we have listed all images in the dirlister to // start the slideshow because the SlideShow objects needs an image list to // work. d->mStartSlideShowWhenDirListerCompleted = true; } void MainWindow::setActiveViewModeAction(QAction* action) { if (action == d->mViewAction) { d->mCurrentMainPageId = ViewMainPageId; // Switching to view mode d->mViewStackedWidget->setCurrentWidget(d->mViewMainPage); openSelectedDocuments(); d->mPreloadDirectionIsForward = true; QTimer::singleShot(VIEW_PRELOAD_DELAY, this, SLOT(preloadNextUrl())); } else { d->mCurrentMainPageId = BrowseMainPageId; // Switching to browse mode d->mViewStackedWidget->setCurrentWidget(d->mBrowseMainPage); if (!d->mViewMainPage->isEmpty() && KProtocolManager::supportsListing(d->mViewMainPage->url())) { // Reset the view to spare resources, but don't do it if we can't // browse the url, otherwise if the user starts Gwenview this way: // gwenview http://example.com/example.png // and switch to browse mode, switching back to view mode won't bring // his image back. d->mViewMainPage->reset(); } setCaption(QString()); } d->autoAssignThumbnailProvider(); toggleSideBar(d->sideBarVisibility()); toggleStatusBar(d->statusBarVisibility()); emit viewModeChanged(); } void MainWindow::slotThumbnailViewIndexActivated(const QModelIndex& index) { if (!index.isValid()) { return; } KFileItem item = d->mDirModel->itemForIndex(index); if (item.isDir()) { // Item is a dir, open it openDirUrl(item.url()); } else { QString protocol = ArchiveUtils::protocolForMimeType(item.mimetype()); if (!protocol.isEmpty()) { // Item is an archive, tweak url then open it QUrl url = item.url(); url.setScheme(protocol); openDirUrl(url); } else { // Item is a document, switch to view mode d->mViewAction->trigger(); } } } void MainWindow::openSelectedDocuments() { if (d->mCurrentMainPageId != ViewMainPageId) { return; } int count = 0; QList urls; const auto list = d->mContextManager->selectedFileItemList(); for (const auto &item : list) { if (!item.isNull() && !ArchiveUtils::fileItemIsDirOrArchive(item)) { urls << item.url(); ++count; if (count == ViewMainPage::MaxViewCount) { break; } } } if (urls.isEmpty()) { // Selection contains no fitting items // Switch back to browsing mode d->mBrowseAction->trigger(); d->mViewMainPage->reset(); return; } QUrl currentUrl = d->mContextManager->currentUrl(); if (currentUrl.isEmpty() || !urls.contains(currentUrl)) { // There is no current URL or it doesn't belong to selection // This can happen when user manually selects a group of items currentUrl = urls.first(); } d->mViewMainPage->openUrls(urls, currentUrl); } void MainWindow::goUp() { if (d->mCurrentMainPageId == BrowseMainPageId) { QUrl url = d->mContextManager->currentDirUrl(); url = KIO::upUrl(url); openDirUrl(url); } else { d->mBrowseAction->trigger(); } } void MainWindow::showStartMainPage() { d->mCurrentMainPageId = StartMainPageId; d->setActionsDisabledOnStartMainPageEnabled(false); d->mSideBar->hide(); d->mViewStackedWidget->setCurrentWidget(d->mStartMainPage); d->updateActions(); updatePreviousNextActions(); d->mContextManager->setCurrentDirUrl(QUrl()); d->mContextManager->setCurrentUrl(QUrl()); d->autoAssignThumbnailProvider(); } void MainWindow::slotStartMainPageUrlSelected(const QUrl &url) { d->setActionsDisabledOnStartMainPageEnabled(true); if (d->mBrowseAction->isChecked()) { // Silently uncheck the action so that setInitialUrl() does the right thing SignalBlocker blocker(d->mBrowseAction); d->mBrowseAction->setChecked(false); } setInitialUrl(url); } void MainWindow::openDirUrl(const QUrl &url) { const QUrl currentUrl = d->mContextManager->currentDirUrl(); if (url == currentUrl) { return; } if (url.isParentOf(currentUrl)) { // Keep first child between url and currentUrl selected // If currentPath is "/home/user/photos/2008/event" // and wantedPath is "/home/user/photos" // pathToSelect should be "/home/user/photos/2008" // To anyone considering using QUrl::toLocalFile() instead of // QUrl::path() here. Please don't, using QUrl::path() is the right // thing to do here. const QString currentPath = QDir::cleanPath(currentUrl.adjusted(QUrl::StripTrailingSlash).path()); const QString wantedPath = QDir::cleanPath(url.adjusted(QUrl::StripTrailingSlash).path()); const QChar separator('/'); const int slashCount = wantedPath.count(separator); const QString pathToSelect = currentPath.section(separator, 0, slashCount + 1); QUrl urlToSelect = url; urlToSelect.setPath(pathToSelect); d->mContextManager->setUrlToSelect(urlToSelect); } d->mThumbnailProvider->stop(); d->mContextManager->setCurrentDirUrl(url); d->mGvCore->addUrlToRecentFolders(url); d->mViewMainPage->reset(); } void MainWindow::folderViewUrlChanged(const QUrl &url) { const QUrl currentUrl = d->mContextManager->currentDirUrl(); if (url == currentUrl) { switch (d->mCurrentMainPageId) { case ViewMainPageId: d->mBrowseAction->trigger(); break; case BrowseMainPageId: d->mViewAction->trigger(); break; case StartMainPageId: break; } } else { openDirUrl(url); } } void MainWindow::toggleSideBar(bool visible) { d->mToggleSideBarAction->setChecked(visible); d->saveSideBarVisibility(visible); d->mSideBar->setVisible(visible); const QString text = QApplication::isRightToLeft() ? QString::fromUtf8(visible ? "▮→" : "▮←") : QString::fromUtf8(visible ? "▮←" : "▮→"); const QString toolTip = visible ? i18nc("@info:tooltip", "Hide sidebar") : i18nc("@info:tooltip", "Show sidebar"); const QList buttonList { d->mBrowseMainPage->toggleSideBarButton(), d->mViewMainPage->toggleSideBarButton() }; for (auto button : buttonList) { button->setText(text); button->setToolTip(toolTip); } } void MainWindow::toggleStatusBar(bool visible) { d->mShowStatusBarAction->setChecked(visible); d->saveStatusBarVisibility(visible); d->mViewMainPage->setStatusBarVisible(visible); d->mBrowseMainPage->setStatusBarVisible(visible); } void MainWindow::slotPartCompleted() { d->updateActions(); const QUrl url = d->mContextManager->currentUrl(); if (!url.isEmpty() && GwenviewConfig::historyEnabled()) { d->mFileOpenRecentAction->addUrl(url); d->mGvCore->addUrlToRecentFiles(url); } if (KProtocolManager::supportsListing(url)) { const QUrl dirUrl = d->mContextManager->currentDirUrl(); d->mGvCore->addUrlToRecentFolders(dirUrl); } } void MainWindow::slotSelectionChanged() { if (d->mCurrentMainPageId == ViewMainPageId) { // The user selected a new file in the thumbnail view, since the // document view is visible, let's show it openSelectedDocuments(); } else { // No document view, we need to load the document to set the undo group // of document factory to the correct QUndoStack QModelIndex index = d->mThumbnailView->currentIndex(); KFileItem item; if (index.isValid()) { item = d->mDirModel->itemForIndex(index); } QUndoGroup* undoGroup = DocumentFactory::instance()->undoGroup(); if (!item.isNull() && !ArchiveUtils::fileItemIsDirOrArchive(item)) { QUrl url = item.url(); Document::Ptr doc = DocumentFactory::instance()->load(url); undoGroup->addStack(doc->undoStack()); undoGroup->setActiveStack(doc->undoStack()); } else { undoGroup->setActiveStack(nullptr); } } // Update UI d->updateActions(); updatePreviousNextActions(); // Start preloading int preloadDelay = d->mCurrentMainPageId == ViewMainPageId ? VIEW_PRELOAD_DELAY : BROWSE_PRELOAD_DELAY; QTimer::singleShot(preloadDelay, this, SLOT(preloadNextUrl())); } void MainWindow::slotCurrentDirUrlChanged(const QUrl &url) { if (url.isValid()) { d->mUrlNavigator->setLocationUrl(url); d->mGoUpAction->setEnabled(url.path() != "/"); } else { d->mGoUpAction->setEnabled(false); } } void MainWindow::slotDirModelNewItems() { if (d->mContextManager->selectionModel()->hasSelection()) { updatePreviousNextActions(); } } void MainWindow::slotDirListerCompleted() { if (d->mStartSlideShowWhenDirListerCompleted) { d->mStartSlideShowWhenDirListerCompleted = false; QTimer::singleShot(0, d->mToggleSlideShowAction, SLOT(trigger())); } if (d->mContextManager->selectionModel()->hasSelection()) { updatePreviousNextActions(); } else { d->goToFirstDocument(); if (!d->mContextManager->selectionModel()->hasSelection()) { const QModelIndex index = d->mThumbnailView->model()->index(0, 0); if (index.isValid()) { d->mThumbnailView->setCurrentIndex(index); } } } d->mThumbnailView->scrollToSelectedIndex(); d->mViewMainPage->thumbnailBar()->scrollToSelectedIndex(); d->mFullScreenContent->thumbnailBar()->scrollToSelectedIndex(); } void MainWindow::goToPrevious() { d->goTo(-1); } void MainWindow::goToNext() { d->goTo(1); } void MainWindow::goToFirst() { d->goToFirstDocument(); } void MainWindow::goToLast() { d->goToLastDocument(); } void MainWindow::goToUrl(const QUrl &url) { if (d->mCurrentMainPageId == ViewMainPageId) { d->mViewMainPage->openUrl(url); } QUrl dirUrl = url; dirUrl = dirUrl.adjusted(QUrl::RemoveFilename); dirUrl.setPath(dirUrl.path() + ""); if (dirUrl != d->mContextManager->currentDirUrl()) { d->mContextManager->setCurrentDirUrl(dirUrl); d->mGvCore->addUrlToRecentFolders(dirUrl); } d->mContextManager->setUrlToSelect(url); } void MainWindow::updatePreviousNextActions() { bool hasPrevious; bool hasNext; QModelIndex currentIndex = d->mContextManager->selectionModel()->currentIndex(); if (currentIndex.isValid() && !d->indexIsDirOrArchive(currentIndex)) { int row = currentIndex.row(); QModelIndex prevIndex = d->mDirModel->index(row - 1, 0); QModelIndex nextIndex = d->mDirModel->index(row + 1, 0); hasPrevious = prevIndex.isValid() && !d->indexIsDirOrArchive(prevIndex); hasNext = nextIndex.isValid() && !d->indexIsDirOrArchive(nextIndex); } else { hasPrevious = false; hasNext = false; } d->mGoToPreviousAction->setEnabled(hasPrevious); d->mGoToNextAction->setEnabled(hasNext); d->mGoToFirstAction->setEnabled(hasPrevious); d->mGoToLastAction->setEnabled(hasNext); } void MainWindow::leaveFullScreen() { if (d->mFullScreenAction->isChecked()) { d->mFullScreenAction->trigger(); } } void MainWindow::toggleFullScreen(bool checked) { setUpdatesEnabled(false); if (checked) { // Save MainWindow config now, this way if we quit while in // fullscreen, we are sure latest MainWindow changes are remembered. KConfigGroup saveConfigGroup = autoSaveConfigGroup(); if (!isFullScreen()) { // Save state if window manager did not already switch to fullscreen. saveMainWindowSettings(saveConfigGroup); d->mStateBeforeFullScreen.mToolBarVisible = toolBar()->isVisible(); } setAutoSaveSettings(saveConfigGroup, false); resetAutoSaveSettings(); // Go full screen KToggleFullScreenAction::setFullScreen(this, true); menuBar()->hide(); toolBar()->hide(); qApp->setProperty("KDE_COLOR_SCHEME_PATH", d->mGvCore->fullScreenPaletteName()); QApplication::setPalette(d->mGvCore->palette(GvCore::FullScreenPalette)); d->setScreenSaverEnabled(false); } else { setAutoSaveSettings(); // Back to normal qApp->setProperty("KDE_COLOR_SCHEME_PATH", QVariant()); QApplication::setPalette(d->mGvCore->palette(GvCore::NormalPalette)); - d->mSlideShow->stop(); + d->mSlideShow->pause(); KToggleFullScreenAction::setFullScreen(this, false); menuBar()->setVisible(d->mShowMenuBarAction->isChecked()); toolBar()->setVisible(d->mStateBeforeFullScreen.mToolBarVisible); d->setScreenSaverEnabled(true); // See resizeEvent d->mFullScreenLeftAt = QDateTime::currentDateTime(); } d->mFullScreenContent->setFullScreenMode(checked); d->mBrowseMainPage->setFullScreenMode(checked); d->mViewMainPage->setFullScreenMode(checked); d->mSaveBar->setFullScreenMode(checked); toggleSideBar(d->sideBarVisibility()); toggleStatusBar(d->statusBarVisibility()); setUpdatesEnabled(true); d->autoAssignThumbnailProvider(); } void MainWindow::saveCurrent() { d->mGvCore->save(d->mContextManager->currentUrl()); } void MainWindow::saveCurrentAs() { d->mGvCore->saveAs(d->mContextManager->currentUrl()); } void MainWindow::reload() { if (d->mCurrentMainPageId == ViewMainPageId) { d->mViewMainPage->reload(); } else { d->mBrowseMainPage->reload(); } } void MainWindow::openFile() { QUrl dirUrl = d->mContextManager->currentDirUrl(); DialogGuard dialog(this); dialog->selectUrl(dirUrl); dialog->setWindowTitle(i18nc("@title:window", "Open Image")); const QStringList mimeFilter = MimeTypeUtils::imageMimeTypes(); dialog->setMimeTypeFilters(mimeFilter); dialog->setAcceptMode(QFileDialog::AcceptOpen); if (!dialog->exec()) { return; } if (!dialog->selectedUrls().isEmpty()) { openUrl(dialog->selectedUrls().first()); } } void MainWindow::openUrl(const QUrl& url) { d->setActionsDisabledOnStartMainPageEnabled(true); d->mContextManager->setUrlToSelect(url); d->mViewAction->trigger(); } void MainWindow::showDocumentInFullScreen(const QUrl &url) { d->mContextManager->setUrlToSelect(url); d->mViewAction->trigger(); d->mFullScreenAction->trigger(); } void MainWindow::toggleSlideShow() { if (d->mSlideShow->isRunning()) { - d->mSlideShow->stop(); + d->mSlideShow->pause(); } else { if (!d->mViewAction->isChecked()) { d->mViewAction->trigger(); } if (!d->mFullScreenAction->isChecked()) { d->mFullScreenAction->trigger(); } QList list; for (int pos = 0; pos < d->mDirModel->rowCount(); ++pos) { QModelIndex index = d->mDirModel->index(pos, 0); KFileItem item = d->mDirModel->itemForIndex(index); MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item); switch (kind) { case MimeTypeUtils::KIND_SVG_IMAGE: case MimeTypeUtils::KIND_RASTER_IMAGE: case MimeTypeUtils::KIND_VIDEO: list << item.url(); break; default: break; } } d->mSlideShow->start(list); } updateSlideShowAction(); } void MainWindow::updateSlideShowAction() { if (d->mSlideShow->isRunning()) { - d->mToggleSlideShowAction->setText(i18n("Stop Slideshow")); + d->mToggleSlideShowAction->setText(i18n("Pause Slideshow")); d->mToggleSlideShowAction->setIcon(QIcon::fromTheme("media-playback-pause")); } else { - d->mToggleSlideShowAction->setText(i18n("Start Slideshow")); + d->mToggleSlideShowAction->setText(d->mFullScreenAction->isChecked() ? i18n("Resume Slideshow") + : i18n("Start Slideshow")); d->mToggleSlideShowAction->setIcon(QIcon::fromTheme("media-playback-start")); } } bool MainWindow::queryClose() { saveConfig(); QList list = DocumentFactory::instance()->modifiedDocumentList(); if (list.size() == 0) { return true; } KGuiItem yes(i18n("Save All Changes"), "document-save-all"); KGuiItem no(i18n("Discard Changes"), "delete"); QString msg = i18np("One image has been modified.", "%1 images have been modified.", list.size()) + '\n' + i18n("If you quit now, your changes will be lost."); int answer = KMessageBox::warningYesNoCancel( this, msg, QString() /* caption */, yes, no); switch (answer) { case KMessageBox::Yes: d->mGvCore->saveAll(); // We need to wait a bit because the DocumentFactory is notified about // saved documents through a queued connection. qApp->processEvents(); return DocumentFactory::instance()->modifiedDocumentList().isEmpty(); case KMessageBox::No: return true; default: // cancel return false; } } void MainWindow::showConfigDialog() { // Save first so changes like thumbnail zoom level are not lost when reloading config saveConfig(); DialogGuard dialog(this); connect(dialog.data(), SIGNAL(settingsChanged(QString)), SLOT(loadConfig())); dialog->exec(); } void MainWindow::toggleMenuBar() { if (!d->mFullScreenAction->isChecked()) { menuBar()->setVisible(d->mShowMenuBarAction->isChecked()); } } void MainWindow::loadConfig() { d->mDirModel->setBlackListedExtensions(GwenviewConfig::blackListedExtensions()); d->mDirModel->adjustKindFilter(MimeTypeUtils::KIND_VIDEO, GwenviewConfig::listVideos()); if (GwenviewConfig::historyEnabled()) { d->mFileOpenRecentAction->loadEntries(KConfigGroup(KSharedConfig::openConfig(), "Recent Files")); foreach(const QUrl& url, d->mFileOpenRecentAction->urls()) { d->mGvCore->addUrlToRecentFiles(url); } } else { d->mFileOpenRecentAction->clear(); } d->mFileOpenRecentAction->setVisible(GwenviewConfig::historyEnabled()); d->mStartMainPage->loadConfig(); d->mViewMainPage->loadConfig(); d->mBrowseMainPage->loadConfig(); d->mContextManager->loadConfig(); d->mSideBar->loadConfig(); } void MainWindow::saveConfig() { d->mFileOpenRecentAction->saveEntries(KConfigGroup(KSharedConfig::openConfig(), "Recent Files")); d->mViewMainPage->saveConfig(); d->mBrowseMainPage->saveConfig(); d->mContextManager->saveConfig(); GwenviewConfig::setFullScreenModeActive(isFullScreen()); } void MainWindow::print() { if (!d->mContextManager->currentUrlIsRasterImage()) { return; } Document::Ptr doc = DocumentFactory::instance()->load(d->mContextManager->currentUrl()); PrintHelper printHelper(this); printHelper.print(doc); } void MainWindow::preloadNextUrl() { static bool disablePreload = qgetenv("GV_MAX_UNREFERENCED_IMAGES") == "0"; if (disablePreload) { qDebug() << "Preloading disabled"; return; } QItemSelection selection = d->mContextManager->selectionModel()->selection(); if (selection.size() != 1) { return; } QModelIndexList indexList = selection.indexes(); if (indexList.isEmpty()) { return; } QModelIndex index = indexList.at(0); if (!index.isValid()) { return; } if (d->mCurrentMainPageId == ViewMainPageId) { // If we are in view mode, preload the next url, otherwise preload the // selected one int offset = d->mPreloadDirectionIsForward ? 1 : -1; index = d->mDirModel->sibling(index.row() + offset, index.column(), index); if (!index.isValid()) { return; } } KFileItem item = d->mDirModel->itemForIndex(index); if (!ArchiveUtils::fileItemIsDirOrArchive(item)) { QUrl url = item.url(); if (url.isLocalFile()) { QSize size = d->mViewStackedWidget->size(); d->mPreloader->preload(url, size); } } } QSize MainWindow::sizeHint() const { return KXmlGuiWindow::sizeHint().expandedTo(QSize(750, 500)); } void MainWindow::showEvent(QShowEvent *event) { // We need to delay initializing the action state until the menu bar has // been initialized, that's why it's done only in the showEvent() d->mShowMenuBarAction->setChecked(menuBar()->isVisible()); KXmlGuiWindow::showEvent(event); } void MainWindow::resizeEvent(QResizeEvent* event) { KXmlGuiWindow::resizeEvent(event); // This is a small hack to execute code after leaving fullscreen, and after // the window has been resized back to its former size. if (d->mFullScreenLeftAt.isValid() && d->mFullScreenLeftAt.secsTo(QDateTime::currentDateTime()) < 2) { if (d->mCurrentMainPageId == BrowseMainPageId) { d->mThumbnailView->scrollToSelectedIndex(); } d->mFullScreenLeftAt = QDateTime(); } } bool MainWindow::eventFilter(QObject *obj, QEvent *event) { Q_UNUSED(obj); Q_UNUSED(event); #ifdef Q_OS_OSX /** * handle Mac OS X file open events (only exist on OS X) */ if (event->type() == QEvent::FileOpen) { QFileOpenEvent *fileOpenEvent = static_cast(event); openUrl(fileOpenEvent->url()); return true; } #endif return false; } void MainWindow::setDistractionFreeMode(bool value) { d->mFullScreenContent->setDistractionFreeMode(value); } void MainWindow::saveProperties(KConfigGroup& group) { group.writeEntry(SESSION_CURRENT_PAGE_KEY, int(d->mCurrentMainPageId)); group.writeEntry(SESSION_URL_KEY, d->mContextManager->currentUrl().toString()); } void MainWindow::readProperties(const KConfigGroup& group) { MainPageId pageId = MainPageId(group.readEntry(SESSION_CURRENT_PAGE_KEY, int(StartMainPageId))); if (pageId == StartMainPageId) { d->mCurrentMainPageId = StartMainPageId; showStartMainPage(); } else if (pageId == BrowseMainPageId) { d->mBrowseAction->trigger(); } else { d->mViewAction->trigger(); } QUrl url = group.readEntry(SESSION_URL_KEY, QUrl()); if (!url.isValid()) { qWarning() << "Invalid url!"; return; } goToUrl(url); } void MainWindow::showFirstDocumentReached() { if (d->mCurrentMainPageId != ViewMainPageId) { return; } HudButtonBox* dlg = new HudButtonBox; dlg->setText(i18n("You reached the first document, what do you want to do?")); dlg->addButton(i18n("Stay There")); dlg->addAction(d->mGoToLastAction, i18n("Go to the Last Document")); dlg->addAction(d->mBrowseAction, i18n("Go Back to the Document List")); dlg->addCountDown(15000); d->mViewMainPage->showMessageWidget(dlg, Qt::AlignCenter); } void MainWindow::showLastDocumentReached() { if (d->mCurrentMainPageId != ViewMainPageId) { return; } HudButtonBox* dlg = new HudButtonBox; dlg->setText(i18n("You reached the last document, what do you want to do?")); dlg->addButton(i18n("Stay There")); dlg->addAction(d->mGoToFirstAction, i18n("Go to the First Document")); dlg->addAction(d->mBrowseAction, i18n("Go Back to the Document List")); dlg->addCountDown(15000); d->mViewMainPage->showMessageWidget(dlg, Qt::AlignCenter); } } // namespace diff --git a/doc/index.docbook b/doc/index.docbook index 7b54f939..95730450 100644 --- a/doc/index.docbook +++ b/doc/index.docbook @@ -1,720 +1,720 @@ ]> Gwenview User Manual Aurélien Gâteau
agateau@kde.org
ChristopherMartin
chrsmrtn@debian.org
Henry de Valence
hdevalence@gmail.com
2005 Aurélien Gâteau 2008 Henry de Valence &FDLNotice; 2018-02-28 Applications 18.04 &gwenview; is an image and video viewer. KDE image viewer artist photo picture
Introduction What is &gwenview; &gwenview; is a fast and easy to use image and video viewer. &gwenview; features two main modes: Browse and View. Both modes can be used in a normal application window and Full Screen. Browse Mode lets you navigate through your computer showing thumbnails of your images, View Mode lets you view images one at a time, and Full Screen lets you make quick slideshows. There is also a start screen that displays a list of recently opened folders and &URL;s as well as your places and tags. Image loading is handled by the &Qt; library, so &gwenview; supports all image formats your &Qt; installation supports. &gwenview; correctly displays images with an alpha channel (transparency) as well as animations. &gwenview; supports the displaying and editing of EXIF comments in JPEG images. Lossless JPEG transforms such as rotations and mirroring are also supported. &gwenview; can read embedded color profiles from PNG and JPEG files. It can use the image color profile together with the display color profile to output correct colors on the screen. The Interface Start Page The start page lists recently opened folders and &URL;s on the left side, and your places and tags on the right side. Start Page Screenshot Image Operations &gwenview; has a few features which are available in both Browse, View, and Full Screen view. &gwenview; has the capability to do basic alteration of your images. Rotate: A rotate operation will rotate the image either to the left or to the right, depending on whether you do &Ctrl;R Edit Rotate Right or &Ctrl;L Edit Rotate Left Edit Mirror : This operation will reflect the image along the vertical axis, as if you were seeing it in a mirror. Edit Flip : This operation will reflect the image upside-down (a reflection along the horizontal axis) &Shift;R Edit Resize : This operation will allow you to shrink or expand the image. Note that if you increase the size of an image, it may appear blurry and/or pixelated. These actions are also available on the Operations tab of the sidebar. If you have edited one or more images a bar with additional actions is displayed at the top of the image. You can undo or redo your changes, go to the previous or next modified image and there are three options to save the changed images. Actions bar for modified images If you have installed the Kipi Plugins, a Plugins menu will be available that will allow you to perform many additional operations on your images. For more information, see the Kipi Plugins documentation. Browse Mode When in Browse Mode, you can easily navigate through your files and folders. The preview window shows thumbnails of the images in the current folder, as well as subfolders. Browse Mode Screenshot Moving the mouse over an image shows buttons to select or rotate the image as well as a button to enter Fullscreen Mode. Modified images are indicated by an icon down right, click it to save the changed image. Clicking on an image takes you into View Mode. You may select multiple images and switch to View Mode to view them side-by-side. The slider at the bottom right allows you to change the size of the images. You can also filter the images by filename, date, tag or rating using the box on the lower left. The toolbar appears in both Browse mode as well as View mode and contains the most commonly used actions. Start Page: Open the start page. Browse: Switches to Browse Mode. View: Switches to View Mode. Full Screen: Switches to Full Screen Mode. Previous: Clicking this icon will go to the previous image in the folder. Next: Clicking this button will go to the next image in the folder. Rotate Left/Right: Same as discussed in Image Operations View Mode View Mode displays full-size images. The same sidebar available in Browse Mode is displayed on the left. At the bottom, there is the Thumbnail Bar, which allows you to scroll through the images in the current folder. The Thumbnail Bar can be minimized by clicking on the Thumbnail Bar button. Clicking again will restore it. To change the size of the thumbnails move the splitter with the &LMB;. View Mode supports viewing multiple images side-by-side. You may select multiple images in Browse Mode before switching to View Mode, or you may click the + button that appears when hovering over images in the Thumbnail Bar to add a pane displaying that image. A - will then appear that will permit you to remove its pane. When multiple images are displayed, a small toolbar appears below each image that permits you to delete the image or remove its pane. You may perform zoom operations independently for each image, or synchronize them. Toggle this by checking the Synchronize to the left of the zoom slider or by pressing &Ctrl;Y. You can switch images by clicking on their pane, or using your keyboard. To switch to the image on the right, press . To switch to the image on the left, press &Shift; . View Mode Screenshot The slider at the bottom right controls the zoom of the image. The Fit, Fill and 100% buttons are next to the zoom slider and are three preset zoom levels. The Fit button zooms the current image to fit the size of the window, the Fill button zooms the image to fill the window by fitting width or height and the 100% button zooms the image to the actual pixel size. The shortcut F switches to fit mode. When an image is in zoom-to-fit mode, you can go to the previous and next image with the arrow keys. When you zoom in, arrow keys are used to scroll the image. This is very similar to the behavior provided by phones or digital cameras. When an image is zoomed in, a bird-eye view appears and lets you scroll the image using the mouse and the arrow keys. The bird-eye view automatically hides itself after a short delay, showing back only while zooming or scrolling. You can define what happens when going to image B after having zoomed in on an area of image A using the options in the Zoom mode group on the Image View page of the &gwenview; configuration window which can be reached using the SettingsConfigure &gwenview;.... If set to Autofit each image, image B is zoomed out to fit the screen. If set to Keep same zoom and position, all images share the same zoom and position: image B is set to the same zoom parameters as image A (and if these are changed, image A will then be displayed with the updated zoom and position). If set to Per image zoom and position, all images remember their own zoom and position: image B is initially set to the same zoom parameters as image A, but will then remember its own zoom and position (if these are changed, image A will not be displayed with the updated zoom and position). You can start directly in View mode by starting &gwenview; from a context menu like Open With in another program or by launching it from the command line with an image as an argument. The following additional image operations are available only in View Mode: &Shift;C Edit Crop : This operation lets you discard parts of the image you don't want. You can access the advanced cropping parameters by ticking Advanced settings check box on the bottom popup pane. Use the corresponding fields to tune up the cropping operation. It is also possible to adjust the cropped area by dragging the gray square handles on the borders of the image. You can move the cropped area by clicking and holding the &LMB; and drag it with the mouse pointer. Press the Crop button to see the results when you are ready. Use the upper popup pane to save the results or undo/redo the operation. Edit Red Eye Reduction : This operation reduces the "red eye" effect commonly found in photographs taken with a flash camera. Full Screen Modes Access Full Screen by pressing the Full Screen button on the toolbar, or by &Ctrl;&Shift;F View Full Screen Mode . To leave this mode press the &Esc; key. Browse Mode Full Screen In Browse Mode you can switch to fullscreen also by clicking on the button that appears when you move the mouse over the thumbnails. Full Screen View Mode Screenshot Going fullscreen while browsing gives you a more immersive experience while you go through your pictures. It is quite nice on your regular computer, but makes even more sense when you connect your laptop to the big TV in the living room to show pictures to your guests. View Mode Full Screen The full screen View Mode shows a slideshow of your images. Access Full Screen Mode by clicking on the button that appears when you move the mouse over the thumbnails in Browse Mode, by pressing the Full Screen button on the taskbar. Full Screen Browse Mode Screenshot The top bar will hide itself automatically; to show it simply move the mouse to the top of the screen. If the mouse cursor is over the top bar, it will not autohide. Most of the buttons on the bar are the same as the ones on the toolbar in Browse or View Modes, except for the Exit Full Screen Mode button which returns you to the &gwenview; window, the -Start/Stop Slideshow button, and the +Pause/Resume Slideshow button, and the Configure Full Screen Mode button which shows a small settings dialog that allows you to easily and quickly configure the slideshow. The slideshow controls there are: The Interval slider controls how long &gwenview; will show an image before it move to the next one. If the Loop check box is checked, when the end of the slideshow is reached, it will continue from the beginning instead of stopping. If the Random check box is checked, instead of progressing through the folder alphabetically, images will be shown in random order. Select Image Information to Display allows you to define what metadata is displayed under the buttons on the toolbar. If the Show thumbnails check box is checked, thumbnails for all images in the current folder will be displayed to the right of the toolbar. The Height slider changes the size of the thumbnails displayed. If enabled, an area that shows you the other images in the current folder will be shown on the top bar. Clicking on one will display it. Sidebar The sidebar on the left is available in the Browse and View modes, but does not appear by default in Browse Mode. Its appearance can be toggled using F4 View Sidebar or using the ▮← / ▮→ button at the left side of the statusbar. When clicked it collapses or expands the sidebar. The sidebar contains several tabs: Folders Displays a list of all folders on your system permitting you to switch between them. In Browse Mode thumbnails from the folder will be displayed, while in View Mode the first image in the folder will appear, from which you can browse through the folder using the Previous and Next buttons or shortcuts. Clicking on a folder multiple times toggles between View Mode and Browse Mode. Information Displays Meta Information like the filename and size. The More... link permits you to view all available metadata and select which data appear in the sidebar. Operations This permits you to perform the previously described global image operations as well as those specific to View Mode. It also permits common file operations like copying, renaming, deleting, and creating new folders. Tips Using the mouse Panning with the mouse Holding down the left mouse button on an image allows you to scroll the image. The mouse wheel will scroll the image up and down. Zooming with the mouse Clicking the middle mouse button will toggle the auto zoom on/off. Hold down the &Ctrl; key, then either use the mouse wheel to zoom in and out or left click to zoom in and right click to zoom out. The mouse wheel, used while holding down the &Alt; key, will scroll the image horizontally. Browsing with the mouse When in Browse mode, clicking an image switches into View mode and shows that image. When in Browse mode, scrolling the mouse wheel will scroll up or down the thumbnail view area. If the Mouse wheel behavior option in SettingsConfigure &gwenview; is set to Browse, scrolling the mouse wheel while in View Mode will move you through the images in the folder. Key bindings &gwenview; comes with a range of keyboard shortcuts, all of which can be viewed and remapped by selecting SettingsConfigure Shortcuts.... Note that in the Files and Folders windows, all the normal KDE shortcuts are functional, unless otherwise remapped. A few of the most useful default bindings are: Space: Displays the next image in the directory. &Backspace;: Displays the previous image in the directory. &Alt;Up: Moves to the parent folder of the current folder. &Ctrl;&Shift;F: Switches into Full Screen Mode. &Esc;: Switches back to Browse Mode. &Ctrl;M: Show or hide the menubar. &Ctrl;B: Show or hide the Thumbnail bar. F4: Show or hide the Sidebar. F6: Make the Location bar editable so that you can directly type in a file path. You can return to the standard Location Bar by pressing the arrow at the right. &Ctrl;R: Rotate the current image to the right. &Ctrl;L: Rotate the current image to the left. &Shift;R: Resize the current image. &Shift;C: Crop the current image. &Ctrl;Y: When multiple images are displayed in View Mode, this synchronizes their views. &Ctrl;S: Save any changes made to the image. Del: Move the current image to the trash. &Shift;Del: Permanently delete the image from the disk. Note that this operation is irreversible and cannot be undone. &Ctrl;P: Print the current image. &Ctrl;O: Open an image using the standard file selection dialog. F: Pressing this shortcut toggles zoom-to-fit on and off. P: Viewing a video this shortcut toggles playback on and off. &Ctrl;T: Edit tags. F2: Rename an image inline. Del: Move an image to the trash. &Shift;Del: Delete an image. &Ctrl;F7: Copy an image. &Ctrl;F8: Move an image. &Ctrl;F9: Link an image. Advanced Configuration Options Some notes on hidden &gwenview; options can be found on this page. The options described on the above-mentioned page may help you tune &gwenview; for specific needs, but please keep in mind there is no warranty they will continue working from one version to another. Credits and Copyright &gwenview; is currently maintained by Aurélien Gâteau This document was written by Christopher Martin This document was updated for &kde; 4 by Henry de Valence &underFDL; &underGPL;
diff --git a/lib/mpris2/mprismediaplayer2player.cpp b/lib/mpris2/mprismediaplayer2player.cpp index 19abe4a7..530c6e73 100644 --- a/lib/mpris2/mprismediaplayer2player.cpp +++ b/lib/mpris2/mprismediaplayer2player.cpp @@ -1,384 +1,384 @@ /* Gwenview: an image viewer Copyright 2018 Friedrich W. H. Kossebau 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 of the License, 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mprismediaplayer2player.h" #include // lib #include #include #include #include #include #include #include // KF #include #include // Qt #include #include #include namespace Gwenview { static const double MAX_RATE = 1.0; static const double MIN_RATE = 1.0; MprisMediaPlayer2Player::MprisMediaPlayer2Player(const QString &objectDBusPath, SlideShow* slideShow, ContextManager* contextManager, QAction* toggleSlideShowAction, QAction* fullScreenAction, QAction* previousAction, QAction* nextAction, QObject* parent) : DBusAbstractAdaptor(objectDBusPath, parent) , mSlideShow(slideShow) , mContextManager(contextManager) , mToggleSlideShowAction(toggleSlideShowAction) , mFullScreenAction(fullScreenAction) , mPreviousAction(previousAction) , mNextAction(nextAction) , mSlideShowEnabled(mToggleSlideShowAction->isEnabled()) , mPreviousEnabled(mPreviousAction->isEnabled()) , mNextEnabled(mNextAction->isEnabled()) { updatePlaybackStatus(); connect(mSlideShow, &SlideShow::stateChanged, this, &MprisMediaPlayer2Player::onSlideShowStateChanged); connect(mSlideShow, &SlideShow::intervalChanged, this, &MprisMediaPlayer2Player::onMetaInfoUpdated); connect(mContextManager, &ContextManager::currentUrlChanged, this, &MprisMediaPlayer2Player::onCurrentUrlChanged); connect(mSlideShow->randomAction(), &QAction::toggled, this, &MprisMediaPlayer2Player::onRandomActionToggled); connect(mToggleSlideShowAction, &QAction::changed, this, &MprisMediaPlayer2Player::onToggleSlideShowActionChanged); connect(mFullScreenAction, &QAction::toggled, this, &MprisMediaPlayer2Player::onFullScreenActionToggled); connect(mNextAction, &QAction::changed, this, &MprisMediaPlayer2Player::onNextActionChanged); connect(mPreviousAction, &QAction::changed, this, &MprisMediaPlayer2Player::onPreviousActionChanged); } MprisMediaPlayer2Player::~MprisMediaPlayer2Player() { } bool MprisMediaPlayer2Player::updatePlaybackStatus() { const QString newStatus = (!mSlideShowEnabled || !mFullScreenAction->isChecked()) ? QStringLiteral("Stopped") : mSlideShow->isRunning() ? QStringLiteral("Playing") : /* else */ QStringLiteral("Paused"); const bool changed = (newStatus != mPlaybackStatus); if (changed) { mPlaybackStatus = newStatus; } return changed; } QString MprisMediaPlayer2Player::playbackStatus() const { return mPlaybackStatus; } bool MprisMediaPlayer2Player::canGoNext() const { return mNextEnabled; } void MprisMediaPlayer2Player::Next() { mNextAction->trigger(); } bool MprisMediaPlayer2Player::canGoPrevious() const { return mPreviousEnabled; } void MprisMediaPlayer2Player::Previous() { mPreviousAction->trigger(); } bool MprisMediaPlayer2Player::canPause() const { return mSlideShowEnabled; } void MprisMediaPlayer2Player::Pause() { - mSlideShow->stop(); + mSlideShow->pause(); } void MprisMediaPlayer2Player::PlayPause() { mToggleSlideShowAction->trigger(); } void MprisMediaPlayer2Player::Stop() { if (mFullScreenAction->isChecked()) { mFullScreenAction->trigger(); } } bool MprisMediaPlayer2Player::canPlay() const { return mSlideShowEnabled; } void MprisMediaPlayer2Player::Play() { if (mSlideShow->isRunning()) { return; } mToggleSlideShowAction->trigger(); } double MprisMediaPlayer2Player::volume() const { return 0; } void MprisMediaPlayer2Player::setVolume(double volume) { Q_UNUSED(volume); } void MprisMediaPlayer2Player::setShuffle(bool isShuffle) { mSlideShow->randomAction()->setChecked(isShuffle); } QVariantMap MprisMediaPlayer2Player::metadata() const { return mMetaData; } qlonglong MprisMediaPlayer2Player::position() const { // milliseconds -> microseconds return mSlideShow->position() * 1000; } double MprisMediaPlayer2Player::rate() const { return 1.0; } void MprisMediaPlayer2Player::setRate(double newRate) { Q_UNUSED(newRate); } double MprisMediaPlayer2Player::minimumRate() const { return MIN_RATE; } double MprisMediaPlayer2Player::maximumRate() const { return MAX_RATE; } bool MprisMediaPlayer2Player::isShuffle() const { return mSlideShow->randomAction()->isChecked(); } bool MprisMediaPlayer2Player::canSeek() const { return false; } bool MprisMediaPlayer2Player::canControl() const { return true; } void MprisMediaPlayer2Player::Seek(qlonglong offset) { Q_UNUSED(offset); } void MprisMediaPlayer2Player::SetPosition(const QDBusObjectPath& trackId, qlonglong pos) { Q_UNUSED(trackId); Q_UNUSED(pos); } void MprisMediaPlayer2Player::OpenUri(const QString& uri) { Q_UNUSED(uri); } void MprisMediaPlayer2Player::onSlideShowStateChanged() { if (!updatePlaybackStatus()) { return; } signalPropertyChange("Position", position()); signalPropertyChange("PlaybackStatus", mPlaybackStatus); } void MprisMediaPlayer2Player::onCurrentUrlChanged(const QUrl& url) { if (url.isEmpty()) { mCurrentDocument = Document::Ptr(); } else { mCurrentDocument = DocumentFactory::instance()->load(url); connect(mCurrentDocument.data(), &Document::metaInfoUpdated, this, &MprisMediaPlayer2Player::onMetaInfoUpdated); } onMetaInfoUpdated(); signalPropertyChange("Position", position()); } void MprisMediaPlayer2Player::onMetaInfoUpdated() { QVariantMap updatedMetaData; if (mCurrentDocument) { const QUrl url = mCurrentDocument->url(); ImageMetaInfoModel* metaInfoModel = mCurrentDocument->metaInfo(); // We need some unique id mapping to urls. The index in the list is not reliable, // as images can be added/removed during a running slideshow // To allow some bidrectional mapping, convert the url to base64 to encode it for // matching the D-Bus object path spec const QString slideId = QString::fromLatin1(url.toString().toUtf8().toBase64(QByteArray::OmitTrailingEquals)); const QDBusObjectPath trackId(QLatin1String("/org/kde/gwenview/imagelist/") + slideId); updatedMetaData.insert(QStringLiteral("mpris:trackid"), QVariant::fromValue(trackId)); // TODO: for videos also get and report the length if (MimeTypeUtils::urlKind(url) != MimeTypeUtils::KIND_VIDEO) { // convert seconds in microseconds const qlonglong duration = qlonglong(mSlideShow->interval() * 1000000); updatedMetaData.insert(QStringLiteral("mpris:length"), duration); } // TODO: update on metadata changes, given user can edit most of this data const QString name = metaInfoModel->getValueForKey(QStringLiteral("General.Name")); updatedMetaData.insert(QStringLiteral("xesam:title"), name); const QString comment = metaInfoModel->getValueForKey(QStringLiteral("General.Comment")); if (!comment.isEmpty()) { updatedMetaData.insert(QStringLiteral("xesam:comment"), comment); } updatedMetaData.insert(QStringLiteral("xesam:url"), url.toString()); // slight bending of semantics :) const KFileItem folderItem(mContextManager->currentDirUrl()); updatedMetaData.insert(QStringLiteral("xesam:album"), folderItem.text()); // TODO: hook up with thumbnail cache and pass that as arturl // updatedMetaData.insert(QStringLiteral("mpris:artUrl"), url.toString()); #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE const QModelIndex index = mContextManager->dirModel()->indexForUrl(url); if (index.isValid()) { const double rating = index.data(SemanticInfoDirModel::RatingRole).toInt() / 10.0; updatedMetaData.insert(QStringLiteral("xesam:userRating"), rating); } #endif // consider export of other metadata where mapping works } if (updatedMetaData != mMetaData) { mMetaData = updatedMetaData; signalPropertyChange("Metadata", mMetaData); } } void MprisMediaPlayer2Player::onRandomActionToggled(bool checked) { signalPropertyChange("Shuffle", checked); } void MprisMediaPlayer2Player::onFullScreenActionToggled() { if (!updatePlaybackStatus()) { return; } signalPropertyChange("Position", position()); signalPropertyChange("PlaybackStatus", mPlaybackStatus); } void MprisMediaPlayer2Player::onToggleSlideShowActionChanged() { const bool isEnabled = mToggleSlideShowAction->isEnabled(); if (mSlideShowEnabled == isEnabled) { return; } mSlideShowEnabled = isEnabled; const bool playbackStatusChanged = updatePlaybackStatus(); signalPropertyChange("CanPlay", mSlideShowEnabled); signalPropertyChange("CanPause", mSlideShowEnabled); if (playbackStatusChanged) { signalPropertyChange("Position", position()); signalPropertyChange("PlaybackStatus", mPlaybackStatus); } } void MprisMediaPlayer2Player::onNextActionChanged() { const bool isEnabled = mNextAction->isEnabled(); if (mNextEnabled == isEnabled) { return; } mNextEnabled = isEnabled; signalPropertyChange("CanGoNext", mNextEnabled); } void MprisMediaPlayer2Player::onPreviousActionChanged() { const bool isEnabled = mPreviousAction->isEnabled(); if (mPreviousEnabled == isEnabled) { return; } mPreviousEnabled = isEnabled; signalPropertyChange("CanGoPrevious", mPreviousEnabled); } } diff --git a/lib/slideshow.cpp b/lib/slideshow.cpp index f14a27e9..019b7a6a 100644 --- a/lib/slideshow.cpp +++ b/lib/slideshow.cpp @@ -1,327 +1,327 @@ /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau 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 of the License, 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "slideshow.h" // libc #include // STL #include // Qt #include #include #include // KDE #include // Local #include #include namespace Gwenview { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) qDebug() << x #else #define LOG(x) ; #endif enum State { - Stopped, + Paused, Started, WaitForEndOfUrl }; /** * This class generate random numbers which are not the same between two runs * of Gwenview. See bug #132334 */ class RandomNumberGenerator { public: RandomNumberGenerator() : mSeed(time(0)) { } int operator()(int n) { return rand_r(&mSeed) % n; } private: unsigned int mSeed; }; struct SlideShowPrivate { QTimer* mTimer; State mState; QVector mUrls; QVector mShuffledUrls; QVector::ConstIterator mStartIt; QUrl mCurrentUrl; QUrl mLastShuffledUrl; QAction* mLoopAction; QAction* mRandomAction; QUrl findNextUrl() { if (GwenviewConfig::random()) { return findNextRandomUrl(); } else { return findNextOrderedUrl(); } } QUrl findNextOrderedUrl() { QVector::ConstIterator it = qFind(mUrls.constBegin(), mUrls.constEnd(), mCurrentUrl); GV_RETURN_VALUE_IF_FAIL2(it != mUrls.constEnd(), QUrl(), "Current url not found in list."); ++it; if (GwenviewConfig::loop()) { // Looping, if we reach the end, start again if (it == mUrls.constEnd()) { it = mUrls.constBegin(); } } else { // Not looping, have we reached the end? // FIXME: stopAtEnd if (/*(it==mUrls.end() && GwenviewConfig::stopAtEnd()) ||*/ it == mStartIt) { it = mUrls.constEnd(); } } if (it != mUrls.constEnd()) { return *it; } else { return QUrl(); } } void initShuffledUrls() { mShuffledUrls = mUrls; RandomNumberGenerator generator; std::random_shuffle(mShuffledUrls.begin(), mShuffledUrls.end(), generator); // Ensure the first url is different from the previous last one, so that // last url does not stay visible twice longer than usual if (mLastShuffledUrl == mShuffledUrls.first() && mShuffledUrls.count() > 1) { qSwap(mShuffledUrls[0], mShuffledUrls[1]); } mLastShuffledUrl = mShuffledUrls.last(); } QUrl findNextRandomUrl() { if (mShuffledUrls.empty()) { if (GwenviewConfig::loop()) { initShuffledUrls(); } else { return QUrl(); } } QUrl url = mShuffledUrls.last(); mShuffledUrls.pop_back(); return url; } void updateTimerInterval() { mTimer->setInterval(int(GwenviewConfig::interval() * 1000)); } void doStart() { if (MimeTypeUtils::urlKind(mCurrentUrl) == MimeTypeUtils::KIND_VIDEO) { LOG("mState = WaitForEndOfUrl"); // Just in case mTimer->stop(); mState = WaitForEndOfUrl; } else { LOG("mState = Started"); mTimer->start(); mState = Started; } } }; SlideShow::SlideShow(QObject* parent) : QObject(parent) , d(new SlideShowPrivate) { - d->mState = Stopped; + d->mState = Paused; d->mTimer = new QTimer(this); connect(d->mTimer, &QTimer::timeout, this, &SlideShow::goToNextUrl); d->mLoopAction = new QAction(this); d->mLoopAction->setText(i18nc("@item:inmenu toggle loop in slideshow", "Loop")); d->mLoopAction->setCheckable(true); connect(d->mLoopAction, &QAction::triggered, this, &SlideShow::updateConfig); d->mRandomAction = new QAction(this); d->mRandomAction->setText(i18nc("@item:inmenu toggle random order in slideshow", "Random")); d->mRandomAction->setCheckable(true); connect(d->mRandomAction, &QAction::toggled, this, &SlideShow::slotRandomActionToggled); connect(d->mRandomAction, &QAction::triggered, this, &SlideShow::updateConfig); d->mLoopAction->setChecked(GwenviewConfig::loop()); d->mRandomAction->setChecked(GwenviewConfig::random()); } SlideShow::~SlideShow() { GwenviewConfig::self()->save(); delete d; } QAction* SlideShow::loopAction() const { return d->mLoopAction; } QAction* SlideShow::randomAction() const { return d->mRandomAction; } void SlideShow::start(const QList& urls) { d->mUrls.resize(urls.size()); qCopy(urls.begin(), urls.end(), d->mUrls.begin()); d->mStartIt = qFind(d->mUrls.constBegin(), d->mUrls.constEnd(), d->mCurrentUrl); if (d->mStartIt == d->mUrls.constEnd()) { qWarning() << "Current url not found in list, aborting.\n"; return; } if (GwenviewConfig::random()) { d->initShuffledUrls(); } d->updateTimerInterval(); d->mTimer->setSingleShot(false); d->doStart(); stateChanged(true); } void SlideShow::setInterval(int intervalInSeconds) { GwenviewConfig::setInterval(double(intervalInSeconds)); d->updateTimerInterval(); emit intervalChanged(intervalInSeconds); } int SlideShow::interval() const { return GwenviewConfig::interval(); } int SlideShow::position() const { // TODO: also support videos // QTimer::remainingTime() returns -1 if inactive // and there are moments where mState == Started but timer already done but not yet next url reached // so handle that if (d->mState == Started) { if (d->mTimer->isActive()) { return interval() * 1000 - d->mTimer->remainingTime(); } // already timeout reached, but not yet progressed to next url return interval(); } return 0; } -void SlideShow::stop() +void SlideShow::pause() { LOG("Stopping timer"); d->mTimer->stop(); - d->mState = Stopped; + d->mState = Paused; stateChanged(false); } void SlideShow::resumeAndGoToNextUrl() { LOG(""); if (d->mState == WaitForEndOfUrl) { goToNextUrl(); } } void SlideShow::goToNextUrl() { LOG(""); QUrl url = d->findNextUrl(); LOG("url:" << url); if (!url.isValid()) { - stop(); + pause(); return; } goToUrl(url); } void SlideShow::setCurrentUrl(const QUrl &url) { LOG(url); if (d->mCurrentUrl == url) { return; } d->mCurrentUrl = url; // Restart timer to avoid showing new url for the remaining time of the old // url - if (d->mState != Stopped) { + if (d->mState != Paused) { d->doStart(); } } bool SlideShow::isRunning() const { - return d->mState != Stopped; + return d->mState != Paused; } void SlideShow::updateConfig() { GwenviewConfig::setLoop(d->mLoopAction->isChecked()); GwenviewConfig::setRandom(d->mRandomAction->isChecked()); } void SlideShow::slotRandomActionToggled(bool on) { - if (on && d->mState != Stopped) { + if (on && d->mState != Paused) { d->initShuffledUrls(); } } } // namespace diff --git a/lib/slideshow.h b/lib/slideshow.h index f7058064..fa7e1bdb 100644 --- a/lib/slideshow.h +++ b/lib/slideshow.h @@ -1,95 +1,95 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau 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 of the License, 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. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef SLIDESHOW_H #define SLIDESHOW_H #include // Qt #include // KDE #include class QAction; namespace Gwenview { struct SlideShowPrivate; class GWENVIEWLIB_EXPORT SlideShow : public QObject { Q_OBJECT public: SlideShow(QObject* parent); virtual ~SlideShow(); void start(const QList& urls); - void stop(); + void pause(); QAction* loopAction() const; QAction* randomAction() const; /** @return true if the slideshow is running */ bool isRunning() const; /** * @return interval in seconds */ int interval() const; /** * @return position in time slot for current image in milliseconds */ int position() const; public Q_SLOTS: void setInterval(int); void setCurrentUrl(const QUrl &url); /** * Resume slideshow and go to next url. */ void resumeAndGoToNextUrl(); Q_SIGNALS: void goToUrl(const QUrl&); /** - * Slideshow has been started or stopped + * Slideshow has been started or paused */ void stateChanged(bool running); /** * Emitted when interval has been changed * @param interval interval in seconds */ void intervalChanged(int interval); private Q_SLOTS: void goToNextUrl(); void updateConfig(); void slotRandomActionToggled(bool on); private: SlideShowPrivate* const d; }; } // namespace #endif // SLIDESHOW_H