diff --git a/app/fileoperations.cpp b/app/fileoperations.cpp index 769888f7..2587f3d5 100644 --- a/app/fileoperations.cpp +++ b/app/fileoperations.cpp @@ -1,244 +1,247 @@ // 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, Cambridge, MA 02110-1301, USA. */ // Self #include "fileoperations.h" // Qt #include #include #include #include // KDE #include #include #include #include #include #include #include // Local #include #include #include namespace Gwenview { namespace FileOperations { static void copyMoveOrLink(Operation operation, const QList& urlList, QWidget* parent, ContextManager* contextManager) { Q_ASSERT(!urlList.isEmpty()); const int numberOfImages = urlList.count(); QFileDialog dialog(parent->nativeParentWidget(), QString()); dialog.setAcceptMode(QFileDialog::AcceptSave); // Figure out what the window title and buttons should say, // depending on the operation and how many images are selected switch (operation) { case COPY: if (numberOfImages == 1) { dialog.setWindowTitle(i18nc("@title:window %1 file name", "Copy %1", urlList.constFirst().fileName())); } else { dialog.setWindowTitle(i18ncp("@title:window %1 number of images", "Copy %1 image", "Copy %1 images", numberOfImages)); } dialog.setLabelText(QFileDialog::DialogLabel::Accept, i18nc("@action:button", "Copy")); break; case MOVE: if (numberOfImages == 1) { dialog.setWindowTitle(i18nc("@title:window %1 file name", "Move %1", urlList.constFirst().fileName())); } else { dialog.setWindowTitle(i18ncp("@title:window %1 number of images", "Move %1 image", "Move %1 images", numberOfImages)); } dialog.setLabelText(QFileDialog::DialogLabel::Accept, i18nc("@action:button", "Move")); break; case LINK: if (numberOfImages == 1) { dialog.setWindowTitle(i18nc("@title:window %1 file name", "Link %1", urlList.constFirst().fileName())); } else { dialog.setWindowTitle(i18ncp("@title:window %1 number of images", "Link %1 image", "Link %1 images", numberOfImages)); } dialog.setLabelText(QFileDialog::DialogLabel::Accept, i18nc("@action:button", "Link")); break; default: Q_ASSERT(0); } if (numberOfImages == 1) { dialog.setFileMode(QFileDialog::AnyFile); dialog.selectUrl(urlList.constFirst()); } else { dialog.setFileMode(QFileDialog::Directory); dialog.setOption(QFileDialog::ShowDirsOnly, true); } - dialog.setDirectoryUrl(contextManager->targetUrl().adjusted(QUrl::RemoveFilename)); + dialog.setDirectoryUrl(contextManager->targetUrl()); if (!dialog.exec()) { return; } QUrl destUrl = dialog.selectedUrls().first(); + if (numberOfImages == 1) { + destUrl = destUrl.adjusted(QUrl::RemoveFilename|QUrl::StripTrailingSlash); + } contextManager->setTargetUrl(destUrl); KIO::CopyJob* job = 0; switch (operation) { case COPY: job = KIO::copy(urlList, destUrl); break; case MOVE: job = KIO::move(urlList, destUrl); break; case LINK: job = KIO::link(urlList, destUrl); break; default: Q_ASSERT(0); } KJobWidgets::setWindow(job, parent); job->uiDelegate()->setAutoErrorHandlingEnabled(true); } static void delOrTrash(KIO::JobUiDelegate::DeletionType deletionType, const QList& urlList, QWidget* parent) { Q_ASSERT(urlList.count() > 0); KIO::JobUiDelegate uiDelegate; uiDelegate.setWindow(parent); if (!uiDelegate.askDeleteConfirmation(urlList, deletionType, KIO::JobUiDelegate::DefaultConfirmation)) { return; } KIO::Job* job = 0; switch (deletionType) { case KIO::JobUiDelegate::Trash: job = KIO::trash(urlList); break; case KIO::JobUiDelegate::Delete: job = KIO::del(urlList); break; default: // e.g. EmptyTrash return; } Q_ASSERT(job); KJobWidgets::setWindow(job,parent); Q_FOREACH(const QUrl &url, urlList) { DocumentFactory::instance()->forget(url); } } void copyTo(const QList& urlList, QWidget* parent, ContextManager* contextManager) { copyMoveOrLink(COPY, urlList, parent, contextManager); } void moveTo(const QList& urlList, QWidget* parent, ContextManager* contextManager) { copyMoveOrLink(MOVE, urlList, parent, contextManager); } void linkTo(const QList& urlList, QWidget* parent, ContextManager* contextManager) { copyMoveOrLink(LINK, urlList, parent, contextManager); } void trash(const QList& urlList, QWidget* parent) { delOrTrash(KIO::JobUiDelegate::Trash, urlList, parent); } void del(const QList& urlList, QWidget* parent) { delOrTrash(KIO::JobUiDelegate::Delete, urlList, parent); } void showMenuForDroppedUrls(QWidget* parent, const QList& urlList, const QUrl &destUrl) { if (urlList.isEmpty()) { qWarning() << "urlList is empty!"; return; } if (!destUrl.isValid()) { qWarning() << "destUrl is not valid!"; return; } QMenu menu(parent); QAction* moveAction = menu.addAction( QIcon::fromTheme("go-jump"), i18n("Move Here")); QAction* copyAction = menu.addAction( QIcon::fromTheme("edit-copy"), i18n("Copy Here")); QAction* linkAction = menu.addAction( QIcon::fromTheme("edit-link"), i18n("Link Here")); menu.addSeparator(); menu.addAction( QIcon::fromTheme("process-stop"), i18n("Cancel")); QAction* action = menu.exec(QCursor::pos()); KIO::Job* job = 0; if (action == moveAction) { job = KIO::move(urlList, destUrl); } else if (action == copyAction) { job = KIO::copy(urlList, destUrl); } else if (action == linkAction) { job = KIO::link(urlList, destUrl); } else { return; } Q_ASSERT(job); KJobWidgets::setWindow(job, parent); } void rename(const QUrl &oldUrl, QWidget* parent) { QString name = QInputDialog::getText(parent, i18nc("@title:window", "Rename") /* caption */, xi18n("Rename %1 to:", oldUrl.fileName()) /* label */, QLineEdit::Normal, oldUrl.fileName() /* value */ ); if (name.isEmpty() || name == oldUrl.fileName()) { return; } QUrl newUrl = oldUrl; newUrl = newUrl.adjusted(QUrl::RemoveFilename); newUrl.setPath(newUrl.path() + name); KIO::SimpleJob* job = KIO::rename(oldUrl, newUrl, KIO::HideProgressInfo); KJobWidgets::setWindow(job, parent); if (!job->exec()) { job->uiDelegate()->showErrorMessage(); return; } ThumbnailProvider::moveThumbnail(oldUrl, newUrl); } } // namespace } // namespace diff --git a/app/mainwindow.cpp b/app/mainwindow.cpp index 6b32e6fa..638ed3ef 100644 --- a/app/mainwindow.cpp +++ b/app/mainwindow.cpp @@ -1,1606 +1,1608 @@ /* 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 // 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 #include #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* BROWSE_MODE_SIDE_BAR_GROUP = "SideBar-BrowseMode"; static const char* VIEW_MODE_SIDE_BAR_GROUP = "SideBar-ViewMode"; static const char* FULLSCREEN_MODE_SIDE_BAR_GROUP = "SideBar-FullScreenMode"; static const char* SIDE_BAR_IS_VISIBLE_KEY = "IsVisible"; static const char* SESSION_CURRENT_PAGE_KEY = "Page"; static const char* SESSION_URL_KEY = "Url"; enum MainPageId { StartMainPageId, BrowseMainPageId, ViewMainPageId }; struct MainWindowState { bool mToolBarVisible; Qt::WindowStates mWindowState; }; /* 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; 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; KToggleFullScreenAction* 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); 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); EventWatcher::install(mSideBar, QList() << QEvent::Show << QEvent::Hide, q, SLOT(updateToggleSideBarAction())); // 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); 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(setWindowTitle(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))); } 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); 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")); 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 = static_cast(view->addAction(KStandardAction::FullScreen, q, SLOT(toggleFullScreen(bool)))); 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("media-skip-backward")); 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("media-skip-forward")); 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->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->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, SIGNAL(toggled(bool)), q, SLOT(toggleSideBar(bool))); 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()))); 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, SIGNAL(urlChanged(QUrl)), q, SLOT(openDirUrl(QUrl))); 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); } const char* sideBarConfigGroupName() const { const char* name = 0; switch (mCurrentMainPageId) { case StartMainPageId: GV_WARN_AND_RETURN_VALUE(BROWSE_MODE_SIDE_BAR_GROUP, "mCurrentMainPageId == 'StartMainPageId'"); break; case BrowseMainPageId: name = BROWSE_MODE_SIDE_BAR_GROUP; break; case ViewMainPageId: name = mViewMainPage->isFullScreenMode() ? FULLSCREEN_MODE_SIDE_BAR_GROUP : VIEW_MODE_SIDE_BAR_GROUP; break; } return name; } void loadSideBarConfig() { static QMap defaultVisibility; if (defaultVisibility.isEmpty()) { defaultVisibility[BROWSE_MODE_SIDE_BAR_GROUP] = true; defaultVisibility[VIEW_MODE_SIDE_BAR_GROUP] = true; defaultVisibility[FULLSCREEN_MODE_SIDE_BAR_GROUP] = false; } const char* name = sideBarConfigGroupName(); KConfigGroup group(KSharedConfig::openConfig(), name); mSideBar->setVisible(group.readEntry(SIDE_BAR_IS_VISIBLE_KEY, defaultVisibility[name])); mSideBar->setCurrentPage(GwenviewConfig::sideBarPage()); q->updateToggleSideBarAction(); } void saveSideBarConfig() const { KConfigGroup group(KSharedConfig::openConfig(), sideBarConfigGroupName()); group.writeEntry(SIDE_BAR_IS_VISIBLE_KEY, mSideBar->isVisible()); GwenviewConfig::setSideBarPage(mSideBar->currentPage()); } 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(); 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::slotModifiedDocumentListChanged() { d->updateActions(); // Update caption QList list = DocumentFactory::instance()->modifiedDocumentList(); bool modified = list.count() > 0; setCaption(d->mCaption, modified); } 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 (d->mCurrentMainPageId != StartMainPageId) { d->saveSideBarConfig(); } 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->loadSideBarConfig(); d->autoAssignThumbnailProvider(); 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() { if (d->mCurrentMainPageId != StartMainPageId) { d->saveSideBarConfig(); 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::toggleSideBar(bool on) { d->mSideBar->setVisible(on); } void MainWindow::toggleStatusBar() { d->mViewMainPage->setStatusBarVisible(d->mShowStatusBarAction->isChecked()); d->mBrowseMainPage->setStatusBarVisible(d->mShowStatusBarAction->isChecked()); } void MainWindow::updateToggleSideBarAction() { SignalBlocker blocker(d->mToggleSideBarAction); bool visible = d->mSideBar->isVisible(); d->mToggleSideBarAction->setChecked(visible); QString text; if (QApplication::isRightToLeft()) { text = QString::fromUtf8(visible ? "▮→" : "▮←"); } else { text = QString::fromUtf8(visible ? "▮←" : "▮→"); } QString toolTip = visible ? i18nc("@info:tooltip", "Hide sidebar") : i18nc("@info:tooltip", "Show sidebar"); QList lst; lst << d->mBrowseMainPage->toggleSideBarButton() << d->mViewMainPage->toggleSideBarButton(); Q_FOREACH(QToolButton * button, lst) { button->setText(text); button->setToolTip(toolTip); } } void MainWindow::slotPartCompleted() { d->updateActions(); const QUrl url = d->mContextManager->currentUrl(); if (!url.isEmpty()) { 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(); } d->mThumbnailView->scrollToSelectedIndex(); d->mViewMainPage->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); d->saveSideBarConfig(); 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(); saveMainWindowSettings(saveConfigGroup); resetAutoSaveSettings(); // Save state d->mStateBeforeFullScreen.mToolBarVisible = toolBar()->isVisible(); d->mStateBeforeFullScreen.mWindowState = windowState(); // Go full screen setWindowState(windowState() | Qt::WindowFullScreen); menuBar()->hide(); toolBar()->hide(); qApp->setProperty("KDE_COLOR_SCHEME_PATH", d->mGvCore->fullScreenPaletteName()); QApplication::setPalette(d->mGvCore->palette(GvCore::FullScreenPalette)); d->mFullScreenContent->setFullScreenMode(true); d->mBrowseMainPage->setFullScreenMode(true); d->mViewMainPage->setFullScreenMode(true); d->mSaveBar->setFullScreenMode(true); d->setScreenSaverEnabled(false); // HACK: Only load sidebar config now, because it looks at // ViewMainPage fullScreenMode property to determine the sidebar // config group. d->loadSideBarConfig(); } else { setAutoSaveSettings(); // Back to normal qApp->setProperty("KDE_COLOR_SCHEME_PATH", QVariant()); QApplication::setPalette(d->mGvCore->palette(GvCore::NormalPalette)); d->mFullScreenContent->setFullScreenMode(false); d->mBrowseMainPage->setFullScreenMode(false); d->mViewMainPage->setFullScreenMode(false); d->mSlideShow->stop(); d->mSaveBar->setFullScreenMode(false); setWindowState(d->mStateBeforeFullScreen.mWindowState); menuBar()->setVisible(d->mShowMenuBarAction->isChecked()); toggleStatusBar(); toolBar()->setVisible(d->mStateBeforeFullScreen.mToolBarVisible); d->setScreenSaverEnabled(true); // Keep this after mViewMainPage->setFullScreenMode(false). // See call to loadSideBarConfig() above. d->loadSideBarConfig(); // See resizeEvent d->mFullScreenLeftAt = QDateTime::currentDateTime(); } 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(); QFileDialog 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->mFullScreenAction->trigger(); } void MainWindow::toggleSlideShow() { if (d->mSlideShow->isRunning()) { d->mSlideShow->stop(); } 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->setIcon(QIcon::fromTheme("media-playback-pause")); } else { d->mToggleSlideShowAction->setText(i18n("Start Slideshow")); d->mToggleSlideShowAction->setIcon(QIcon::fromTheme("media-playback-start")); } } bool MainWindow::queryClose() { saveConfig(); d->saveSideBarConfig(); 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")); 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() { ConfigDialog dialog(this); connect(&dialog, 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()); d->mFileOpenRecentAction->loadEntries(KConfigGroup(KSharedConfig::openConfig(), "Recent Files")); foreach(const QUrl &url, d->mFileOpenRecentAction->urls()) { d->mGvCore->addUrlToRecentFiles(url); } d->mStartMainPage->loadConfig(); d->mViewMainPage->loadConfig(); d->mBrowseMainPage->loadConfig(); + d->mContextManager->loadConfig(); d->mShowStatusBarAction->setChecked(GwenviewConfig::statusBarIsVisible()); toggleStatusBar(); } void MainWindow::saveConfig() { d->mFileOpenRecentAction->saveEntries(KConfigGroup(KSharedConfig::openConfig(), "Recent Files")); GwenviewConfig::setStatusBarIsVisible(d->mShowStatusBarAction->isChecked()); d->mViewMainPage->saveConfig(); d->mBrowseMainPage->saveConfig(); + d->mContextManager->saveConfig(); } 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/app/org.kde.gwenview.desktop b/app/org.kde.gwenview.desktop index 7dd5c3ca..1a90b785 100644 --- a/app/org.kde.gwenview.desktop +++ b/app/org.kde.gwenview.desktop @@ -1,193 +1,193 @@ [Desktop Entry] Name=Gwenview Name[ar]=جوِينفيو Name[ast]=Gwenview Name[be]=Gwenview Name[bg]=Gwenview Name[bs]=Gwenview Name[ca]=Gwenview Name[ca@valencia]=Gwenview Name[cs]=Gwenview Name[da]=Gwenview Name[de]=Gwenview Name[el]=Gwenview Name[en_GB]=Gwenview Name[eo]=Gwenview Name[es]=Gwenview Name[et]=Gwenview Name[eu]=Gwenview Name[fa]=Gwenview Name[fi]=Gwenview Name[fr]=Gwenview Name[ga]=Gwenview Name[gl]=Gwenview Name[hi]=ग्वेन-व्यू Name[hne]=ग्वेन-व्यू Name[hr]=Gwenview Name[hu]=Gwenview Name[ia]=Gwenview Name[id]=Gwenview Name[is]=Gwenview Name[it]=Gwenview Name[ja]=Gwenview Name[kk]=Gwenview Name[km]=Gwenview Name[ko]=Gwenview Name[ku]=Gwenview Name[lt]=Gwenview Name[lv]=Gwenview Name[mr]=ग्वेनव्यु Name[nb]=Gwenview Name[nds]=Gwenview Name[ne]=जीवेनभ्यू Name[nl]=Gwenview Name[nn]=Gwenview Name[oc]=Gwenview Name[pa]=ਜੀਵੀਨ-ਵਿਊ Name[pl]=Gwenview Name[pt]=Gwenview Name[pt_BR]=Gwenview Name[ro]=Gwenview Name[ru]=Gwenview Name[si]=Gwenview Name[sk]=Gwenview Name[sl]=Gwenview Name[sr]=Гвенвју Name[sr@ijekavian]=Гвенвју Name[sr@ijekavianlatin]=GwenView Name[sr@latin]=GwenView Name[sv]=Gwenview Name[th]=เกวนวิว Name[tr]=Gwenview Name[ug]=Gwenview Name[uk]=Gwenview Name[vi]=Gwenview Name[x-test]=xxGwenviewxx Name[zh_CN]=Gwenview Name[zh_TW]=影像檢視_Gwenview GenericName=KDE Image Viewer GenericName[ar]=عارض صور كدي GenericName[bg]=Преглед на изображения в KDE GenericName[bs]=KDE Prikazivač slika GenericName[ca]=Visor d'imatges del KDE GenericName[ca@valencia]=Visor d'imatges del KDE GenericName[cs]=Prohlížeč obrázků KDE GenericName[da]=KDE billedfremviser GenericName[de]=KDE-Bildbetrachter GenericName[el]=Προβολέας εικόνων του KDE GenericName[en_GB]=KDE Image Viewer GenericName[es]=Visor de imágenes de KDE GenericName[et]=KDE pildinäitaja GenericName[fa]=مشاهده‌گر تصویر کی‌دی‌ای GenericName[fi]=KDE:n kuvankatselin GenericName[fr]=Afficheur d'images de KDE GenericName[ga]=Amharcán Íomhánna KDE GenericName[gl]=Visor de imaxes de KDE GenericName[he]=מציג תמונות של KDE GenericName[hu]=KDE képnézegető GenericName[ia]=Visor de imagine de KDE GenericName[id]=Penampil Citra KDE GenericName[is]=Myndskoðari fyrir KDE GenericName[it]=Visore di immagini per KDE GenericName[ja]=KDE 画像ビューア GenericName[kk]=KDE кескін қарау құралы GenericName[ko]=KDE 그림 뷰어 GenericName[lt]=KDE Paveikslėlių žiūryklė GenericName[mr]=केडीई प्रतिमा प्रदर्शक GenericName[nb]=KDE bildeviser GenericName[nds]=KDE-Bildkieker GenericName[nl]=KDE afbeeldingenviewer GenericName[nn]=Biletvisar for KDE GenericName[pa]=ਕੇਡੀਈ ਚਿੱਤਰ ਦਰਸ਼ਕ GenericName[pl]=Przeglądarka obrazów GenericName[pt]=Visualizador de Imagens do KDE GenericName[pt_BR]=Visualizador de imagens do KDE GenericName[ro]=Vizualizor de imagini pentru KDE GenericName[ru]=Программа просмотра изображений GenericName[sk]=Prehliadač obrázkov KDE GenericName[sl]=Pregledovalnik slik za KDE GenericName[sr]=КДЕ приказивач слика GenericName[sr@ijekavian]=КДЕ приказивач слика GenericName[sr@ijekavianlatin]=KDE prikazivač slika GenericName[sr@latin]=KDE prikazivač slika GenericName[sv]=Bildvisare för KDE GenericName[tr]=KDE Resim Gösterici GenericName[uk]=Переглядач зображень KDE GenericName[x-test]=xxKDE Image Viewerxx GenericName[zh_CN]=KDE 图像查看器 GenericName[zh_TW]=KDE 影像檢視程式 Comment=A simple image viewer Comment[ar]=عارض صور بسيط Comment[bg]=Програма за преглед на изображения Comment[bs]=Jednostavan prikazivač slika Comment[ca]=Un visualitzador d'imatges senzill Comment[ca@valencia]=Un visualitzador d'imatges senzill Comment[cs]=Jednoduchý prohlížeč obrázků Comment[da]=Simpel billedfremviser Comment[de]=Ein einfacher Bildbetrachter Comment[el]=Ένας απλός προβολέας εικόνων Comment[en_GB]=A simple image viewer Comment[eo]=Simpla bildorigardilo Comment[es]=Un visor de imágenes sencillo Comment[et]=Lihtne pildinäitaja Comment[eu]=Irudi ikustaile bakuna Comment[fa]=مشاهده‌گر تصویر ساده Comment[fi]=Yksinkertainen kuvankatselin Comment[fr]=Un afficheur simple d'images Comment[ga]=Amharcán simplí íomhánna Comment[gl]=Un visor de imaxes sinxelo Comment[he]=מציג תמונות פשוט Comment[hi]=एक सरल चित्र प्रदर्शक Comment[hne]=एक सरल फोटू प्रदर्सक Comment[hr]=Jednostavni preglednik slika Comment[hu]=Egyszerű képnézegető Comment[ia]=Un simplice visor de imagine Comment[id]=Sebuah penampil citra sederhana Comment[is]=Einfaldur myndskoðari Comment[it]=Un semplice visore di immagini Comment[ja]=シンプルな画像ビューア Comment[kk]=Қарапайым кескінді қарау құралы Comment[km]=កម្មវិធី​មើល​រូបភាព​ធម្មតា Comment[ko]=간단한 그림 뷰어 Comment[ku]=Nîşanderê wêneyan yê hêsanî Comment[lt]=Paprasta paveikslėlių žiūryklė Comment[lv]=Vienkāršs attēlu skatītājs Comment[mr]=एक सोपा प्रतिमा प्रदर्शक Comment[nb]=En enkel bildeviser Comment[nds]=En eenfach Bildkieker Comment[nl]=Een eenvoudige afbeeldingenviewer Comment[nn]=Ein enkel biletvisar Comment[pa]=ਇੱਕ ਸਧਾਰਨ ਚਿੱਤਰ ਦਰਸ਼ਕ Comment[pl]=Prosta przeglądarka obrazów Comment[pt]=Um visualizador de imagens simples Comment[pt_BR]=Um visualizador de imagens simples Comment[ro]=Un vizualizator de imagini simplu Comment[ru]=Просмотр изображений Comment[si]=සරල පිංතූර දසුන Comment[sk]=Jednoduchý prehliadač obrázkov Comment[sl]=Preprost pregledovalnik slik Comment[sr]=Једноставан приказивач слика Comment[sr@ijekavian]=Једноставан приказивач слика Comment[sr@ijekavianlatin]=Jednostavan prikazivač slika Comment[sr@latin]=Jednostavan prikazivač slika Comment[sv]=En enkel bildvisare Comment[th]=เครื่องมือแสดงภาพแบบพื้นฐาน Comment[tr]=Basit bir resim gösterici Comment[ug]=ئاددىي سۈرەت كۆرگۈ Comment[uk]=Простий переглядач зображень Comment[vi]=Bộ xem ảnh đơn giản Comment[x-test]=xxA simple image viewerxx Comment[zh_CN]=简单图像查看器 Comment[zh_TW]=一個簡單的影像檢視程式 Exec=gwenview %U Terminal=false Icon=gwenview Type=Application Categories=Qt;KDE;Graphics;Viewer;Photography; -MimeType=inode/directory;image/gif;image/jpeg;image/png;image/bmp;image/x-eps;image/x-icns;image/x-ico;image/x-portable-bitmap;image/x-portable-graymap;image/x-portable-pixmap;image/x-xbitmap;image/x-xpixmap;image/tiff;image/x-psd;image/x-webp;image/webp; +MimeType=inode/directory;image/gif;image/jpeg;image/png;image/bmp;image/x-eps;image/x-icns;image/x-ico;image/x-portable-bitmap;image/x-portable-graymap;image/x-portable-pixmap;image/x-xbitmap;image/x-xpixmap;image/tiff;image/x-psd;image/x-webp;image/webp;image/x-tga; X-DocPath=gwenview/index.html # InitialPreference should be greater than Okular so that Gwenview is the # primary application associated with images, but less than Konqueror or Dolphin # so that Gwenview is not the primary applications for folders. InitialPreference=8 X-DBUS-ServiceName=org.kde.gwenview diff --git a/lib/contextmanager.cpp b/lib/contextmanager.cpp index 67665eba..ba44a414 100644 --- a/lib/contextmanager.cpp +++ b/lib/contextmanager.cpp @@ -1,353 +1,364 @@ /* 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 "contextmanager.h" #include "contextmanager.h" // Qt #include #include #include #include // KDE #include #include #include // Local #include #include +#include #include namespace Gwenview { struct ContextManagerPrivate { SortedDirModel* mDirModel; QItemSelectionModel* mSelectionModel; QUrl mCurrentDirUrl; QUrl mCurrentUrl; QUrl mUrlToSelect; QUrl mTargetUrl; bool mSelectedFileItemListNeedsUpdate; QSet mQueuedSignals; KFileItemList mSelectedFileItemList; bool mDirListerFinished = false; QTimer* mQueuedSignalsTimer; void queueSignal(const QByteArray& signal) { mQueuedSignals << signal; mQueuedSignalsTimer->start(); } void updateSelectedFileItemList() { if (!mSelectedFileItemListNeedsUpdate) { return; } mSelectedFileItemList.clear(); QItemSelection selection = mSelectionModel->selection(); Q_FOREACH(const QModelIndex & index, selection.indexes()) { mSelectedFileItemList << mDirModel->itemForIndex(index); } // At least add current url if it's valid (it may not be in // the list if we are viewing a non-browsable url, for example // using http protocol) if (mSelectedFileItemList.isEmpty() && mCurrentUrl.isValid()) { KFileItem item(mCurrentUrl); mSelectedFileItemList << item; } mSelectedFileItemListNeedsUpdate = false; } }; ContextManager::ContextManager(SortedDirModel* dirModel, QObject* parent) : QObject(parent) , d(new ContextManagerPrivate) { d->mQueuedSignalsTimer = new QTimer(this); d->mQueuedSignalsTimer->setInterval(100); d->mQueuedSignalsTimer->setSingleShot(true); connect(d->mQueuedSignalsTimer, &QTimer::timeout, this, &ContextManager::emitQueuedSignals); d->mDirModel = dirModel; connect(d->mDirModel, &SortedDirModel::dataChanged, this, &ContextManager::slotDirModelDataChanged); /* HACK! In extended-selection mode, when the current index is removed, * QItemSelectionModel selects the previous index if there is one, if not it * selects the next index. This is not what we want: when the user removes * an image, he expects to go to the next one, not the previous one. * * To overcome this, we must connect to the mDirModel.rowsAboutToBeRemoved() * signal *before* QItemSelectionModel connects to it, so that our slot is * called before QItemSelectionModel slot. This allows us to pick a new * current index ourself, leaving QItemSelectionModel slot with nothing to * do. * * This is the reason ContextManager creates a QItemSelectionModel itself: * doing so ensures QItemSelectionModel cannot be connected to the * mDirModel.rowsAboutToBeRemoved() signal before us. */ connect(d->mDirModel, &SortedDirModel::rowsAboutToBeRemoved, this, &ContextManager::slotRowsAboutToBeRemoved); connect(d->mDirModel, &SortedDirModel::rowsInserted, this, &ContextManager::slotRowsInserted); connect(d->mDirModel->dirLister(), SIGNAL(redirection(QUrl)), SLOT(slotDirListerRedirection(QUrl))); connect(d->mDirModel->dirLister(), static_cast(&KDirLister::completed), this, &ContextManager::slotDirListerCompleted); d->mSelectionModel = new QItemSelectionModel(d->mDirModel); connect(d->mSelectionModel, &QItemSelectionModel::selectionChanged, this, &ContextManager::slotSelectionChanged); connect(d->mSelectionModel, &QItemSelectionModel::currentChanged, this, &ContextManager::slotCurrentChanged); d->mSelectedFileItemListNeedsUpdate = false; } ContextManager::~ContextManager() { delete d; } +void ContextManager::loadConfig() +{ + setTargetUrl(QUrl(GwenviewConfig::lastTargetDir())); +} + +void ContextManager::saveConfig() const +{ + GwenviewConfig::setLastTargetDir(targetUrl().toString()); +} + QItemSelectionModel* ContextManager::selectionModel() const { return d->mSelectionModel; } void ContextManager::setCurrentUrl(const QUrl ¤tUrl) { if (d->mCurrentUrl == currentUrl) { return; } d->mCurrentUrl = currentUrl; if (!d->mCurrentUrl.isEmpty()) { Document::Ptr doc = DocumentFactory::instance()->load(currentUrl); QUndoGroup* undoGroup = DocumentFactory::instance()->undoGroup(); undoGroup->addStack(doc->undoStack()); undoGroup->setActiveStack(doc->undoStack()); } d->mSelectedFileItemListNeedsUpdate = true; currentUrlChanged(currentUrl); } KFileItemList ContextManager::selectedFileItemList() const { d->updateSelectedFileItemList(); return d->mSelectedFileItemList; } void ContextManager::setCurrentDirUrl(const QUrl &url) { if (url == d->mCurrentDirUrl) { return; } if (url.isValid() && KProtocolManager::supportsListing(url)) { d->mCurrentDirUrl = url; d->mDirModel->dirLister()->openUrl(url); d->mDirListerFinished = false; } else { d->mCurrentDirUrl.clear(); d->mDirModel->dirLister()->clear(); } currentDirUrlChanged(url); } QUrl ContextManager::currentDirUrl() const { return d->mCurrentDirUrl; } QUrl ContextManager::currentUrl() const { return d->mCurrentUrl; } SortedDirModel* ContextManager::dirModel() const { return d->mDirModel; } void ContextManager::slotDirModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight) { // Data change can happen in the following cases: // - items have been renamed // - item bytes have been modified // - item meta info has been retrieved or modified // // If a selected item is affected, schedule emission of a // selectionDataChanged() signal. Don't emit it directly to avoid spamming // the context items in case of a mass change. QModelIndexList selectionList = d->mSelectionModel->selectedIndexes(); if (selectionList.isEmpty()) { return; } QModelIndexList changedList; for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { changedList << d->mDirModel->index(row, 0); } QModelIndexList& shortList = selectionList; QModelIndexList& longList = changedList; if (shortList.length() > longList.length()) { qSwap(shortList, longList); } Q_FOREACH(const QModelIndex & index, shortList) { if (longList.contains(index)) { d->mSelectedFileItemListNeedsUpdate = true; d->queueSignal("selectionDataChanged"); return; } } } void ContextManager::slotSelectionChanged() { d->mSelectedFileItemListNeedsUpdate = true; if (!d->mSelectionModel->hasSelection()) { setCurrentUrl(QUrl()); } d->queueSignal("selectionChanged"); } void Gwenview::ContextManager::slotCurrentChanged(const QModelIndex& index) { QUrl url = d->mDirModel->urlForIndex(index); setCurrentUrl(url); } void ContextManager::emitQueuedSignals() { Q_FOREACH(const QByteArray & signal, d->mQueuedSignals) { QMetaObject::invokeMethod(this, signal.data()); } d->mQueuedSignals.clear(); } void Gwenview::ContextManager::slotRowsAboutToBeRemoved(const QModelIndex& /*parent*/, int start, int end) { QModelIndex oldCurrent = d->mSelectionModel->currentIndex(); if (oldCurrent.row() < start || oldCurrent.row() > end) { // currentIndex has not been removed return; } QModelIndex newCurrent; if (end + 1 < d->mDirModel->rowCount()) { newCurrent = d->mDirModel->index(end + 1, 0); } else if (start > 0) { newCurrent = d->mDirModel->index(start - 1, 0); } else { // No index we can select, nothing to do return; } d->mSelectionModel->select(oldCurrent, QItemSelectionModel::Deselect); d->mSelectionModel->setCurrentIndex(newCurrent, QItemSelectionModel::Select); } bool ContextManager::currentUrlIsRasterImage() const { return MimeTypeUtils::urlKind(currentUrl()) == MimeTypeUtils::KIND_RASTER_IMAGE; } QUrl ContextManager::urlToSelect() const { return d->mUrlToSelect; } void ContextManager::setUrlToSelect(const QUrl &url) { GV_RETURN_IF_FAIL(url.isValid()); d->mUrlToSelect = url; setCurrentDirUrl(url.adjusted(QUrl::RemoveFilename)); setCurrentUrl(url); selectUrlToSelect(); } QUrl ContextManager::targetUrl() const { return d->mTargetUrl; } void ContextManager::setTargetUrl(const QUrl &url) { GV_RETURN_IF_FAIL(url.isValid()); d->mTargetUrl = url; } void ContextManager::slotRowsInserted() { // We reach this method when rows have been inserted in the model, but views // may not have been updated yet and thus do not have the matching items. // Delay the selection of mUrlToSelect so that the view items exist. // // Without this, when Gwenview is started with an image as argument and the // thumbnail bar is visible, the image will not be selected in the thumbnail // bar. if (d->mUrlToSelect.isValid()) { QMetaObject::invokeMethod(this, "selectUrlToSelect", Qt::QueuedConnection); } } void ContextManager::selectUrlToSelect() { // Because of the queued connection above we might be called several times in a row // In this case we don't want the warning below if (d->mUrlToSelect.isEmpty()) { return; } GV_RETURN_IF_FAIL(d->mUrlToSelect.isValid()); QModelIndex index = d->mDirModel->indexForUrl(d->mUrlToSelect); if (index.isValid()) { d->mSelectionModel->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect); d->mUrlToSelect = QUrl(); } else if (d->mDirListerFinished) { // Desired URL cannot be found in the directory // Clear the selection to avoid dragging any local files into context // and manually set current URL d->mSelectionModel->clearSelection(); setCurrentUrl(d->mUrlToSelect); d->mUrlToSelect.clear(); } } void ContextManager::slotDirListerRedirection(const QUrl &newUrl) { setCurrentDirUrl(newUrl); } void ContextManager::slotDirListerCompleted() { d->mDirListerFinished = true; } } // namespace diff --git a/lib/contextmanager.h b/lib/contextmanager.h index d7399505..e35f883e 100644 --- a/lib/contextmanager.h +++ b/lib/contextmanager.h @@ -1,101 +1,104 @@ /* 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 CONTEXTMANAGER_H #define CONTEXTMANAGER_H #include // Qt #include // KDE #include #include class QItemSelectionModel; class QModelIndex; namespace Gwenview { class SortedDirModel; struct ContextManagerPrivate; /** * Manages the state of the application. * TODO: Most of GvCore should be merged in this class */ class GWENVIEWLIB_EXPORT ContextManager : public QObject { Q_OBJECT public: ContextManager(SortedDirModel*, QObject* parent); ~ContextManager(); + void loadConfig(); + void saveConfig() const; + QUrl currentUrl() const; void setCurrentDirUrl(const QUrl&); QUrl currentDirUrl() const; void setCurrentUrl(const QUrl ¤tUrl); KFileItemList selectedFileItemList() const; SortedDirModel* dirModel() const; QItemSelectionModel* selectionModel() const; bool currentUrlIsRasterImage() const; QUrl urlToSelect() const; void setUrlToSelect(const QUrl&); QUrl targetUrl() const; void setTargetUrl(const QUrl&); Q_SIGNALS: void currentDirUrlChanged(const QUrl&); void currentUrlChanged(const QUrl&); void selectionChanged(); void selectionDataChanged(); private Q_SLOTS: void slotDirModelDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); void slotSelectionChanged(); void slotCurrentChanged(const QModelIndex&); void emitQueuedSignals(); void slotRowsAboutToBeRemoved(const QModelIndex& /*parent*/, int start, int end); void slotRowsInserted(); void selectUrlToSelect(); void slotDirListerRedirection(const QUrl&); void slotDirListerCompleted(); private: ContextManagerPrivate* const d; }; } // namespace #endif /* CONTEXTMANAGER_H */ diff --git a/lib/gwenviewconfig.kcfg b/lib/gwenviewconfig.kcfg index 5ed6f72e..7cab5bb8 100644 --- a/lib/gwenviewconfig.kcfg +++ b/lib/gwenviewconfig.kcfg @@ -1,267 +1,273 @@ lib/sorting.h lib/zoommode.h lib/mousewheelbehavior.h lib/documentview/documentview.h lib/documentview/rasterimageview.h lib/print/printoptionspage.h General.Name,General.ImageSize,Exif.Photo.ExposureTime,Exif.Photo.Flash 100 true 0.5 The percentage of memory used by Gwenview before it warns the user and suggest saving changes. new A list of filename extensions Gwenview should not try to load. We exclude *.new as well because this is the extension used for temporary files by KSaveFile. false true + + + QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation)).toString() + + + Horizontal 1 false false information General.Name,Exif.Image.DateTime 75 true RasterImageView::AlphaBackgroundCheckBoard #ffffff MouseWheelBehavior::Scroll false true 350, 100 DocumentView::SoftwareAnimation ZoomMode::Autofit Defines what happens when going to image B after having zoomed in on an area of image A. If set to Autofit, image B is zoomed out to fit the screen. If set to KeepSame, 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 Individual, 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). 128 3./2. false Sorting::Name 1 true Qt::AlignHCenter | Qt::AlignVCenter PrintOptionsPage::ScaleToPage false 15.0 10.0 PrintOptionsPage::Centimeters true false true false false 5.0 24 false diff --git a/part/gvpart.desktop b/part/gvpart.desktop index d63b1e68..06f7b45e 100644 --- a/part/gvpart.desktop +++ b/part/gvpart.desktop @@ -1,71 +1,71 @@ [Desktop Entry] Type=Service Name=Gwenview Image Viewer Name[ar]=جوِينفيو مستعرض الصورة Name[bg]=Програма за снимки Gwenview Name[bs]=Gvenview prikazivač slika Name[ca]=Visualitzador d'imatges Gwenview Name[ca@valencia]=Visualitzador d'imatges Gwenview Name[cs]=Prohlížeč obrázků Gwenview Name[da]=Gwenview billedfremviser Name[de]=Gwenview Bildbetrachter Name[el]=Προβολέας εικόνων Gwenview Name[en_GB]=Gwenview Image Viewer Name[eo]=Gwenview bildorigardilo Name[es]=Visor de imágenes Gwenview Name[et]=Gwenview pildinäitaja Name[eu]=Gwenview irudi ikustailea Name[fa]=مشاهده‌گر تصویر Gwenview Name[fi]=Gwenview-kuvankatselin Name[fr]=Afficheur d'images Gwenview Name[ga]=Amharcán Íomhánna Gwenview Name[gl]=Visor de imaxes Gwenview Name[he]=מציג התמונות Gwenview Name[hi]=ग्वेन-व्यू छवि प्रदर्शक Name[hne]=ग्वेन-व्यू फोटू प्रदर्सक Name[hr]=Preglednik slika Gwenview Name[hu]=Gwenview képnézegető Name[ia]=Visor de imagine Gwenview Name[id]=Penampil Citra Gwenview Name[is]=Gwenview myndskoðari Name[it]=Visore di immagini Gwenview Name[ja]=Gwenview 画像ビューア Name[kk]=Gwenview кескінді қарау құралы Name[km]=កម្មវិធី​មើល​រូបភាព​របស់ Gwenview Name[ko]=Gwenview 그림 뷰어 Name[ku]=Gwenview Nîşanderê Wêneyan Name[lt]=Gwenview paveikslėlių žiūryklė Name[lv]=Gwenview attēlu skatītājs Name[mr]=ग्वेनव्यु प्रतिमा प्रदर्शक Name[nb]=Bildeviseren Gwenview Name[nds]=Bildkieker Gwenview Name[ne]=जीवेनभ्यू छवि दर्शक Name[nl]=GWenview Afbeeldingenviewer Name[nn]=Gwenview biletvisar Name[pa]=ਜੀਵੀਨ-ਵਿਊ ਚਿੱਤਰ ਦਰਸ਼ਕ Name[pl]=Przeglądarka obrazów Gwenview Name[pt]=Visualizador de Imagens Gwenview Name[pt_BR]=Visualizador de imagens do Gwenview Name[ro]=Vizualizator de imagini KView Name[ru]=Просмотр изображений в Gwenview Name[si]=Gwenview පිංතූර දසුන Name[sk]=Prehliadač obrázkov Gwenview Name[sl]=Pregledovalnik slik Gwenview Name[sr]=Гвенвју приказивач слика Name[sr@ijekavian]=Гвенвју приказивач слика Name[sr@ijekavianlatin]=GwenView prikazivač slika Name[sr@latin]=GwenView prikazivač slika Name[sv]=Gwenview bildvisare Name[th]=เกวนวิว - เครื่องมือแสดงภาพ Name[tr]=Gwenview Resim Gösterici Name[ug]=Gwenview سۈرەت كۆرگۈ Name[uk]=Переглядач зображень Gwenview Name[vi]=Bộ xem ảnh Gwenview Name[x-test]=xxGwenview Image Viewerxx Name[zh_CN]=Gwenview 图像查看器 Name[zh_TW]=Gwenview 影像檢視程式 -MimeType=image/gif;image/jpeg;image/jp2;image/png;image/bmp;image/x-eps;image/x-icns;image/x-ico;image/x-portable-bitmap;image/x-portable-pixmap;image/x-xbitmap;image/x-xpixmap;image/x-webp;image/webp; +MimeType=image/gif;image/jpeg;image/jp2;image/png;image/bmp;image/x-eps;image/x-icns;image/x-ico;image/x-portable-bitmap;image/x-portable-pixmap;image/x-xbitmap;image/x-xpixmap;image/x-webp;image/webp;image/x-tga; X-KDE-ServiceTypes=KParts/ReadOnlyPart X-KDE-Library=gvpart InitialPreference=12 Icon=gwenview