2
Gwenview: an image viewer
3
Copyright 2007 AurĆ©lien GĆ¢teau <agateau@kde.org>
5
This program is free software; you can redistribute it and/or
6
modify it under the terms of the GNU General Public License
7
as published by the Free Software Foundation; either version 2
8
of the License, or (at your option) any later version.
10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public License for more details.
15
You should have received a copy of the GNU General Public License
16
along with this program; if not, write to the Free Software
17
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20
#include "viewmainpage.moc"
24
#include <QItemSelectionModel>
27
#include <QToolButton>
28
#include <QVBoxLayout>
31
#include <kactioncollection.h>
32
#include <kactioncategory.h>
36
#include <kmessagebox.h>
37
#include <ktoggleaction.h>
40
#include "fileoperations.h"
42
#include <lib/document/document.h>
43
#include <lib/documentview/abstractdocumentviewadapter.h>
44
#include <lib/documentview/documentview.h>
45
#include <lib/documentview/documentviewcontainer.h>
46
#include <lib/documentview/documentviewcontroller.h>
47
#include <lib/documentview/documentviewsynchronizer.h>
48
#include <lib/gwenviewconfig.h>
49
#include <lib/paintutils.h>
50
#include <lib/semanticinfo/sorteddirmodel.h>
51
#include <lib/slidecontainer.h>
52
#include <lib/slideshow.h>
53
#include <lib/statusbartoolbutton.h>
54
#include <lib/thumbnailview/thumbnailbarview.h>
55
#include <lib/zoomwidget.h>
64
#define LOG(x) kDebug() << x
69
const int ViewMainPage::MaxViewCount = 6;
71
static QString rgba(const QColor &color)
73
return QString::fromAscii("rgba(%1, %2, %3, %4)")
80
static QString gradient(Qt::Orientation orientation, const QColor &color, int value)
83
if (orientation == Qt::Horizontal) {
91
"qlineargradient(x1:0, y1:0, x2:%1, y2:%2,"
92
"stop:0 %3, stop: 1 %4)";
96
.arg(rgba(PaintUtils::adjustedHsv(color, 0, 0, qMin(255 - color.value(), value / 2))))
97
.arg(rgba(PaintUtils::adjustedHsv(color, 0, 0, -qMin(color.value(), value / 2))))
102
* Layout of mThumbnailSplitter is:
104
* +-mThumbnailSplitter------------------------------------------------+
105
* |+-mAdapterContainer-----------------------------------------------+|
106
* ||+-mDocumentViewContainer----------------------------------------+||
107
* |||+-DocumentView----------------++-DocumentView-----------------+|||
114
* |||+-----------------------------++------------------------------+|||
115
* ||+---------------------------------------------------------------+||
116
* ||+-mToolContainer------------------------------------------------+||
118
* ||+---------------------------------------------------------------+||
119
* ||+-mStatusBarContainer-------------------------------------------+||
120
* |||[mToggleSideBarButton][mToggleThumbnailBarButton] [mZoomWidget]|||
121
* ||+---------------------------------------------------------------+||
122
* |+-----------------------------------------------------------------+|
123
* |===================================================================|
124
* |+-mThumbnailBar---------------------------------------------------+|
127
* |+-----------------------------------------------------------------+|
128
* +-------------------------------------------------------------------+
130
struct ViewMainPagePrivate {
132
SlideShow* mSlideShow;
133
KActionCollection* mActionCollection;
134
QSplitter *mThumbnailSplitter;
135
QWidget* mAdapterContainer;
136
DocumentViewController* mDocumentViewController;
137
QList<DocumentView*> mDocumentViews;
138
DocumentViewSynchronizer* mSynchronizer;
139
QToolButton* mToggleSideBarButton;
140
QToolButton* mToggleThumbnailBarButton;
141
ZoomWidget* mZoomWidget;
142
DocumentViewContainer* mDocumentViewContainer;
143
SlideContainer* mToolContainer;
144
QWidget* mStatusBarContainer;
145
ThumbnailBarView* mThumbnailBar;
146
KToggleAction* mToggleThumbnailBarAction;
147
KToggleAction* mSynchronizeAction;
148
QCheckBox* mSynchronizeCheckBox;
150
bool mFullScreenMode;
151
QPalette mNormalPalette;
152
QPalette mFullScreenPalette;
154
bool mThumbnailBarVisibleBeforeFullScreen;
156
void setupThumbnailBar()
158
mThumbnailBar = new ThumbnailBarView;
159
ThumbnailBarItemDelegate* delegate = new ThumbnailBarItemDelegate(mThumbnailBar);
160
mThumbnailBar->setItemDelegate(delegate);
161
mThumbnailBar->setVisible(GwenviewConfig::thumbnailBarIsVisible());
162
mThumbnailBar->setSelectionMode(QAbstractItemView::ExtendedSelection);
165
void setupThumbnailBarStyleSheet()
167
Qt::Orientation orientation = mThumbnailBar->orientation();
168
QColor bgColor = mNormalPalette.color(QPalette::Normal, QPalette::Window);
169
QColor bgSelColor = mNormalPalette.color(QPalette::Normal, QPalette::Highlight);
171
// Avoid dark and bright colors
172
bgColor.setHsv(bgColor.hue(), bgColor.saturation(), (127 + 3 * bgColor.value()) / 4);
174
QColor leftBorderColor = PaintUtils::adjustedHsv(bgColor, 0, 0, qMin(20, 255 - bgColor.value()));
175
QColor rightBorderColor = PaintUtils::adjustedHsv(bgColor, 0, 0, -qMin(40, bgColor.value()));
176
QColor borderSelColor = PaintUtils::adjustedHsv(bgSelColor, 0, 0, -qMin(60, bgSelColor.value()));
179
"#thumbnailBarView {"
180
" background-color: rgba(0, 0, 0, 10%);"
185
" background-color: %1;"
186
" border-left: 1px solid %2;"
187
" border-right: 1px solid %3;"
189
itemCss = itemCss.arg(
190
gradient(orientation, bgColor, 46),
191
gradient(orientation, leftBorderColor, 36),
192
gradient(orientation, rightBorderColor, 26));
195
"QListView::item:selected {"
196
" background-color: %1;"
197
" border-left: 1px solid %2;"
198
" border-right: 1px solid %2;"
200
itemSelCss = itemSelCss.arg(
201
gradient(orientation, bgSelColor, 56),
202
rgba(borderSelColor));
204
QString css = viewCss + itemCss + itemSelCss;
205
if (orientation == Qt::Vertical) {
206
css.replace("left", "top").replace("right", "bottom");
209
mThumbnailBar->setStyleSheet(css);
212
void setupAdapterContainer()
214
mAdapterContainer = new QWidget;
216
QVBoxLayout* layout = new QVBoxLayout(mAdapterContainer);
217
layout->setMargin(0);
218
layout->setSpacing(0);
219
mDocumentViewContainer = new DocumentViewContainer;
220
mDocumentViewContainer->setAutoFillBackground(true);
221
mDocumentViewContainer->setBackgroundRole(QPalette::Base);
222
layout->addWidget(mDocumentViewContainer);
223
layout->addWidget(mToolContainer);
224
layout->addWidget(mStatusBarContainer);
227
void setupDocumentViewController()
229
mDocumentViewController = new DocumentViewController(mActionCollection, q);
230
mDocumentViewController->setZoomWidget(mZoomWidget);
231
mDocumentViewController->setToolContainer(mToolContainer);
232
mSynchronizer = new DocumentViewSynchronizer(q);
235
DocumentView* createDocumentView()
237
DocumentView* view = mDocumentViewContainer->createView();
239
// Connect context menu
240
QObject::connect(view, SIGNAL(contextMenuRequested()),
241
q, SLOT(showContextMenu()));
243
QObject::connect(view, SIGNAL(completed()),
244
q, SIGNAL(completed()));
245
QObject::connect(view, SIGNAL(previousImageRequested()),
246
q, SIGNAL(previousImageRequested()));
247
QObject::connect(view, SIGNAL(nextImageRequested()),
248
q, SIGNAL(nextImageRequested()));
249
QObject::connect(view, SIGNAL(captionUpdateRequested(QString)),
250
q, SIGNAL(captionUpdateRequested(QString)));
251
QObject::connect(view, SIGNAL(toggleFullScreenRequested()),
252
q, SIGNAL(toggleFullScreenRequested()));
253
QObject::connect(view, SIGNAL(focused(DocumentView*)),
254
q, SLOT(slotViewFocused(DocumentView*)));
255
QObject::connect(view, SIGNAL(hudTrashClicked(DocumentView*)),
256
q, SLOT(trashView(DocumentView*)));
257
QObject::connect(view, SIGNAL(hudDeselectClicked(DocumentView*)),
258
q, SLOT(deselectView(DocumentView*)));
260
QObject::connect(view, SIGNAL(videoFinished()),
261
mSlideShow, SLOT(resumeAndGoToNextUrl()));
263
mDocumentViews << view;
268
void deleteDocumentView(DocumentView* view)
270
mDocumentViewContainer->deleteView(view);
271
mDocumentViews.removeOne(view);
274
void setupToolContainer()
276
mToolContainer = new SlideContainer;
279
void setupStatusBar()
281
mStatusBarContainer = new QWidget;
282
mToggleSideBarButton = new StatusBarToolButton;
283
mToggleThumbnailBarButton = new StatusBarToolButton;
284
mZoomWidget = new ZoomWidget;
285
mSynchronizeCheckBox = new QCheckBox(i18n("Synchronize"));
286
mSynchronizeCheckBox->hide();
288
QHBoxLayout* layout = new QHBoxLayout(mStatusBarContainer);
289
layout->setMargin(0);
290
layout->setSpacing(0);
291
layout->addWidget(mToggleSideBarButton);
292
layout->addWidget(mToggleThumbnailBarButton);
293
layout->addStretch();
294
layout->addWidget(mSynchronizeCheckBox);
295
layout->addStretch();
296
layout->addWidget(mZoomWidget);
301
Qt::Orientation orientation = GwenviewConfig::thumbnailBarOrientation();
302
mThumbnailSplitter = new Splitter(orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal, q);
303
mThumbnailBar->setOrientation(orientation);
304
mThumbnailBar->setRowCount(GwenviewConfig::thumbnailBarRowCount());
305
mThumbnailSplitter->addWidget(mAdapterContainer);
306
mThumbnailSplitter->addWidget(mThumbnailBar);
307
mThumbnailSplitter->setSizes(GwenviewConfig::thumbnailSplitterSizes());
309
QVBoxLayout* layout = new QVBoxLayout(q);
310
layout->setMargin(0);
311
layout->addWidget(mThumbnailSplitter);
316
QPalette palette = mFullScreenMode ? mFullScreenPalette : mNormalPalette;
317
mDocumentViewContainer->setPalette(palette);
320
void saveSplitterConfig()
322
if (mThumbnailBar->isVisible()) {
323
GwenviewConfig::setThumbnailSplitterSizes(mThumbnailSplitter->sizes());
327
DocumentView* currentView() const {
328
return mDocumentViewController->view();
331
void setCurrentView(DocumentView* view)
333
DocumentView* oldView = currentView();
334
if (view == oldView) {
338
oldView->setCurrent(false);
340
view->setCurrent(true);
341
mDocumentViewController->setView(view);
342
mSynchronizer->setCurrentView(view);
344
QModelIndex index = indexForView(view);
345
if (!index.isValid()) {
346
kWarning() << "No index found for current view";
349
mThumbnailBar->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Current);
352
QModelIndex indexForView(DocumentView* view) const {
353
KUrl url = view->url();
354
if (!url.isValid()) {
355
kWarning() << "View does not display any document!";
356
return QModelIndex();
359
// FIXME: Ugly coupling!
360
SortedDirModel* model = static_cast<SortedDirModel*>(mThumbnailBar->model());
361
return model->indexForUrl(url);
365
ViewMainPage::ViewMainPage(QWidget* parent, SlideShow* slideShow, KActionCollection* actionCollection)
367
, d(new ViewMainPagePrivate)
370
d->mSlideShow = slideShow;
371
d->mActionCollection = actionCollection;
372
d->mFullScreenMode = false;
373
d->mCompareMode = false;
374
d->mThumbnailBarVisibleBeforeFullScreen = false;
375
d->mFullScreenPalette = QPalette(palette());
376
d->mFullScreenPalette.setColor(QPalette::Base, Qt::black);
377
d->mFullScreenPalette.setColor(QPalette::Text, Qt::white);
379
QShortcut* toggleFullScreenShortcut = new QShortcut(this);
380
toggleFullScreenShortcut->setKey(Qt::Key_Return);
381
connect(toggleFullScreenShortcut, SIGNAL(activated()), SIGNAL(toggleFullScreenRequested()));
383
d->setupToolContainer();
386
d->setupAdapterContainer();
388
d->setupThumbnailBar();
392
d->setupDocumentViewController();
394
KActionCategory* view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection);
396
d->mToggleThumbnailBarAction = view->add<KToggleAction>(QString("toggle_thumbnailbar"));
397
d->mToggleThumbnailBarAction->setText(i18n("Thumbnail Bar"));
398
d->mToggleThumbnailBarAction->setIcon(KIcon("folder-image"));
399
d->mToggleThumbnailBarAction->setShortcut(Qt::CTRL | Qt::Key_B);
400
d->mToggleThumbnailBarAction->setChecked(GwenviewConfig::thumbnailBarIsVisible());
401
connect(d->mToggleThumbnailBarAction, SIGNAL(triggered(bool)),
402
this, SLOT(setThumbnailBarVisibility(bool)));
403
d->mToggleThumbnailBarButton->setDefaultAction(d->mToggleThumbnailBarAction);
405
d->mSynchronizeAction = view->add<KToggleAction>("synchronize_views");
406
d->mSynchronizeAction->setText(i18n("Synchronize"));
407
d->mSynchronizeAction->setShortcut(Qt::CTRL | Qt::Key_Y);
408
connect(d->mSynchronizeAction, SIGNAL(toggled(bool)),
409
d->mSynchronizer, SLOT(setActive(bool)));
410
// Ensure mSynchronizeAction and mSynchronizeCheckBox are in sync
411
connect(d->mSynchronizeAction, SIGNAL(toggled(bool)),
412
d->mSynchronizeCheckBox, SLOT(setChecked(bool)));
413
connect(d->mSynchronizeCheckBox, SIGNAL(toggled(bool)),
414
d->mSynchronizeAction, SLOT(setChecked(bool)));
417
ViewMainPage::~ViewMainPage()
422
void ViewMainPage::loadConfig()
424
// FIXME: Not symetric with saveConfig(). Check if it matters.
425
Q_FOREACH(DocumentView * view, d->mDocumentViews) {
426
view->loadAdapterConfig();
429
Qt::Orientation orientation = GwenviewConfig::thumbnailBarOrientation();
430
d->mThumbnailSplitter->setOrientation(orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal);
431
d->mThumbnailBar->setOrientation(orientation);
432
d->setupThumbnailBarStyleSheet();
434
int oldRowCount = d->mThumbnailBar->rowCount();
435
int newRowCount = GwenviewConfig::thumbnailBarRowCount();
436
if (oldRowCount != newRowCount) {
437
d->mThumbnailBar->setUpdatesEnabled(false);
438
int gridSize = d->mThumbnailBar->gridSize().width();
440
d->mThumbnailBar->setRowCount(newRowCount);
442
// Adjust splitter to ensure thumbnail size remains the same
443
int delta = (newRowCount - oldRowCount) * gridSize;
444
QList<int> sizes = d->mThumbnailSplitter->sizes();
445
Q_ASSERT(sizes.count() == 2);
448
d->mThumbnailSplitter->setSizes(sizes);
450
d->mThumbnailBar->setUpdatesEnabled(true);
454
void ViewMainPage::saveConfig()
456
d->saveSplitterConfig();
457
GwenviewConfig::setThumbnailBarIsVisible(d->mToggleThumbnailBarAction->isChecked());
460
void ViewMainPage::setThumbnailBarVisibility(bool visible)
462
d->saveSplitterConfig();
463
d->mThumbnailBar->setVisible(visible);
466
int ViewMainPage::statusBarHeight() const
468
return d->mStatusBarContainer->height();
471
void ViewMainPage::setFullScreenMode(bool fullScreenMode)
473
d->mFullScreenMode = fullScreenMode;
474
d->mStatusBarContainer->setVisible(!fullScreenMode);
476
if (fullScreenMode) {
477
d->mThumbnailBarVisibleBeforeFullScreen = d->mToggleThumbnailBarAction->isChecked();
478
if (d->mThumbnailBarVisibleBeforeFullScreen) {
479
d->mToggleThumbnailBarAction->trigger();
481
} else if (d->mThumbnailBarVisibleBeforeFullScreen) {
482
d->mToggleThumbnailBarAction->trigger();
484
d->mToggleThumbnailBarAction->setEnabled(!fullScreenMode);
487
bool ViewMainPage::isFullScreenMode() const
489
return d->mFullScreenMode;
492
ThumbnailBarView* ViewMainPage::thumbnailBar() const
494
return d->mThumbnailBar;
497
inline void addActionToMenu(KMenu* menu, KActionCollection* actionCollection, const char* name)
499
QAction* action = actionCollection->action(name);
501
menu->addAction(action);
505
void ViewMainPage::showContextMenu()
508
addActionToMenu(&menu, d->mActionCollection, "fullscreen");
510
addActionToMenu(&menu, d->mActionCollection, "go_previous");
511
addActionToMenu(&menu, d->mActionCollection, "go_next");
512
if (d->currentView()->canZoom()) {
514
addActionToMenu(&menu, d->mActionCollection, "view_actual_size");
515
addActionToMenu(&menu, d->mActionCollection, "view_zoom_to_fit");
516
addActionToMenu(&menu, d->mActionCollection, "view_zoom_in");
517
addActionToMenu(&menu, d->mActionCollection, "view_zoom_out");
519
if (d->mCompareMode) {
521
addActionToMenu(&menu, d->mActionCollection, "synchronize_views");
525
addActionToMenu(&menu, d->mActionCollection, "file_copy_to");
526
addActionToMenu(&menu, d->mActionCollection, "file_move_to");
527
addActionToMenu(&menu, d->mActionCollection, "file_link_to");
529
addActionToMenu(&menu, d->mActionCollection, "file_open_with");
530
menu.exec(QCursor::pos());
533
QSize ViewMainPage::sizeHint() const
535
return QSize(400, 300);
538
KUrl ViewMainPage::url() const
540
if (!d->currentView()) {
541
LOG("!d->documentView()");
545
return d->currentView()->url();
548
Document::Ptr ViewMainPage::currentDocument() const
550
if (!d->currentView()) {
551
LOG("!d->documentView()");
552
return Document::Ptr();
555
return d->currentView()->document();
558
bool ViewMainPage::isEmpty() const
560
if (!d->currentView()) {
563
return d->currentView()->isEmpty();
566
RasterImageView* ViewMainPage::imageView() const
568
if (!d->currentView()) {
571
return d->currentView()->imageView();
574
DocumentView* ViewMainPage::documentView() const
576
return d->currentView();
579
void ViewMainPage::setNormalPalette(const QPalette& palette)
581
d->mNormalPalette = palette;
583
d->setupThumbnailBarStyleSheet();
586
void ViewMainPage::openUrl(const KUrl& url)
588
openUrls(KUrl::List() << url, url);
591
void ViewMainPage::openUrls(const KUrl::List& _urls, const KUrl& currentUrl)
593
QSet<KUrl> urls = _urls.toSet();
594
d->mCompareMode = urls.count() > 1;
596
// Destroy views which show urls we don't care about, remove from "urls" the
597
// urls which already have a view.
598
Q_FOREACH(DocumentView * view, d->mDocumentViews) {
599
KUrl url = view->url();
600
if (urls.contains(url)) {
601
// view displays an url we must display, keep it
604
// view url is not interesting, drop it
605
d->deleteDocumentView(view);
609
// Create view for remaining urls
610
typedef QPair<DocumentView*, KUrl> ViewUrlPair;
611
QList<ViewUrlPair> urlForViewList;
612
Q_FOREACH(const KUrl & url, urls) {
613
if (d->mDocumentViews.count() >= MaxViewCount) {
614
kWarning() << "Too many documents to show";
617
DocumentView* view = d->createDocumentView();
618
urlForViewList << qMakePair(view, url);
621
d->mDocumentViewContainer->updateLayout();
623
// Load urls for new views. Do it only now because the view must have the
624
// correct size before it starts loading its url. Do not do it later because
625
// view->url() needs to be set for the next loop.
626
Q_FOREACH(const ViewUrlPair & pair, urlForViewList) {
627
pair.first->openUrl(pair.second);
631
Q_FOREACH(DocumentView * view, d->mDocumentViews) {
632
view->setCompareMode(d->mCompareMode);
633
if (view->url() == currentUrl) {
634
d->setCurrentView(view);
636
view->setCurrent(false);
640
d->mSynchronizeCheckBox->setVisible(d->mCompareMode);
641
if (d->mCompareMode) {
642
d->mSynchronizer->setDocumentViews(d->mDocumentViews);
643
d->mSynchronizer->setActive(d->mSynchronizeCheckBox->isChecked());
645
d->mSynchronizer->setDocumentViews(QList<DocumentView*>());
646
d->mSynchronizer->setActive(false);
650
void ViewMainPage::reload()
652
Document::Ptr doc = d->currentView()->document();
654
kWarning() << "!doc";
657
if (doc->isModified()) {
658
KGuiItem cont = KStandardGuiItem::cont();
659
cont.setText(i18nc("@action:button", "Discard Changes and Reload"));
660
int answer = KMessageBox::warningContinueCancel(this,
661
i18nc("@info", "This image has been modified. Reloading it will discard all your changes."),
662
QString() /* caption */,
664
if (answer != KMessageBox::Continue) {
669
// Call openUrl again because DocumentView may need to switch to a new
670
// adapter (for example because document was broken and it is not anymore)
671
d->currentView()->openUrl(doc->url());
674
void ViewMainPage::reset()
676
d->mDocumentViewController->setView(0);
677
d->mDocumentViewContainer->reset();
678
d->mDocumentViews.clear();
681
void ViewMainPage::slotViewFocused(DocumentView* view)
683
d->setCurrentView(view);
686
void ViewMainPage::trashView(DocumentView* view)
688
KUrl url = view->url();
690
FileOperations::trash(KUrl::List() << url, this);
693
void ViewMainPage::deselectView(DocumentView* view)
695
DocumentView* newCurrentView = 0;
696
if (view == d->currentView()) {
697
// We need to find a new view to set as current
698
int idx = d->mDocumentViews.indexOf(view);
699
// Look for the next visible view after the current one
700
for (int newIdx = idx + 1; newIdx < d->mDocumentViews.count(); ++newIdx) {
701
newCurrentView = d->mDocumentViews.at(newIdx);
704
if (!newCurrentView) {
705
// No visible view found after the current one, look before
706
for (int newIdx = idx - 1; newIdx >= 0; --newIdx) {
707
newCurrentView = d->mDocumentViews.at(newIdx);
711
if (!newCurrentView) {
712
kWarning() << "No view found to set as current, this should not happen!";
716
QModelIndex index = d->indexForView(view);
717
QItemSelectionModel* selectionModel = d->mThumbnailBar->selectionModel();
718
selectionModel->select(index, QItemSelectionModel::Deselect);
720
if (newCurrentView) {
721
d->setCurrentView(newCurrentView);
725
QToolButton* ViewMainPage::toggleSideBarButton() const
727
return d->mToggleSideBarButton;