1/*
2Gwenview: an image viewer
3Copyright 2007 Aurélien Gâteau <agateau@kde.org>
4
5This program is free software; you can redistribute it and/or
6modify it under the terms of the GNU General Public License
7as published by the Free Software Foundation; either version 2
8of the License, or (at your option) any later version.
9
10This program is distributed in the hope that it will be useful,
11but WITHOUT ANY WARRANTY; without even the implied warranty of
12MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program; if not, write to the Free Software
17Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
19*/
20#include "mainwindow.moc"
21#include <config-gwenview.h>
22
23// Qt
24#include <QApplication>
25#include <QDateTime>
26#include <QDesktopWidget>
27#include <QLabel>
28#include <QPushButton>
29#include <QShortcut>
30#include <QSlider>
31#include <QSplitter>
32#include <QStackedWidget>
33#include <QTimer>
34#include <QUndoGroup>
35#include <QVBoxLayout>
36
37// KDE
38#include <KIO/NetAccess>
39#include <KActionCategory>
40#include <KActionCollection>
41#include <KAction>
42#include <KApplication>
43#include <KDirLister>
44#include <KEditToolBar>
45#include <KFileDialog>
46#include <KFileItem>
47#include <KLocale>
48#include <KMenuBar>
49#include <KMessageBox>
50#include <KNotificationRestrictions>
51#include <KProtocolManager>
52#include <KLinkItemSelectionModel>
53#include <KRecentFilesAction>
54#include <KStatusBar>
55#include <KStandardDirs>
56#include <KStandardGuiItem>
57#include <KStandardShortcut>
58#include <KToggleFullScreenAction>
59#include <KToolBar>
60#include <KUrl>
61#include <KUrlNavigator>
62#include <KXMLGUIFactory>
63#include <KWindowSystem>
64
65// Local
66#include "configdialog.h"
67#include "documentinfoprovider.h"
68#include "viewmainpage.h"
69#include "fileopscontextmanageritem.h"
70#include "folderviewcontextmanageritem.h"
71#include "fullscreencontent.h"
72#include "gvcore.h"
73#include "imageopscontextmanageritem.h"
74#include "infocontextmanageritem.h"
75#ifdef KIPI_FOUND
76#include "kipiexportaction.h"
77#include "kipiinterface.h"
78#endif
79#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
80#include "semanticinfocontextmanageritem.h"
81#endif
82#include "preloader.h"
83#include "savebar.h"
84#include "sidebar.h"
85#include "splitter.h"
86#include "startmainpage.h"
87#include "thumbnailviewhelper.h"
88#include "browsemainpage.h"
89#include <lib/hud/hudbuttonbox.h>
90#include <lib/archiveutils.h>
91#include <lib/contextmanager.h>
92#include <lib/disabledactionshortcutmonitor.h>
93#include <lib/document/documentfactory.h>
94#include <lib/documentonlyproxymodel.h>
95#include <lib/eventwatcher.h>
96#include <lib/gvdebug.h>
97#include <lib/gwenviewconfig.h>
98#include <lib/mimetypeutils.h>
99#include <lib/print/printhelper.h>
100#include <lib/slideshow.h>
101#include <lib/signalblocker.h>
102#include <lib/semanticinfo/sorteddirmodel.h>
103#include <lib/thumbnailprovider/thumbnailprovider.h>
104#include <lib/thumbnailview/thumbnailbarview.h>
105#include <lib/thumbnailview/thumbnailview.h>
106#include <lib/urlutils.h>
107
108namespace Gwenview
109{
110
111#undef ENABLE_LOG
112#undef LOG
113//#define ENABLE_LOG
114#ifdef ENABLE_LOG
115#define LOG(x) kDebug() << x
116#else
117#define LOG(x) ;
118#endif
119
120static const int BROWSE_PRELOAD_DELAY = 1000;
121static const int VIEW_PRELOAD_DELAY = 100;
122
123static const char* BROWSE_MODE_SIDE_BAR_GROUP = "SideBar-BrowseMode";
124static const char* VIEW_MODE_SIDE_BAR_GROUP = "SideBar-ViewMode";
125static const char* FULLSCREEN_MODE_SIDE_BAR_GROUP = "SideBar-FullScreenMode";
126static const char* SIDE_BAR_IS_VISIBLE_KEY = "IsVisible";
127
128static const char* SESSION_CURRENT_PAGE_KEY = "Page";
129static const char* SESSION_URL_KEY = "Url";
130
131enum MainPageId {
132 StartMainPageId,
133 BrowseMainPageId,
134 ViewMainPageId
135};
136
137struct MainWindowState
138{
139 bool mToolBarVisible;
140 Qt::WindowStates mWindowState;
141};
142
143/*
144
145Layout of the main window looks like this:
146
147.-mCentralSplitter-----------------------------.
148|.-mSideBar--. .-mContentWidget---------------.|
149|| | |.-mSaveBar-------------------.||
150|| | || |||
151|| | |'----------------------------'||
152|| | |.-mViewStackedWidget---------.||
153|| | || |||
154|| | || |||
155|| | || |||
156|| | || |||
157|| | |'----------------------------'||
158|'-----------' '------------------------------'|
159'----------------------------------------------'
160
161*/
162struct MainWindow::Private
163{
164 GvCore* mGvCore;
165 MainWindow* q;
166 QSplitter* mCentralSplitter;
167 QWidget* mContentWidget;
168 ViewMainPage* mViewMainPage;
169 KUrlNavigator* mUrlNavigator;
170 ThumbnailView* mThumbnailView;
171 ThumbnailView* mActiveThumbnailView;
172 DocumentInfoProvider* mDocumentInfoProvider;
173 ThumbnailViewHelper* mThumbnailViewHelper;
174 QPointer<ThumbnailProvider> mThumbnailProvider;
175 BrowseMainPage* mBrowseMainPage;
176 StartMainPage* mStartMainPage;
177 SideBar* mSideBar;
178 QStackedWidget* mViewStackedWidget;
179 FullScreenContent* mFullScreenContent;
180 SaveBar* mSaveBar;
181 bool mStartSlideShowWhenDirListerCompleted;
182 SlideShow* mSlideShow;
183 Preloader* mPreloader;
184 bool mPreloadDirectionIsForward;
185#ifdef KIPI_FOUND
186 KIPIInterface* mKIPIInterface;
187#endif
188
189 QActionGroup* mViewModeActionGroup;
190 KRecentFilesAction* mFileOpenRecentAction;
191 KAction* mBrowseAction;
192 KAction* mViewAction;
193 KAction* mGoUpAction;
194 KAction* mGoToPreviousAction;
195 KAction* mGoToNextAction;
196 KAction* mGoToFirstAction;
197 KAction* mGoToLastAction;
198 KToggleAction* mToggleSideBarAction;
199 KToggleFullScreenAction* mFullScreenAction;
200 KAction* mToggleSlideShowAction;
201 KToggleAction* mShowMenuBarAction;
202#ifdef KIPI_FOUND
203 KIPIExportAction* mKIPIExportAction;
204#endif
205
206 SortedDirModel* mDirModel;
207 DocumentOnlyProxyModel* mThumbnailBarModel;
208 KLinkItemSelectionModel* mThumbnailBarSelectionModel;
209 ContextManager* mContextManager;
210
211 MainWindowState mStateBeforeFullScreen;
212
213 QString mCaption;
214
215 MainPageId mCurrentMainPageId;
216
217 QDateTime mFullScreenLeftAt;
218 KNotificationRestrictions* mNotificationRestrictions;
219
220 void setupContextManager()
221 {
222 mContextManager = new ContextManager(mDirModel, q);
223 connect(mContextManager, SIGNAL(selectionChanged()),
224 q, SLOT(slotSelectionChanged()));
225 connect(mContextManager, SIGNAL(currentDirUrlChanged(KUrl)),
226 q, SLOT(slotCurrentDirUrlChanged(KUrl)));
227 }
228
229 void setupWidgets()
230 {
231 mFullScreenContent = new FullScreenContent(q);
232 connect(mContextManager, SIGNAL(currentUrlChanged(KUrl)), mFullScreenContent, SLOT(setCurrentUrl(KUrl)));
233
234 mCentralSplitter = new Splitter(Qt::Horizontal, q);
235 q->setCentralWidget(mCentralSplitter);
236
237 // Left side of splitter
238 mSideBar = new SideBar(mCentralSplitter);
239 EventWatcher::install(mSideBar, QList<QEvent::Type>() << QEvent::Show << QEvent::Hide,
240 q, SLOT(updateToggleSideBarAction()));
241
242 // Right side of splitter
243 mContentWidget = new QWidget(mCentralSplitter);
244
245 mSaveBar = new SaveBar(mContentWidget, q->actionCollection());
246 connect(mContextManager, SIGNAL(currentUrlChanged(KUrl)), mSaveBar, SLOT(setCurrentUrl(KUrl)));
247 mViewStackedWidget = new QStackedWidget(mContentWidget);
248 QVBoxLayout* layout = new QVBoxLayout(mContentWidget);
249 layout->addWidget(mSaveBar);
250 layout->addWidget(mViewStackedWidget);
251 layout->setMargin(0);
252 layout->setSpacing(0);
253 ////
254
255 mStartSlideShowWhenDirListerCompleted = false;
256 mSlideShow = new SlideShow(q);
257 connect(mContextManager, SIGNAL(currentUrlChanged(KUrl)), mSlideShow, SLOT(setCurrentUrl(KUrl)));
258
259 setupThumbnailView(mViewStackedWidget);
260 setupViewMainPage(mViewStackedWidget);
261 setupStartMainPage(mViewStackedWidget);
262 mViewStackedWidget->addWidget(mBrowseMainPage);
263 mViewStackedWidget->addWidget(mViewMainPage);
264 mViewStackedWidget->addWidget(mStartMainPage);
265 mViewStackedWidget->setCurrentWidget(mBrowseMainPage);
266
267 mCentralSplitter->setStretchFactor(0, 0);
268 mCentralSplitter->setStretchFactor(1, 1);
269
270 connect(mSaveBar, SIGNAL(requestSaveAll()),
271 mGvCore, SLOT(saveAll()));
272 connect(mSaveBar, SIGNAL(goToUrl(KUrl)),
273 q, SLOT(goToUrl(KUrl)));
274
275 connect(mSlideShow, SIGNAL(goToUrl(KUrl)),
276 q, SLOT(goToUrl(KUrl)));
277 }
278
279 void setupThumbnailView(QWidget* parent)
280 {
281 Q_ASSERT(mContextManager);
282 mBrowseMainPage = new BrowseMainPage(parent, q->actionCollection(), mGvCore);
283
284 mThumbnailView = mBrowseMainPage->thumbnailView();
285 mThumbnailView->setSelectionModel(mContextManager->selectionModel());
286 mUrlNavigator = mBrowseMainPage->urlNavigator();
287
288 mDocumentInfoProvider = new DocumentInfoProvider(mDirModel);
289 mThumbnailView->setDocumentInfoProvider(mDocumentInfoProvider);
290
291 mThumbnailViewHelper = new ThumbnailViewHelper(mDirModel, q->actionCollection());
292 connect(mContextManager, SIGNAL(currentDirUrlChanged(KUrl)),
293 mThumbnailViewHelper, SLOT(setCurrentDirUrl(KUrl)));
294 mThumbnailView->setThumbnailViewHelper(mThumbnailViewHelper);
295
296 mThumbnailBarSelectionModel = new KLinkItemSelectionModel(mThumbnailBarModel, mContextManager->selectionModel(), q);
297
298 // Connect thumbnail view
299 connect(mThumbnailView, SIGNAL(indexActivated(QModelIndex)),
300 q, SLOT(slotThumbnailViewIndexActivated(QModelIndex)));
301
302 // Connect delegate
303 QAbstractItemDelegate* delegate = mThumbnailView->itemDelegate();
304 connect(delegate, SIGNAL(saveDocumentRequested(KUrl)),
305 mGvCore, SLOT(save(KUrl)));
306 connect(delegate, SIGNAL(rotateDocumentLeftRequested(KUrl)),
307 mGvCore, SLOT(rotateLeft(KUrl)));
308 connect(delegate, SIGNAL(rotateDocumentRightRequested(KUrl)),
309 mGvCore, SLOT(rotateRight(KUrl)));
310 connect(delegate, SIGNAL(showDocumentInFullScreenRequested(KUrl)),
311 q, SLOT(showDocumentInFullScreen(KUrl)));
312 connect(delegate, SIGNAL(setDocumentRatingRequested(KUrl,int)),
313 mGvCore, SLOT(setRating(KUrl,int)));
314
315 // Connect url navigator
316 connect(mUrlNavigator, SIGNAL(urlChanged(KUrl)),
317 q, SLOT(openDirUrl(KUrl)));
318 }
319
320 void setupViewMainPage(QWidget* parent)
321 {
322 mViewMainPage = new ViewMainPage(parent, mSlideShow, q->actionCollection(), mGvCore);
323 connect(mViewMainPage, SIGNAL(captionUpdateRequested(QString)),
324 q, SLOT(setCaption(QString)));
325 connect(mViewMainPage, SIGNAL(completed()),
326 q, SLOT(slotPartCompleted()));
327 connect(mViewMainPage, SIGNAL(previousImageRequested()),
328 q, SLOT(goToPrevious()));
329 connect(mViewMainPage, SIGNAL(nextImageRequested()),
330 q, SLOT(goToNext()));
331
332 setupThumbnailBar(mViewMainPage->thumbnailBar());
333 }
334
335 void setupThumbnailBar(ThumbnailView* bar)
336 {
337 Q_ASSERT(mThumbnailBarModel);
338 Q_ASSERT(mThumbnailBarSelectionModel);
339 Q_ASSERT(mDocumentInfoProvider);
340 Q_ASSERT(mThumbnailViewHelper);
341 bar->setModel(mThumbnailBarModel);
342 bar->setSelectionModel(mThumbnailBarSelectionModel);
343 bar->setDocumentInfoProvider(mDocumentInfoProvider);
344 bar->setThumbnailViewHelper(mThumbnailViewHelper);
345 }
346
347 void setupStartMainPage(QWidget* parent)
348 {
349 mStartMainPage = new StartMainPage(parent, mGvCore);
350 connect(mStartMainPage, SIGNAL(urlSelected(KUrl)),
351 q, SLOT(slotStartMainPageUrlSelected(KUrl)));
352 }
353
354 void installDisabledActionShortcutMonitor(QAction* action, const char* slot)
355 {
356 DisabledActionShortcutMonitor* monitor = new DisabledActionShortcutMonitor(action, q);
357 connect(monitor, SIGNAL(activated()), q, slot);
358 }
359
360 void setupActions()
361 {
362 KActionCollection* actionCollection = q->actionCollection();
363 KActionCategory* file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection);
364 KActionCategory* view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection);
365
366 file->addAction(KStandardAction::Save, q, SLOT(saveCurrent()));
367 file->addAction(KStandardAction::SaveAs, q, SLOT(saveCurrentAs()));
368 file->addAction(KStandardAction::Open, q, SLOT(openFile()));
369 mFileOpenRecentAction = KStandardAction::openRecent(q, SLOT(openUrl(KUrl)), q);
370 file->addAction("file_open_recent", mFileOpenRecentAction);
371 file->addAction(KStandardAction::Print, q, SLOT(print()));
372 file->addAction(KStandardAction::Quit, KApplication::kApplication(), SLOT(closeAllWindows()));
373
374 KAction* action = file->addAction("reload", q, SLOT(reload()));
375 action->setText(i18nc("@action reload the currently viewed image", "Reload"));
376 action->setIcon(KIcon("view-refresh"));
377 action->setShortcut(Qt::Key_F5);
378
379 mBrowseAction = view->addAction("browse");
380 mBrowseAction->setText(i18nc("@action:intoolbar Switch to file list", "Browse"));
381 mBrowseAction->setToolTip(i18nc("@info:tooltip", "Browse folders for images"));
382 mBrowseAction->setCheckable(true);
383 mBrowseAction->setIcon(KIcon("view-list-icons"));
384 mBrowseAction->setShortcut(Qt::Key_Escape);
385 connect(mViewMainPage, SIGNAL(goToBrowseModeRequested()),
386 mBrowseAction, SLOT(trigger()));
387
388 mViewAction = view->addAction("view");
389 mViewAction->setText(i18nc("@action:intoolbar Switch to image view", "View"));
390 mViewAction->setToolTip(i18nc("@info:tooltip", "View selected images"));
391 mViewAction->setIcon(KIcon("view-preview"));
392 mViewAction->setCheckable(true);
393
394 mViewModeActionGroup = new QActionGroup(q);
395 mViewModeActionGroup->addAction(mBrowseAction);
396 mViewModeActionGroup->addAction(mViewAction);
397
398 connect(mViewModeActionGroup, SIGNAL(triggered(QAction*)),
399 q, SLOT(setActiveViewModeAction(QAction*)));
400
401 mFullScreenAction = static_cast<KToggleFullScreenAction*>(view->addAction(KStandardAction::FullScreen, q, SLOT(toggleFullScreen(bool))));
402 KShortcut shortcut = mFullScreenAction->shortcut();
403 if (shortcut.alternate().isEmpty()) {
404 shortcut.setAlternate(Qt::Key_F11);
405 }
406 mFullScreenAction->setShortcut(shortcut);
407 connect(mViewMainPage, SIGNAL(toggleFullScreenRequested()),
408 mFullScreenAction, SLOT(trigger()));
409
410 KAction* leaveFullScreenAction = view->addAction("leave_fullscreen", q, SLOT(leaveFullScreen()));
411 leaveFullScreenAction->setIcon(KIcon("view-restore"));
412 leaveFullScreenAction->setPriority(QAction::LowPriority);
413 leaveFullScreenAction->setText(i18nc("@action", "Leave Fullscreen Mode"));
414
415 mGoToPreviousAction = view->addAction("go_previous", q, SLOT(goToPrevious()));
416 mGoToPreviousAction->setPriority(QAction::LowPriority);
417 mGoToPreviousAction->setIcon(KIcon("media-skip-backward"));
418 mGoToPreviousAction->setText(i18nc("@action Go to previous image", "Previous"));
419 mGoToPreviousAction->setToolTip(i18nc("@info:tooltip", "Go to previous image"));
420 mGoToPreviousAction->setShortcut(Qt::Key_Backspace);
421 installDisabledActionShortcutMonitor(mGoToPreviousAction, SLOT(showFirstDocumentReached()));
422
423 mGoToNextAction = view->addAction("go_next", q, SLOT(goToNext()));
424 mGoToNextAction->setPriority(QAction::LowPriority);
425 mGoToNextAction->setIcon(KIcon("media-skip-forward"));
426 mGoToNextAction->setText(i18nc("@action Go to next image", "Next"));
427 mGoToNextAction->setToolTip(i18nc("@info:tooltip", "Go to next image"));
428 mGoToNextAction->setShortcut(Qt::Key_Space);
429 installDisabledActionShortcutMonitor(mGoToNextAction, SLOT(showLastDocumentReached()));
430
431 mGoToFirstAction = view->addAction("go_first", q, SLOT(goToFirst()));
432 mGoToFirstAction->setPriority(QAction::LowPriority);
433 mGoToFirstAction->setText(i18nc("@action Go to first image", "First"));
434 mGoToFirstAction->setToolTip(i18nc("@info:tooltip", "Go to first image"));
435 mGoToFirstAction->setShortcut(Qt::Key_Home);
436
437 mGoToLastAction = view->addAction("go_last", q, SLOT(goToLast()));
438 mGoToLastAction->setPriority(QAction::LowPriority);
439 mGoToLastAction->setText(i18nc("@action Go to last image", "Last"));
440 mGoToLastAction->setToolTip(i18nc("@info:tooltip", "Go to last image"));
441 mGoToLastAction->setShortcut(Qt::Key_End);
442
443 mPreloadDirectionIsForward = true;
444
445 mGoUpAction = view->addAction(KStandardAction::Up, q, SLOT(goUp()));
446
447 action = view->addAction("go_start_page", q, SLOT(showStartMainPage()));
448 action->setPriority(QAction::LowPriority);
449 action->setIcon(KIcon("go-home"));
450 action->setText(i18nc("@action", "Start Page"));
451 action->setToolTip(i18nc("@info:tooltip", "Open the start page"));
452
453 mToggleSideBarAction = view->add<KToggleAction>("toggle_sidebar");
454 connect(mToggleSideBarAction, SIGNAL(toggled(bool)),
455 q, SLOT(toggleSideBar(bool)));
456 mToggleSideBarAction->setIcon(KIcon("view-sidetree"));
457 mToggleSideBarAction->setShortcut(Qt::Key_F4);
458 mToggleSideBarAction->setText(i18nc("@action", "Sidebar"));
459 connect(mBrowseMainPage->toggleSideBarButton(), SIGNAL(clicked()),
460 mToggleSideBarAction, SLOT(trigger()));
461 connect(mViewMainPage->toggleSideBarButton(), SIGNAL(clicked()),
462 mToggleSideBarAction, SLOT(trigger()));
463
464 mToggleSlideShowAction = view->addAction("toggle_slideshow", q, SLOT(toggleSlideShow()));
465 q->updateSlideShowAction();
466 connect(mSlideShow, SIGNAL(stateChanged(bool)),
467 q, SLOT(updateSlideShowAction()));
468
469 mShowMenuBarAction = static_cast<KToggleAction*>(view->addAction(KStandardAction::ShowMenubar, q, SLOT(toggleMenuBar())));
470
471 view->addAction(KStandardAction::KeyBindings, q->guiFactory(),
472 SLOT(configureShortcuts()));
473
474 view->addAction(KStandardAction::Preferences, q,
475 SLOT(showConfigDialog()));
476
477 view->addAction(KStandardAction::ConfigureToolbars, q,
478 SLOT(configureToolbars()));
479
480#ifdef KIPI_FOUND
481 mKIPIExportAction = new KIPIExportAction(q);
482 actionCollection->addAction("kipi_export", mKIPIExportAction);
483#endif
484 }
485
486 void setupUndoActions()
487 {
488 // There is no KUndoGroup similar to KUndoStack. This code basically
489 // does the same as KUndoStack, but for the KUndoGroup actions.
490 QUndoGroup* undoGroup = DocumentFactory::instance()->undoGroup();
491 QAction* action;
492 KActionCollection* actionCollection = q->actionCollection();
493 KActionCategory* edit = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "Edit"), actionCollection);
494
495 action = undoGroup->createRedoAction(actionCollection);
496 action->setObjectName(KStandardAction::name(KStandardAction::Redo));
497 action->setIcon(KIcon("edit-redo"));
498 action->setIconText(i18n("Redo"));
499 action->setShortcuts(KStandardShortcut::redo());
500 edit->addAction(action->objectName(), action);
501
502 action = undoGroup->createUndoAction(actionCollection);
503 action->setObjectName(KStandardAction::name(KStandardAction::Undo));
504 action->setIcon(KIcon("edit-undo"));
505 action->setIconText(i18n("Undo"));
506 action->setShortcuts(KStandardShortcut::undo());
507 edit->addAction(action->objectName(), action);
508 }
509
510 void setupContextManagerItems()
511 {
512 Q_ASSERT(mContextManager);
513 KActionCollection* actionCollection = q->actionCollection();
514
515 // Create context manager items
516 FolderViewContextManagerItem* folderViewItem = new FolderViewContextManagerItem(mContextManager);
517 connect(folderViewItem, SIGNAL(urlChanged(KUrl)),
518 q, SLOT(openDirUrl(KUrl)));
519
520 InfoContextManagerItem* infoItem = new InfoContextManagerItem(mContextManager);
521
522#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
523 SemanticInfoContextManagerItem* semanticInfoItem = 0;
524 semanticInfoItem = new SemanticInfoContextManagerItem(mContextManager, actionCollection, mViewMainPage);
525#endif
526
527 ImageOpsContextManagerItem* imageOpsItem =
528 new ImageOpsContextManagerItem(mContextManager, q);
529
530 FileOpsContextManagerItem* fileOpsItem = new FileOpsContextManagerItem(mContextManager, mThumbnailView, actionCollection, q);
531
532 // Fill sidebar
533 SideBarPage* page;
534 page = new SideBarPage(i18n("Folders"));
535 page->setObjectName(QLatin1String("folders"));
536 page->addWidget(folderViewItem->widget());
537 page->layout()->setMargin(0);
538 mSideBar->addPage(page);
539
540 page = new SideBarPage(i18n("Information"));
541 page->setObjectName(QLatin1String("information"));
542 page->addWidget(infoItem->widget());
543#ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE
544 if (semanticInfoItem) {
545 page->addWidget(semanticInfoItem->widget());
546 }
547#endif
548 mSideBar->addPage(page);
549
550 page = new SideBarPage(i18n("Operations"));
551 page->setObjectName(QLatin1String("operations"));
552 page->addWidget(imageOpsItem->widget());
553 page->addWidget(fileOpsItem->widget());
554 page->addStretch();
555 mSideBar->addPage(page);
556 }
557
558 void initDirModel()
559 {
560 mDirModel->setKindFilter(
561 MimeTypeUtils::KIND_DIR
562 | MimeTypeUtils::KIND_ARCHIVE
563 | MimeTypeUtils::KIND_RASTER_IMAGE
564 | MimeTypeUtils::KIND_SVG_IMAGE
565 | MimeTypeUtils::KIND_VIDEO);
566
567 connect(mDirModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
568 q, SLOT(slotDirModelNewItems()));
569
570 connect(mDirModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
571 q, SLOT(updatePreviousNextActions()));
572 connect(mDirModel, SIGNAL(modelReset()),
573 q, SLOT(updatePreviousNextActions()));
574
575 connect(mDirModel->dirLister(), SIGNAL(completed()),
576 q, SLOT(slotDirListerCompleted()));
577 }
578
579 void setupThumbnailBarModel()
580 {
581 mThumbnailBarModel = new DocumentOnlyProxyModel(q);
582 mThumbnailBarModel->setSourceModel(mDirModel);
583 }
584
585 bool indexIsDirOrArchive(const QModelIndex& index) const
586 {
587 Q_ASSERT(index.isValid());
588 KFileItem item = mDirModel->itemForIndex(index);
589 return ArchiveUtils::fileItemIsDirOrArchive(item);
590 }
591
592 void goTo(const QModelIndex& index)
593 {
594 if (!index.isValid()) {
595 return;
596 }
597 mThumbnailView->setCurrentIndex(index);
598 mThumbnailView->scrollTo(index);
599 }
600
601 void goTo(int offset)
602 {
603 mPreloadDirectionIsForward = offset > 0;
604 QModelIndex index = mContextManager->selectionModel()->currentIndex();
605 index = mDirModel->index(index.row() + offset, 0);
606 if (index.isValid() && !indexIsDirOrArchive(index)) {
607 goTo(index);
608 }
609 }
610
611 void goToFirstDocument()
612 {
613 QModelIndex index;
614 for (int row = 0;; ++row) {
615 index = mDirModel->index(row, 0);
616 if (!index.isValid()) {
617 return;
618 }
619
620 if (!indexIsDirOrArchive(index)) {
621 break;
622 }
623 }
624 goTo(index);
625 }
626
627 void goToLastDocument()
628 {
629 QModelIndex index = mDirModel->index(mDirModel->rowCount() - 1, 0);
630 goTo(index);
631 }
632
633 void setupFullScreenContent()
634 {
635 mFullScreenContent->init(q->actionCollection(), mViewMainPage, mSlideShow);
636 setupThumbnailBar(mFullScreenContent->thumbnailBar());
637 }
638
639 inline void setActionEnabled(const char* name, bool enabled)
640 {
641 QAction* action = q->actionCollection()->action(name);
642 if (action) {
643 action->setEnabled(enabled);
644 } else {
645 kWarning() << "Action" << name << "not found";
646 }
647 }
648
649 void setActionsDisabledOnStartMainPageEnabled(bool enabled)
650 {
651 mBrowseAction->setEnabled(enabled);
652 mViewAction->setEnabled(enabled);
653 mToggleSideBarAction->setEnabled(enabled);
654 mFullScreenAction->setEnabled(enabled);
655 mToggleSlideShowAction->setEnabled(enabled);
656
657 setActionEnabled("reload", enabled);
658 setActionEnabled("go_start_page", enabled);
659 setActionEnabled("add_folder_to_places", enabled);
660 }
661
662 void updateActions()
663 {
664 bool isRasterImage = false;
665 bool canSave = false;
666 bool isModified = false;
667 const KUrl url = mContextManager->currentUrl();
668
669 if (url.isValid()) {
670 isRasterImage = mContextManager->currentUrlIsRasterImage();
671 canSave = isRasterImage;
672 isModified = DocumentFactory::instance()->load(url)->isModified();
673 if (mCurrentMainPageId != ViewMainPageId) {
674 // Saving only makes sense if exactly one image is selected
675 QItemSelection selection = mThumbnailView->selectionModel()->selection();
676 QModelIndexList indexList = selection.indexes();
677 if (indexList.count() != 1) {
678 canSave = false;
679 }
680 }
681 }
682
683 KActionCollection* actionCollection = q->actionCollection();
684 actionCollection->action("file_save")->setEnabled(canSave && isModified);
685 actionCollection->action("file_save_as")->setEnabled(canSave);
686 actionCollection->action("file_print")->setEnabled(isRasterImage);
687 }
688
689 const char* sideBarConfigGroupName() const
690 {
691 const char* name = 0;
692 switch (mCurrentMainPageId) {
693 case StartMainPageId:
694 GV_WARN_AND_RETURN_VALUE(BROWSE_MODE_SIDE_BAR_GROUP, "mCurrentMainPageId == 'StartMainPageId'");
695 break;
696 case BrowseMainPageId:
697 name = BROWSE_MODE_SIDE_BAR_GROUP;
698 break;
699 case ViewMainPageId:
700 name = mViewMainPage->isFullScreenMode()
701 ? FULLSCREEN_MODE_SIDE_BAR_GROUP
702 : VIEW_MODE_SIDE_BAR_GROUP;
703 break;
704 }
705 return name;
706 }
707
708 void loadSideBarConfig()
709 {
710 static QMap<const char*, bool> defaultVisibility;
711 if (defaultVisibility.isEmpty()) {
712 defaultVisibility[BROWSE_MODE_SIDE_BAR_GROUP] = true;
713 defaultVisibility[VIEW_MODE_SIDE_BAR_GROUP] = true;
714 defaultVisibility[FULLSCREEN_MODE_SIDE_BAR_GROUP] = false;
715 }
716
717 const char* name = sideBarConfigGroupName();
718 KConfigGroup group(KGlobal::config(), name);
719 mSideBar->setVisible(group.readEntry(SIDE_BAR_IS_VISIBLE_KEY, defaultVisibility[name]));
720 mSideBar->setCurrentPage(GwenviewConfig::sideBarPage());
721 q->updateToggleSideBarAction();
722 }
723
724 void saveSideBarConfig() const
725 {
726 KConfigGroup group(KGlobal::config(), sideBarConfigGroupName());
727 group.writeEntry(SIDE_BAR_IS_VISIBLE_KEY, mSideBar->isVisible());
728 GwenviewConfig::setSideBarPage(mSideBar->currentPage());
729 }
730
731 void setScreenSaverEnabled(bool enabled)
732 {
733 // Always delete mNotificationRestrictions, it does not hurt
734 delete mNotificationRestrictions;
735 if (!enabled) {
736 mNotificationRestrictions = new KNotificationRestrictions(KNotificationRestrictions::ScreenSaver, q);
737 } else {
738 mNotificationRestrictions = 0;
739 }
740 }
741
742 void assignThumbnailProviderToThumbnailView(ThumbnailView* thumbnailView)
743 {
744 GV_RETURN_IF_FAIL(thumbnailView);
745 if (mActiveThumbnailView) {
746 mActiveThumbnailView->setThumbnailProvider(0);
747 }
748 thumbnailView->setThumbnailProvider(mThumbnailProvider);
749 mActiveThumbnailView = thumbnailView;
750 if (mActiveThumbnailView->isVisible()) {
751 mThumbnailProvider->stop();
752 mActiveThumbnailView->generateThumbnailsForItems();
753 }
754 }
755
756 void autoAssignThumbnailProvider()
757 {
758 if (mCurrentMainPageId == ViewMainPageId) {
759 if (q->windowState() & Qt::WindowFullScreen) {
760 assignThumbnailProviderToThumbnailView(mFullScreenContent->thumbnailBar());
761 } else {
762 assignThumbnailProviderToThumbnailView(mViewMainPage->thumbnailBar());
763 }
764 } else if (mCurrentMainPageId == BrowseMainPageId) {
765 assignThumbnailProviderToThumbnailView(mThumbnailView);
766 } else if (mCurrentMainPageId == StartMainPageId) {
767 assignThumbnailProviderToThumbnailView(mStartMainPage->recentFoldersView());
768 }
769 }
770};
771
772MainWindow::MainWindow()
773: KXmlGuiWindow(),
774 d(new MainWindow::Private)
775{
776 d->q = this;
777 d->mCurrentMainPageId = StartMainPageId;
778 d->mDirModel = new SortedDirModel(this);
779 d->setupContextManager();
780 d->setupThumbnailBarModel();
781 d->mGvCore = new GvCore(this, d->mDirModel);
782 d->mPreloader = new Preloader(this);
783 d->mNotificationRestrictions = 0;
784 d->mThumbnailProvider = new ThumbnailProvider();
785 d->mActiveThumbnailView = 0;
786 d->initDirModel();
787 d->setupWidgets();
788 d->setupActions();
789 d->setupUndoActions();
790 d->setupContextManagerItems();
791 d->setupFullScreenContent();
792 d->updateActions();
793 updatePreviousNextActions();
794 d->mSaveBar->initActionDependentWidgets();
795
796 createGUI();
797 loadConfig();
798
799 connect(DocumentFactory::instance(), SIGNAL(modifiedDocumentListChanged()),
800 SLOT(slotModifiedDocumentListChanged()));
801
802#ifdef KIPI_FOUND
803 d->mKIPIInterface = new KIPIInterface(this);
804 d->mKIPIExportAction->setKIPIInterface(d->mKIPIInterface);
805#endif
806 setAutoSaveSettings();
807}
808
809MainWindow::~MainWindow()
810{
811 if (GwenviewConfig::deleteThumbnailCacheOnExit()) {
812 const QString dir = ThumbnailProvider::thumbnailBaseDir();
813 if (QFile::exists(dir)) {
814 KIO::NetAccess::del(KUrl::fromPath(dir), this);
815 }
816 }
817 delete d->mThumbnailProvider;
818 delete d;
819}
820
821ContextManager* MainWindow::contextManager() const
822{
823 return d->mContextManager;
824}
825
826ViewMainPage* MainWindow::viewMainPage() const
827{
828 return d->mViewMainPage;
829}
830
831void MainWindow::setCaption(const QString& caption)
832{
833 // Keep a trace of caption to use it in slotModifiedDocumentListChanged()
834 d->mCaption = caption;
835 KXmlGuiWindow::setCaption(caption);
836}
837
838void MainWindow::setCaption(const QString& caption, bool modified)
839{
840 d->mCaption = caption;
841 KXmlGuiWindow::setCaption(caption, modified);
842}
843
844void MainWindow::slotModifiedDocumentListChanged()
845{
846 d->updateActions();
847
848 // Update caption
849 QList<KUrl> list = DocumentFactory::instance()->modifiedDocumentList();
850 bool modified = list.count() > 0;
851 setCaption(d->mCaption, modified);
852}
853
854void MainWindow::setInitialUrl(const KUrl& _url)
855{
856 Q_ASSERT(_url.isValid());
857 KUrl url = UrlUtils::fixUserEnteredUrl(_url);
858 if (url.protocol() == "http" || url.protocol() == "https") {
859 d->mGvCore->addUrlToRecentUrls(url);
860 }
861 if (UrlUtils::urlIsDirectory(url)) {
862 d->mBrowseAction->trigger();
863 openDirUrl(url);
864 } else {
865 d->mViewAction->trigger();
866 d->mViewMainPage->openUrl(url);
867 d->mContextManager->setUrlToSelect(url);
868 }
869}
870
871void MainWindow::startSlideShow()
872{
873 d->mViewAction->trigger();
874 // We need to wait until we have listed all images in the dirlister to
875 // start the slideshow because the SlideShow objects needs an image list to
876 // work.
877 d->mStartSlideShowWhenDirListerCompleted = true;
878}
879
880void MainWindow::setActiveViewModeAction(QAction* action)
881{
882 if (d->mCurrentMainPageId != StartMainPageId) {
883 d->saveSideBarConfig();
884 }
885 if (action == d->mViewAction) {
886 d->mCurrentMainPageId = ViewMainPageId;
887 // Switching to view mode
888 d->mViewStackedWidget->setCurrentWidget(d->mViewMainPage);
889 if (d->mViewMainPage->isEmpty()) {
890 openSelectedDocuments();
891 }
892 d->mPreloadDirectionIsForward = true;
893 QTimer::singleShot(VIEW_PRELOAD_DELAY, this, SLOT(preloadNextUrl()));
894 } else {
895 d->mCurrentMainPageId = BrowseMainPageId;
896 // Switching to browse mode
897 d->mViewStackedWidget->setCurrentWidget(d->mBrowseMainPage);
898 if (!d->mViewMainPage->isEmpty()
899 && KProtocolManager::supportsListing(d->mViewMainPage->url())) {
900 // Reset the view to spare resources, but don't do it if we can't
901 // browse the url, otherwise if the user starts Gwenview this way:
902 // gwenview http://example.com/example.png
903 // and switch to browse mode, switching back to view mode won't bring
904 // his image back.
905 d->mViewMainPage->reset();
906 }
907 setCaption(QString());
908 }
909 d->loadSideBarConfig();
910 d->autoAssignThumbnailProvider();
911
912 emit viewModeChanged();
913}
914
915void MainWindow::slotThumbnailViewIndexActivated(const QModelIndex& index)
916{
917 if (!index.isValid()) {
918 return;
919 }
920
921 KFileItem item = d->mDirModel->itemForIndex(index);
922 if (item.isDir()) {
923 // Item is a dir, open it
924 openDirUrl(item.url());
925 } else {
926 QString protocol = ArchiveUtils::protocolForMimeType(item.mimetype());
927 if (!protocol.isEmpty()) {
928 // Item is an archive, tweak url then open it
929 KUrl url = item.url();
930 url.setProtocol(protocol);
931 openDirUrl(url);
932 } else {
933 // Item is a document, switch to view mode
934 d->mViewAction->trigger();
935 }
936 }
937}
938
939static bool indexRowLessThan(const QModelIndex& i1, const QModelIndex& i2)
940{
941 return i1.row() < i2.row();
942}
943
944void MainWindow::openSelectedDocuments()
945{
946 if (d->mCurrentMainPageId != ViewMainPageId) {
947 return;
948 }
949
950 QModelIndex currentIndex = d->mThumbnailView->currentIndex();
951 if (!currentIndex.isValid()) {
952 return;
953 }
954
955 int count = 0;
956
957 KUrl::List urls;
958 KUrl currentUrl;
959 QModelIndex firstDocumentIndex;
960 QModelIndexList list = d->mThumbnailView->selectionModel()->selectedIndexes();
961 // Make 'list' follow the same order as 'mThumbnailView'
962 qSort(list.begin(), list.end(), indexRowLessThan);
963
964 Q_FOREACH(const QModelIndex& index, list) {
965 KFileItem item = d->mDirModel->itemForIndex(index);
966 if (!item.isNull() && !ArchiveUtils::fileItemIsDirOrArchive(item)) {
967 KUrl url = item.url();
968 if (!firstDocumentIndex.isValid()) {
969 firstDocumentIndex = index;
970 }
971 urls << url;
972 if (index == currentIndex) {
973 currentUrl = url;
974 }
975 ++count;
976 if (count == ViewMainPage::MaxViewCount) {
977 break;
978 }
979 }
980 }
981 if (urls.isEmpty()) {
982 // No image to display
983 return;
984 }
985 if (currentUrl.isEmpty()) {
986 // Current index is not selected, or it is not a document: set
987 // firstDocumentIndex as current
988 GV_RETURN_IF_FAIL(firstDocumentIndex.isValid());
989 d->mThumbnailView->selectionModel()->setCurrentIndex(firstDocumentIndex, QItemSelectionModel::Current);
990 currentUrl = urls.first();
991 }
992
993 d->mViewMainPage->openUrls(urls, currentUrl);
994}
995
996void MainWindow::goUp()
997{
998 if (d->mCurrentMainPageId == BrowseMainPageId) {
999 KUrl url = d->mContextManager->currentDirUrl();
1000 url = url.upUrl();
1001 openDirUrl(url);
1002 } else {
1003 d->mBrowseAction->trigger();
1004 }
1005}
1006
1007void MainWindow::showStartMainPage()
1008{
1009 if (d->mCurrentMainPageId != StartMainPageId) {
1010 d->saveSideBarConfig();
1011 d->mCurrentMainPageId = StartMainPageId;
1012 }
1013 d->setActionsDisabledOnStartMainPageEnabled(false);
1014
1015 d->mSideBar->hide();
1016 d->mViewStackedWidget->setCurrentWidget(d->mStartMainPage);
1017
1018 d->updateActions();
1019 updatePreviousNextActions();
1020 d->mContextManager->setCurrentDirUrl(KUrl());
1021 d->mContextManager->setCurrentUrl(KUrl());
1022
1023 d->autoAssignThumbnailProvider();
1024}
1025
1026void MainWindow::slotStartMainPageUrlSelected(const KUrl& url)
1027{
1028 d->setActionsDisabledOnStartMainPageEnabled(true);
1029
1030 if (d->mBrowseAction->isChecked()) {
1031 // Silently uncheck the action so that setInitialUrl() does the right thing
1032 SignalBlocker blocker(d->mBrowseAction);
1033 d->mBrowseAction->setChecked(false);
1034 }
1035
1036 setInitialUrl(url);
1037}
1038
1039void MainWindow::openDirUrl(const KUrl& url)
1040{
1041 const KUrl currentUrl = d->mContextManager->currentDirUrl();
1042
1043 if (url.equals(currentUrl, KUrl::CompareWithoutTrailingSlash)) {
1044 return;
1045 }
1046
1047 if (url.isParentOf(currentUrl)) {
1048 // Keep first child between url and currentUrl selected
1049 // If currentPath is "/home/user/photos/2008/event"
1050 // and wantedPath is "/home/user/photos"
1051 // pathToSelect should be "/home/user/photos/2008"
1052
1053 // To anyone considering using KUrl::toLocalFile() instead of
1054 // KUrl::path() here. Please don't, using KUrl::path() is the right
1055 // thing to do here.
1056 const QString currentPath = QDir::cleanPath(currentUrl.path(KUrl::RemoveTrailingSlash));
1057 const QString wantedPath = QDir::cleanPath(url.path(KUrl::RemoveTrailingSlash));
1058 const QChar separator('/');
1059 const int slashCount = wantedPath.count(separator);
1060 const QString pathToSelect = currentPath.section(separator, 0, slashCount + 1);
1061 KUrl urlToSelect = url;
1062 urlToSelect.setPath(pathToSelect);
1063 d->mContextManager->setUrlToSelect(urlToSelect);
1064 }
1065 d->mThumbnailProvider->stop();
1066 d->mContextManager->setCurrentDirUrl(url);
1067 d->mGvCore->addUrlToRecentFolders(url);
1068 d->mViewMainPage->reset();
1069}
1070
1071void MainWindow::toggleSideBar(bool on)
1072{
1073 d->mSideBar->setVisible(on);
1074}
1075
1076void MainWindow::updateToggleSideBarAction()
1077{
1078 SignalBlocker blocker(d->mToggleSideBarAction);
1079 bool visible = d->mSideBar->isVisible();
1080 d->mToggleSideBarAction->setChecked(visible);
1081
1082 QString text;
1083 if (QApplication::isRightToLeft()) {
1084 text = QString::fromUtf8(visible ? "▮→" : "▮←");
1085 } else {
1086 text = QString::fromUtf8(visible ? "▮←" : "▮→");
1087 }
1088 QString toolTip = visible ? i18nc("@info:tooltip", "Hide sidebar") : i18nc("@info:tooltip", "Show sidebar");
1089
1090 QList<QToolButton*> lst;
1091 lst << d->mBrowseMainPage->toggleSideBarButton()
1092 << d->mViewMainPage->toggleSideBarButton();
1093 Q_FOREACH(QToolButton * button, lst) {
1094 button->setText(text);
1095 button->setToolTip(toolTip);
1096 }
1097}
1098
1099void MainWindow::slotPartCompleted()
1100{
1101 d->updateActions();
1102 KUrl url = d->mViewMainPage->url();
1103 // remember the opened file
1104 if (!url.isEmpty()) {
1105 d->mFileOpenRecentAction->addUrl(url);
1106 }
1107 if (!KProtocolManager::supportsListing(url)) {
1108 return;
1109 }
1110
1111 KUrl dirUrl = url;
1112 dirUrl.setFileName(QString());
1113 if (dirUrl.equals(d->mContextManager->currentDirUrl(), KUrl::CompareWithoutTrailingSlash)) {
1114 QModelIndex index = d->mDirModel->indexForUrl(url);
1115 QItemSelectionModel* selectionModel = d->mThumbnailView->selectionModel();
1116 if (index.isValid() && !selectionModel->isSelected(index)) {
1117 // FIXME: QGV Reactivating this line prevents navigation to prev/next image
1118 //selectionModel->select(index, QItemSelectionModel::SelectCurrent);
1119 }
1120 } else {
1121 d->mContextManager->setCurrentDirUrl(dirUrl);
1122 d->mGvCore->addUrlToRecentFolders(dirUrl);
1123 }
1124}
1125
1126void MainWindow::slotSelectionChanged()
1127{
1128 if (d->mCurrentMainPageId == ViewMainPageId) {
1129 // The user selected a new file in the thumbnail view, since the
1130 // document view is visible, let's show it
1131 openSelectedDocuments();
1132 } else {
1133 // No document view, we need to load the document to set the undo group
1134 // of document factory to the correct QUndoStack
1135 QModelIndex index = d->mThumbnailView->currentIndex();
1136 KFileItem item;
1137 if (index.isValid()) {
1138 item = d->mDirModel->itemForIndex(index);
1139 }
1140 QUndoGroup* undoGroup = DocumentFactory::instance()->undoGroup();
1141 if (!item.isNull() && !ArchiveUtils::fileItemIsDirOrArchive(item)) {
1142 KUrl url = item.url();
1143 Document::Ptr doc = DocumentFactory::instance()->load(url);
1144 undoGroup->addStack(doc->undoStack());
1145 undoGroup->setActiveStack(doc->undoStack());
1146 } else {
1147 undoGroup->setActiveStack(0);
1148 }
1149 }
1150
1151 // Update UI
1152 d->updateActions();
1153 updatePreviousNextActions();
1154
1155 // Start preloading
1156 int preloadDelay = d->mCurrentMainPageId == ViewMainPageId ? VIEW_PRELOAD_DELAY : BROWSE_PRELOAD_DELAY;
1157 QTimer::singleShot(preloadDelay, this, SLOT(preloadNextUrl()));
1158}
1159
1160void MainWindow::slotCurrentDirUrlChanged(const KUrl& url)
1161{
1162 if (url.isValid()) {
1163 d->mUrlNavigator->setLocationUrl(url);
1164 d->mGoUpAction->setEnabled(url.path() != "/");
1165 } else {
1166 d->mGoUpAction->setEnabled(false);
1167 }
1168}
1169
1170void MainWindow::slotDirModelNewItems()
1171{
1172 if (d->mThumbnailView->selectionModel()->hasSelection()) {
1173 updatePreviousNextActions();
1174 }
1175}
1176
1177void MainWindow::slotDirListerCompleted()
1178{
1179 if (d->mStartSlideShowWhenDirListerCompleted) {
1180 d->mStartSlideShowWhenDirListerCompleted = false;
1181 QTimer::singleShot(0, d->mToggleSlideShowAction, SLOT(trigger()));
1182 }
1183 if (d->mThumbnailView->selectionModel()->hasSelection()) {
1184 updatePreviousNextActions();
1185 } else if (!d->mContextManager->urlToSelect().isValid()) {
1186 d->goToFirstDocument();
1187 }
1188 d->mThumbnailView->scrollToSelectedIndex();
1189 d->mViewMainPage->thumbnailBar()->scrollToSelectedIndex();
1190}
1191
1192void MainWindow::goToPrevious()
1193{
1194 d->goTo(-1);
1195}
1196
1197void MainWindow::goToNext()
1198{
1199 d->goTo(1);
1200}
1201
1202void MainWindow::goToFirst()
1203{
1204 d->goToFirstDocument();
1205}
1206
1207void MainWindow::goToLast()
1208{
1209 d->goToLastDocument();
1210}
1211
1212void MainWindow::goToUrl(const KUrl& url)
1213{
1214 if (d->mCurrentMainPageId == ViewMainPageId) {
1215 d->mViewMainPage->openUrl(url);
1216 }
1217 KUrl dirUrl = url;
1218 dirUrl.setFileName("");
1219 if (!dirUrl.equals(d->mContextManager->currentDirUrl(), KUrl::CompareWithoutTrailingSlash)) {
1220 d->mContextManager->setCurrentDirUrl(dirUrl);
1221 d->mGvCore->addUrlToRecentFolders(dirUrl);
1222 }
1223 d->mContextManager->setUrlToSelect(url);
1224}
1225
1226void MainWindow::updatePreviousNextActions()
1227{
1228 bool hasPrevious;
1229 bool hasNext;
1230 QModelIndex currentIndex = d->mContextManager->selectionModel()->currentIndex();
1231 if (currentIndex.isValid() && !d->indexIsDirOrArchive(currentIndex)) {
1232 int row = currentIndex.row();
1233 QModelIndex prevIndex = d->mDirModel->index(row - 1, 0);
1234 QModelIndex nextIndex = d->mDirModel->index(row + 1, 0);
1235 hasPrevious = prevIndex.isValid() && !d->indexIsDirOrArchive(prevIndex);
1236 hasNext = nextIndex.isValid() && !d->indexIsDirOrArchive(nextIndex);
1237 } else {
1238 hasPrevious = false;
1239 hasNext = false;
1240 }
1241
1242 d->mGoToPreviousAction->setEnabled(hasPrevious);
1243 d->mGoToNextAction->setEnabled(hasNext);
1244 d->mGoToFirstAction->setEnabled(hasPrevious);
1245 d->mGoToLastAction->setEnabled(hasNext);
1246}
1247
1248void MainWindow::leaveFullScreen()
1249{
1250 if (d->mFullScreenAction->isChecked()) {
1251 d->mFullScreenAction->trigger();
1252 }
1253}
1254
1255void MainWindow::toggleFullScreen(bool checked)
1256{
1257 setUpdatesEnabled(false);
1258 d->saveSideBarConfig();
1259 if (checked) {
1260 // Save MainWindow config now, this way if we quit while in
1261 // fullscreen, we are sure latest MainWindow changes are remembered.
1262 saveMainWindowSettings(autoSaveConfigGroup());
1263 resetAutoSaveSettings();
1264
1265 // Save state
1266 d->mStateBeforeFullScreen.mToolBarVisible = toolBar()->isVisible();
1267 d->mStateBeforeFullScreen.mWindowState = windowState();
1268
1269 // Go full screen
1270 setWindowState(windowState() | Qt::WindowFullScreen);
1271 menuBar()->hide();
1272 toolBar()->hide();
1273
1274 QApplication::setPalette(d->mGvCore->palette(GvCore::FullScreenPalette));
1275 d->mFullScreenContent->setFullScreenMode(true);
1276 d->mBrowseMainPage->setFullScreenMode(true);
1277 d->mViewMainPage->setFullScreenMode(true);
1278 d->mSaveBar->setFullScreenMode(true);
1279 d->setScreenSaverEnabled(false);
1280
1281 // HACK: Only load sidebar config now, because it looks at
1282 // ViewMainPage fullScreenMode property to determine the sidebar
1283 // config group.
1284 d->loadSideBarConfig();
1285 } else {
1286 setAutoSaveSettings();
1287
1288 // Back to normal
1289 QApplication::setPalette(d->mGvCore->palette(GvCore::NormalPalette));
1290 d->mFullScreenContent->setFullScreenMode(false);
1291 d->mBrowseMainPage->setFullScreenMode(false);
1292 d->mViewMainPage->setFullScreenMode(false);
1293 d->mSlideShow->stop();
1294 d->mSaveBar->setFullScreenMode(false);
1295 setWindowState(d->mStateBeforeFullScreen.mWindowState);
1296 menuBar()->setVisible(d->mShowMenuBarAction->isChecked());
1297 toolBar()->setVisible(d->mStateBeforeFullScreen.mToolBarVisible);
1298
1299 d->setScreenSaverEnabled(true);
1300
1301 // Keep this after mViewMainPage->setFullScreenMode(false).
1302 // See call to loadSideBarConfig() above.
1303 d->loadSideBarConfig();
1304
1305 // See resizeEvent
1306 d->mFullScreenLeftAt = QDateTime::currentDateTime();
1307 }
1308 setUpdatesEnabled(true);
1309 d->autoAssignThumbnailProvider();
1310}
1311
1312void MainWindow::saveCurrent()
1313{
1314 d->mGvCore->save(d->mContextManager->currentUrl());
1315}
1316
1317void MainWindow::saveCurrentAs()
1318{
1319 d->mGvCore->saveAs(d->mContextManager->currentUrl());
1320}
1321
1322void MainWindow::reload()
1323{
1324 if (d->mCurrentMainPageId == ViewMainPageId) {
1325 d->mViewMainPage->reload();
1326 } else {
1327 d->mBrowseMainPage->reload();
1328 }
1329}
1330
1331void MainWindow::openFile()
1332{
1333 KUrl dirUrl = d->mContextManager->currentDirUrl();
1334
1335 KFileDialog dialog(dirUrl, QString(), this);
1336 dialog.setCaption(i18nc("@title:window", "Open Image"));
1337 const QStringList mimeFilter = MimeTypeUtils::imageMimeTypes();
1338 dialog.setMimeFilter(mimeFilter);
1339 dialog.setOperationMode(KFileDialog::Opening);
1340 if (!dialog.exec()) {
1341 return;
1342 }
1343
1344 openUrl(dialog.selectedUrl());
1345}
1346
1347void MainWindow::openUrl(const KUrl& url)
1348{
1349 d->setActionsDisabledOnStartMainPageEnabled(true);
1350 d->mViewAction->trigger();
1351 d->mViewMainPage->openUrl(url);
1352 d->mContextManager->setUrlToSelect(url);
1353}
1354
1355void MainWindow::showDocumentInFullScreen(const KUrl& url)
1356{
1357 d->mViewMainPage->openUrl(url);
1358 d->mContextManager->setUrlToSelect(url);
1359 d->mFullScreenAction->trigger();
1360}
1361
1362void MainWindow::toggleSlideShow()
1363{
1364 if (d->mSlideShow->isRunning()) {
1365 d->mSlideShow->stop();
1366 } else {
1367 if (!d->mViewAction->isChecked()) {
1368 d->mViewAction->trigger();
1369 }
1370 if (!d->mFullScreenAction->isChecked()) {
1371 d->mFullScreenAction->trigger();
1372 }
1373 QList<KUrl> list;
1374 for (int pos = 0; pos < d->mDirModel->rowCount(); ++pos) {
1375 QModelIndex index = d->mDirModel->index(pos, 0);
1376 KFileItem item = d->mDirModel->itemForIndex(index);
1377 MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item);
1378 switch (kind) {
1379 case MimeTypeUtils::KIND_SVG_IMAGE:
1380 case MimeTypeUtils::KIND_RASTER_IMAGE:
1381 case MimeTypeUtils::KIND_VIDEO:
1382 list << item.url();
1383 break;
1384 default:
1385 break;
1386 }
1387 }
1388 d->mSlideShow->start(list);
1389 }
1390 updateSlideShowAction();
1391}
1392
1393void MainWindow::updateSlideShowAction()
1394{
1395 if (d->mSlideShow->isRunning()) {
1396 d->mToggleSlideShowAction->setText(i18n("Stop Slideshow"));
1397 d->mToggleSlideShowAction->setIcon(KIcon("media-playback-pause"));
1398 } else {
1399 d->mToggleSlideShowAction->setText(i18n("Start Slideshow"));
1400 d->mToggleSlideShowAction->setIcon(KIcon("media-playback-start"));
1401 }
1402}
1403
1404bool MainWindow::queryClose()
1405{
1406 saveConfig();
1407 d->saveSideBarConfig();
1408 QList<KUrl> list = DocumentFactory::instance()->modifiedDocumentList();
1409 if (list.size() == 0) {
1410 return true;
1411 }
1412
1413 KGuiItem yes(i18n("Save All Changes"), "document-save-all");
1414 KGuiItem no(i18n("Discard Changes"));
1415 QString msg = i18np("One image has been modified.", "%1 images have been modified.", list.size())
1416 + '\n'
1417 + i18n("If you quit now, your changes will be lost.");
1418 int answer = KMessageBox::warningYesNoCancel(
1419 this,
1420 msg,
1421 QString() /* caption */,
1422 yes,
1423 no);
1424
1425 switch (answer) {
1426 case KMessageBox::Yes:
1427 d->mGvCore->saveAll();
1428 // We need to wait a bit because the DocumentFactory is notified about
1429 // saved documents through a queued connection.
1430 qApp->processEvents();
1431 return DocumentFactory::instance()->modifiedDocumentList().isEmpty();
1432
1433 case KMessageBox::No:
1434 return true;
1435
1436 default: // cancel
1437 return false;
1438 }
1439}
1440
1441void MainWindow::showConfigDialog()
1442{
1443 ConfigDialog dialog(this);
1444 connect(&dialog, SIGNAL(settingsChanged(QString)), SLOT(loadConfig()));
1445 dialog.exec();
1446}
1447
1448void MainWindow::toggleMenuBar()
1449{
1450 if (!d->mFullScreenAction->isChecked()) {
1451 menuBar()->setVisible(d->mShowMenuBarAction->isChecked());
1452 }
1453}
1454
1455void MainWindow::loadConfig()
1456{
1457 d->mDirModel->setBlackListedExtensions(GwenviewConfig::blackListedExtensions());
1458 d->mDirModel->adjustKindFilter(MimeTypeUtils::KIND_VIDEO, GwenviewConfig::listVideos());
1459
1460 d->mFileOpenRecentAction->loadEntries(KConfigGroup(KGlobal::config(), "Recent Files"));
1461 d->mStartMainPage->loadConfig();
1462 d->mViewMainPage->loadConfig();
1463 d->mBrowseMainPage->loadConfig();
1464}
1465
1466void MainWindow::saveConfig()
1467{
1468 d->mFileOpenRecentAction->saveEntries(KConfigGroup(KGlobal::config(), "Recent Files"));
1469 d->mViewMainPage->saveConfig();
1470 d->mBrowseMainPage->saveConfig();
1471}
1472
1473void MainWindow::print()
1474{
1475 if (!d->mContextManager->currentUrlIsRasterImage()) {
1476 return;
1477 }
1478
1479 Document::Ptr doc = DocumentFactory::instance()->load(d->mContextManager->currentUrl());
1480 PrintHelper printHelper(this);
1481 printHelper.print(doc);
1482}
1483
1484void MainWindow::preloadNextUrl()
1485{
1486 static bool disablePreload = qgetenv("GV_MAX_UNREFERENCED_IMAGES") == "0";
1487 if (disablePreload) {
1488 kDebug() << "Preloading disabled";
1489 return;
1490 }
1491 QItemSelection selection = d->mThumbnailView->selectionModel()->selection();
1492 if (selection.size() != 1) {
1493 return;
1494 }
1495
1496 QModelIndexList indexList = selection.indexes();
1497 if (indexList.isEmpty()) {
1498 return;
1499 }
1500
1501 QModelIndex index = indexList.at(0);
1502 if (!index.isValid()) {
1503 return;
1504 }
1505
1506 if (d->mCurrentMainPageId == ViewMainPageId) {
1507 // If we are in view mode, preload the next url, otherwise preload the
1508 // selected one
1509 int offset = d->mPreloadDirectionIsForward ? 1 : -1;
1510 index = d->mDirModel->sibling(index.row() + offset, index.column(), index);
1511 if (!index.isValid()) {
1512 return;
1513 }
1514 }
1515
1516 KFileItem item = d->mDirModel->itemForIndex(index);
1517 if (!ArchiveUtils::fileItemIsDirOrArchive(item)) {
1518 KUrl url = item.url();
1519 if (url.isLocalFile()) {
1520 QSize size = d->mViewStackedWidget->size();
1521 d->mPreloader->preload(url, size);
1522 }
1523 }
1524}
1525
1526QSize MainWindow::sizeHint() const
1527{
1528 return KXmlGuiWindow::sizeHint().expandedTo(QSize(750, 500));
1529}
1530
1531void MainWindow::showEvent(QShowEvent *event)
1532{
1533 // We need to delay initializing the action state until the menu bar has
1534 // been initialized, that's why it's done only in the showEvent()
1535 d->mShowMenuBarAction->setChecked(menuBar()->isVisible());
1536 KXmlGuiWindow::showEvent(event);
1537}
1538
1539void MainWindow::resizeEvent(QResizeEvent* event)
1540{
1541 KXmlGuiWindow::resizeEvent(event);
1542 // This is a small hack to execute code after leaving fullscreen, and after
1543 // the window has been resized back to its former size.
1544 if (d->mFullScreenLeftAt.isValid() && d->mFullScreenLeftAt.secsTo(QDateTime::currentDateTime()) < 2) {
1545 if (d->mCurrentMainPageId == BrowseMainPageId) {
1546 d->mThumbnailView->scrollToSelectedIndex();
1547 }
1548 d->mFullScreenLeftAt = QDateTime();
1549 }
1550}
1551
1552void MainWindow::setDistractionFreeMode(bool value)
1553{
1554 d->mFullScreenContent->setDistractionFreeMode(value);
1555}
1556
1557void MainWindow::saveProperties(KConfigGroup& group)
1558{
1559 group.writeEntry(SESSION_CURRENT_PAGE_KEY, int(d->mCurrentMainPageId));
1560 group.writeEntry(SESSION_URL_KEY, d->mContextManager->currentUrl());
1561}
1562
1563void MainWindow::readProperties(const KConfigGroup& group)
1564{
1565 MainPageId pageId = MainPageId(group.readEntry(SESSION_CURRENT_PAGE_KEY, int(StartMainPageId)));
1566 if (pageId == StartMainPageId) {
1567 d->mCurrentMainPageId = StartMainPageId;
1568 showStartMainPage();
1569 } else if (pageId == BrowseMainPageId) {
1570 d->mBrowseAction->trigger();
1571 } else {
1572 d->mViewAction->trigger();
1573 }
1574 KUrl url = group.readEntry(SESSION_URL_KEY, KUrl());
1575 if (!url.isValid()) {
1576 kWarning() << "Invalid url!";
1577 return;
1578 }
1579 goToUrl(url);
1580}
1581
1582void MainWindow::showFirstDocumentReached()
1583{
1584 if (d->mCurrentMainPageId != ViewMainPageId) {
1585 return;
1586 }
1587 HudButtonBox* dlg = new HudButtonBox;
1588 dlg->setText(i18n("You reached the first document, what do you want to do?"));
1589 dlg->addButton(i18n("Stay There"));
1590 dlg->addAction(d->mGoToLastAction, i18n("Go to the Last Document"));
1591 dlg->addAction(d->mBrowseAction, i18n("Go Back to the Document List"));
1592 dlg->addCountDown(15000);
1593 d->mViewMainPage->showMessageWidget(dlg, Qt::AlignCenter);
1594}
1595
1596void MainWindow::showLastDocumentReached()
1597{
1598 if (d->mCurrentMainPageId != ViewMainPageId) {
1599 return;
1600 }
1601 HudButtonBox* dlg = new HudButtonBox;
1602 dlg->setText(i18n("You reached the last document, what do you want to do?"));
1603 dlg->addButton(i18n("Stay There"));
1604 dlg->addAction(d->mGoToFirstAction, i18n("Go to the First Document"));
1605 dlg->addAction(d->mBrowseAction, i18n("Go Back to the Document List"));
1606 dlg->addCountDown(15000);
1607 d->mViewMainPage->showMessageWidget(dlg, Qt::AlignCenter);
1608}
1609
1610} // namespace
1611