diff --git a/ChangeLog b/ChangeLog index bac1021d..fd526077 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,404 +1,407 @@ 2004-04-19 Jesper K. Pedersen + * BUGFIX: The wrong image was invoked in the external viewer if + the thumbnail view was scrolled. + * BUGFIX: If description was not generated then other option groups was not printed in HTML exports. 2004-04-18 Jesper K. Pedersen * When opening the image config dialog, show a message box with a link to the help page. * Made it possible to re enable message boxes where the user previously checked do-not-show-again * Its now possible to find images where only a set of items are on (e.g. only me, and no one else) * BUGFIX: When generating HTML, only generate image on disk * Added a dialog with a description on how to enable webservers to server KimDaBa files. 2004-04-16 Jesper K. Pedersen * BUGFIX: If the destination directory for HTML export exists, offer to remove it. This fixes the problem where it would instead just be put in a subdirectory. * Loading descriptions from EXIF information can now be disabled * BUGFIX: when exporting the image loader also saved the large images (like 1024x768) to tumbnails dir. Please remove all thumbnails using the maintenance menu item. * BUGFIX: images inline in export files was broken if the extension was different from .jpg * Now its possible to ask for a .kim file on the HTML generated pages * BUGFIX: pressing cancel in export dialog still continue exporting. * BUGFIX: It was not possible to import twice in one session. * BUGFIX: images was rotated too must during export * now its possible to export images, and leave the .kim file next to the image set rather than putting them into the .kim file. 2004-04-15 Jesper K. Pedersen * Its now possible to delete images from within the image configuration dialog * Now you may click on a .kim file from say konqueror to start importing that file into your db. 2004-04-14 Jesper K. Pedersen * added Import/Export so its possible to share images with friends, without them having to write comments, and tell how is on the images. 2004-04-12 Jesper K. Pedersen * Its now possible to configure the toolbar * Added run slide show as a tool button * Now its possible to configure the shortcuts in the viewer. 2004-04-11 Jesper K. Pedersen * added run randomized slideshow * added run slide show to the thumbnail view, thus making it much easier to start a slide show if the images in view. * Made slide show timeout configurable * Made slide show size configurable, plus made it possible to specify full screen for configuration for configuration of normal view. 2004-03-27 Jesper K. Pedersen * rewrote the handling of dates in the image property dialog, so the GUI is much easier to work with - this must have been the oldest wish on my which list ;-) 2004-03-21 Jesper K. Pedersen * Added Images->sort to sort images in thumbnail view. Thanks to Thomas Schwarzgruber * Added Maintenance->remove all thumbnails and Maintenance->build thumbnails 2004-03-20 Jesper K. Pedersen * BUGFIX: now we are doing boundary check when panning, so you can't pan outside the image. * generating HTML and viewing images will now be the context if no images are selected. Context is the current search or current browse - e.g. all images from say Las Vegas. 2004-03-17 Jesper K. Pedersen * Added context menu for sorting options listbox (thanks to Reimar Imhof for a patch) 2004-03-10 Jesper K. Pedersen * BUGFIX: Fixed crash when moving images arround 2004-03-09 Jesper K. Pedersen * BUGFIX: If you have to rotate an image to view it, you get black stripes besides your image because of the resolution of the image (it's much higher than wide). So far so good. But if you zoom into the image via pressing '+' key, the black stripes don't change -> the shown width stays the same although the (virtually) height changed. Thanks to Thomas Schwarzgruber for a patch. 2004-03-07 Jesper K. Pedersen * BUGFIX: When renaming an item in the listselect, then category image is now also renamed. * BUGFIX: In the settings dialog, member group tab, ensure that the category list is updated when entering the page. * replaced "None" with "No Persons", "No Keywords" etc. * the count text in the browser is now "categories" rather than "images" when displaying categories like "persons", "locations" etc. * BUGFIX: Finally made panning work. Its now bound to the middle mouse button, which is more compatible to a wide range of application, rather than Ctrl+left button as was the case previously. 2004-03-02 Jesper K. Pedersen * BUGFIX: previous fix to member groups was broken. * use smoothScale rather than scale to get better looking thumbnails. 2004-02-15 Jesper K. Pedersen * BUGFIX: Pressing the rename button in member groups made KimDaBa crash if no member groups are selected. --------------------------- version 1.1 released -------------------------------------- 2004-02-04 Jesper K. Pedersen * BUGFIX: description on index page was controlled by "description" checkbox rather than the description for each page 2004-02-01 Jesper K. Pedersen * BUGFIX: option groups was not available for translations. * BUGFIX: the string **NONE** was not made available for translation everywhere, which resulted in the search dialog did not work for translated languages. 2004-01-31 Jesper K. Pedersen * BUGFIX: changed viewer from QDialog to QWidget as base class so the browser window could get on top of it. 2004-01-29 Jesper K. Pedersen * Made the image preview size configurable. * Ensure enable/disable state of menu bar is correct 2004-01-27 Jesper K. Pedersen * Made the KimDaBa properties dialog look better. 2004-01-26 Jesper K. Pedersen * BUGFIX: Changed QActions to KActions in the viewer to avoid mem error when the viewer closes. * BUGFIX: Fixed bug introduced yesterday, where rotating multiple images resulting in them getting an odd angle. 2004-01-25 Jesper K. Pedersen * Split up the dialog for HTML generation, plus made it possible to specify which of the option groups (Persons/Location/...) plus description - to generate. * The image configuration dialog may now be closed by pressing Ctrl+return * Added a number of tooltips * Added a donate dialog, which makes it possible for people to donate money. * BUGFIX: when generating HTML, its no longer a problem if several images have the same base name, ie. if you may now generate holiday1/me.jpg andd holiday2/me.jpg at the same time. * BUGFIX: When the user presses the right mouse button on one of the list boxes containing options (Persons, Loactions, Keywords etc.) to show the context menu, then the state of the item was changed (from selected to non-selected or visa versa). * The total amount of images in the status bar is now updated when new images are found or existing images are deleted. * BUGFIX: don't load the search and multi-config images through the imageinfo clas as this will make KimDaBa ask whether date should be trusted. * Added a configuration option for whether EXIF rotation information should be used or not. 2004-01-23 Jesper K. Pedersen * BUGFIX: It was not possible to generate HTML more than once per session. * Read date, orientation and description out of EXIF data. 2004-01-21 Jesper K. Pedersen * Thanks to Teemu Rytilahti , KimDaBa will now offer themes when generating HTML. * Heavily optimized browsing functions. Now everything is hopefully lightning fast. 2004-01-20 Jesper K. Pedersen * The thumbnail overview and the image viewer now has a menu item for invoking an external program on the images. The list includes all programs claiming they can handle image/jpeg from KTrader. * Implemented Maintenance/Display Images not on Disk * Implemented Maintenance/Recalculate Checksum (Useful if images has been change for example using gimp) * Implemented Maintenance/Rescan for new Images * Only do auto save if there has been changes since the last auto save. 2004-01-19 Jesper K. Pedersen * MD5 sums are now calculated for each image, so that KimDaBa can track movement of images in the directories 2004-01-18 Jesper K. Pedersen * Move cursor to the lower right corner when viewer goes into fullscreen mode * BUGFIX: Generate HTML with nothing selected now export all images from thumbnail view. * BUGFIX: Context menu for thumbnail view used to show up in the browser too. * BUGFIX: searches using the textbox didn't work. * BUGFIX: ensure that tip of the day has focus when it starts up. 2004-01-10 Jesper K. Pedersen * Improved HTML generation. 2004-01-04 Jesper K. Pedersen * The tool tip for the thumbnail view now contains an enlargement of the image. In addition the tool tip needs to be disabled, rather than it automatically hides. The tool tip window will temporarily hide when the mouse is outside the thumbnail view. The placement of the tool tip window is now improved, so it doesn't get partly of screen. 2004-01-03 Jesper K. Pedersen * Added an item to the context menu in the viewer for setting the current image as the background. (Thanks to Teemu Rytilahti ) for a patch with this feature. * New Feature: It is now possible to show the content views (those with overview of persons, locations, keywords etc) either with a list view or with an icon view. In addition it is also possible to show a different icon for each item (one icon for Joe, and another for Bill). 2003-12-30 Jesper K. Pedersen * BUGFIX: Member groups are not shown in the browser, in case the member group do not contain any items. 2003-12-26 Jesper K. Pedersen * Update thumnail view when an image has been rotated. * Added "View Selected" and "View Selected in new window" to the context menu for the thumbnail overview. * Enable/Disable state of "configure property items" are now correctly set. * On public demand, the file name is now shown in the status bar. 2003-12-13 Jesper K. Pedersen * BUGFIX: fixed crash where KimDaBa crashes if you right click outside an item in one of the list boxes with properties (Persons, Locations, ...) 2003-12-05 Jesper K. Pedersen * BUGFIX: KimDaBa used to crash on moving images around. 2003-12-01 Jesper K. Pedersen * New Feature: Its now possible to drag(copy) images from kimdaba to say the desktop. Unfortunately KimDaBa crashes in KDE code if I drag an QImage out, so I can only drag the filename out, which means that rotated images are not rotated when dragged out. Still better than nothing ;-) * Go out of full screen if an item from the info box in the viewer is selected, and the viewer is on the same monitor as the browser. * Added Settings | Configure Shortcuts * Edit->Options was moved Settings->Configure Kimdaba. * New Feature: It is now possible to using fish:// or ftp:// when exporting to HTML * BUGFIX: Don't show HTML export dialog in case no item was selected. ---------------------------------- version 1.0 released -------------------- 2003-11-27 Jesper K. Pedersen * BUGFIX: Previously the image loader was asked to load all images on start up, making machines with low memory crash. This was clearly unintentional, and as such a bug. 2003-11-21 Jesper K. Pedersen * BUGFIX: indicator marks are now properly shown during selection of drawing on images. 2003-11-13 Jesper K. Pedersen * BUGFIX: File names did not contain an extension in the ThumbNail directory. This resulted in a name class if two files existed with same base name but different extension. * Completed the KimDaBa handbook. * BUGFIX: KimDaBa did always show one image less than the index promised. * IMPROVEMENT: Exchanged Ctrl+plus and Ctrl+minus in the viewer, so ctrl+plus now means run faster and ctrl+minus means run slower. This seems much more intuitive. 2003-10-21 Jesper K. Pedersen * BUGFIX: First search for an image, then set properties, and the app crashed. 2003-10-05 Jesper K. Pedersen * BUGFIX: KimDaBa crashed when it found new images at start up. * Started writing a handbook for KimDaBa. * BUGFIX: if the slideshow was running very fast, then keyevents got utterly much behind making it almost impossible to stop the slideshow. * Its now possible to configure image properties from within the viewer. 2003-10-04 Jesper K. Pedersen * Added new feature, which allows you to specify a default search option, that applies to all searches. That way you can exclude all private images, when looking at images with say, your parents. * BUGFIX: If a user added a new group, but never added any items to the group, then the group would not be available later for adding new items, but it was still available for selecting in configuration listbox and for searching. * BUGFIX: When a group was renamed or delete, the old name would still be available in the configuration listbox. * Added status bar icon for dirtyness. * BUGFIX: In the image config, selecting an item from the listbox did not mark the image dirty. * BUGFIX: Pressing Cancel in the find dialog, does no longer change the content of the browser window. diff --git a/mainview.cpp b/mainview.cpp index 468df4b4..5e7964a4 100644 --- a/mainview.cpp +++ b/mainview.cpp @@ -1,1153 +1,1154 @@ /* * Copyright (c) 2003-2004 Jesper K. Pedersen * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License version 2 as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. **/ #include "mainview.h" #include #include #include "thumbnailview.h" #include "thumbnail.h" #include "imageconfig.h" #include #include #include #include #include #include "viewer.h" #include #include #include "showbusycursor.h" #include #include #include #include #include "htmlexportdialog.h" #include #include "imagecounter.h" #include #include #include "options.h" #include "browser.h" #include "imagedb.h" #include "util.h" #include #include #include #include "deletedialog.h" #include #include #include #include #include #include #include #include #include #include #include "externalpopup.h" #include #include #include "deletethumbnailsdialog.h" #include "thumbnailbuilder.h" #include #include "export.h" #include "import.h" MainView* MainView::_instance = 0; MainView::MainView( QWidget* parent, const char* name ) :KMainWindow( parent, name ), _imageConfigure(0), _dirty( false ), _autoSaveDirty( false ), _deleteDialog( 0 ), _dirtyIndicator(0), _htmlDialog(0) { _instance = this; load(); // To avoid a race conditions where both the image loader thread creates an instance of // Options, and where the main thread crates an instance, we better get it created now. connect( Options::instance(), SIGNAL( changed() ), this, SLOT( slotChanges() ) ); _stack = new QWidgetStack( this ); _browser = new Browser( _stack ); connect( _browser, SIGNAL( showingOverview() ), this, SLOT( showBrowser() ) ); connect( _browser, SIGNAL( pathChanged( const QString& ) ), this, SLOT( pathChanged( const QString& ) ) ); _thumbNailView = new ThumbNailView( _stack ); connect( _thumbNailView, SIGNAL( fileNameChanged( const QString& ) ), this, SLOT( slotSetFileName( const QString& ) ) ); _stack->addWidget( _browser ); _stack->addWidget( _thumbNailView ); setCentralWidget( _stack ); _stack->raiseWidget( _browser ); _optionsDialog = 0; setupMenuBar(); // Setting up status bar QHBox* indicators = new QHBox( statusBar() ); _dirtyIndicator = new QLabel( indicators ); setDirty( _dirty ); // Might already have been made dirty by load above _lockedIndicator = new QLabel( indicators ); setLocked( Options::instance()->isLocked() ); statusBar()->addWidget( indicators, 0, true ); ImageCounter* partial = new ImageCounter( statusBar() ); statusBar()->addWidget( partial, 0, true ); ImageCounter* total = new ImageCounter( statusBar() ); statusBar()->addWidget( total, 0, true ); // Misc _autoSaveTimer = new QTimer( this ); connect( _autoSaveTimer, SIGNAL( timeout() ), this, SLOT( slotAutoSave() ) ); startAutoSaveTimer(); connect( ImageDB::instance(), SIGNAL( matchCountChange( int, int, int ) ), partial, SLOT( setMatchCount( int, int, int ) ) ); connect( ImageDB::instance(), SIGNAL( totalChanged( int ) ), total, SLOT( setTotal( int ) ) ); connect( _browser, SIGNAL( showingOverview() ), partial, SLOT( showingOverview() ) ); connect( ImageDB::instance(), SIGNAL( searchCompleted() ), this, SLOT( showThumbNails() ) ); connect( Options::instance(), SIGNAL( optionGroupsChanged() ), this, SLOT( slotOptionGroupChanged() ) ); connect( _thumbNailView, SIGNAL( selectionChanged() ), this, SLOT( slotThumbNailSelectionChanged() ) ); connect( ImageDB::instance(), SIGNAL( dirty() ), this, SLOT( markDirty() ) ); total->setTotal( ImageDB::instance()->totalCount() ); statusBar()->message(i18n("Welcome to KimDaBa"), 5000 ); QTimer::singleShot( 0, this, SLOT( delayedInit() ) ); } void MainView::delayedInit() { KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); if ( args->isSet( "import" ) ) { // I need to do this in delayed init to get the import window on top of the normal window Import::imageImport( KCmdLineArgs::makeURL( args->getOption("import") ) ); } else { // I need to postpone this otherwise the tip dialog will not get focus on start up KTipDialog::showTip( this ); } } bool MainView::slotExit() { if ( _dirty || !ImageDB::instance()->isClipboardEmpty() ) { int ret = QMessageBox::warning( this, i18n("Save Changes?"), i18n("Do you want to save the changes?"), QMessageBox::Yes, QMessageBox::No, QMessageBox::Cancel ); if ( ret == QMessageBox::Cancel ) return false; if ( ret == QMessageBox::Yes ) { slotSave(); } if ( ret == QMessageBox::No ) { QDir().remove( Options::instance()->imageDirectory() + QString::fromLatin1("/.#index.xml") ); } } qApp->quit(); return true; } void MainView::slotOptions() { if ( ! _optionsDialog ) { _optionsDialog = new OptionsDialog( this ); connect( _optionsDialog, SIGNAL( changed() ), this, SLOT( reloadThumbNail() ) ); } _optionsDialog->exec(); startAutoSaveTimer(); // In case auto save period has changed, we better restart the timer. } void MainView::slotConfigureAllImages() { configureImages( false ); } void MainView::slotConfigureImagesOneAtATime() { configureImages( true ); } void MainView::configureImages( bool oneAtATime ) { ImageInfoList list = selected(); if ( list.count() == 0 ) { QMessageBox::warning( this, i18n("No Selection"), i18n("No item is selected.") ); } else { configureImages( list, oneAtATime ); } } void MainView::configureImages( const ImageInfoList& list, bool oneAtATime ) { _instance->configImages( list, oneAtATime ); } void MainView::configImages( const ImageInfoList& list, bool oneAtATime ) { createImageConfig(); int x = _thumbNailView->contentsX(); int y = _thumbNailView->contentsY() + Options::instance()->thumbSize()/2; int w = _thumbNailView->contentsWidth(); int h =_thumbNailView->contentsHeight() - Options::instance()->thumbSize()/2; QString firstName; QString lastName; QIconViewItem* item = _thumbNailView->findFirstVisibleItem( QRect(x,y,w,h) ); if ( item ) { ThumbNail* tn = static_cast( item ); firstName = tn->fileName(); } item = _thumbNailView->findLastVisibleItem( QRect(x,y,w,h) ); if ( item ) { ThumbNail* tn = static_cast( item ); lastName = tn->fileName(); } _imageConfigure->configure( list, oneAtATime ); if ( _imageConfigure->thumbnailShouldReload() ) { reloadThumbNail(); QIconViewItem* firstItem = 0; QIconViewItem* lastItem = 0; for ( QIconViewItem* item = _thumbNailView->firstItem(); item; item = item->nextItem() ) { ThumbNail* tn = static_cast( item ); if ( tn->fileName() == firstName ) firstItem = item; if ( tn->fileName() == lastName ) lastItem = item; } if ( lastItem ) _thumbNailView->ensureItemVisible( lastItem ); if ( firstItem ) _thumbNailView->ensureItemVisible( firstItem ); } } void MainView::slotSearch() { createImageConfig(); ImageSearchInfo searchInfo = _imageConfigure->search(); if ( !searchInfo.isNull() ) _browser->addSearch( searchInfo ); } void MainView::createImageConfig() { ShowBusyCursor dummy; if ( _imageConfigure ) return; _imageConfigure = new ImageConfig( this, "_imageConfigure" ); connect( _imageConfigure, SIGNAL( changed() ), this, SLOT( slotChanges() ) ); } void MainView::slotSave() { statusBar()->message(i18n("Saving..."), 5000 ); save( Options::instance()->imageDirectory() + QString::fromLatin1("/index.xml") ); setDirty( false ); QDir().remove( Options::instance()->imageDirectory() + QString::fromLatin1("/.#index.xml") ); statusBar()->message(i18n("Saving... Done"), 5000 ); } void MainView::save( const QString& fileName ) { ShowBusyCursor dummy; QDomDocument doc; doc.appendChild( doc.createProcessingInstruction( QString::fromLatin1("xml"), QString::fromLatin1("version=\"1.0\" encoding=\"UTF-8\"") ) ); QDomElement elm = doc.createElement( QString::fromLatin1("KimDaBa") ); doc.appendChild( elm ); Options::instance()->save( elm ); ImageDB::instance()->save( elm ); QFile out( fileName ); if ( !out.open( IO_WriteOnly ) ) KMessageBox::sorry( this, i18n( "Could not open file '%1'" ).arg( fileName ) ); else { QCString s = doc.toCString(); out.writeBlock( s.data(), s.size()-1 ); out.close(); } } void MainView::slotDeleteSelected() { if ( ! _deleteDialog ) _deleteDialog = new DeleteDialog( this ); if ( _deleteDialog->exec( selected() ) == QDialog::Accepted ) setDirty( true ); reloadThumbNail(); } ImageInfoList MainView::selected() { ImageInfoList list; for ( QIconViewItem* item = _thumbNailView->firstItem(); item; item = item->nextItem() ) { if ( item->isSelected() ) { ThumbNail* tn = dynamic_cast( item ); Q_ASSERT( tn ); list.append( tn->imageInfo() ); } } return list; } ImageInfoList MainView::currentView() { ImageInfoList list; for ( QIconViewItem* item = _thumbNailView->firstItem(); item; item = item->nextItem() ) { ThumbNail* tn = dynamic_cast( item ); Q_ASSERT( tn ); list.append( tn->imageInfo() ); } return list; } void MainView::slotViewNewWindow() { slotView( false, false ); } ImageInfoList MainView::getSelectedOnDisk() { ImageInfoList listOnDisk; ImageInfoList list = selected(); if ( list.count() == 0 ) list = ImageDB::instance()->currentContext( true ); for( ImageInfoListIterator it( list ); *it; ++it ) { if ( (*it)->imageOnDisk() ) listOnDisk.append( *it ); } return listOnDisk; } void MainView::slotView( bool reuse, bool slideShow, bool random ) { ImageInfoList listOnDisk = getSelectedOnDisk(); if ( listOnDisk.count() == 0 ) { QMessageBox::warning( this, i18n("No Images to Display"), i18n("None of the selected images were available on the disk.") ); } if (random) listOnDisk = Util::shuffle( listOnDisk ); if ( listOnDisk.count() != 0 ) { Viewer* viewer; if ( reuse && Viewer::latest() ) { viewer = Viewer::latest(); topLevelWidget()->raise(); setActiveWindow(); } else { viewer = new Viewer( "viewer" ); } viewer->show( slideShow ); viewer->load( listOnDisk ); viewer->raise(); } } void MainView::slotSortByDateAndTime() { ImageInfoList sorted; typedef QMap SortMap; SortMap map; bool hasShownMessage = false; ImageInfoList listOnDisk = getSelectedOnDisk();// just sort images available (on disk) ImageInfoList list; list = selected(); //I don't use currentContext because the user could easily sort whole db without wanting it // (remember the option is called "Sort Selected" -> if there are no selecetd -> warning if ( list.count() == 0 ) QMessageBox::warning( this, i18n("No Selection"), i18n("No item is selected.") ); else { // Do sorting here ImageInfoListIterator it_selected( listOnDisk ); ImageInfoList& images = ImageDB::instance()->images(); ImageInfoListIterator it_all( images ); int index = images.find(it_selected.toFirst()); //gets the index of the first selected image it_all.toFirst(); it_all+=index; //sets it_all to same image as it_selected //calculate for every picture the time in seconds from 1970 and put it into map //since the time in seconds is the key, and the ImageInfo is the data we get //it sorted by the map automatically for( it_selected.toFirst(); *it_selected; ++it_selected, ++it_all ){ ImageDate imagedate= (*it_selected)->startDate(); QDateTime datetime; if ((*it_selected)->MD5Sum() != (*it_all)->MD5Sum() && hasShownMessage==false){ if(KMessageBox::warningYesNo(0,i18n("You are about to sort a set of images with others in between" "
This might result in an unexpected sort order
" "

Are you sure you want to continue?

"), QString::null, KStdGuiItem::yes(), KStdGuiItem::no(), QString::null, KMessageBox::Dangerous)==KMessageBox::No) return; hasShownMessage = true; } int year = 1752; int month = 1; int day = 1; if ( imagedate.year() != 0 ) year = imagedate.year(); if ( imagedate.month() != 0 ) month = imagedate.month(); if ( imagedate.day() != 0 ) day = imagedate.day(); datetime.setDate( QDate(year,month,day) ); if(imagedate.hasValidTime()) datetime.setTime(imagedate.getTime()); else datetime.setTime(QTime(0,0,0)); uint timeseconds = datetime.toTime_t(); while(map.contains(timeseconds))//if two or more pictures has the same time timeseconds += 1; ImageInfo *test = *it_selected; map[timeseconds]= *test; } SortMap::Iterator iterator; //"copy" the new order in the sorted list for(iterator=map.begin(); iterator!=map.end(); ++iterator){ for (ImageInfoListIterator it2( listOnDisk );*it2; ++it2){ if( iterator.data().MD5Sum() == (*it2)->MD5Sum() ) { sorted.append( *it2 ); break; } } } //remove every item of sorted from the images_list(the main list) //because we add all the images from sorted afterwards //(we would have them doubled if we wouldn't remove them) for( ImageInfoListIterator it3( sorted );*it3; ++it3 ) { if (!(images.removeRef((*it3)))){ KMessageBox::error(0,i18n("MD5Sum failure; try recalcing your MD5Sums.")); return; } } //insert sorted block of images in place of block of unsorted images // index represents the place of the first selected unsorted image for( ImageInfoListIterator it4( sorted );*it4; ++it4,index++){ images.insert(index,(*it4)); } _thumbNailView->reload(); markDirty(); } } QString MainView::welcome() { WelComeDialog dialog( this ); dialog.exec(); return dialog.configFileName(); } void MainView::slotChanges() { setDirty( true ); } void MainView::closeEvent( QCloseEvent* e ) { bool quit = true; quit = slotExit(); // If I made it here, then the user canceled if ( !quit ) e->ignore(); } void MainView::slotLimitToSelected() { ShowBusyCursor dummy; for ( QIconViewItem* item = _thumbNailView->firstItem(); item; item = item->nextItem() ) { ThumbNail* tn = dynamic_cast( item ); Q_ASSERT( tn ); tn->imageInfo()->setVisible( item->isSelected() ); } reloadThumbNail(); } void MainView::setupMenuBar() { // File menu KStdAction::save( this, SLOT( slotSave() ), actionCollection() ); KStdAction::quit( this, SLOT( slotExit() ), actionCollection() ); _generateHtml = new KAction( i18n("Generate HTML..."), 0, this, SLOT( slotExportToHTML() ), actionCollection(), "exportHTML" ); new KAction( i18n( "Import..."), 0, this, SLOT( slotImport() ), actionCollection(), "import" ); new KAction( i18n( "Export..."), 0, this, SLOT( slotExport() ), actionCollection(), "export" ); // Go menu KAction* a = KStdAction::back( _browser, SLOT( back() ), actionCollection() ); connect( _browser, SIGNAL( canGoBack( bool ) ), a, SLOT( setEnabled( bool ) ) ); a->setEnabled( false ); a = KStdAction::forward( _browser, SLOT( forward() ), actionCollection() ); connect( _browser, SIGNAL( canGoForward( bool ) ), a, SLOT( setEnabled( bool ) ) ); a->setEnabled( false ); a = KStdAction::home( _browser, SLOT( home() ), actionCollection() ); // The Edit menu _cut = KStdAction::cut( _thumbNailView, SLOT( slotCut() ), actionCollection() ); _paste = KStdAction::paste( _thumbNailView, SLOT( slotPaste() ), actionCollection() ); _selectAll = KStdAction::selectAll( _thumbNailView, SLOT( slotSelectAll() ), actionCollection() ); KStdAction::find( this, SLOT( slotSearch() ), actionCollection() ); _deleteSelected = new KAction( i18n( "Delete Selected" ), Key_Delete, this, SLOT( slotDeleteSelected() ), actionCollection(), "deleteSelected" ); _configOneAtATime = new KAction( i18n( "Configure images &one at a Time" ), CTRL+Key_1, this, SLOT( slotConfigureImagesOneAtATime() ), actionCollection(), "oneProp" ); _configAllSimultaniously = new KAction( i18n( "Configure &all images simultaneously" ), CTRL+Key_2, this, SLOT( slotConfigureAllImages() ), actionCollection(), "allProp" ); // The Images menu _view = new KAction( i18n("View"), Key_I, this, SLOT( slotView() ), actionCollection(), "viewImages" ); _viewInNewWindow = new KAction( i18n("View (In New Window)"), CTRL+Key_I, this, SLOT( slotViewNewWindow() ), actionCollection(), "viewImagesNewWindow" ); _runSlideShow = new KAction( i18n("Run Slide Show"), QString::fromLatin1("kview"), Key_S, this, SLOT( slotRunSlideShow() ), actionCollection(), "runSlideShow" ); _runRandomSlideShow = new KAction( i18n( "Run Randomized Slide Show" ), SHIFT+Key_S, this, SLOT( slotRunRandomizedSlideShow() ), actionCollection(), "runRandomizedSlideShow" ); _sortByDateAndTime = new KAction( i18n("Sort Selected by Date and Time"), 0, this, SLOT( slotSortByDateAndTime() ), actionCollection(), "sortImages" ); _limitToMarked = new KAction( i18n("Limit View to Marked"), 0, this, SLOT( slotLimitToSelected() ), actionCollection(), "limitToMarked" ); _lock = new KAction( i18n("Lock Images"), 0, this, SLOT( lockToDefaultScope() ), actionCollection(), "lockToDefaultScope" ); _unlock = new KAction( i18n("Unlock"), 0, this, SLOT( unlockFromDefaultScope() ), actionCollection(), "unlockFromDefaultScope" ); new KAction( i18n("Change Password"), 0, this, SLOT( changePassword() ), actionCollection(), "changeScopePasswd" ); _setDefaultPos = new KAction( i18n("Mark Current View as Lock"), 0, this, SLOT( setDefaultScopePositive() ), actionCollection(), "setDefaultScopePositive" ); _setDefaultNeg = new KAction( i18n("Mark Everything but the Current View as Lock"), 0, this, SLOT( setDefaultScopeNegative() ), actionCollection(), "setDefaultScopeNegative" ); // Maintenance new KAction( i18n("Display Images not on Disk"), 0, this, SLOT( slotShowNotOnDisk() ), actionCollection(), "findUnavailableImages" ); new KAction( i18n("Recalculate Checksum"), 0, ImageDB::instance(), SLOT( slotRecalcCheckSums() ), actionCollection(), "rebuildMD5s" ); new KAction( i18n("Rescan for images"), 0, ImageDB::instance(), SLOT( slotRescan() ), actionCollection(), "rescan" ); new KAction( i18n("Read time info from files..."), 0, ImageDB::instance(), SLOT( slotTimeInfo() ), actionCollection(), "readTime" ); new KAction( i18n("Remove all thumbnails..."), 0, this, SLOT( slotRemoveAllThumbnails() ), actionCollection(), "removeAllThumbs" ); new KAction( i18n("Build thumbnails"), 0, this, SLOT( slotBuildThumbnails() ), actionCollection(), "buildThumbs" ); // Settings KStdAction::preferences( this, SLOT( slotOptions() ), actionCollection() ); KStdAction::keyBindings( this, SLOT( slotConfigureKeyBindings() ), actionCollection() ); KStdAction::configureToolbars( this, SLOT( slotConfigureToolbars() ), actionCollection() ); new KAction( i18n("Enable all messages"), 0, this, SLOT( slotReenableMessages() ), actionCollection(), "readdAllMessages" ); _viewMenu = new KActionMenu( i18n("Configure View"), QString::fromLatin1( "view_choose" ), actionCollection(), "configureView" ); _viewMenu->setDelayed( false ); connect( _browser, SIGNAL( showsContentView( bool ) ), _viewMenu, SLOT( setEnabled( bool ) ) ); _smallListView = new KRadioAction( i18n("Small List View"), KShortcut(), _browser, SLOT( slotSmallListView() ), _viewMenu ); _viewMenu->insert( _smallListView ); _smallListView->setExclusiveGroup( QString::fromLatin1("configureview") ); _largeListView = new KRadioAction( i18n("Large List View"), KShortcut(), _browser, SLOT( slotLargeListView() ), _viewMenu ); _viewMenu->insert( _largeListView ); _largeListView->setExclusiveGroup( QString::fromLatin1("configureview") ); _smallIconView = new KRadioAction( i18n("Small Icon View"), KShortcut(), _browser, SLOT( slotSmallIconView() ), _viewMenu ); _viewMenu->insert( _smallIconView ); _smallIconView->setExclusiveGroup( QString::fromLatin1("configureview") ); _largeIconView = new KRadioAction( i18n("Large Icon View"), KShortcut(), _browser, SLOT( slotLargeIconView() ), _viewMenu ); _viewMenu->insert( _largeIconView ); _largeIconView->setExclusiveGroup( QString::fromLatin1("configureview") ); connect( _browser, SIGNAL( currentSizeAndTypeChanged( Options::ViewSize, Options::ViewType ) ), this, SLOT( slotUpdateViewMenu( Options::ViewSize, Options::ViewType ) ) ); // The help menu KStdAction::tipOfDay( this, SLOT(showTipOfDay()), actionCollection() ); KToggleAction* taction = new KToggleAction( i18n("Show Tooltips on Images"), CTRL+Key_T, actionCollection(), "showToolTipOnImages" ); connect( taction, SIGNAL( toggled( bool ) ), _thumbNailView, SLOT( showToolTipsOnImages( bool ) ) ); new KAction( i18n("Run KimDaBa Demo"), 0, this, SLOT( runDemo() ), actionCollection(), "runDemo" ); new KAction( i18n("Donate Money..."), 0, this, SLOT( donateMoney() ), actionCollection(), "donate" ); connect( _thumbNailView, SIGNAL( changed() ), this, SLOT( slotChanges() ) ); createGUI( QString::fromLatin1( "kimdabaui.rc" ) ); } void MainView::slotExportToHTML() { ImageInfoList list = getSelectedOnDisk(); if ( list.count() == 0 ) { list = ImageDB::instance()->currentContext( true ); if ( list.count() != _thumbNailView->count() && _stack->visibleWidget() == _thumbNailView ) { int code = KMessageBox::warningContinueCancel( this, i18n("You are about to generate an HTML page for %1 images, " "which are all the images in your current context. " "If you only want to generate HTML for the set of images you " "are currently looking at, then choose select all from the " "edit menu and choose generate.") .arg( list.count() ), QString::null, KStdGuiItem::cont(), QString::fromLatin1( "generateMoreImagesThatCurrentView" ) ); if ( code == KMessageBox::Cancel ) return; } } if ( ! _htmlDialog ) _htmlDialog = new HTMLExportDialog( this, "htmlExportDialog" ); _htmlDialog->exec( list ); } void MainView::startAutoSaveTimer() { int i = Options::instance()->autoSave(); _autoSaveTimer->stop(); if ( i != 0 ) { _autoSaveTimer->start( i * 1000 * 60 ); } } void MainView::slotAutoSave() { if ( _autoSaveDirty ) { statusBar()->message(i18n("Auto saving....")); save( Options::instance()->imageDirectory() + QString::fromLatin1("/.#index.xml") ); statusBar()->message(i18n("Auto saving.... Done"), 5000); _autoSaveDirty = false; } } void MainView::showThumbNails() { reloadThumbNail(); _stack->raiseWidget( _thumbNailView ); _thumbNailView->setFocus(); updateStates( true ); } void MainView::showBrowser() { _stack->raiseWidget( _browser ); _browser->setFocus(); updateStates( false ); } void MainView::slotOptionGroupChanged() { Q_ASSERT( !_imageConfigure || !_imageConfigure->isShown() ); delete _imageConfigure; _imageConfigure = 0; } void MainView::showTipOfDay() { KTipDialog::showTip( this, QString::null, true ); } void MainView::pathChanged( const QString& path ) { static bool itemVisible = false; if ( path.isEmpty() ) { if ( itemVisible ) { statusBar()->removeItem( 0 ); itemVisible = false; } } else if ( !itemVisible ) { statusBar()->insertItem( path, 0 ); itemVisible = true; } else statusBar()->changeItem( path, 0 ); } void MainView::runDemo() { KProcess* process = new KProcess; *process << "kimdaba" << "-demo"; process->start(); } void MainView::load() { // Let first try to find a config file. KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); QString configFile = QString::null; if ( args->isSet( "c" ) ) configFile = args->getOption( "c" ); else if ( args->isSet( "demo" ) ) configFile = Util::setupDemo(); else { KSimpleConfig config( QString::fromLatin1("kimdaba") ); if ( config.hasKey( QString::fromLatin1("configfile") ) ) { configFile = config.readEntry( QString::fromLatin1("configfile") ); if ( !QFileInfo( configFile ).exists() ) configFile = welcome(); } else configFile = welcome(); } Util::checkForBackupFile( configFile ); QDomDocument doc; QFile file( configFile ); if ( !file.exists() ) { // Load a default setup QFile file( locate( "data", QString::fromLatin1( "kimdaba/default-setup" ) ) ); if ( !file.open( IO_ReadOnly ) ) { KMessageBox::information( 0, i18n( "

KimDaBa was unable to load a default setup, which indicates an installation error

" "

If you have installed KimDaBa yourself, then you must remember to set the environment variable " "KDEDIRS, to point to the topmost installation directory.

" "

If you for example ran configure with --prefix=/usr/local/kde, then you must use the following " "environment variable setup (this example is for Bash and compatible shells):

" "

export KDEDIRS=/usr/local/kde

" "

In case you already have KDEDIRS set, simply append the string as if you where setting the PATH " "environment variable

"), i18n("No default setup file found") ); } else { QTextStream stream( &file ); QString str = stream.read(); str = str.replace( QString::fromLatin1( "Persons" ), i18n( "Persons" ) ); str = str.replace( QString::fromLatin1( "Locations" ), i18n( "Locations" ) ); str = str.replace( QString::fromLatin1( "Keywords" ), i18n( "Keywords" ) ); str = str.replace( QRegExp( QString::fromLatin1("imageDirectory=\"[^\"]*\"")), QString::fromLatin1("") ); str = str.replace( QRegExp( QString::fromLatin1("htmlBaseDir=\"[^\"]*\"")), QString::fromLatin1("") ); str = str.replace( QRegExp( QString::fromLatin1("htmlBaseURL=\"[^\"]*\"")), QString::fromLatin1("") ); doc.setContent( str ); } } else { if ( !file.open( IO_ReadOnly ) ) { KMessageBox::error( this, i18n("Unable to open '%1' for reading").arg( configFile ), i18n("Error running demo") ); exit(-1); } QString errMsg; int errLine; int errCol; if ( !doc.setContent( &file, false, &errMsg, &errLine, &errCol )) { KMessageBox::error( this, i18n("Error on line %1 column %2 in file %3: %4").arg( errLine ).arg( errCol ).arg( configFile ).arg( errMsg ) ); exit(-1); } } // Now read the content of the file. QDomElement top = doc.documentElement(); if ( top.isNull() ) { KMessageBox::error( this, i18n("Error in file %1: No elements found").arg( configFile ) ); exit(-1); } if ( top.tagName().lower() != QString::fromLatin1( "kimdaba" ) ) { KMessageBox::error( this, i18n("Error in file %1: expected 'KimDaBa' as top element but found '%2'").arg( configFile ).arg( top.tagName() ) ); exit(-1); } QDomElement config; QDomElement options; QDomElement configWindowSetup; QDomElement images; QDomElement blockList; QDomElement memberGroups; for ( QDomNode node = top.firstChild(); !node.isNull(); node = node.nextSibling() ) { if ( node.isElement() ) { QDomElement elm = node.toElement(); QString tag = elm.tagName().lower(); if ( tag == QString::fromLatin1( "config" ) ) config = elm; else if ( tag == QString::fromLatin1( "options" ) ) options = elm; else if ( tag == QString::fromLatin1( "configwindowsetup" ) ) configWindowSetup = elm; else if ( tag == QString::fromLatin1("images") ) images = elm; else if ( tag == QString::fromLatin1( "blocklist" ) ) blockList = elm; else if ( tag == QString::fromLatin1( "member-groups" ) ) memberGroups = elm; else { KMessageBox::error( this, i18n("Error in file %1: unexpected element: '%2*").arg( configFile ).arg( tag ) ); } } } if ( config.isNull() ) KMessageBox::sorry( this, i18n("Unable to find 'Config' tag in configuration file %1").arg( configFile ) ); if ( options.isNull() ) KMessageBox::sorry( this, i18n("Unable to find 'Options' tag in configuration file %1").arg( configFile ) ); if ( configWindowSetup.isNull() ) KMessageBox::sorry( this, i18n("Unable to find 'ConfigWindowSetup' tag in configuration file %1").arg( configFile ) ); if ( images.isNull() ) KMessageBox::sorry( this, i18n("Unable to find 'Images' tag in configuration file %1").arg( configFile ) ); file.close(); Options::setup( config, options, configWindowSetup, memberGroups, QFileInfo( configFile ).dirPath( true ) ); bool newImages = ImageDB::setup( images, blockList ); if ( newImages ) setDirty( true ); } void MainView::contextMenuEvent( QContextMenuEvent* ) { if ( _stack->visibleWidget() == _thumbNailView ) { QPopupMenu menu( this, "context popup menu"); _configOneAtATime->plug( &menu ); _configAllSimultaniously->plug( &menu ); _runSlideShow->plug( &menu ); _runRandomSlideShow->plug( &menu ); menu.insertSeparator(); _view->plug( &menu ); _viewInNewWindow->plug( &menu ); ExternalPopup* externalCommands = new ExternalPopup( &menu ); ImageInfo* info = 0; - QIconViewItem* item = _thumbNailView->findItem( _thumbNailView->mapFromGlobal( QCursor::pos() ) ); + QIconViewItem* item = + _thumbNailView->findItem( _thumbNailView->viewportToContents( _thumbNailView->mapFromGlobal( QCursor::pos() ) ) ); if ( item ) info = static_cast(item)->imageInfo(); externalCommands->populate( info, selected() ); int id = menu.insertItem( i18n( "Invoke External Program" ), externalCommands ); if ( info == 0 && selected().count() == 0 ) menu.setItemEnabled( id, false ); menu.exec( QCursor::pos() ); } } void MainView::markDirty() { setDirty( true ); } void MainView::setDirty( bool dirty ) { static QPixmap* dirtyPix = new QPixmap( SmallIcon( QString::fromLatin1( "3floppy_unmount" ) ) ); if ( _dirtyIndicator ) { // Might not yet have been created. _dirtyIndicator->setFixedWidth( dirtyPix->width() ); if ( dirty ) _dirtyIndicator->setPixmap( *dirtyPix ); else _dirtyIndicator->setPixmap( QPixmap() ); } _dirty = dirty; _autoSaveDirty = dirty; } void MainView::setDefaultScopePositive() { Options::instance()->setCurrentLock( _browser->currentContext(), false ); } void MainView::setDefaultScopeNegative() { Options::instance()->setCurrentLock( _browser->currentContext(), true ); } void MainView::lockToDefaultScope() { int i = KMessageBox::warningContinueCancel( this, i18n( "

The password protection is only a means of allowing your little sister " "to look in your images, without getting to those embarrassing images from " "your last party.

" "

In other words, anyone with access to the index.xml file can easily circumvent " "this password.

"), i18n("Password protection"), KStdGuiItem::cont(), QString::fromLatin1( "lockPassWordIsNotEncruption" ) ); if ( i == KMessageBox::Cancel ) return; setLocked( true ); } void MainView::unlockFromDefaultScope() { QCString passwd; bool OK = ( Options::instance()->password().isEmpty() ); while ( !OK ) { int code = KPasswordDialog::getPassword( passwd, i18n("Type in Password to unlock")); if ( code == QDialog::Rejected ) return; OK = (Options::instance()->password() == QString(passwd)); if ( !OK ) KMessageBox::sorry( this, i18n("Invalid Password") ); } setLocked( false ); } void MainView::setLocked( bool locked ) { static QPixmap* lockedPix = new QPixmap( SmallIcon( QString::fromLatin1( "key" ) ) ); _lockedIndicator->setFixedWidth( lockedPix->width() ); if ( locked ) _lockedIndicator->setPixmap( *lockedPix ); else _lockedIndicator->setPixmap( QPixmap() ); Options::instance()->setLocked( locked ); _lock->setEnabled( !locked ); _unlock->setEnabled( locked ); _setDefaultPos->setEnabled( !locked ); _setDefaultNeg->setEnabled( !locked ); _browser->reload(); } void MainView::changePassword() { QCString passwd; bool OK = ( Options::instance()->password().isEmpty() ); while ( !OK ) { int code = KPasswordDialog::getPassword( passwd, i18n("Type in old Password")); if ( code == QDialog::Rejected ) return; OK = (Options::instance()->password() == QString(passwd)); if ( !OK ) KMessageBox::sorry( this, i18n("Invalid Password") ); } int code = KPasswordDialog::getNewPassword( passwd, i18n("Type in New Password")); if ( code == QDialog::Accepted ) Options::instance()->setPassword( passwd ); } void MainView::slotConfigureKeyBindings() { Viewer* viewer = new Viewer( "viewer" ); // Do not show, this is only used to get a key configuration KKeyDialog* dialog = new KKeyDialog(); dialog->insert( actionCollection(), i18n( "General" ) ); dialog->insert( viewer->actions(), i18n("Viewer") ); dialog->configure(); dialog->commitChanges(); delete dialog; delete viewer; } void MainView::slotSetFileName( const QString& fileName ) { statusBar()->message( fileName, 4000 ); } void MainView::slotThumbNailSelectionChanged() { bool oneSelected = false; bool manySelected = false; for ( QIconViewItem* item = _thumbNailView->firstItem(); item; item = item->nextItem() ) { if ( item->isSelected() ) { if ( ! oneSelected ) oneSelected = true; else { manySelected = true; break; } } } _configAllSimultaniously->setEnabled( manySelected ); _configOneAtATime->setEnabled( oneSelected ); _sortByDateAndTime->setEnabled( manySelected ); } void MainView::reloadThumbNail() { _thumbNailView->reload(); slotThumbNailSelectionChanged(); } void MainView::slotUpdateViewMenu( Options::ViewSize size, Options::ViewType type ) { if ( size == Options::Small && type == Options::ListView ) _smallListView->setChecked( true ); else if ( size == Options::Large && type == Options::ListView ) _largeListView->setChecked( true ); else if ( size == Options::Small && type == Options::IconView ) _smallIconView->setChecked( true ); else if ( size == Options::Large && type == Options::IconView ) _largeIconView->setChecked( true ); } void MainView::slotShowNotOnDisk() { _stack->raiseWidget( _thumbNailView ); ImageDB::instance()->showUnavailableImages(); _thumbNailView->reload(); } void MainView::donateMoney() { Donate donate( this, "Donate Money" ); donate.exec(); } void MainView::updateStates( bool thumbNailView ) { _cut->setEnabled( thumbNailView ); _paste->setEnabled( thumbNailView ); _selectAll->setEnabled( thumbNailView ); _deleteSelected->setEnabled( thumbNailView ); _limitToMarked->setEnabled( thumbNailView ); } void MainView::slotRemoveAllThumbnails() { QStringList files; DeleteThumbnailsDialog dialog( this ); dialog.exec(); } void MainView::slotBuildThumbnails() { new ThumbnailBuilder( this ); // It will delete itself } void MainView::slotRunSlideShow() { slotView( true, true ); } void MainView::slotRunRandomizedSlideShow() { slotView( true, true, true ); } MainView* MainView::theMainView() { Q_ASSERT( _instance ); return _instance; } void MainView::slotConfigureToolbars() { saveMainWindowSettings(KGlobal::config(), QString::fromLatin1("MainWindow")); KEditToolbar dlg(actionCollection()); connect(&dlg, SIGNAL( newToolbarConfig() ), SLOT( slotNewToolbarConfig() )); dlg.exec(); } void MainView::slotNewToolbarConfig() { createGUI(); applyMainWindowSettings(KGlobal::config(), QString::fromLatin1("MainWindow")); } void MainView::slotImport() { Import::imageImport(); } void MainView::slotExport() { ImageInfoList list = getSelectedOnDisk(); if ( list.count() == 0 ) { KMessageBox::sorry( this, i18n("No images to export") ); } else Export::imageExport( list ); } void MainView::slotReenableMessages() { int ret = KMessageBox::questionYesNo( this, i18n("

Really enable all messageboxes where you previously " "checked the do-not-show-again check box?

" ) ); if ( ret == KMessageBox::Yes ) KMessageBox::enableAllMessages(); } #include "mainview.moc"