Kraxy/EBN: missing Q_OBJECT
[kphotoalbum.git] / ThumbnailView / ThumbnailModel.cpp
bloba4eeeb3a341028344b8eb5672fd2733c0b0a98a1
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.
18 #include "ThumbnailModel.h"
19 #include "Cell.h"
20 #include "ThumbnailCache.h"
21 #include "DB/ImageDB.h"
22 #include "ThumbnailWidget.h"
23 #include "ThumbnailPainter.h"
24 #include "ImageManager/Manager.h"
25 #include "Settings/SettingsData.h"
27 ThumbnailView::ThumbnailModel::ThumbnailModel( ThumbnailFactory* factory)
28 : ThumbnailComponent( factory ),
29 _sortDirection( Settings::SettingsData::instance()->showNewestThumbnailFirst() ? NewestFirst : OldestFirst )
31 connect( DB::ImageDB::instance(), SIGNAL( imagesDeleted( const DB::Result& ) ), this, SLOT( imagesDeletedFromDB( const DB::Result& ) ) );
34 static bool stackOrderComparator(const DB::ResultId& a, const DB::ResultId& b) {
35 return a.fetchInfo()->stackOrder() < b.fetchInfo()->stackOrder();
38 void ThumbnailView::ThumbnailModel::updateDisplayModel()
40 // FIXME: this can be probalby made obsolete by that new shiny thing in the DB
42 ImageManager::Manager::instance()->stop( painter(), ImageManager::StopOnlyNonPriorityLoads );
44 // Note, this can be simplified, if we make the database backend already
45 // return things in the right order. Then we only need one pass while now
46 // we need to go through the list two times.
48 /* Extract all stacks we have first. Different stackid's might be
49 * intermingled in the result so we need to know this ahead before
50 * creating the display list.
52 typedef QList<DB::ResultId> StackList;
53 typedef QMap<DB::StackID, StackList> StackMap;
54 StackMap stackContents;
55 Q_FOREACH(DB::ResultId id, _imageList) {
56 DB::ImageInfoPtr imageInfo = id.fetchInfo();
57 if (imageInfo->isStacked()) {
58 DB::StackID stackid = imageInfo->stackId();
59 stackContents[stackid].append(id);
64 * All stacks need to be ordered in their stack order. We don't rely that
65 * the images actually came in the order necessary.
67 for (StackMap::iterator it = stackContents.begin(); it != stackContents.end(); ++it) {
68 qStableSort(it->begin(), it->end(), stackOrderComparator);
71 /* Build the final list to be displayed. That is basically the sequence
72 * we got from the original, but the stacks shown with all images together
73 * in the right sequence or collapsed showing only the top image.
75 _displayList = DB::Result();
76 QSet<DB::StackID> alreadyShownStacks;
77 Q_FOREACH(DB::ResultId id, _imageList) {
78 DB::ImageInfoPtr imageInfo = id.fetchInfo();
79 if (imageInfo->isStacked()) {
80 DB::StackID stackid = imageInfo->stackId();
81 if (alreadyShownStacks.contains(stackid))
82 continue;
83 StackMap::iterator found = stackContents.find(stackid);
84 Q_ASSERT(found != stackContents.end());
85 const StackList& orderedStack = *found;
86 if (_expandedStacks.contains(stackid)) {
87 Q_FOREACH(DB::ResultId id, orderedStack) {
88 _displayList.append(id);
90 } else {
91 _displayList.append(orderedStack.at(0));
93 alreadyShownStacks.insert(stackid);
95 else {
96 _displayList.append(id);
100 if ( _sortDirection != OldestFirst )
101 _displayList = _displayList.reversed();
103 model()->updateIndexCache();
105 cache()->setDisplayList(_displayList);
107 emit collapseAllStacksEnabled( _expandedStacks.size() > 0);
108 emit expandAllStacksEnabled( _allStacks.size() != model()->_expandedStacks.size() );
109 if ( widget()->isVisible() ) {
110 widget()->updateGridSize();
111 widget()->repaintScreen();
116 * Return the file name shown in cell (row,col) if a thumbnail is shown in this cell or null otherwise.
118 DB::ResultId ThumbnailView::ThumbnailModel::imageAt( int row, int col ) const
120 const int index = row * widget()->numCols() + col;
121 if (index >= _displayList.size())
122 return DB::ResultId::null;
123 else
124 return _displayList.at(index);
127 DB::ResultId ThumbnailView::ThumbnailModel::imageAt( const Cell& cell ) const
129 return imageAt( cell.row(), cell.col() );
133 * Returns the file name shown at viewport position (x,y) if a thumbnail is shown at this position or QString::null otherwise.
135 DB::ResultId ThumbnailView::ThumbnailModel::imageAt( const QPoint& coordinate, CoordinateSystem system ) const
137 QPoint contentsPos = widget()->viewportToContentsAdjusted( coordinate, system );
138 int col = widget()->columnAt( contentsPos.x() );
139 int row = widget()->rowAt( contentsPos.y() );
141 QRect cellRect = const_cast<ThumbnailWidget*>(widget())->cellGeometry( row, col );
143 if ( cellRect.contains( contentsPos ) )
144 return imageAt( row, col );
145 else
146 return DB::ResultId::null;
149 void ThumbnailView::ThumbnailModel::toggleStackExpansion(const DB::ResultId& id)
151 DB::ImageInfoPtr imageInfo = id.fetchInfo();
152 if (imageInfo) {
153 DB::StackID stackid = imageInfo->stackId();
154 if (_expandedStacks.contains(stackid))
155 _expandedStacks.remove(stackid);
156 else
157 _expandedStacks.insert(stackid);
158 updateDisplayModel();
162 void ThumbnailView::ThumbnailModel::collapseAllStacks()
164 _expandedStacks.clear();
165 updateDisplayModel();
168 void ThumbnailView::ThumbnailModel::expandAllStacks()
170 _expandedStacks = _allStacks;
171 updateDisplayModel();
175 DB::Result ThumbnailView::ThumbnailModel::selection(bool keepSortOrderOfDatabase) const
177 // Notice, for some reason the API here offers a list of selected
178 // items, while _selectedFiles only is a set, that's why we need to
179 // iterate though _imageList and insert those selected into the result.
181 DB::Result images = _displayList;
182 if ( keepSortOrderOfDatabase && _sortDirection == NewestFirst )
183 images = images.reversed();
185 DB::Result res;
186 Q_FOREACH(DB::ResultId id, images) {
187 if (isSelected(id))
188 res.append(id);
190 return res;
193 void ThumbnailView::ThumbnailModel::setImageList(const DB::Result& items)
195 _imageList = items;
196 _allStacks.clear();
197 Q_FOREACH(DB::ImageInfoPtr info, items.fetchInfos()) {
198 if ( info && info->isStacked() )
199 _allStacks << info->stackId();
201 // FIXME: see comments in the function -- is it really needed at all?
202 // TODO(hzeller): yes so that you don't have to scroll to the page in question
203 // to force loading; this is esp. painful if you have a whole bunch of new
204 // images in your collection (I usually add 100 at time) - your first walk through
205 // it will be slow.
206 // But yeah, this needs optimization, so leaving this commented out for now.
207 //generateMissingThumbnails( items );
208 updateDisplayModel();
211 // TODO(hzeller) figure out if this should return the _imageList or _displayList.
212 DB::Result ThumbnailView::ThumbnailModel::imageList(Order order) const
214 if ( order == SortedOrder && _sortDirection == NewestFirst )
215 return _displayList.reversed();
216 else
217 return _displayList;
220 DB::ResultId ThumbnailView::ThumbnailModel::currentItem() const
222 return _currentItem;
225 void ThumbnailView::ThumbnailModel::imagesDeletedFromDB( const DB::Result& list )
227 Q_FOREACH( DB::ResultId id, list ) {
228 _displayList.removeAll( id );
229 _imageList.removeAll(id);
231 updateDisplayModel();
234 void ThumbnailView::ThumbnailModel::selectRange( Cell pos1, Cell pos2 )
236 ensureCellsSorted( pos1, pos2 );
238 if ( pos1.row() == pos2.row() ) {
239 // This is the case where images from only one row is selected.
240 for ( int col = pos1.col(); col <= pos2.col(); ++ col )
241 select( pos1.row(), col );
243 else {
244 // We know we have at least two rows.
246 // first row
247 for ( int col = pos1.col(); col < widget()->numCols(); ++ col )
248 select( pos1.row(), col );
250 // rows in between
251 for ( int row = pos1.row()+1; row < pos2.row(); ++row )
252 for ( int col = 0; col < widget()->numCols(); ++ col )
253 select( row, col );
255 // last row
256 for ( int col = 0; col <= pos2.col(); ++ col )
257 select( pos2.row(), col );
261 void ThumbnailView::ThumbnailModel::select( const Cell& cell )
263 select( cell.row(), cell.col() );
266 void ThumbnailView::ThumbnailModel::select( int row, int col )
268 DB::ResultId id = imageAt( row, col );
269 if ( !id.isNull() ) {
270 _selectedFiles.insert( id );
271 widget()->updateCell( id );
273 possibleEmitSelectionChanged();
276 void ThumbnailView::ThumbnailModel::clearSelection()
278 IdSet oldSelection = _selectedFiles;
279 _selectedFiles.clear();
280 for( IdSet::const_iterator idIt = oldSelection.begin(); idIt != oldSelection.end(); ++idIt ) {
281 widget()->updateCell( *idIt );
283 possibleEmitSelectionChanged();
286 void ThumbnailView::ThumbnailModel::toggleSelection( const DB::ResultId& id )
288 if ( isSelected( id ) )
289 _selectedFiles.remove( id );
290 else
291 _selectedFiles.insert( id );
293 widget()->updateCell( id );
294 possibleEmitSelectionChanged();
297 void ThumbnailView::ThumbnailModel::possibleEmitSelectionChanged()
299 static IdSet oldSelection;
300 if ( oldSelection != _selectedFiles ) {
301 oldSelection = _selectedFiles;
302 emit selectionChanged( _selectedFiles.count() );
306 void ThumbnailView::ThumbnailModel::selectAll()
308 _selectedFiles.clear();
309 Q_FOREACH(DB::ResultId id, _displayList) {
310 _selectedFiles.insert(id);
312 possibleEmitSelectionChanged();
313 widget()->repaintScreen();
317 This very specific method will make the item specified by id selected,
318 if there only are one item selected. This is used from the Viewer when
319 you start it without a selection, and are going forward or backward.
321 void ThumbnailView::ThumbnailModel::changeSingleSelection(const DB::ResultId& id)
323 if ( _selectedFiles.size() == 1 ) {
324 widget()->updateCell( *(_selectedFiles.begin()) );
325 _selectedFiles.clear();
326 _selectedFiles.insert( id );
327 widget()->updateCell( id );
328 possibleEmitSelectionChanged();
329 widget()->scrollToCell( model()->positionForMediaId( id ) );
333 void ThumbnailView::ThumbnailModel::ensureCellsSorted( Cell& pos1, Cell& pos2 )
335 if ( pos2.row() < pos1.row() || ( pos2.row() == pos1.row() && pos2.col() < pos1.col() ) ) {
336 Cell tmp = pos1;
337 pos1 = pos2;
338 pos2 = tmp;
343 int ThumbnailView::ThumbnailModel::indexOf(const DB::ResultId& id ) const
345 Q_ASSERT( !id.isNull() );
346 if ( !_idToIndex.contains(id) )
347 return -1;
348 else
349 return _idToIndex[id];
355 * return the position (row,col) for the given media id.
357 ThumbnailView::Cell ThumbnailView::ThumbnailModel::positionForMediaId( const DB::ResultId& id ) const
359 int index = indexOf(id);
360 if ( index == -1 )
361 return Cell( 0, 0 );
363 int row = index / widget()->numCols();
364 int col = index % widget()->numCols();
365 return Cell( row, col );
368 void ThumbnailView::ThumbnailModel::updateIndexCache()
370 _idToIndex.clear();
371 int index = 0;
372 Q_FOREACH(DB::ResultId id, _displayList) {
373 _idToIndex[id] = index;
374 ++index;
379 void ThumbnailView::ThumbnailModel::setCurrentItem( const DB::ResultId& id )
381 _currentItem = id;
384 void ThumbnailView::ThumbnailModel::setCurrentItem( const Cell& cell )
386 _currentItem = imageAt( cell );
389 DB::ResultId ThumbnailView::ThumbnailModel::rightDropItem() const
391 return _rightDrop;
394 void ThumbnailView::ThumbnailModel::setRightDropItem( const DB::ResultId& item )
396 _rightDrop = item;
399 DB::ResultId ThumbnailView::ThumbnailModel::leftDropItem() const
401 return _leftDrop;
404 void ThumbnailView::ThumbnailModel::setLeftDropItem( const DB::ResultId& item )
406 _leftDrop = item;
409 void ThumbnailView::ThumbnailModel::setSortDirection( SortDirection direction )
411 if ( direction == _sortDirection )
412 return;
414 Settings::SettingsData::instance()->setShowNewestFirst( direction == NewestFirst );
415 _displayList = _displayList.reversed();
416 updateIndexCache();
417 cache()->setDisplayList(_displayList);
418 if ( !currentItem().isNull() ) {
419 const Cell cell = positionForMediaId( currentItem() );
420 widget()->ensureCellVisible( cell.row(), cell.col() );
423 widget()->repaintScreen();
425 _sortDirection = direction;
428 bool ThumbnailView::ThumbnailModel::isItemInExpandedStack( const DB::StackID& id ) const
430 return _expandedStacks.contains(id);
433 int ThumbnailView::ThumbnailModel::imageCount() const
435 return _displayList.size();
438 DB::ResultId ThumbnailView::ThumbnailModel::imageAt( int index ) const
440 Q_ASSERT( index >= 0 && index < imageCount() );
441 return _displayList.at(index);
444 bool ThumbnailView::ThumbnailModel::isSelected( const DB::ResultId& id ) const
446 return _selectedFiles.contains(id);
449 void ThumbnailView::ThumbnailModel::select( const DB::ResultId& id )
451 _selectedFiles.insert( id );
452 widget()->updateCell( id );
453 possibleEmitSelectionChanged();
456 ThumbnailView::IdSet ThumbnailView::ThumbnailModel::selectionSet() const
458 return _selectedFiles;
461 void ThumbnailView::ThumbnailModel::setSelection( const IdSet& ids )
463 IdSet changedFiles= _selectedFiles;
464 changedFiles.unite( ids );
466 _selectedFiles = ids;
467 Q_FOREACH( const DB::ResultId& id, changedFiles )
468 widget()->updateCell( id );