diff --git a/ThumbnailView/ThumbnailWidget.cpp b/ThumbnailView/ThumbnailWidget.cpp index 9c88960f..7a46c1ff 100644 --- a/ThumbnailView/ThumbnailWidget.cpp +++ b/ThumbnailView/ThumbnailWidget.cpp @@ -1,453 +1,456 @@ /* Copyright (C) 2003-2020 The KPhotoAlbum Development Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ThumbnailWidget.h" #include "CellGeometry.h" #include "Delegate.h" #include "KeyboardEventHandler.h" #include "SelectionMaintainer.h" #include "ThumbnailDND.h" #include "ThumbnailFactory.h" #include "ThumbnailModel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include +namespace +{ +QColor contrastColor(const QColor &color) +{ + if (color.red() < 127 && color.green() < 127 && color.blue() < 127) + return Qt::white; + else + return Qt::black; +} +} + /** * \class ThumbnailView::ThumbnailWidget * This is the widget which shows the thumbnails. * * In previous versions this was implemented using a QIconView, but there * simply was too many problems, so after years of tears and pains I * rewrote it. */ ThumbnailView::ThumbnailWidget::ThumbnailWidget(ThumbnailFactory *factory) : QListView() , ThumbnailComponent(factory) , m_isSettingDate(false) , m_gridResizeInteraction(factory) , m_wheelResizing(false) , m_externallyResizing(false) , m_selectionInteraction(factory) , m_mouseTrackingHandler(factory) , m_mouseHandler(&m_mouseTrackingHandler) , m_dndHandler(new ThumbnailDND(factory)) , m_pressOnStackIndicator(false) , m_keyboardHandler(new KeyboardEventHandler(factory)) , m_videoThumbnailCycler(new VideoThumbnailCycler(model())) { setModel(ThumbnailComponent::model()); setResizeMode(QListView::Adjust); setViewMode(QListView::IconMode); setUniformItemSizes(true); setSelectionMode(QAbstractItemView::ExtendedSelection); // It beats me why I need to set mouse tracking on both, but without it doesn't work. viewport()->setMouseTracking(true); setMouseTracking(true); connect(selectionModel(), SIGNAL(currentChanged(QModelIndex, QModelIndex)), this, SLOT(scheduleDateChangeSignal())); viewport()->setAcceptDrops(true); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); connect(&m_mouseTrackingHandler, &MouseTrackingInteraction::fileIdUnderCursorChanged, this, &ThumbnailWidget::fileIdUnderCursorChanged); connect(m_keyboardHandler, &KeyboardEventHandler::showSelection, this, &ThumbnailWidget::showSelection); updatePalette(); setItemDelegate(new Delegate(factory, this)); connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(emitSelectionChangedSignal())); setDragEnabled(false); // We run our own dragging, so disable QListView's version. connect(verticalScrollBar(), SIGNAL(valueChanged(int)), model(), SLOT(updateVisibleRowInfo())); setupDateChangeTimer(); } bool ThumbnailView::ThumbnailWidget::isGridResizing() const { return m_mouseHandler->isResizingGrid() || m_wheelResizing || m_externallyResizing; } void ThumbnailView::ThumbnailWidget::keyPressEvent(QKeyEvent *event) { if (!m_keyboardHandler->keyPressEvent(event)) QListView::keyPressEvent(event); } void ThumbnailView::ThumbnailWidget::keyReleaseEvent(QKeyEvent *event) { const bool propagate = m_keyboardHandler->keyReleaseEvent(event); if (propagate) QListView::keyReleaseEvent(event); } bool ThumbnailView::ThumbnailWidget::isMouseOverStackIndicator(const QPoint &point) { // first check if image is stack, if not return. DB::ImageInfoPtr imageInfo = mediaIdUnderCursor().info(); if (!imageInfo) return false; if (!imageInfo->isStacked()) return false; const QModelIndex index = indexUnderCursor(); const QRect itemRect = visualRect(index); const QPixmap pixmap = index.data(Qt::DecorationRole).value(); if (pixmap.isNull()) return false; const QRect pixmapRect = cellGeometryInfo()->iconGeometry(pixmap).translated(itemRect.topLeft()); const QRect blackOutRect = pixmapRect.adjusted(0, 0, -10, -10); return pixmapRect.contains(point) && !blackOutRect.contains(point); } static bool isMouseResizeGesture(QMouseEvent *event) { return (event->button() & Qt::MidButton) || ((event->modifiers() & Qt::ControlModifier) && (event->modifiers() & Qt::AltModifier)); } void ThumbnailView::ThumbnailWidget::mousePressEvent(QMouseEvent *event) { if ((!(event->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier))) && isMouseOverStackIndicator(event->pos())) { model()->toggleStackExpansion(mediaIdUnderCursor()); m_pressOnStackIndicator = true; return; } if (isMouseResizeGesture(event)) m_mouseHandler = &m_gridResizeInteraction; else m_mouseHandler = &m_selectionInteraction; if (!m_mouseHandler->mousePressEvent(event)) QListView::mousePressEvent(event); if (event->button() & Qt::RightButton) //get out of selection mode if this is a right click m_mouseHandler = &m_mouseTrackingHandler; } void ThumbnailView::ThumbnailWidget::mouseMoveEvent(QMouseEvent *event) { if (m_pressOnStackIndicator) return; if (!m_mouseHandler->mouseMoveEvent(event)) QListView::mouseMoveEvent(event); } void ThumbnailView::ThumbnailWidget::mouseReleaseEvent(QMouseEvent *event) { if (m_pressOnStackIndicator) { m_pressOnStackIndicator = false; return; } if (!m_mouseHandler->mouseReleaseEvent(event)) QListView::mouseReleaseEvent(event); m_mouseHandler = &m_mouseTrackingHandler; } void ThumbnailView::ThumbnailWidget::mouseDoubleClickEvent(QMouseEvent *event) { if (isMouseOverStackIndicator(event->pos())) { model()->toggleStackExpansion(mediaIdUnderCursor()); m_pressOnStackIndicator = true; } else if (!(event->modifiers() & Qt::ControlModifier)) { DB::FileName id = mediaIdUnderCursor(); if (!id.isNull()) emit showImage(id); } } void ThumbnailView::ThumbnailWidget::wheelEvent(QWheelEvent *event) { if (event->modifiers() & Qt::ControlModifier) { event->setAccepted(true); if (!m_wheelResizing) m_gridResizeInteraction.enterGridResizingMode(); m_wheelResizing = true; model()->beginResetModel(); const int delta = -event->delta() / 20; static int _minimum_ = Settings::SettingsData::instance()->minimumThumbnailSize(); Settings::SettingsData::instance()->setActualThumbnailSize(qMax(_minimum_, Settings::SettingsData::instance()->actualThumbnailSize() + delta)); cellGeometryInfo()->calculateCellSize(); model()->endResetModel(); } else { int delta = event->delta() / 5; QWheelEvent newevent = QWheelEvent(event->pos(), delta, event->buttons(), nullptr); QListView::wheelEvent(&newevent); } } void ThumbnailView::ThumbnailWidget::emitDateChange() { if (m_isSettingDate) return; int row = currentIndex().row(); if (row == -1) return; DB::FileName fileName = model()->imageAt(row); if (fileName.isNull()) return; static QDateTime lastDate; QDateTime date = fileName.info()->date().start(); if (date != lastDate) { lastDate = date; if (date.date().year() != 1900) emit currentDateChanged(date); } } /** * scroll to the date specified with the parameter date. * The boolean includeRanges tells whether we accept range matches or not. */ void ThumbnailView::ThumbnailWidget::gotoDate(const DB::ImageDate &date, bool includeRanges) { m_isSettingDate = true; DB::FileName candidate = DB::ImageDB::instance() ->findFirstItemInRange(model()->imageList(ViewOrder), date, includeRanges); if (!candidate.isNull()) setCurrentItem(candidate); m_isSettingDate = false; } void ThumbnailView::ThumbnailWidget::setExternallyResizing(bool state) { m_externallyResizing = state; } void ThumbnailView::ThumbnailWidget::reload(SelectionUpdateMethod method) { SelectionMaintainer maintainer(this, model()); ThumbnailComponent::model()->beginResetModel(); cellGeometryInfo()->flushCache(); updatePalette(); ThumbnailComponent::model()->endResetModel(); if (method == ClearSelection) maintainer.disable(); } DB::FileName ThumbnailView::ThumbnailWidget::mediaIdUnderCursor() const { const QModelIndex index = indexUnderCursor(); if (index.isValid()) return model()->imageAt(index.row()); else return DB::FileName(); } QModelIndex ThumbnailView::ThumbnailWidget::indexUnderCursor() const { return indexAt(mapFromGlobal(QCursor::pos())); } void ThumbnailView::ThumbnailWidget::dragMoveEvent(QDragMoveEvent *event) { m_dndHandler->contentsDragMoveEvent(event); } void ThumbnailView::ThumbnailWidget::dragLeaveEvent(QDragLeaveEvent *event) { m_dndHandler->contentsDragLeaveEvent(event); } void ThumbnailView::ThumbnailWidget::dropEvent(QDropEvent *event) { m_dndHandler->contentsDropEvent(event); } void ThumbnailView::ThumbnailWidget::dragEnterEvent(QDragEnterEvent *event) { m_dndHandler->contentsDragEnterEvent(event); } void ThumbnailView::ThumbnailWidget::setCurrentItem(const DB::FileName &fileName) { if (fileName.isNull()) return; const int row = model()->indexOf(fileName); setCurrentIndex(QListView::model()->index(row, 0)); } DB::FileName ThumbnailView::ThumbnailWidget::currentItem() const { if (!currentIndex().isValid()) return DB::FileName(); return model()->imageAt(currentIndex().row()); } void ThumbnailView::ThumbnailWidget::updatePalette() { QPalette pal = palette(); pal.setBrush(QPalette::Base, QColor(Settings::SettingsData::instance()->backgroundColor())); pal.setBrush(QPalette::Text, contrastColor(QColor(Settings::SettingsData::instance()->backgroundColor()))); setPalette(pal); } int ThumbnailView::ThumbnailWidget::cellWidth() const { return visualRect(QListView::model()->index(0, 0)).size().width(); } void ThumbnailView::ThumbnailWidget::emitSelectionChangedSignal() { emit selectionCountChanged(selection(ExpandCollapsedStacks).size()); } void ThumbnailView::ThumbnailWidget::scheduleDateChangeSignal() { m_dateChangedTimer->start(200); } /** * During profiling, I found that emitting the dateChanged signal was * rather expensive, so now I delay that signal, so it is only emitted 200 * msec after the scroll, which means it will not be emitted when the user * holds down, say the page down key for scrolling. */ void ThumbnailView::ThumbnailWidget::setupDateChangeTimer() { m_dateChangedTimer = new QTimer(this); m_dateChangedTimer->setSingleShot(true); connect(m_dateChangedTimer, &QTimer::timeout, this, &ThumbnailWidget::emitDateChange); } void ThumbnailView::ThumbnailWidget::showEvent(QShowEvent *event) { model()->updateVisibleRowInfo(); QListView::showEvent(event); } DB::FileNameList ThumbnailView::ThumbnailWidget::selection(ThumbnailView::SelectionMode mode) const { DB::FileNameList res; const auto indexSelection = selectedIndexes(); for (const QModelIndex &index : indexSelection) { const DB::FileName currFileName = model()->imageAt(index.row()); bool includeAllStacks = false; switch (mode) { case IncludeAllStacks: includeAllStacks = true; /* FALLTHROUGH */ case ExpandCollapsedStacks: { // if the selected image belongs to a collapsed thread, // imply that all images in the stack are selected: DB::ImageInfoPtr imageInfo = currFileName.info(); if (imageInfo && imageInfo->isStacked() && (includeAllStacks || !model()->isItemInExpandedStack(imageInfo->stackId()))) { // add all images in the same stack res.append(DB::ImageDB::instance()->getStackFor(currFileName)); } else res.append(currFileName); } break; case NoExpandCollapsedStacks: res.append(currFileName); break; } } return res; } bool ThumbnailView::ThumbnailWidget::isSelected(const DB::FileName &fileName) const { return selection(NoExpandCollapsedStacks).indexOf(fileName) != -1; } /** This very specific method will make the item specified by id selected, if there only are one item selected. This is used from the Viewer when you start it without a selection, and are going forward or backward. */ void ThumbnailView::ThumbnailWidget::changeSingleSelection(const DB::FileName &fileName) { if (selection(NoExpandCollapsedStacks).size() == 1) { QItemSelectionModel *selection = selectionModel(); selection->select(model()->fileNameToIndex(fileName), QItemSelectionModel::ClearAndSelect); setCurrentItem(fileName); } } void ThumbnailView::ThumbnailWidget::select(const DB::FileNameList &items) { QItemSelection selection; QModelIndex start; QModelIndex end; int count = 0; for (const DB::FileName &fileName : items) { QModelIndex index = model()->fileNameToIndex(fileName); if (count == 0) { start = index; end = index; } else if (index.row() == end.row() + 1) { end = index; } else { selection.merge(QItemSelection(start, end), QItemSelectionModel::Select); start = index; end = index; } count++; } if (count > 0) { selection.merge(QItemSelection(start, end), QItemSelectionModel::Select); } selectionModel()->select(selection, QItemSelectionModel::Select); } bool ThumbnailView::ThumbnailWidget::isItemUnderCursorSelected() const { return widget()->selection(ExpandCollapsedStacks).contains(mediaIdUnderCursor()); } -QColor ThumbnailView::contrastColor(const QColor &color) -{ - if (color.red() < 127 && color.green() < 127 && color.blue() < 127) - return Qt::white; - else - return Qt::black; -} - // vi:expandtab:tabstop=4 shiftwidth=4: diff --git a/ThumbnailView/ThumbnailWidget.h b/ThumbnailView/ThumbnailWidget.h index 205f5513..eda88690 100644 --- a/ThumbnailView/ThumbnailWidget.h +++ b/ThumbnailView/ThumbnailWidget.h @@ -1,158 +1,156 @@ -/* Copyright (C) 2003-2019 The KPhotoAlbum Development Team +/* Copyright (C) 2003-2020 The KPhotoAlbum Development Team This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef THUMBNAILVIEW_THUMBNAILWIDGET_H #define THUMBNAILVIEW_THUMBNAILWIDGET_H #include "GridResizeInteraction.h" #include "MouseTrackingInteraction.h" #include "SelectionInteraction.h" #include "ThumbnailComponent.h" #include "VideoThumbnailCycler.h" #include "enums.h" #include #include class QTimer; class QDateTime; namespace DB { class ImageDate; class Id; class FileNameList; } namespace ThumbnailView { class ThumbnailPainter; class CellGeometry; class ThumbnailModel; class ThumbnailFactory; class KeyboardEventHandler; class ThumbnailDND; class ThumbnailWidget : public QListView, private ThumbnailComponent { Q_OBJECT public: explicit ThumbnailWidget(ThumbnailFactory *factory); void reload(SelectionUpdateMethod method); DB::FileName mediaIdUnderCursor() const; QModelIndex indexUnderCursor() const; bool isMouseOverStackIndicator(const QPoint &point); bool isGridResizing() const; void setCurrentItem(const DB::FileName &fileName); DB::FileName currentItem() const; void changeSingleSelection(const DB::FileName &fileName); // Misc int cellWidth() const; void showEvent(QShowEvent *) override; DB::FileNameList selection(ThumbnailView::SelectionMode mode) const; bool isSelected(const DB::FileName &id) const; void select(const DB::FileNameList &); bool isItemUnderCursorSelected() const; public slots: void gotoDate(const DB::ImageDate &date, bool includeRanges); /** * @brief setExternallyResizing * Used by the GridResizeSlider to indicate that the grid is being resized. * @param state true, if the grid is being resized by an external widget, false if not */ void setExternallyResizing(bool state); signals: void showImage(const DB::FileName &id); void showSelection(); void fileIdUnderCursorChanged(const DB::FileName &id); void currentDateChanged(const QDateTime &); void selectionCountChanged(int numberOfItemsSelected); protected: // event handlers void keyPressEvent(QKeyEvent *) override; void keyReleaseEvent(QKeyEvent *) override; void mousePressEvent(QMouseEvent *) override; void mouseMoveEvent(QMouseEvent *) override; void mouseReleaseEvent(QMouseEvent *) override; void mouseDoubleClickEvent(QMouseEvent *) override; void wheelEvent(QWheelEvent *) override; // Drag and drop void dragEnterEvent(QDragEnterEvent *event) override; void dragMoveEvent(QDragMoveEvent *) override; void dragLeaveEvent(QDragLeaveEvent *) override; void dropEvent(QDropEvent *) override; private slots: void emitDateChange(); void scheduleDateChangeSignal(); void emitSelectionChangedSignal(); private: friend class GridResizeInteraction; inline ThumbnailModel *model() { return ThumbnailComponent::model(); } inline const ThumbnailModel *model() const { return ThumbnailComponent::model(); } void updatePalette(); void setupDateChangeTimer(); /** * When the user selects a date on the date bar the thumbnail view will * position itself accordingly. As a consequence, the thumbnail view * is telling the date bar which date it moved to. This is all fine * except for the fact that the date selected in the date bar, may be * for an image which is in the middle of a line, while the date * emitted from the thumbnail view is for the top most image in * the view (that is the first image on the line), which results in a * different cell being selected in the date bar, than what the user * selected. * Therefore we need this variable to disable the emission of the date * change while setting the date. */ bool m_isSettingDate; GridResizeInteraction m_gridResizeInteraction; bool m_wheelResizing; bool m_externallyResizing; SelectionInteraction m_selectionInteraction; MouseTrackingInteraction m_mouseTrackingHandler; MouseInteraction *m_mouseHandler; ThumbnailDND *m_dndHandler; bool m_pressOnStackIndicator; QTimer *m_dateChangedTimer; friend class SelectionInteraction; friend class KeyboardEventHandler; friend class ThumbnailDND; friend class ThumbnailModel; KeyboardEventHandler *m_keyboardHandler; QScopedPointer m_videoThumbnailCycler; }; - -QColor contrastColor(const QColor &color); } #endif /* THUMBNAILVIEW_THUMBNAILWIDGET_H */ // vi:expandtab:tabstop=4 shiftwidth=4: