SVN_SILENT: revert stack indicator hot area position to original values.
[kphotoalbum.git] / ThumbnailView / ThumbnailWidget.cpp
blob231a7c2c49d7bf2bca29e610da3086cc15f9244d
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 "ThumbnailWidget.h"
19 #include "ThumbnailCache.h"
20 #include "ThumbnailDND.h"
21 #include "KeyboardEventHandler.h"
22 #include "ThumbnailFactory.h"
23 #include "ThumbnailModel.h"
24 #include "CellGeometry.h"
25 #include "ThumbnailWidget.moc"
26 #include "ThumbnailPainter.h"
27 #include <math.h>
29 #include <klocale.h>
30 #include <qcursor.h>
31 #include <qfontmetrics.h>
32 #include <qpainter.h>
34 #include "Browser/BrowserWidget.h"
35 #include "DB/ImageDB.h"
36 #include "DB/ImageInfoPtr.h"
37 #include "DB/ResultId.h"
38 #include "ImageManager/Manager.h"
39 #include "Settings/SettingsData.h"
40 #include "Utilities/Set.h"
42 /**
43 * \class ThumbnailView::ThumbnailWidget
44 * This is the widget which shows the thumbnails.
46 * In previous versions this was implemented using a QIconView, but there
47 * simply was too many problems, so after years of tears and pains I
48 * rewrote it.
50 using Utilities::StringSet;
52 ThumbnailView::ThumbnailWidget::ThumbnailWidget( ThumbnailFactory* factory)
53 :Q3GridView(),
54 ThumbnailComponent( factory ),
55 _isSettingDate(false),
56 _gridResizeInteraction( this ),
57 _wheelResizing( false ),
58 _selectionInteraction( factory ),
59 _mouseTrackingHandler( factory ),
60 _mouseHandler( &_mouseTrackingHandler ),
61 _dndHandler( new ThumbnailDND( factory ) ),
62 _keyboardHandler( new KeyboardEventHandler( factory ) )
64 setFocusPolicy( Qt::WheelFocus );
65 updateCellSize();
67 // It beats me why I need to set mouse tracking on both, but without it doesn't work.
68 viewport()->setMouseTracking( true );
69 setMouseTracking( true );
71 connect( this, SIGNAL( contentsMoving( int, int ) ), this, SLOT( emitDateChange( int, int ) ) );
72 connect( this, SIGNAL( contentsMoving( int, int ) ), this, SLOT( slotViewChanged( int, int ) ));
73 viewport()->setAcceptDrops( true );
75 setVScrollBarMode( AlwaysOn );
76 setHScrollBarMode( AlwaysOff );
78 connect( &_mouseTrackingHandler, SIGNAL( fileIdUnderCursorChanged( DB::ResultId ) ), this, SIGNAL( fileIdUnderCursorChanged( DB::ResultId ) ) );
79 connect( _keyboardHandler, SIGNAL( showSelection() ), this, SIGNAL( showSelection() ) );
82 bool ThumbnailView::ThumbnailWidget::isGridResizing()
84 return _mouseHandler->isResizingGrid() || _wheelResizing;
87 OVERRIDE void ThumbnailView::ThumbnailWidget::paintCell( QPainter * p, int row, int col )
89 painter()->paintCell( p, row, col );
92 void ThumbnailView::ThumbnailWidget::generateMissingThumbnails(const DB::Result& items) const
94 // TODO(hzeller) before release: run this asynchronously.
95 // For 100'000+ files, this will be slow.
96 // jkt: this is dead slow even with 20k pictures on NFS
97 // TODO-2(hzeller): This should be handled at startup or when new images
98 // enter the database; not here.
99 // TODO-3(hzeller): With TODO-2 implemented, we probably don't need an
100 // existence cache anymore in ThumbnailStorage
102 // Thumbnails are generated/stored in two sizes: 128x128 and 256x256.
103 // Requesting the bigger one will make sure that both are stored.
104 const QSize size(256, 256);
105 ImageManager::Manager* imgManager = ImageManager::Manager::instance();
106 Q_FOREACH(DB::ImageInfoPtr info, items.fetchInfos()) {
107 const QString image = info->fileName(DB::AbsolutePath);
108 if (imgManager->thumbnailsExist(image)) {
109 continue;
111 ImageManager::ImageRequest* request
112 = new ImageManager::ImageRequest(image, size, info->angle(), NULL);
113 request->setPriority( ImageManager::BuildScopeThumbnails );
114 imgManager->load( request );
118 /** @short It seems that Q3GridView's viewportToContents() is slightly off */
119 QPoint ThumbnailView::ThumbnailWidget::viewportToContentsAdjusted( const QPoint& coordinate, CoordinateSystem system ) const
121 QPoint contentsPos = coordinate;
122 if ( system == ViewportCoordinates ) {
123 contentsPos = viewportToContents( coordinate );
124 contentsPos.rx() -= 3;
125 contentsPos.ry() -= 3;
127 return contentsPos;
133 * Request a repaint of the cell showing filename
135 * QGridView::updateCell has some problems when we scroll during a keyboard selection, so thatswhy we role our own one.
137 void ThumbnailView::ThumbnailWidget::updateCell( const DB::ResultId& id )
139 if ( id.isNull() )
140 return;
142 painter()->repaint(id);
145 void ThumbnailView::ThumbnailWidget::updateCell( int row, int col )
147 updateCell( model()->imageAt( row, col ) );
152 * Update the grid size depending on the size of the widget
154 void ThumbnailView::ThumbnailWidget::updateGridSize()
156 int thumbnailsPerRow = width() / cellWidth();
157 int numRowsPerPage = height() / cellHeight();
158 setNumCols( thumbnailsPerRow );
159 setNumRows(qMax(numRowsPerPage,
160 static_cast<int>(
161 ceil(static_cast<double>(model()->imageCount()) / thumbnailsPerRow))));
162 const QSize cellSize = cellGeometryInfo()->cellSize();
163 const int border = Settings::SettingsData::instance()->thumbnailSpace();
164 QSize thumbSize(cellSize.width() - 2 * border,
165 cellSize.height() - 2 * border);
166 cache()->setThumbnailSize(thumbSize);
169 void ThumbnailView::ThumbnailWidget::showEvent( QShowEvent* )
171 updateGridSize();
174 void ThumbnailView::ThumbnailWidget::keyPressEvent( QKeyEvent* event )
176 _keyboardHandler->keyPressEvent( event );
179 void ThumbnailView::ThumbnailWidget::keyReleaseEvent( QKeyEvent* event )
181 const bool propogate = _keyboardHandler->keyReleaseEvent( event );
182 if ( propogate )
183 Q3GridView::keyReleaseEvent(event);
187 /** @short Scroll the viewport so that the specified cell is visible */
188 void ThumbnailView::ThumbnailWidget::scrollToCell( const Cell& newPos )
190 model()->setCurrentItem( newPos );
192 // Scroll if necesary
193 if ( newPos.row() > lastVisibleRow( FullyVisible ) )
194 setContentsPos( contentsX(), cellGeometry( newPos.row(), newPos.col() ).top() -
195 (numRowsPerPage()-1)*cellHeight() );
197 if ( newPos.row() < firstVisibleRow( FullyVisible ) )
198 setContentsPos( contentsX(), cellGeometry( newPos.row(), newPos.col() ).top() );
202 * Return the number of complete rows per page
204 int ThumbnailView::ThumbnailWidget::numRowsPerPage() const
206 return height() / cellHeight();
209 bool ThumbnailView::ThumbnailWidget::isMouseOverStackIndicator( const QPoint& point )
211 Cell pos = cellAtCoordinate( point, ViewportCoordinates );
212 QRect cellRect = cellGeometry(pos.row(), pos.col() ).adjusted( 0, 0, -10, -10 ); // FIXME: what area should be "hot"?
213 bool correctArea = !cellRect.contains( viewportToContentsAdjusted( point, ViewportCoordinates ) );
214 if (!correctArea)
215 return false;
216 DB::ImageInfoPtr imageInfo = mediaIdUnderCursor().fetchInfo();
217 return imageInfo && imageInfo->isStacked();
220 static bool isMouseResizeGesture( QMouseEvent* event )
222 return
223 (event->button() & Qt::MidButton) ||
224 ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::AltModifier));
227 void ThumbnailView::ThumbnailWidget::mousePressEvent( QMouseEvent* event )
229 if ( isMouseOverStackIndicator( event->pos() ) ) {
230 model()->toggleStackExpansion( mediaIdUnderCursor() );
231 return;
234 if ( isMouseResizeGesture( event ) )
235 _mouseHandler = &_gridResizeInteraction;
236 else
237 _mouseHandler = &_selectionInteraction;
239 _mouseHandler->mousePressEvent( event );
241 if (event->button() & Qt::RightButton) //get out of selection mode if this is a right click
242 _mouseHandler = &_mouseTrackingHandler;
246 void ThumbnailView::ThumbnailWidget::mouseMoveEvent( QMouseEvent* event )
248 _mouseHandler->mouseMoveEvent( event );
251 void ThumbnailView::ThumbnailWidget::mouseReleaseEvent( QMouseEvent* event )
253 _mouseHandler->mouseReleaseEvent( event );
254 _mouseHandler = &_mouseTrackingHandler;
257 void ThumbnailView::ThumbnailWidget::mouseDoubleClickEvent( QMouseEvent * event )
259 if ( isMouseOverStackIndicator( event->pos() ) ) {
260 model()->toggleStackExpansion( mediaIdUnderCursor() );
261 } else if ( !( event->modifiers() & Qt::ControlModifier ) ) {
262 DB::ResultId id = model()->imageAt( event->pos(), ViewportCoordinates );
263 if ( !id.isNull() )
264 emit showImage( id );
268 void ThumbnailView::ThumbnailWidget::wheelEvent( QWheelEvent* event )
270 if ( event->modifiers() & Qt::ControlModifier ) {
271 event->setAccepted(true);
273 _wheelResizing = true;
275 int delta = event->delta() / 20;
277 Settings::SettingsData::instance()->setThumbSize( qMax( 32, cellWidth() + delta ) );
279 updateCellSize();
281 else
282 Q3GridView::wheelEvent(event);
286 void ThumbnailView::ThumbnailWidget::emitDateChange( int x, int y )
288 if ( _isSettingDate )
289 return;
291 // Unfortunately the contentsMoving signal is emitted *before* the move, so we need to find out what is on the new position ourself.
292 DB::ResultId id = model()->imageAt( rowAt(y), columnAt(x) );
293 if ( id.isNull() )
294 return;
296 static QDateTime lastDate;
297 QDateTime date = id.fetchInfo()->date().start();
298 if ( date != lastDate ) {
299 lastDate = date;
300 if ( date.date().year() != 1900 )
301 emit currentDateChanged( date );
305 void ThumbnailView::ThumbnailWidget::slotViewChanged(int , int y) {
306 if (isGridResizing())
307 return;
308 int startIndex = rowAt(y) * numCols();
309 int endIndex = (rowAt( y + visibleHeight() ) + 1) * numCols();
310 if (endIndex > model()->imageCount())
311 endIndex = model()->imageCount();
312 cache()->setHotArea(startIndex, endIndex);
316 * scroll to the date specified with the parameter date.
317 * The boolean includeRanges tells whether we accept range matches or not.
319 void ThumbnailView::ThumbnailWidget::gotoDate( const DB::ImageDate& date, bool includeRanges )
321 _isSettingDate = true;
322 DB::ResultId candidate = DB::ImageDB::instance()
323 ->findFirstItemInRange(model()->imageList(ViewOrder), date, includeRanges);
324 if ( !candidate.isNull() ) {
325 scrollToCell( model()->positionForMediaId( candidate ) );
326 model()->setCurrentItem( candidate );
328 _isSettingDate = false;
332 * Returns the first row that is at least partly visible.
334 int ThumbnailView::ThumbnailWidget::firstVisibleRow( VisibleState state ) const
336 int firstRow = rowAt( contentsY() );
337 if ( state == FullyVisible && rowAt( contentsY() + cellHeight()-1 ) != firstRow )
338 firstRow += 1;
340 return firstRow;
343 int ThumbnailView::ThumbnailWidget::lastVisibleRow( VisibleState state ) const
345 int lastRow = rowAt( contentsY() + visibleHeight() );
346 if ( state == FullyVisible && rowAt( contentsY() + visibleHeight() - cellHeight() -1 ) != lastRow )
347 lastRow -= 1;
348 return lastRow;
351 ThumbnailView::Cell ThumbnailView::ThumbnailWidget::cellAtCoordinate( const QPoint& pos, CoordinateSystem system ) const
353 QPoint contentsPos = pos;
354 if ( system == ViewportCoordinates )
355 contentsPos = viewportToContentsAdjusted( pos, system );
357 int col = columnAt( contentsPos.x() );
358 int row = rowAt( contentsPos.y() );
359 return Cell( row, col );
363 void ThumbnailView::ThumbnailWidget::resizeEvent( QResizeEvent* e )
365 Q3GridView::resizeEvent( e );
366 updateGridSize();
370 bool ThumbnailView::ThumbnailWidget::isFocusAtLastCell() const
372 return model()->positionForMediaId(model()->currentItem() ) == lastCell();
375 bool ThumbnailView::ThumbnailWidget::isFocusAtFirstCell() const
377 return model()->positionForMediaId(model()->currentItem()) == Cell(0,0);
381 * Return the coordinates of the last cell with a thumbnail in
383 ThumbnailView::Cell ThumbnailView::ThumbnailWidget::lastCell() const
385 return Cell((model()->imageCount() - 1) / numCols(),
386 (model()->imageCount() - 1) % numCols());
389 void ThumbnailView::ThumbnailWidget::reload(bool flushCache, bool clearSelection)
391 if ( flushCache )
392 cache()->clear();
393 if ( clearSelection )
394 model()->clearSelection();
395 updateCellSize();
396 repaintScreen();
399 void ThumbnailView::ThumbnailWidget::repaintScreen()
401 QPalette p;
402 p.setColor( QPalette::Base, Settings::SettingsData::instance()->backgroundColor() );
403 p.setColor( QPalette::Foreground, p.color(QPalette::WindowText ) );
404 setPalette(p);
406 const int first = firstVisibleRow( PartlyVisible );
407 const int last = lastVisibleRow( PartlyVisible );
408 for ( int row = first; row <= last; ++row )
409 for ( int col = 0; col < numCols(); ++col )
410 Q3GridView::repaintCell( row, col );
413 DB::ResultId ThumbnailView::ThumbnailWidget::mediaIdUnderCursor() const
415 return model()->imageAt( mapFromGlobal( QCursor::pos() ), ViewportCoordinates );
419 void ThumbnailView::ThumbnailWidget::contentsDragMoveEvent( QDragMoveEvent* event )
421 _dndHandler->contentsDragMoveEvent(event);
424 void ThumbnailView::ThumbnailWidget::contentsDragLeaveEvent( QDragLeaveEvent* event )
426 _dndHandler->contentsDragLeaveEvent( event );
429 void ThumbnailView::ThumbnailWidget::contentsDropEvent( QDropEvent* event )
431 _dndHandler->contentsDropEvent( event );
435 void ThumbnailView::ThumbnailWidget::dimensionChange( int oldNumRows, int /*oldNumCols*/ )
437 if ( oldNumRows != numRows() )
438 repaintScreen();
441 void ThumbnailView::ThumbnailWidget::updateCellSize()
443 const QSize cellSize = cellGeometryInfo()->cellSize();
444 setCellWidth( cellSize.width() );
446 const int oldHeight = cellHeight();
447 const int height = cellSize.height() + 2 + cellGeometryInfo()->textHeight( QFontMetrics( font() ).height(), true );
448 setCellHeight( height );
449 updateGridSize();
450 if ( height != oldHeight && ! model()->currentItem().isNull() ) {
451 const Cell c = model()->positionForMediaId(model()->currentItem());
452 ensureCellVisible( c.row(), c.col() );
456 void ThumbnailView::ThumbnailWidget::viewportPaintEvent( QPaintEvent* e )
458 QPainter p( viewport() );
459 p.fillRect( numCols() * cellWidth(), 0, width(), height(), palette().color(QPalette::Base) );
460 p.fillRect( 0, numRows() * cellHeight(), width(), height(), palette().color(QPalette::Base) );
461 p.end();
462 Q3GridView::viewportPaintEvent( e );
465 void ThumbnailView::ThumbnailWidget::contentsDragEnterEvent( QDragEnterEvent * event )
467 _dndHandler->contentsDragEnterEvent( event );