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"
31 #include <qfontmetrics.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"
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
50 using Utilities::StringSet
;
52 ThumbnailView::ThumbnailWidget::ThumbnailWidget( ThumbnailFactory
* factory
)
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
);
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
)) {
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;
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
)
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
,
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
* )
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
);
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, -15, -15 ); // FIXME: what area should be "hot"?
213 bool correctArea
= !cellRect
.contains( viewportToContentsAdjusted( point
, ViewportCoordinates
) );
216 DB::ImageInfoPtr imageInfo
= mediaIdUnderCursor().fetchInfo();
217 return imageInfo
&& imageInfo
->isStacked();
220 static bool isMouseResizeGesture( QMouseEvent
* event
)
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() );
234 if ( isMouseResizeGesture( event
) )
235 _mouseHandler
= &_gridResizeInteraction
;
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
);
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
) );
282 Q3GridView::wheelEvent(event
);
286 void ThumbnailView::ThumbnailWidget::emitDateChange( int x
, int y
)
288 if ( _isSettingDate
)
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
) );
296 static QDateTime lastDate
;
297 QDateTime date
= id
.fetchInfo()->date().start();
298 if ( date
!= lastDate
) {
300 if ( date
.date().year() != 1900 )
301 emit
currentDateChanged( date
);
305 void ThumbnailView::ThumbnailWidget::slotViewChanged(int , int y
) {
306 if (isGridResizing())
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
)
343 int ThumbnailView::ThumbnailWidget::lastVisibleRow( VisibleState state
) const
345 int lastRow
= rowAt( contentsY() + visibleHeight() );
346 if ( state
== FullyVisible
&& rowAt( contentsY() + visibleHeight() - cellHeight() -1 ) != 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
);
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
)
393 if ( clearSelection
)
394 model()->clearSelection();
399 void ThumbnailView::ThumbnailWidget::repaintScreen()
402 p
.setColor( QPalette::Base
, Settings::SettingsData::instance()->backgroundColor() );
403 p
.setColor( QPalette::Foreground
, p
.color(QPalette::WindowText
) );
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() )
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
);
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
) );
462 Q3GridView::viewportPaintEvent( e
);
465 void ThumbnailView::ThumbnailWidget::contentsDragEnterEvent( QDragEnterEvent
* event
)
467 _dndHandler
->contentsDragEnterEvent( event
);