Fix regressions
[kphotoalbum.git] / MainWindow / Window.cpp
blobb84fa10f7929f639e675635012e9d3e052e6b846
1 /* Copyright (C) 2003-2009 Jesper K. Pedersen <blackie@kde.org>
3 This program is free software; you can redistribute it and/or
4 modify it under the terms of the GNU General Public
5 License as published by the Free Software Foundation; either
6 version 2 of the License, or (at your option) any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program; see the file COPYING. If not, write to
15 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
16 Boston, MA 02110-1301, USA.
19 #include "Window.h"
20 #include "ThumbnailView/ThumbnailFacade.h"
21 #include <KActionCollection>
22 #include "BreadcrumbViewer.h"
23 #include <QDebug>
25 #include "StatisticsDialog.h"
26 #include "Settings/SettingsDialog.h"
27 #include <qapplication.h>
28 #include <QMoveEvent>
29 #include <QResizeEvent>
30 #include <QContextMenuEvent>
31 #include <QLabel>
32 #include <QPixmap>
33 #include <QCloseEvent>
34 #include <QVBoxLayout>
35 #include <Q3Frame>
36 #include "ThumbnailView/ThumbnailBuilder.h"
37 #include "AnnotationDialog/Dialog.h"
38 #include <qdir.h>
39 #include <qmessagebox.h>
40 #include "Viewer/ViewerWidget.h"
41 #include "WelcomeDialog.h"
42 #include <qcursor.h>
43 #include "Utilities/ShowBusyCursor.h"
44 #include <klocale.h>
46 #include <q3widgetstack.h>
47 #include "HTMLGenerator/HTMLDialog.h"
48 #include <kstatusbar.h>
49 #include "ImageCounter.h"
50 #include <qtimer.h>
51 #include <kmessagebox.h>
52 #include "Settings/SettingsData.h"
53 #include "Browser/BrowserWidget.h"
54 #include "DB/ImageDB.h"
55 #include "Utilities/Util.h"
56 #include "Utilities/List.h"
57 #include <kapplication.h>
58 #include <ktip.h>
59 #include <KProcess>
60 #include "DeleteDialog.h"
61 #include <ksimpleconfig.h>
62 #include <kcmdlineargs.h>
63 #include <QMenu>
64 #include <kiconloader.h>
65 #include <kpassworddialog.h>
66 #include <KShortcutsDialog>
67 #include <kdebug.h>
68 #include "ExternalPopup.h"
69 #include <kstandardaction.h>
70 #include <kedittoolbar.h>
71 #include "ImportExport/Export.h"
72 #include "ImportExport/Import.h"
73 #include <config-kpa-kipi.h>
74 #ifdef HASKIPI
75 # include "Plugins/Interface.h"
76 # include <libkipi/pluginloader.h>
77 # include <libkipi/plugin.h>
78 #endif
79 #include <config-kpa-exiv2.h>
80 #ifdef HAVE_EXIV2
81 # include "Exif/ReReadDialog.h"
82 #endif
83 #include "ImageManager/ImageLoader.h"
84 #include "SplashScreen.h"
85 #include <qobject.h>
86 #include "SearchBar.h"
87 #include "TokenEditor.h"
88 #include "DB/CategoryCollection.h"
89 #include <qlayout.h>
90 #include "DateBar/DateBarWidget.h"
91 #include "DB/ImageDateCollection.h"
92 #include "InvalidDateFinder.h"
93 #include "DB/ImageInfo.h"
94 #include "DB/ResultId.h"
95 #include "DB/Result.h"
96 #ifdef HAVE_STDLIB_H
97 # include <stdlib.h>
98 #endif
99 #ifdef HAVE_EXIV2
100 # include "Exif/Info.h"
101 # include "Exif/InfoDialog.h"
102 # include "Exif/Database.h"
103 #endif
105 #include "FeatureDialog.h"
106 #include "ImageManager/ImageRequest.h"
107 #include "ImageManager/Manager.h"
109 #include <config-kpa-sqldb.h>
110 #ifdef SQLDB_SUPPORT
111 # include "SQLDB/Database.h"
112 # include "SQLDB/ConfigFileHandler.h"
113 # include "SQLDB/QueryErrors.h"
114 # include <kprogressdialog.h>
115 #endif
116 #include <krun.h>
117 #include <kglobal.h>
118 #include <kvbox.h>
119 #include "DirtyIndicator.h"
120 #include "Utilities/ShowBusyCursor.h"
121 #include <KToggleAction>
122 #include <KActionMenu>
123 #include <KHBox>
124 #include <K3URLDrag>
125 #include <qclipboard.h>
126 #include <stdexcept>
127 #include <KInputDialog>
128 #include "DB/Result.h"
129 #include "ThumbnailView/enums.h"
131 MainWindow::Window* MainWindow::Window::_instance = 0;
133 MainWindow::Window::Window( QWidget* parent )
134 :KXmlGuiWindow( parent ),
135 _annotationDialog(0),
136 _deleteDialog( 0 ), _htmlDialog(0), _tokenEditor( 0 )
138 SplashScreen::instance()->message( i18n("Loading Database") );
139 _instance = this;
141 bool gotConfigFile = load();
142 if ( !gotConfigFile )
143 exit(0);
144 SplashScreen::instance()->message( i18n("Loading Main Window") );
146 QWidget* top = new QWidget( this );
147 QVBoxLayout* lay = new QVBoxLayout( top );
148 setCentralWidget( top );
150 _stack = new Q3WidgetStack( top, "_stack" );
151 lay->addWidget( _stack, 1 );
153 _dateBar = new DateBar::DateBarWidget( top );
154 lay->addWidget( _dateBar );
156 Q3Frame* line = new Q3Frame( top );
157 line->setFrameStyle( Q3Frame::HLine | Q3Frame::Plain );
158 line->setLineWidth(1);
159 lay->addWidget( line );
161 _browser = new Browser::BrowserWidget( _stack );
162 _thumbnailView = new ThumbnailView::ThumbnailFacade();
164 _stack->addWidget( _browser );
165 _stack->addWidget( _thumbnailView->gui() );
166 _stack->raiseWidget( _browser );
168 _optionsDialog = 0;
169 setupMenuBar();
171 createSarchBar();
172 setupStatusBar();
174 // Misc
175 _autoSaveTimer = new QTimer( this );
176 connect( _autoSaveTimer, SIGNAL( timeout() ), this, SLOT( slotAutoSave() ) );
177 startAutoSaveTimer();
179 connect( _browser, SIGNAL( showingOverview() ), this, SLOT( showBrowser() ) );
180 connect( _browser, SIGNAL( pathChanged( const Browser::BreadcrumbList& ) ), _pathIndicator, SLOT( setBreadcrumbs( const Browser::BreadcrumbList& ) ) );
181 connect( _pathIndicator, SIGNAL( widenToBreadcrumb( const Browser::Breadcrumb& ) ), _browser, SLOT( widenToBreadcrumb( const Browser::Breadcrumb& ) ) );
182 connect( _browser, SIGNAL( pathChanged( const Browser::BreadcrumbList& ) ), this, SLOT( updateDateBar( const Browser::BreadcrumbList& ) ) );
183 connect( _dateBar, SIGNAL( dateSelected( const DB::ImageDate&, bool ) ), _thumbnailView, SLOT( gotoDate( const DB::ImageDate&, bool ) ) );
184 connect( _dateBar, SIGNAL( toolTipInfo( const QString& ) ), this, SLOT( showDateBarTip( const QString& ) ) );
185 connect( Settings::SettingsData::instance(), SIGNAL( histogramSizeChanged( const QSize& ) ), _dateBar, SLOT( setHistogramBarSize( const QSize& ) ) );
188 connect( _dateBar, SIGNAL( dateRangeChange( const DB::ImageDate& ) ),
189 this, SLOT( setDateRange( const DB::ImageDate& ) ) );
190 connect( _dateBar, SIGNAL( dateRangeCleared() ), this, SLOT( clearDateRange() ) );
192 connect( _thumbnailView, SIGNAL( showImage( const DB::ResultId& ) ), this, SLOT( showImage( const DB::ResultId& ) ) );
193 connect( _thumbnailView, SIGNAL( showSelection() ), this, SLOT( slotView() ) );
194 connect( _thumbnailView, SIGNAL( currentDateChanged( const QDateTime& ) ), _dateBar, SLOT( setDate( const QDateTime& ) ) );
196 connect( _thumbnailView, SIGNAL( fileIdUnderCursorChanged( const DB::ResultId& ) ), this, SLOT( slotSetFileName( const DB::ResultId& ) ) );
197 connect( DB::ImageDB::instance(), SIGNAL( totalChanged( uint ) ), this, SLOT( updateDateBar() ) );
198 connect( DB::ImageDB::instance(), SIGNAL( dirty() ), _dirtyIndicator, SLOT( markDirtySlot() ) );
199 connect( DB::ImageDB::instance()->categoryCollection(), SIGNAL( categoryCollectionChanged() ), this, SLOT( slotOptionGroupChanged() ) );
200 connect( _browser, SIGNAL( imageCount(uint)), _partial, SLOT( showBrowserMatches(uint) ) );
201 connect( _thumbnailView, SIGNAL( selectionChanged(int) ), this, SLOT( slotThumbNailSelectionChanged(int) ) );
203 connect( _dirtyIndicator, SIGNAL( dirty() ), _thumbnailView, SLOT(repaintScreen() ) );
205 QTimer::singleShot( 0, this, SLOT( delayedInit() ) );
206 slotThumbNailSelectionChanged(0);
208 // Automatically save toolbar settings
209 setAutoSaveSettings();
212 MainWindow::Window::~Window()
214 DB::ImageDB::deleteInstance();
215 #ifdef HAVE_EXIV2
216 Exif::Database::deleteInstance();
217 #endif
220 void MainWindow::Window::delayedInit()
222 SplashScreen* splash = SplashScreen::instance();
223 setupPluginMenu();
225 if ( Settings::SettingsData::instance()->searchForImagesOnStart() ) {
226 splash->message( i18n("Searching for New Files") );
227 qApp->processEvents();
228 QTimer* timer = new QTimer( this );
229 connect( timer, SIGNAL( timeout() ), DB::ImageDB::instance(), SLOT( slotRescan() ) );
230 timer->setSingleShot( true );
231 timer->start( 0 );
234 if ( !Settings::SettingsData::instance()->delayLoadingPlugins() ) {
235 splash->message( i18n( "Loading Plug-ins" ) );
236 loadPlugins();
239 splash->done();
240 show();
242 KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
243 if ( args->isSet( "import" ) ) {
244 // I need to do this in delayed init to get the import window on top of the normal window
245 ImportExport::Import::imageImport( KCmdLineArgs::makeURL( args->getOption("import").toLocal8Bit() ) );
247 else {
248 // I need to postpone this otherwise the tip dialog will not get focus on start up
249 KTipDialog::showTip( this );
253 #ifdef HAVE_EXIV2
254 Exif::Database* exifDB = Exif::Database::instance(); // Load the database
255 if ( exifDB->isAvailable() && !exifDB->isOpen() ) {
256 KMessageBox::sorry( this, i18n("EXIF database cannot be opened. Check that the image root directory is writable.") );
258 #endif
262 bool MainWindow::Window::slotExit()
264 if ( Utilities::runningDemo() ) {
265 QString txt = i18n("<p><b>Delete Your Temporary Demo Database</b></p>"
266 "<p>I hope you enjoyed the KPhotoAlbum demo. The demo database was copied to "
267 "/tmp, should it be deleted now? If you do not delete it, it will waste disk space; "
268 "on the other hand, if you want to come back and try the demo again, you "
269 "might want to keep it around with the changes you made through this session.</p>" );
270 int ret = KMessageBox::questionYesNoCancel( this, txt, i18n("Delete Demo Database"),
271 KStandardGuiItem::yes(), KStandardGuiItem::no(), KStandardGuiItem::cancel(),
272 QString::fromLatin1("deleteDemoDatabase") );
273 if ( ret == KMessageBox::Cancel )
274 return false;
275 else if ( ret == KMessageBox::Yes ) {
276 Utilities::deleteDemo();
277 goto doQuit;
279 else {
280 // pass through to the check for dirtyness.
284 if ( _dirtyIndicator->isSaveDirty() ) {
285 int ret = KMessageBox::warningYesNoCancel( this, i18n("Do you want to save the changes?"),
286 i18n("Save Changes?") );
287 if ( ret == KMessageBox::Cancel )
288 return false;
289 if ( ret == KMessageBox::Yes ) {
290 slotSave();
292 if ( ret == KMessageBox::No ) {
293 QDir().remove( Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1(".#index.xml") );
297 doQuit:
298 qApp->quit();
299 return true;
302 void MainWindow::Window::slotOptions()
304 if ( ! _optionsDialog ) {
305 _optionsDialog = new Settings::SettingsDialog( this );
306 connect( _optionsDialog, SIGNAL( changed() ), this, SLOT( reloadThumbnailsAndFlushCache() ) );
307 connect( _optionsDialog, SIGNAL( changed() ), this, SLOT( startAutoSaveTimer() ) );
309 _optionsDialog->show();
312 void MainWindow::Window::slotCreateImageStack()
314 const DB::Result& list = selected();
315 if (list.size() < 2) {
316 // it doesn't make sense to make a stack from one image, does it?
317 return;
320 bool ok = DB::ImageDB::instance()->stack( list );
321 if ( !ok ) {
322 if ( KMessageBox::questionYesNo( this,
323 i18n("Some of the selected images already belong to a stack. "
324 "Do you want to remove them from their stacks and create a "
325 "completely new one?"), i18n("Stacking Error")) == KMessageBox::Yes ) {
326 DB::ImageDB::instance()->unstack( list );
327 if ( ! DB::ImageDB::instance()->stack( list ) ) {
328 KMessageBox::sorry( this,
329 i18n("Unknown error, stack creation failed."),
330 i18n("Stacking Error"));
331 return;
333 } else {
334 return;
338 // FIXME: here we should invoke a fancy dialog for user's pleasure
340 DirtyIndicator::markDirty();
341 // The current item might have just became invisible
342 _thumbnailView->setCurrentItem( list.at(0) );
343 _thumbnailView->updateDisplayModel();
346 /** @short Make the selected image the head of a stack
348 * The whole point of image stacking is to group images together and then select
349 * one of them as the "most important". This function is (maybe just a
350 * temporary) way of promoting a selected image to the "head" of a stack it
351 * belongs to. In future, it might get replaced by a Ligtroom-like interface.
352 * */
353 void MainWindow::Window::slotSetStackHead()
355 const DB::Result& list = selected();
356 if ( list.size() != 1 ) {
357 // this should be checked by enabling/disabling of QActions
358 return;
361 setStackHead( *list.begin() );
364 void MainWindow::Window::setStackHead( const DB::ResultId image )
366 if ( ! image.fetchInfo()->isStacked() )
367 return;
369 unsigned int oldOrder = image.fetchInfo()->stackOrder();
371 DB::Result others = DB::ImageDB::instance()->getStackFor( image );
372 others.fetchInfos();
373 for ( DB::Result::const_iterator it = others.begin(); it != others.end(); ++it ) {
374 DB::ResultId current = *it;
375 if ( current == image ) {
376 current.fetchInfo()->setStackOrder( 1 );
377 } else if ( current.fetchInfo()->stackOrder() < oldOrder ) {
378 current.fetchInfo()->setStackOrder( current.fetchInfo()->stackOrder() + 1 );
382 DirtyIndicator::markDirty();
383 _thumbnailView->updateDisplayModel();
386 void MainWindow::Window::slotUnStackImages()
388 const DB::Result& list = selected();
389 if (list.isEmpty())
390 return;
392 DB::ImageDB::instance()->unstack( list );
393 DirtyIndicator::markDirty();
394 _thumbnailView->updateDisplayModel();
397 void MainWindow::Window::slotConfigureAllImages()
399 configureImages( false );
402 void MainWindow::Window::slotConfigureImagesOneAtATime()
404 configureImages( true );
407 void MainWindow::Window::configureImages( bool oneAtATime )
409 const DB::Result& list = selected();
410 if (list.isEmpty()) {
411 KMessageBox::sorry( this, i18n("No item is selected."), i18n("No Selection") );
413 else {
414 DB::ImageInfoList images;
415 Q_FOREACH(DB::ImageInfoPtr info, list.fetchInfos()) {
416 images.append(info);
418 configureImages( images, oneAtATime );
422 void MainWindow::Window::configureImages( const DB::ImageInfoList& list, bool oneAtATime )
424 _instance->configImages( list, oneAtATime );
428 void MainWindow::Window::configImages( const DB::ImageInfoList& list, bool oneAtATime )
430 createAnnotationDialog();
431 _annotationDialog->configure( list, oneAtATime );
432 if ( _annotationDialog->thumbnailShouldReload() )
433 reloadThumbnails(true);
434 else if ( _annotationDialog->thumbnailTextShouldReload() )
435 _thumbnailView->reload(false, false);
439 void MainWindow::Window::slotSearch()
441 createAnnotationDialog();
442 DB::ImageSearchInfo searchInfo = _annotationDialog->search();
443 if ( !searchInfo.isNull() )
444 _browser->addSearch( searchInfo );
447 void MainWindow::Window::createAnnotationDialog()
449 Utilities::ShowBusyCursor dummy;
450 if ( !_annotationDialog.isNull() )
451 return;
453 _annotationDialog = new AnnotationDialog::Dialog( 0 );
456 void MainWindow::Window::slotSave()
458 Utilities::ShowBusyCursor dummy;
459 statusBar()->showMessage(i18n("Saving..."), 5000 );
460 DB::ImageDB::instance()->save( Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1("index.xml"), false );
461 _dirtyIndicator->saved();
462 QDir().remove( Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1(".#index.xml") );
463 statusBar()->showMessage(i18n("Saving... Done"), 5000 );
466 void MainWindow::Window::slotDeleteSelected()
468 if ( ! _deleteDialog )
469 _deleteDialog = new DeleteDialog( this );
470 if ( _deleteDialog->exec( selected() ) != QDialog::Accepted )
471 return;
473 DirtyIndicator::markDirty();
476 void MainWindow::Window::slotCopySelectedURLs()
478 KUrl::List urls;
479 Q_FOREACH(const DB::ImageInfoPtr info, selected().fetchInfos()) {
480 const QString fileName = info->fileName(DB::AbsolutePath);
481 urls.append( fileName );
483 QMimeData* mimeData = new QMimeData;
484 urls.populateMimeData(mimeData);
486 QApplication::clipboard()->setMimeData( mimeData );
489 void MainWindow::Window::slotReReadExifInfo()
491 #ifdef HAVE_EXIV2
492 QStringList files = DB::ImageDB::instance()->CONVERT(selectedOnDisk());
493 static Exif::ReReadDialog* dialog = 0;
494 if ( ! dialog )
495 dialog = new Exif::ReReadDialog( this );
496 if ( dialog->exec( files ) == QDialog::Accepted )
497 DirtyIndicator::markDirty();
498 #endif
502 DB::Result MainWindow::Window::selected(bool keepSortOrderOfDatabase)
504 if ( _thumbnailView->gui() == _stack->visibleWidget() )
505 return _thumbnailView->selection( keepSortOrderOfDatabase );
506 else
507 return DB::Result();
510 void MainWindow::Window::slotViewNewWindow()
512 slotView( false, false );
516 * Returns a list of files that are both selected and on disk. If there are no
517 * selected files, returns all files form current context that are on disk.
518 * */
519 DB::Result MainWindow::Window::selectedOnDisk()
521 const DB::Result& list = selected();
522 if (list.isEmpty())
523 return DB::ImageDB::instance()->currentScope( true );
525 DB::Result listOnDisk;
526 Q_FOREACH(DB::ResultId id, list) {
527 const QString fileName = id.fetchInfo()->fileName(DB::AbsolutePath);
528 if ( DB::ImageInfo::imageOnDisk( fileName ) )
529 listOnDisk.append(id);
532 return listOnDisk;
535 void MainWindow::Window::slotView( bool reuse, bool slideShow, bool random )
537 launchViewer( selected(), reuse, slideShow, random );
540 void MainWindow::Window::launchViewer(const DB::Result& inputMediaList, bool reuse, bool slideShow, bool random)
542 DB::Result mediaList = inputMediaList;
543 int seek = -1;
544 if (mediaList.isEmpty()) {
545 mediaList = _thumbnailView->imageList( ThumbnailView::ViewOrder );
546 } else if (mediaList.size() == 1) {
547 // we fake it so it appears the user has selected all images
548 // and magically scrolls to the originally selected one
549 DB::ResultId first = mediaList.at(0);
550 mediaList = _thumbnailView->imageList( ThumbnailView::ViewOrder );
551 seek = mediaList.indexOf(first);
554 if (mediaList.isEmpty())
555 mediaList = DB::ImageDB::instance()->currentScope( false );
557 if (mediaList.isEmpty()) {
558 KMessageBox::sorry( this, i18n("There are no images to be shown.") );
559 return;
562 if (random) {
563 mediaList = DB::Result(Utilities::shuffleList(mediaList.rawIdList()));
566 // Here, we need to switch back to the StringList until the Viewer is
567 // converted.
568 QStringList fileNameList = DB::ImageDB::instance()->CONVERT(mediaList);
570 Viewer::ViewerWidget* viewer;
571 if ( reuse && Viewer::ViewerWidget::latest() ) {
572 viewer = Viewer::ViewerWidget::latest();
573 viewer->raise();
574 viewer->activateWindow();
576 else
577 viewer = new Viewer::ViewerWidget(Viewer::ViewerWidget::ViewerWindow,
578 &_viewerInputMacros);
579 connect( viewer, SIGNAL( rotated() ), this, SLOT( reloadThumbnailsAndFlushCache() ) );
580 connect( viewer, SIGNAL( soughtTo(const DB::ResultId&) ), _thumbnailView, SLOT( changeSingleSelection(const DB::ResultId&) ) );
582 viewer->show( slideShow );
583 viewer->load( fileNameList, seek < 0 ? 0 : seek );
584 viewer->raise();
587 void MainWindow::Window::slotSortByDateAndTime()
589 DB::ImageDB::instance()->sortAndMergeBackIn( selected( true /* sort with oldest first */ ));
590 showThumbNails( DB::ImageDB::instance()->search( Browser::BrowserWidget::instance()->currentContext() ) );
591 DirtyIndicator::markDirty();
595 QString MainWindow::Window::welcome()
597 WelcomeDialog dialog( this );
598 dialog.exec();
599 return dialog.configFileName();
602 void MainWindow::Window::closeEvent( QCloseEvent* e )
604 bool quit = true;
605 quit = slotExit();
606 // If I made it here, then the user canceled
607 if ( !quit )
608 e->ignore();
609 else
610 e->setAccepted(true);
614 void MainWindow::Window::slotLimitToSelected()
616 Utilities::ShowBusyCursor dummy;
617 showThumbNails( selected() );
620 void MainWindow::Window::setupMenuBar()
622 // File menu
623 KStandardAction::save( this, SLOT( slotSave() ), actionCollection() );
624 KStandardAction::quit( this, SLOT( slotExit() ), actionCollection() );
625 _generateHtml = actionCollection()->addAction( QString::fromLatin1("exportHTML") );
626 _generateHtml->setText( i18n("Generate HTML...") );
627 connect( _generateHtml, SIGNAL(triggered()), this, SLOT( slotExportToHTML() ) );
629 KAction* a = actionCollection()->addAction( QString::fromLatin1("import"), this, SLOT( slotImport() ) );
630 a->setText( i18n( "Import...") );
632 a = actionCollection()->addAction( QString::fromLatin1("export"), this, SLOT( slotExport() ) );
633 a->setText( i18n( "Export/Copy Images...") );
636 // Go menu
637 a = KStandardAction::back( _browser, SLOT( back() ), actionCollection() );
638 connect( _browser, SIGNAL( canGoBack( bool ) ), a, SLOT( setEnabled( bool ) ) );
639 a->setEnabled( false );
641 a = KStandardAction::forward( _browser, SLOT( forward() ), actionCollection() );
642 connect( _browser, SIGNAL( canGoForward( bool ) ), a, SLOT( setEnabled( bool ) ) );
643 a->setEnabled( false );
645 a = KStandardAction::home( _browser, SLOT( home() ), actionCollection() );
646 connect( a, SIGNAL( activated() ), _dateBar, SLOT( clearSelection() ) );
648 a = KStandardAction::redisplay( _browser, SLOT( go() ), actionCollection() );
650 // The Edit menu
651 KStandardAction::copy( this, SLOT( slotCopySelectedURLs() ), actionCollection() );
652 _selectAll = KStandardAction::selectAll( _thumbnailView, SLOT( selectAll() ), actionCollection() );
653 KStandardAction::find( this, SLOT( slotSearch() ), actionCollection() );
655 _deleteSelected = actionCollection()->addAction(QString::fromLatin1("deleteSelected"));
656 _deleteSelected->setText( i18n( "Delete Selected" ) );
657 _deleteSelected->setIcon( KIcon( QString::fromLatin1("edit-delete") ) );
658 _deleteSelected->setShortcut( Qt::Key_Delete );
659 connect( _deleteSelected, SIGNAL( triggered() ), this, SLOT( slotDeleteSelected() ) );
661 a = actionCollection()->addAction(QString::fromLatin1("removeTokens"), this, SLOT( slotRemoveTokens() ));
662 a->setText( i18n("Remove Tokens") );
664 a = actionCollection()->addAction(QString::fromLatin1("showListOfFiles"), this, SLOT( slotShowListOfFiles() ));
665 a->setText( i18n("Open List of Files...")) ;
668 _configOneAtATime = actionCollection()->addAction( QString::fromLatin1("oneProp"), this, SLOT( slotConfigureImagesOneAtATime() ) );
669 _configOneAtATime->setText( i18n( "Annotate Individual Items" ) );
670 _configOneAtATime->setShortcut( Qt::CTRL+Qt::Key_1 );
672 _configAllSimultaniously = actionCollection()->addAction( QString::fromLatin1("allProp"), this, SLOT( slotConfigureAllImages() ) );
673 _configAllSimultaniously->setText( i18n( "Annotate Multiple Items at a Time" ) );
674 _configAllSimultaniously->setShortcut( Qt::CTRL+Qt::Key_2 );
676 _createImageStack = actionCollection()->addAction( QString::fromLatin1("createImageStack"), this, SLOT( slotCreateImageStack() ) );
677 _createImageStack->setText( i18n("Merge Images into a Stack") );
678 _createImageStack->setShortcut( Qt::CTRL + Qt::Key_3 );
680 _unStackImages = actionCollection()->addAction( QString::fromLatin1("unStackImages"), this, SLOT( slotUnStackImages() ) );
681 _unStackImages->setText( i18n("Remove Images from Stack") );
683 _setStackHead = actionCollection()->addAction( QString::fromLatin1("setStackHead"), this, SLOT( slotSetStackHead() ) );
684 _setStackHead->setText( i18n("Set as First Image in Stack") );
685 _setStackHead->setShortcut( Qt::CTRL + Qt::Key_4 );
687 _rotLeft = actionCollection()->addAction( QString::fromLatin1("rotateLeft"), this, SLOT( slotRotateSelectedLeft() ) );
688 _rotLeft->setText( i18n( "Rotate counterclockwise" ) );
689 _rotLeft->setShortcut( 0 );
692 _rotRight = actionCollection()->addAction( QString::fromLatin1("rotateRight"), this, SLOT( slotRotateSelectedRight() ) );
693 _rotRight->setText( i18n( "Rotate clockwise" ) );
696 // The Images menu
697 _view = actionCollection()->addAction( QString::fromLatin1("viewImages"), this, SLOT( slotView() ) );
698 _view->setText( i18n("View") );
699 _view->setShortcut( Qt::CTRL+Qt::Key_I );
701 _viewInNewWindow = actionCollection()->addAction( QString::fromLatin1("viewImagesNewWindow"), this, SLOT( slotViewNewWindow() ) );
702 _viewInNewWindow->setText( i18n("View (In New Window)") );
704 _runSlideShow = actionCollection()->addAction( QString::fromLatin1("runSlideShow"), this, SLOT( slotRunSlideShow() ) );
705 _runSlideShow->setText( i18n("Run Slide Show") );
706 _runSlideShow->setIcon( KIcon( QString::fromLatin1("view-presentation") ) );
707 _runSlideShow->setShortcut( Qt::CTRL+Qt::Key_R );
709 _runRandomSlideShow = actionCollection()->addAction( QString::fromLatin1("runRandomizedSlideShow"), this, SLOT( slotRunRandomizedSlideShow() ) );
710 _runRandomSlideShow->setText( i18n( "Run Randomized Slide Show" ) );
712 a = actionCollection()->addAction( QString::fromLatin1("collapseAllStacks"),
713 _thumbnailView, SLOT( collapseAllStacks() ) );
714 connect(_thumbnailView, SIGNAL( collapseAllStacksEnabled(bool) ), a, SLOT( setEnabled(bool) ));
715 a->setEnabled(false);
716 a->setText( i18n("Collapse all stacks" ));
718 a = actionCollection()->addAction( QString::fromLatin1("expandAllStacks"),
719 _thumbnailView, SLOT( expandAllStacks() ) );
720 connect(_thumbnailView, SIGNAL( expandAllStacksEnabled(bool) ), a, SLOT( setEnabled(bool) ));
721 a->setEnabled(false);
722 a->setText( i18n("Expand all stacks" ));
724 QActionGroup* grp = new QActionGroup( this );
726 a = actionCollection()->add<KToggleAction>( QString::fromLatin1("orderIncr"), this, SLOT( slotOrderIncr() ) );
727 a->setText( i18n("Show &Oldest First") ) ;
728 a->setActionGroup(grp);
729 a->setChecked( !Settings::SettingsData::instance()->showNewestThumbnailFirst() );
731 a = actionCollection()->add<KToggleAction>( QString::fromLatin1("orderDecr"), this, SLOT( slotOrderDecr() ) );
732 a->setText( i18n("Show &Newest First") );
733 a->setActionGroup(grp);
734 a->setChecked( Settings::SettingsData::instance()->showNewestThumbnailFirst() );
736 _sortByDateAndTime = actionCollection()->addAction( QString::fromLatin1("sortImages"), this, SLOT( slotSortByDateAndTime() ) );
737 _sortByDateAndTime->setText( i18n("Sort Selected by Date && Time") );
739 _limitToMarked = actionCollection()->addAction( QString::fromLatin1("limitToMarked"), this, SLOT( slotLimitToSelected() ) );
740 _limitToMarked->setText( i18n("Limit View to Marked") );
742 _jumpToContext = actionCollection()->addAction( QString::fromLatin1("jumpToContext"), this, SLOT( slotJumpToContext() ) );
743 _jumpToContext->setText( i18n("Jump to Context") );
744 _jumpToContext->setShortcut( Qt::CTRL+Qt::Key_J );
745 _jumpToContext->setIcon( KIcon( QString::fromLatin1( "kphotoalbum" ) ) ); // icon suggestion: go-jump (don't know the exact meaning though, so I didn't replace it right away
747 _lock = actionCollection()->addAction( QString::fromLatin1("lockToDefaultScope"), this, SLOT( lockToDefaultScope() ) );
748 _lock->setText( i18n("Lock Images") );
750 _unlock = actionCollection()->addAction( QString::fromLatin1("unlockFromDefaultScope"), this, SLOT( unlockFromDefaultScope() ) );
751 _unlock->setText( i18n("Unlock") );
753 a = actionCollection()->addAction( QString::fromLatin1("changeScopePasswd"), this, SLOT( changePassword() ) );
754 a->setText( i18n("Change Password...") );
755 a->setShortcut( 0 );
757 _setDefaultPos = actionCollection()->addAction( QString::fromLatin1("setDefaultScopePositive"), this, SLOT( setDefaultScopePositive() ) );
758 _setDefaultPos->setText( i18n("Lock Away All Other Items") );
760 _setDefaultNeg = actionCollection()->addAction( QString::fromLatin1("setDefaultScopeNegative"), this, SLOT( setDefaultScopeNegative() ) );
761 _setDefaultNeg->setText( i18n("Lock Away Current Set of Items") );
763 // Maintenance
764 a = actionCollection()->addAction( QString::fromLatin1("findUnavailableImages"), this, SLOT( slotShowNotOnDisk() ) );
765 a->setText( i18n("Display Images and Videos Not on Disk") );
767 a = actionCollection()->addAction( QString::fromLatin1("findImagesWithInvalidDate"), this, SLOT( slotShowImagesWithInvalidDate() ) );
768 a->setText( i18n("Display Images and Videos with Incomplete Dates...") );
770 #ifdef DOES_STILL_NOT_WORK_IN_KPA4
771 a = actionCollection()->addAction( QString::fromLatin1("findImagesWithChangedMD5Sum"), this, SLOT( slotShowImagesWithChangedMD5Sum() ) );
772 a->setText( i18n("Display Images and Videos with Changed MD5 Sum") );
773 #endif //DOES_STILL_NOT_WORK_IN_KPA4
775 a = actionCollection()->addAction( QString::fromLatin1("rebuildMD5s"), this, SLOT( slotRecalcCheckSums() ) );
776 a->setText( i18n("Recalculate Checksum") );
778 a = actionCollection()->addAction( QString::fromLatin1("rescan"), DB::ImageDB::instance(), SLOT( slotRescan() ) );
779 a->setText( i18n("Rescan for Images and Videos") );
781 #ifdef HAVE_EXIV2
782 a = actionCollection()->addAction( QString::fromLatin1( "recreateExifDB" ), this, SLOT( slotRecreateExifDB() ) );
783 a->setText( i18n("Recreate Exif Search Database") );
785 a = actionCollection()->addAction( QString::fromLatin1("reReadExifInfo"), this, SLOT( slotReReadExifInfo() ) );
786 a->setText( i18n("Read EXIF Info From Files...") );
787 #endif
789 #ifdef SQLDB_SUPPORT
790 a = actionCollection()->addAction( QString::fromLatin1("convertBackend"), this, SLOT( convertBackend() ) );
791 a->setText( i18n("Convert Backend...(Experimental!)" ) );
792 #endif
795 a = actionCollection()->addAction( QString::fromLatin1("buildThumbs"), this, SLOT( slotBuildThumbnails() ) );
796 a->setText( i18n("Build Thumbnails") );
798 a = actionCollection()->addAction( QString::fromLatin1("statistics"), this, SLOT( slotStatistics() ) );
799 a->setText( i18n("Statistics") );
802 // Settings
803 KStandardAction::preferences( this, SLOT( slotOptions() ), actionCollection() );
804 KStandardAction::keyBindings( this, SLOT( slotConfigureKeyBindings() ), actionCollection() );
805 KStandardAction::configureToolbars( this, SLOT( slotConfigureToolbars() ), actionCollection() );
807 a = actionCollection()->addAction( QString::fromLatin1("readdAllMessages"), this, SLOT( slotReenableMessages() ) );
808 a->setText( i18n("Enable All Messages") );
810 _viewMenu = actionCollection()->add<KActionMenu>( QString::fromLatin1("configureView") );
811 _viewMenu->setText( i18n("Configure Current View") );
813 _viewMenu->setIcon( KIcon( QString::fromLatin1( "view-list-details" ) ) );
814 _viewMenu->setDelayed( false );
816 QActionGroup* viewGrp = new QActionGroup( this );
817 viewGrp->setExclusive( true );
819 _smallListView = actionCollection()->add<KToggleAction>( QString::fromLatin1("smallListView"), _browser, SLOT( slotSmallListView() ) );
820 _smallListView->setText( i18n("Tree") );
821 _viewMenu->addAction( _smallListView );
822 _smallListView->setActionGroup( viewGrp );
824 _largeListView = actionCollection()->add<KToggleAction>( QString::fromLatin1("largelistview"), _browser, SLOT( slotLargeListView() ) );
825 _largeListView->setText( i18n("Tree with User Icons") );
826 _viewMenu->addAction( _largeListView );
827 _largeListView->setActionGroup( viewGrp );
829 #if 0 // I see no reason for this one.
830 _smallIconView = actionCollection()->add<KToggleAction>( QString::fromLatin1("smalliconview"), _browser, SLOT( slotSmallIconView() ) );
831 _smallIconView->setText( i18n("Icons") );
832 _viewMenu->addAction( _smallIconView );
833 _smallIconView->setActionGroup( viewGrp );
834 #endif
836 _largeIconView = actionCollection()->add<KToggleAction>( QString::fromLatin1("largeiconview"), _browser, SLOT( slotLargeIconView() ) );
837 _largeIconView->setText( i18n("Icons") );
838 _viewMenu->addAction( _largeIconView );
839 _largeIconView->setActionGroup( viewGrp );
841 connect( _browser, SIGNAL( isViewChangeable( bool ) ), viewGrp, SLOT( setEnabled( bool ) ) );
843 connect( _browser, SIGNAL( currentViewTypeChanged( DB::Category::ViewType ) ),
844 this, SLOT( slotUpdateViewMenu( DB::Category::ViewType ) ) );
845 // The help menu
846 KStandardAction::tipOfDay( this, SLOT(showTipOfDay()), actionCollection() );
848 a = actionCollection()->add<KToggleAction>( QString::fromLatin1("showToolTipOnImages") );
849 a->setText( i18n("Show Tooltips in Thumbnails Window") );
850 a->setShortcut( Qt::CTRL+Qt::Key_T );
851 connect( a, SIGNAL(toggled(bool)), _thumbnailView, SLOT( showToolTipsOnImages( bool ) ) );
854 a = actionCollection()->addAction( QString::fromLatin1("runDemo"), this, SLOT( runDemo() ) );
855 a->setText( i18n("Run KPhotoAlbum Demo") );
857 a = actionCollection()->addAction( QString::fromLatin1("features"), this, SLOT( showFeatures() ) );
858 a->setText( i18n("KPhotoAlbum Feature Status") );
860 a = actionCollection()->addAction( QString::fromLatin1("showVideo"), this, SLOT(showVideos()) );
861 a->setText( i18n( "Show Demo Videos") );
863 // Context menu actions
864 #ifdef HAVE_EXIV2
865 _showExifDialog = actionCollection()->addAction( QString::fromLatin1("showExifInfo"), this, SLOT( slotShowExifInfo() ) );
866 _showExifDialog->setText( i18n("Show Exif Info") );
867 #endif
868 _recreateThumbnails = actionCollection()->addAction( QString::fromLatin1("recreateThumbnails"), _thumbnailView, SLOT( slotRecreateThumbnail() ) );
869 _recreateThumbnails->setText( i18n("Recreate Selected Thumbnails") );
871 createGUI( QString::fromLatin1( "kphotoalbumui.rc" ) );
874 void MainWindow::Window::slotExportToHTML()
876 if ( ! _htmlDialog )
877 _htmlDialog = new HTMLGenerator::HTMLDialog( this );
878 _htmlDialog->exec(selectedOnDisk());
881 void MainWindow::Window::startAutoSaveTimer()
883 int i = Settings::SettingsData::instance()->autoSave();
884 _autoSaveTimer->stop();
885 if ( i != 0 ) {
886 _autoSaveTimer->start( i * 1000 * 60 );
890 void MainWindow::Window::slotAutoSave()
892 if ( _dirtyIndicator->isAutoSaveDirty() ) {
893 Utilities::ShowBusyCursor dummy;
894 statusBar()->showMessage(i18n("Auto saving...."));
895 DB::ImageDB::instance()->save( Settings::SettingsData::instance()->imageDirectory() + QString::fromLatin1(".#index.xml"), true );
896 statusBar()->showMessage(i18n("Auto saving.... Done"), 5000);
897 _dirtyIndicator->autoSaved();
902 void MainWindow::Window::showThumbNails()
904 reloadThumbnails(false);
905 _stack->raiseWidget( _thumbnailView->gui() );
906 _thumbnailView->gui()->setFocus();
907 updateStates( true );
910 void MainWindow::Window::showBrowser()
912 statusBar()->clearMessage();
913 _stack->raiseWidget( _browser );
914 _browser->setFocus();
915 updateStates( false );
919 void MainWindow::Window::slotOptionGroupChanged()
921 // FIXME: What if annotation dialog is open? (if that's possible)
922 delete _annotationDialog;
923 _annotationDialog = 0;
924 DirtyIndicator::markDirty();
927 void MainWindow::Window::showTipOfDay()
929 KTipDialog::showTip( this, QString(), true );
933 void MainWindow::Window::runDemo()
935 KProcess* process = new KProcess;
936 *process << QLatin1String("kphotoalbum") << QLatin1String("-demo");
937 process->startDetached();
940 bool MainWindow::Window::load()
942 // Let first try to find a config file.
943 KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
944 QString configFile;
946 if ( args->isSet( "c" ) ) {
947 configFile = args->getOption( "c" );
949 else if ( args->isSet( "demo" ) )
950 configFile = Utilities::setupDemo();
951 else {
952 bool showWelcome = false;
953 KConfigGroup config = KGlobal::config()->group(QString());
954 if ( config.hasKey( QString::fromLatin1("configfile") ) ) {
955 configFile = config.readEntry<QString>( QString::fromLatin1("configfile"), QString() );
956 if ( !QFileInfo( configFile ).exists() )
957 showWelcome = true;
959 else
960 showWelcome = true;
962 if ( showWelcome ) {
963 SplashScreen::instance()->hide();
964 configFile = welcome();
967 if ( configFile.isNull() )
968 return false;
970 if (configFile.startsWith( QString::fromLatin1( "~" ) ) )
971 configFile = QDir::home().path() + QString::fromLatin1( "/" ) + configFile.mid(1);
973 // To avoid a race conditions where both the image loader thread creates an instance of
974 // Options, and where the main thread crates an instance, we better get it created now.
975 Settings::SettingsData::setup( QFileInfo( configFile ).absolutePath() );
977 if ( Settings::SettingsData::instance()->showSplashScreen() ) {
978 SplashScreen::instance()->show();
979 qApp->processEvents();
982 // Choose backend
983 QString backEnd = Settings::SettingsData::instance()->backend();
984 // Command line override for backend
985 if ( args->isSet( "e" ) )
986 backEnd = args->getOption( "e" );
988 // Initialize correct back-end
989 if ( backEnd == QString::fromLatin1("sql") ) {
990 #ifdef SQLDB_SUPPORT
991 // SQL back-end needs some extra configuration first
992 KConfigGroup config = KGlobal::config()->group(QString::fromLatin1("SQLDB"));
993 try {
994 SQLDB::DatabaseAddress address = SQLDB::readConnectionParameters(config);
996 // Initialize SQLDB with the paramaters
997 DB::ImageDB::setupSQLDB(address);
998 return true;
1000 catch (SQLDB::Error& e){
1001 KMessageBox::error(this, i18n("SQL backend initialization failed, "
1002 "because following error occurred:\n%1",e.whatAsQString()));
1004 #else
1005 KMessageBox::error(this, i18n("SQL database support is not compiled in."));
1006 #endif
1008 else if ( backEnd == QString::fromLatin1("xml") );
1009 else {
1010 KMessageBox::error(this, i18n("Invalid database backend: %1",backEnd));
1013 if (backEnd != QString::fromLatin1("xml")) {
1014 int answer =
1015 KMessageBox::questionYesNo(this, i18n("Do you want to use XML backend instead?"));
1016 if (answer != KMessageBox::Yes)
1017 return false;
1020 DB::ImageDB::setupXMLDB( configFile );
1022 return true;
1025 void MainWindow::Window::contextMenuEvent( QContextMenuEvent* e )
1027 if ( _stack->visibleWidget() == _thumbnailView->gui() ) {
1028 QMenu menu( this );
1029 menu.addAction( _configOneAtATime );
1030 menu.addAction( _configAllSimultaniously );
1031 menu.addAction( _createImageStack );
1032 menu.addAction( _unStackImages );
1033 menu.addAction( _setStackHead );
1034 menu.addSeparator();
1035 menu.addAction( _runSlideShow );
1036 menu.addAction(_runRandomSlideShow );
1037 #ifdef HAVE_EXIV2
1038 menu.addAction( _showExifDialog);
1039 #endif
1041 menu.addSeparator();
1042 menu.addAction(_rotLeft);
1043 menu.addAction(_rotRight);
1044 menu.addAction(_recreateThumbnails);
1045 menu.addSeparator();
1047 menu.addAction(_view);
1048 menu.addAction(_viewInNewWindow);
1050 ExternalPopup* externalCommands = new ExternalPopup( &menu );
1051 DB::ImageInfoPtr info = _thumbnailView->mediaIdUnderCursor().fetchInfo();
1053 externalCommands->populate( info, DB::ImageDB::instance()->CONVERT(selected() ));
1054 QAction* action = menu.addMenu( externalCommands );
1055 if (info.isNull() && selected().isEmpty())
1056 action->setEnabled( false );
1058 menu.exec( QCursor::pos() );
1060 delete externalCommands;
1062 e->setAccepted(true);
1065 void MainWindow::Window::setDefaultScopePositive()
1067 Settings::SettingsData::instance()->setCurrentLock( _browser->currentContext(), false );
1070 void MainWindow::Window::setDefaultScopeNegative()
1072 Settings::SettingsData::instance()->setCurrentLock( _browser->currentContext(), true );
1075 void MainWindow::Window::lockToDefaultScope()
1077 int i = KMessageBox::warningContinueCancel( this,
1078 i18n( "<p>The password protection is only a means of allowing your little sister "
1079 "to look in your images, without getting to those embarrassing images from "
1080 "your last party.</p>"
1081 "<p>In other words, anyone with access to the index.xml file can easily "
1082 "circumvent this password.</b></p>"),
1083 i18n("Password Protection"),
1084 KStandardGuiItem::cont(), KStandardGuiItem::cancel(),
1085 QString::fromLatin1( "lockPassWordIsNotEncruption" ) );
1086 if ( i == KMessageBox::Cancel )
1087 return;
1089 setLocked( true, false );
1092 void MainWindow::Window::unlockFromDefaultScope()
1094 bool OK = ( Settings::SettingsData::instance()->password().isEmpty() );
1095 while ( !OK ) {
1096 KPasswordDialog dialog( this );
1097 dialog.setPrompt( i18n("Type in Password to Unlock") );
1098 const int code = dialog.exec();
1099 if ( code == QDialog::Rejected )
1100 return;
1101 const QString passwd = dialog.password();
1103 OK = (Settings::SettingsData::instance()->password() == passwd);
1105 if ( !OK )
1106 KMessageBox::sorry( this, i18n("Invalid password.") );
1108 setLocked( false, false );
1111 void MainWindow::Window::setLocked( bool locked, bool force )
1113 static QPixmap* lockedPix = new QPixmap( SmallIcon( QString::fromLatin1( "object-locked" ) ) );
1114 _lockedIndicator->setFixedWidth( lockedPix->width() );
1116 if ( locked )
1117 _lockedIndicator->setPixmap( *lockedPix );
1118 else
1119 _lockedIndicator->setPixmap( QPixmap() );
1121 Settings::SettingsData::instance()->setLocked( locked, force );
1123 _lock->setEnabled( !locked );
1124 _unlock->setEnabled( locked );
1125 _setDefaultPos->setEnabled( !locked );
1126 _setDefaultNeg->setEnabled( !locked );
1127 _browser->reload();
1130 void MainWindow::Window::changePassword()
1132 bool OK = ( Settings::SettingsData::instance()->password().isEmpty() );
1134 KPasswordDialog dialog;
1136 while ( !OK ) {
1137 dialog.setPrompt( i18n("Type in Old Password") );
1138 const int code = dialog.exec();
1139 if ( code == QDialog::Rejected )
1140 return;
1141 const QString passwd = dialog.password();
1143 OK = (Settings::SettingsData::instance()->password() == QString(passwd));
1145 if ( !OK )
1146 KMessageBox::sorry( this, i18n("Invalid password.") );
1149 dialog.setPrompt( i18n("Type in New Password") );
1150 const int code = dialog.exec();
1151 if ( code == QDialog::Accepted )
1152 Settings::SettingsData::instance()->setPassword( dialog.password() );
1155 void MainWindow::Window::slotConfigureKeyBindings()
1157 Viewer::ViewerWidget* viewer = new Viewer::ViewerWidget; // Do not show, this is only used to get a key configuration
1158 KShortcutsDialog* dialog = new KShortcutsDialog();
1159 dialog->addCollection( actionCollection(), i18n( "General" ) );
1160 dialog->addCollection( viewer->actions(), i18n("Viewer") );
1162 #ifdef HASKIPI
1163 loadPlugins();
1164 KIPI::PluginLoader::PluginList list = _pluginLoader->pluginList();
1165 for( KIPI::PluginLoader::PluginList::Iterator it = list.begin(); it != list.end(); ++it ) {
1166 KIPI::Plugin* plugin = (*it)->plugin();
1167 if ( plugin )
1168 dialog->addCollection( plugin->actionCollection(), (*it)->comment() );
1170 #endif
1172 createAnnotationDialog();
1173 dialog->addCollection( _annotationDialog->actions(), i18n("Annotation Dialog" ) );
1175 dialog->configure();
1177 delete dialog;
1178 delete viewer;
1181 void MainWindow::Window::slotSetFileName( const DB::ResultId& id )
1183 if ( id.isNull() )
1184 statusBar()->clearMessage();
1185 else
1186 statusBar()->showMessage( id.fetchInfo()->fileName(DB::AbsolutePath), 4000 );
1189 void MainWindow::Window::slotThumbNailSelectionChanged(int selectionSize)
1191 _configAllSimultaniously->setEnabled(selectionSize > 1);
1192 _configOneAtATime->setEnabled(selectionSize >= 1);
1193 _createImageStack->setEnabled(selectionSize > 1);
1194 _unStackImages->setEnabled(selectionSize >= 1);
1195 _setStackHead->setEnabled(selectionSize == 1); // FIXME: do we want to check if it's stacked here?
1196 _sortByDateAndTime->setEnabled(selectionSize > 1);
1197 _recreateThumbnails->setEnabled(selectionSize >= 1);
1198 _rotLeft->setEnabled(selectionSize >= 1);
1199 _rotRight->setEnabled(selectionSize >= 1);
1202 void MainWindow::Window::rotateSelected( int angle )
1204 const DB::Result& list = selected();
1205 if (list.isEmpty()) {
1206 KMessageBox::sorry( this, i18n("No item is selected."),
1207 i18n("No Selection") );
1208 } else {
1209 Q_FOREACH(DB::ImageInfoPtr info, list.fetchInfos()) {
1210 info->rotate(angle);
1212 _dirtyIndicator->markDirty();
1213 reloadThumbnailsAfterRotation();
1217 void MainWindow::Window::slotRotateSelectedLeft()
1219 rotateSelected( -90 );
1222 void MainWindow::Window::slotRotateSelectedRight()
1224 rotateSelected( 90 );
1227 void MainWindow::Window::reloadThumbnails(bool flushCache)
1229 _thumbnailView->reload( flushCache );
1230 slotThumbNailSelectionChanged( _thumbnailView->selection().size() );
1233 void MainWindow::Window::reloadThumbnailsAndFlushCache()
1235 reloadThumbnails(true);
1238 void MainWindow::Window::reloadThumbnailsAfterRotation()
1240 _thumbnailView->reload( true, false );
1243 void MainWindow::Window::slotUpdateViewMenu( DB::Category::ViewType type )
1245 if ( type == DB::Category::TreeView )
1246 _smallListView->setChecked( true );
1247 else if ( type == DB::Category::ThumbedTreeView )
1248 _largeListView->setChecked( true );
1249 #if 0
1250 else if ( type == DB::Category::IconView )
1251 _smallIconView->setChecked( true );
1252 #endif
1253 else if ( type == DB::Category::ThumbedIconView )
1254 _largeIconView->setChecked( true );
1257 void MainWindow::Window::slotShowNotOnDisk()
1259 DB::Result notOnDisk;
1260 Q_FOREACH(DB::ResultId id, DB::ImageDB::instance()->images()) {
1261 const DB::ImageInfoPtr info = id.fetchInfo();
1262 QFileInfo fi( info->fileName(DB::AbsolutePath) );
1263 if ( !fi.exists() )
1264 notOnDisk.append(id);
1267 showThumbNails(notOnDisk);
1271 void MainWindow::Window::slotShowImagesWithChangedMD5Sum()
1273 #ifdef DOES_STILL_NOT_WORK_IN_KPA4
1274 Utilities::ShowBusyCursor dummy;
1275 StringSet changed = DB::ImageDB::instance()->imagesWithMD5Changed();
1276 showThumbNails( changed.toList() );
1277 #else // DOES_STILL_NOT_WORK_IN_KPA4
1278 qFatal("Code commented out in MainWindow::Window::slotShowImagesWithChangedMD5Sum");
1279 #endif // DOES_STILL_NOT_WORK_IN_KPA4
1283 void MainWindow::Window::updateStates( bool thumbNailView )
1285 _selectAll->setEnabled( thumbNailView );
1286 _deleteSelected->setEnabled( thumbNailView );
1287 _limitToMarked->setEnabled( thumbNailView );
1290 void MainWindow::Window::slotRunSlideShow()
1292 slotView( true, true );
1295 void MainWindow::Window::slotRunRandomizedSlideShow()
1297 slotView( true, true, true );
1300 MainWindow::Window* MainWindow::Window::theMainWindow()
1302 Q_ASSERT( _instance );
1303 return _instance;
1306 void MainWindow::Window::slotConfigureToolbars()
1308 KEditToolBar dlg(guiFactory());
1309 connect(&dlg, SIGNAL( newToolbarConfig() ),
1310 SLOT( slotNewToolbarConfig() ));
1311 dlg.exec();
1315 void MainWindow::Window::slotNewToolbarConfig()
1317 createGUI();
1318 createSarchBar();
1321 void MainWindow::Window::slotImport()
1323 ImportExport::Import::imageImport();
1326 void MainWindow::Window::slotExport()
1328 ImportExport::Export::imageExport(selectedOnDisk());
1331 void MainWindow::Window::slotReenableMessages()
1333 int ret = KMessageBox::questionYesNo( this, i18n("<p>Really enable all message boxes where you previously "
1334 "checked the do-not-show-again check box?</p>" ) );
1335 if ( ret == KMessageBox::Yes )
1336 KMessageBox::enableAllMessages();
1340 void MainWindow::Window::setupPluginMenu()
1342 QMenu* menu = findChild<QMenu*>( QString::fromLatin1("plugins") );
1343 if ( !menu ) {
1344 KMessageBox::error( this, i18n("<p>KPhotoAlbum hit an internal error (missing plug-in menu in MainWindow::Window::setupPluginMenu). This indicate that you forgot to do a make install. If you did compile KPhotoAlbum yourself, then please run make install. If not, please report this as a bug.</p><p>KPhotoAlbum will continue execution, but it is not entirely unlikely that it will crash later on due to the missing make install.</p>" ), i18n("Internal Error") );
1345 _hasLoadedPlugins = true;
1346 return; // This is no good, but lets try and continue.
1350 #ifdef HASKIPI
1351 connect( menu, SIGNAL( aboutToShow() ), this, SLOT( loadPlugins() ) );
1352 _hasLoadedPlugins = false;
1353 #else
1354 menu->setEnabled(false);
1355 _hasLoadedPlugins = true;
1356 #endif
1359 void MainWindow::Window::loadPlugins()
1361 #ifdef HASKIPI
1362 Utilities::ShowBusyCursor dummy;
1363 if ( _hasLoadedPlugins )
1364 return;
1366 _pluginInterface = new Plugins::Interface( this, "demo interface" );
1367 connect( _pluginInterface, SIGNAL( imagesChanged( const KUrl::List& ) ), this, SLOT( slotImagesChanged( const KUrl::List& ) ) );
1369 QStringList ignores;
1370 ignores << QString::fromLatin1( "CommentsEditor" )
1371 << QString::fromLatin1( "HelloWorld" );
1373 _pluginLoader = new KIPI::PluginLoader( ignores, _pluginInterface );
1374 connect( _pluginLoader, SIGNAL( replug() ), this, SLOT( plug() ) );
1375 _pluginLoader->loadPlugins();
1377 // Setup signals
1378 connect( _thumbnailView, SIGNAL( selectionChanged(int) ), this, SLOT( slotSelectionChanged(int) ) );
1379 _hasLoadedPlugins = true;
1381 // Make sure selection is updated also when plugin loading is
1382 // delayed. This is needed, because selection might already be
1383 // non-empty when loading the plugins.
1384 slotSelectionChanged(selected().size());
1385 #endif // HASKIPI
1389 void MainWindow::Window::plug()
1391 #ifdef HASKIPI
1392 unplugActionList( QString::fromLatin1("import_actions") );
1393 unplugActionList( QString::fromLatin1("export_actions") );
1394 unplugActionList( QString::fromLatin1("image_actions") );
1395 unplugActionList( QString::fromLatin1("tool_actions") );
1396 unplugActionList( QString::fromLatin1("batch_actions") );
1398 QList<QAction*> importActions;
1399 QList<QAction*> exportActions;
1400 QList<QAction*> imageActions;
1401 QList<QAction*> toolsActions;
1402 QList<QAction*> batchActions;
1404 KIPI::PluginLoader::PluginList list = _pluginLoader->pluginList();
1405 for( KIPI::PluginLoader::PluginList::Iterator it = list.begin(); it != list.end(); ++it ) {
1406 KIPI::Plugin* plugin = (*it)->plugin();
1407 if ( !plugin || !(*it)->shouldLoad() )
1408 continue;
1410 plugin->setup( this );
1412 QList<KAction*> actions = plugin->actions();
1413 for( QList<KAction*>::Iterator it = actions.begin(); it != actions.end(); ++it ) {
1414 KIPI::Category category = plugin->category( *it );
1415 if ( category == KIPI::ImagesPlugin || category == KIPI::CollectionsPlugin )
1416 imageActions.append( *it );
1418 else if ( category == KIPI::ImportPlugin )
1419 importActions.append( *it );
1421 else if ( category == KIPI::ExportPlugin )
1422 exportActions.append( *it );
1424 else if ( category == KIPI::ToolsPlugin )
1425 toolsActions.append( *it );
1427 else if ( category == KIPI::BatchPlugin )
1428 batchActions.append( *it );
1430 else {
1431 kDebug() << "Unknow category\n";
1434 KConfigGroup group = KGlobal::config()->group( QString::fromLatin1("Shortcuts") );
1435 plugin->actionCollection()->importGlobalShortcuts( &group );
1439 setPluginMenuState( "importplugin", importActions );
1440 setPluginMenuState( "exportplugin", exportActions );
1441 setPluginMenuState( "imagesplugins", imageActions );
1442 setPluginMenuState( "batch_plugins", batchActions );
1443 setPluginMenuState( "tool_plugins", toolsActions );
1445 // For this to work I need to pass false as second arg for createGUI
1446 plugActionList( QString::fromLatin1("import_actions"), importActions );
1447 plugActionList( QString::fromLatin1("export_actions"), exportActions );
1448 plugActionList( QString::fromLatin1("image_actions"), imageActions );
1449 plugActionList( QString::fromLatin1("tool_actions"), toolsActions );
1450 plugActionList( QString::fromLatin1("batch_actions"), batchActions );
1451 #endif
1454 void MainWindow::Window::setPluginMenuState( const char* name, const QList<QAction*>& actions )
1456 QMenu* menu = findChild<QMenu*>( QString::fromLatin1(name) );
1457 if ( menu )
1458 menu->setEnabled(actions.count() != 0);
1463 void MainWindow::Window::slotImagesChanged( const KUrl::List& urls )
1465 for( KUrl::List::ConstIterator it = urls.begin(); it != urls.end(); ++it ) {
1466 ImageManager::Manager::instance()->removeThumbnail( (*it).path() );
1468 _dirtyIndicator->markDirty();
1469 reloadThumbnails(true);
1472 DB::ImageSearchInfo MainWindow::Window::currentContext()
1474 return _browser->currentContext();
1477 QString MainWindow::Window::currentBrowseCategory() const
1479 return _browser->currentCategory();
1482 void MainWindow::Window::slotSelectionChanged( int count )
1484 #ifdef HASKIPI
1485 _pluginInterface->slotSelectionChanged( count != 0 );
1486 #else
1487 Q_UNUSED( count );
1488 #endif
1491 void MainWindow::Window::resizeEvent( QResizeEvent* )
1493 if ( Settings::SettingsData::ready() && isVisible() )
1494 Settings::SettingsData::instance()->setWindowGeometry( Settings::MainWindow, geometry() );
1497 void MainWindow::Window::moveEvent( QMoveEvent * )
1499 if ( Settings::SettingsData::ready() && isVisible() )
1500 Settings::SettingsData::instance()->setWindowGeometry( Settings::MainWindow, geometry() );
1504 void MainWindow::Window::slotRemoveTokens()
1506 if ( !_tokenEditor )
1507 _tokenEditor = new TokenEditor( this );
1508 _tokenEditor->show();
1509 connect( _tokenEditor, SIGNAL( finished() ), _browser, SLOT( go() ) );
1512 void MainWindow::Window::slotShowListOfFiles()
1514 QStringList list = KInputDialog::getMultiLineText( i18n("Open List of Files"), i18n("Enter file names") )
1515 .split( QChar::fromLatin1('\n'), QString::SkipEmptyParts );
1516 if ( list.isEmpty() )
1517 return;
1519 DB::Result out;
1520 for ( QStringList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it ) {
1521 QString fileName = Utilities::imageFileNameToAbsolute( *it );
1522 DB::ResultId id = DB::ImageDB::instance()->ID_FOR_FILE(fileName);
1523 if ( !id.isNull() )
1524 out.append(id);
1527 if (out.isEmpty())
1528 KMessageBox::sorry( this, i18n("No images matching your input were found."), i18n("No Matches") );
1529 else
1530 showThumbNails( out );
1533 void MainWindow::Window::updateDateBar( const Browser::BreadcrumbList& path )
1535 static QString lastPath = QString::fromLatin1("ThisStringShouldNeverBeSeenSoWeUseItAsInitialContent");
1536 if ( path.toString() != lastPath )
1537 updateDateBar();
1538 lastPath = path.toString();
1541 void MainWindow::Window::updateDateBar()
1543 _dateBar->setImageDateCollection( DB::ImageDB::instance()->rangeCollection() );
1547 void MainWindow::Window::slotShowImagesWithInvalidDate()
1549 InvalidDateFinder finder( this );
1550 if ( finder.exec() == QDialog::Accepted )
1551 showThumbNails();
1554 void MainWindow::Window::showDateBarTip( const QString& msg )
1556 statusBar()->showMessage( msg, 3000 );
1559 void MainWindow::Window::slotJumpToContext()
1561 DB::ResultId id =_thumbnailView->currentItem();
1562 if ( !id.isNull() ) {
1563 // QWERTY: addImageView should take id as well.
1564 QString fileName = id.fetchInfo()->fileName(DB::AbsolutePath);
1565 _browser->addImageView( fileName );
1569 void MainWindow::Window::setDateRange( const DB::ImageDate& range )
1571 DB::ImageDB::instance()->setDateRange( range, _dateBar->includeFuzzyCounts() );
1572 _browser->reload();
1573 reloadThumbnails(false);
1576 void MainWindow::Window::clearDateRange()
1578 DB::ImageDB::instance()->clearDateRange();
1579 _browser->reload();
1580 reloadThumbnails(false);
1583 void MainWindow::Window::showThumbNails(const DB::Result& items)
1585 _thumbnailView->setImageList( items );
1586 _partial->setMatchCount(items.size());
1587 showThumbNails();
1590 void MainWindow::Window::convertBackend()
1592 #ifdef SQLDB_SUPPORT
1593 // Converting from SQLDB to the same SQLDB will not work and there
1594 // is currently no way to check if two SQL back-ends use the same
1595 // database. So this is my current workaround for it.
1596 if (dynamic_cast<SQLDB::Database*>(DB::ImageDB::instance())) {
1597 KMessageBox::sorry(this, i18n("Database conversion from SQL database is not yet supported."));
1598 return;
1601 KConfigGroup config = KGlobal::config()->group( QString::fromLatin1("SQLDB") );
1602 if (!config.exists()) {
1603 int ret =
1604 KMessageBox::questionYesNo(this, i18n("You should set SQL database settings before the conversion. "
1605 "Do you want to do this now?"));
1606 if (ret != KMessageBox::Yes)
1607 return;
1608 if (!_optionsDialog)
1609 _optionsDialog = new Settings::SettingsDialog(this);
1610 _optionsDialog->showBackendPage();
1611 ret = _optionsDialog->exec();
1612 if (ret != Settings::SettingsDialog::Accepted)
1613 return;
1615 try {
1616 SQLDB::DatabaseAddress address = SQLDB::readConnectionParameters(config);
1618 SQLDB::Database sqlBackend(address);
1620 // TODO: ask if old database should be flushed first
1622 KProgressDialog dialog(this);
1623 dialog.setModal(true);
1624 dialog.setCaption(i18n("Converting database"));
1625 dialog.setLabelText
1626 (QString::fromLatin1("<p><b><nobr>%1</nobr></b></p><p>%2</p>")
1627 .arg(i18n("Converting database to SQL."))
1628 .arg(i18n("Please wait.")));
1629 dialog.setAllowCancel(false);
1630 dialog.setAutoClose(true);
1631 dialog.setFixedSize(dialog.sizeHint());
1632 dialog.setMinimumDuration(0);
1633 qApp->processEvents();
1635 DB::ImageDB::instance()->convertBackend(&sqlBackend, dialog.progressBar());
1637 KMessageBox::information(this, i18n("Database conversion is ready."));
1639 catch (SQLDB::Error& e) {
1640 KMessageBox::error(this, i18n("Database conversion failed, because following error occurred:\n%1",e.whatAsQString()));
1642 #endif
1645 void MainWindow::Window::slotRecalcCheckSums()
1647 DB::ImageDB::instance()->slotRecalcCheckSums( selected() );
1650 void MainWindow::Window::slotShowExifInfo()
1652 #ifdef HAVE_EXIV2
1653 DB::Result items = selectedOnDisk();
1654 if (!items.isEmpty()) {
1655 Exif::InfoDialog* exifDialog = new Exif::InfoDialog(items.at(0), this);
1656 exifDialog->show();
1658 #endif
1661 void MainWindow::Window::showFeatures()
1663 FeatureDialog dialog(this);
1664 dialog.exec();
1667 void MainWindow::Window::showImage( const DB::ResultId& id )
1669 launchViewer(DB::Result(id), true, false, false);
1672 void MainWindow::Window::slotBuildThumbnails()
1674 new ThumbnailView::ThumbnailBuilder( this );
1677 void MainWindow::Window::slotOrderIncr()
1679 _thumbnailView->setSortDirection( ThumbnailView::OldestFirst );
1682 void MainWindow::Window::slotOrderDecr()
1684 _thumbnailView->setSortDirection( ThumbnailView::NewestFirst );
1687 void MainWindow::Window::showVideos()
1689 KRun::runUrl(KUrl(QString::fromLatin1("http://www.kphotoalbum.org/videos.html")), QString::fromLatin1( "text/html" ), this );
1692 void MainWindow::Window::slotStatistics()
1694 static StatisticsDialog* dialog = new StatisticsDialog(this);
1695 dialog->show();
1698 void MainWindow::Window::setupStatusBar()
1700 // Avoid flicker in the statusbar when moving over dates from the datebar
1701 QFont f( statusBar()->font() );
1702 f.setStyleHint( QFont::TypeWriter );
1703 f.setFamily( QString::fromLatin1( "courier" ) );
1704 f.setBold( true );
1705 statusBar()->setFont( f );
1707 KHBox* indicators = new KHBox( statusBar());
1708 _dirtyIndicator = new DirtyIndicator( indicators );
1710 _lockedIndicator = new QLabel( indicators );
1711 setLocked( Settings::SettingsData::instance()->locked(), true );
1713 statusBar()->addPermanentWidget( indicators, 0 );
1715 _partial = new ImageCounter( statusBar() );
1716 statusBar()->addPermanentWidget( _partial, 0 );
1718 ImageCounter* total = new ImageCounter( statusBar() );
1719 statusBar()->addPermanentWidget( total, 0 );
1720 total->setTotal( DB::ImageDB::instance()->totalCount() );
1721 connect( DB::ImageDB::instance(), SIGNAL( totalChanged( uint ) ), total, SLOT( setTotal( uint ) ) );
1723 _pathIndicator = new BreadcrumbViewer;
1724 statusBar()->addWidget( _pathIndicator, 1 );
1727 void MainWindow::Window::slotRecreateExifDB()
1729 #ifdef HAVE_EXIV2
1730 Exif::Database::instance()->recreate();
1731 #endif
1734 void MainWindow::Window::createSarchBar()
1736 // Set up the search tool bar
1737 SearchBar* bar = new SearchBar( this );
1738 bar->setLineEditEnabled(false);
1739 bar->setObjectName( QString::fromAscii("searchBar" ) );
1741 connect( bar, SIGNAL( textChanged( const QString& ) ), _browser, SLOT( slotLimitToMatch( const QString& ) ) );
1742 connect( bar, SIGNAL( returnPressed() ), _browser, SLOT( slotInvokeSeleted() ) );
1743 connect( bar, SIGNAL( keyPressed( QKeyEvent* ) ), _browser, SLOT( scrollKeyPressed( QKeyEvent* ) ) );
1744 connect( _browser, SIGNAL( viewChanged() ), bar, SLOT( reset() ) );
1745 connect( _browser, SIGNAL( isSearchable( bool ) ), bar, SLOT( setLineEditEnabled( bool ) ) );
1748 #include "Window.moc"