1 /* ============================================================ 2 * 3 * This file is a part of digiKam project 4 * https://www.digikam.org 5 * 6 * Date : 2009-03-25 7 * Description : Tree View for album models 8 * 9 * Copyright (C) 2009-2010 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de> 10 * Copyright (C) 2010-2011 by Andi Clemens <andi dot clemens at gmail dot com> 11 * Copyright (C) 2009-2021 by Gilles Caulier <caulier dot gilles at gmail dot com> 12 * 13 * This program is free software; you can redistribute it 14 * and/or modify it under the terms of the GNU General 15 * Public License as published by the Free Software Foundation; 16 * either version 2, or (at your option) 17 * any later version. 18 * 19 * This program is distributed in the hope that it will be useful, 20 * but WITHOUT ANY WARRANTY; without even the implied warranty of 21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 * GNU General Public License for more details. 23 * 24 * ============================================================ */ 25 26 #ifndef DIGIKAM_ABSTRACT_ALBUM_TREE_VIEW_H 27 #define DIGIKAM_ABSTRACT_ALBUM_TREE_VIEW_H 28 29 // Qt includes 30 31 #include <QTreeView> 32 33 // Local includes 34 35 #include "albummanager.h" 36 #include "albummodel.h" 37 #include "albumfiltermodel.h" 38 #include "albumpointer.h" 39 #include "statesavingobject.h" 40 41 namespace Digikam 42 { 43 44 class ContextMenuHelper; 45 class TagModificationHelper; 46 47 // NOTE: This structure name can be in conflict with QAbstractItemView::State. 48 struct State; 49 50 class AbstractAlbumTreeView; 51 52 /** 53 * Base class for all tree views that display Album-based content provided by an 54 * AbstractSpecificAlbumModel. This class enables various utility functions like 55 * selecting albums on mouse actions or providing an infrastructure for 56 * displaying a context menu for albums. 57 * 58 * Context menu handling is implemented as template methods with hook methods 59 * that can be implemented by subclasses to provide a custom behavior. In 60 * default mode no context menu is shown at all. It must be enabled via a call 61 * to setEnableContextMenu. 62 */ 63 class AbstractAlbumTreeView : public QTreeView, 64 public StateSavingObject 65 { 66 Q_OBJECT 67 68 public: 69 70 enum Flag 71 { 72 /** 73 * Create a default model. Not supported by abstract classes. 74 * Not part of default flags! 75 */ 76 CreateDefaultModel, 77 78 /** 79 * Create a default filter model. 80 */ 81 CreateDefaultFilterModel, 82 83 /** 84 * Create a delegate which paints according to settings. 85 * If not set, the Qt default delegate of the view is used. 86 */ 87 CreateDefaultDelegate, 88 89 /** 90 * Show the count according to the settings. 91 * If not set, call setShowCount() on the model yourself. 92 */ 93 ShowCountAccordingToSettings, 94 95 /** 96 * Always show the inclusive counts. 97 * Not part of default flags! 98 */ 99 AlwaysShowInclusiveCounts, 100 101 DefaultFlags = CreateDefaultFilterModel | CreateDefaultDelegate | ShowCountAccordingToSettings 102 }; 103 Q_DECLARE_FLAGS(Flags, Flag) 104 105 public: 106 107 /** 108 * Constructs an album tree view. 109 * If you give 0 for model, call setAlbumModel afterwards. 110 * If you supply 0 for filterModel, call setAlbumFilterModel afterwards. 111 */ 112 AbstractAlbumTreeView(QWidget* const parent, Flags flags); 113 ~AbstractAlbumTreeView() override; 114 115 AbstractSpecificAlbumModel* albumModel() const; 116 AlbumFilterModel* albumFilterModel() const; 117 118 /// Enable expanding of tree items on single click on the item (default: off) 119 void setExpandOnSingleClick(const bool doThat); 120 121 /// Expand an item when making it the new current item 122 void setExpandNewCurrentItem(const bool doThat); 123 124 /** 125 * Sets whether to select an album on click via the album manager or not. 126 * 127 * @param selectOnClick if true, a click on an item automatically sets this 128 * item as the current album in the album manager 129 */ 130 void setSelectAlbumOnClick(const bool selectOnClick); 131 132 /** 133 * Determines the global decision to show a popup menu or not. More detailed 134 * decision at which position a menu can be shown and where not can be made 135 * by implementing showContextMenuAt. 136 * 137 * @param enable if true, a context menu can be shown 138 */ 139 void setEnableContextMenu(const bool enable); 140 141 /** 142 * Sets whether to select the album under the mouse cursor on a context menu 143 * request (so that the album is shown using the album manager) or not 144 * 145 * Defaults to true. 146 * 147 * @param select true if a context menu request shall select the album 148 */ 149 void setSelectOnContextMenu(const bool select); 150 151 /** 152 * Set the context menu title and icon. 153 * This is used by the default implementation of contextMenuIcon() 154 * and contextMenuTitle(). You can alternatively reimplement these methods. 155 */ 156 void setContextMenuIcon(const QPixmap& pixmap); 157 void setContextMenuTitle(const QString& title); 158 159 /** 160 * This is a combination of indexAt() checked with visualRect(). 161 * p must be in the viewport currently. Decoration will not be included. 162 * Suitable for mouse click positions. 163 */ 164 QModelIndex indexVisuallyAt(const QPoint& p); 165 166 /** 167 * Ensures that every current match is visible by expanding all parent 168 * entries. 169 * 170 * @param index the index to start ensuring expansion state 171 * @return <code>true</code> if there was a match under <code>index</code>. 172 * This return value can normally be ignored by the caller because 173 * it is only used for an internal recursion. 174 */ 175 bool expandMatches(const QModelIndex& index); 176 177 //@{ 178 /** 179 * Implements state loading for the album tree view in a somewhat clumsy 180 * procedure because the model may not be fully loaded when this method is 181 * called. Therefore the config is first parsed into d->statesByAlbumId 182 * which holds the state of all tree view entries indexed by album id. 183 * Afterwards an initial sync run is done restoring the state of all model 184 * entries that are already present at this time. Every processed entry 185 * is removed from d->statesByAlbumId. If there are still entries left in 186 * this map we assume that the model is not fully loaded at the moment. 187 * Therefore the rowsInserted signal is connected to a slot that restores 188 * the state of new rows based on the remaining entries in 189 * d->statesByAlbumId. 190 */ 191 void doLoadState() override; 192 void doSaveState() override; 193 //@} 194 195 /** 196 * Some treeviews shall control the global current album kept by AlbumManager. 197 * Other treeview are self-contained and shall not change the current album. 198 * Default: false 199 */ 200 void setAlbumManagerCurrentAlbum(const bool setCurrentAlbum); 201 202 /** 203 * Add a context menu element which can add actions to the context 204 * menu when the menu is generated. 205 * First, addCustomContextMenuActions is called, then 206 * all elements' addActions method is called in order of addition. 207 */ 208 class ContextMenuElement 209 { 210 public: 211 212 ContextMenuElement() = default; 213 virtual ~ContextMenuElement() = default; 214 215 /** 216 * Add actions to the context menu being generated 217 * 218 * @param view The AbstractAlbumTreeView which generates the menu 219 * @param cmh helper object to create the context menu 220 * @param album the album on which the context menu will be created. May be null if 221 * it is requested on no tag entry 222 */ 223 virtual void addActions(AbstractAlbumTreeView* view, 224 ContextMenuHelper& cmh, 225 Album* album) = 0; 226 227 private: 228 229 Q_DISABLE_COPY(ContextMenuElement) 230 }; 231 232 void addContextMenuElement(ContextMenuElement* const element); 233 void removeContextMenuElement(ContextMenuElement* const element); 234 QList<ContextMenuElement*> contextMenuElements() const; 235 236 template<class A> 237 QList<A*> currentAlbums(); 238 239 // for internal use: public viewportEvent 240 bool viewportEvent(QEvent* event) override; 241 242 /** 243 * @brief selectedItems() - 244 */ 245 QList<Album*> selectedItems(); 246 247 public Q_SLOTS: 248 249 void setSearchTextSettings(const SearchTextSettings& settings); 250 251 /** 252 * Selects the given album. 253 * 254 * @param albums the albums to select 255 * @param selectInAlbumManager the album will be set as current album, if both 256 * this parameter is true and setAlbumManagerCurrentAlbum() was set to true. 257 */ 258 void setCurrentAlbums(const QList<Album*>& albums, bool selectInAlbumManager = true); 259 260 /** 261 * Adapt the column sizes to the contents of the tree view. 262 */ 263 void adaptColumnsToContent(); 264 265 /** 266 * Scrolls to the first selected album if there is one. 267 */ 268 void scrollToSelectedAlbum(); 269 270 /** 271 * Expands the complete tree under the given index. 272 * 273 * @param index the index to start expanding everything 274 */ 275 void expandEverything(const QModelIndex& index); 276 277 /** 278 * @brief slotExpandNode - expands recursively selected nodes 279 */ 280 void slotExpandNode(); 281 282 /** 283 * @brief slotCollapseNode - collapse recursively selected nodes 284 */ 285 void slotCollapseNode(); 286 287 Q_SIGNALS: 288 289 /// Emitted when the currently selected album changes 290 void currentAlbumChanged(Album* currentAlbum); 291 292 /// Emitted when the current selection changes. Use currentChanged unless in multi-selection mode. 293 void selectedAlbumsChanged(const QList<Album*>& selectedAlbums); 294 295 protected Q_SLOTS: 296 297 // override if implemented behavior is not as intended 298 virtual void slotRootAlbumAvailable(); 299 300 void slotSearchTextSettingsChanged(bool wasSearching, bool searching); 301 void slotSearchTextSettingsAboutToChange(bool searched, bool willSearch); 302 void slotCurrentChanged(); 303 void slotSelectionChanged(); 304 305 void albumSettingsChanged(); 306 307 protected: 308 309 // context menu handling 310 311 /** 312 * Hook method to implement that determines if a context menu shall be 313 * displayed for the given event at the position coded in the event. 314 * 315 * @param event context menu event to react on 316 * @param albumForEvent the album at the mouse position or null if there is 317 * no album at that position 318 * @return true if a context menu shall be displayed at the event 319 * coordinates, else false 320 */ 321 virtual bool showContextMenuAt(QContextMenuEvent* event, Album* albumForEvent); 322 323 /** 324 * Hook method that can be implemented to return a special icon used for the 325 * context menu. 326 * 327 * @return the icon for the context menu 328 */ 329 virtual QPixmap contextMenuIcon() const; 330 331 /** 332 * Hook method to implement that returns the title for the context menu. 333 * 334 * @return title for the context menu 335 */ 336 virtual QString contextMenuTitle() const; 337 338 /** 339 * Hook method to add custom actions to the generated context menu. 340 * 341 * @param cmh helper object to create the context menu 342 * @param album tag on which the context menu will be created. May be null if 343 * it is requested on no tag entry 344 */ 345 virtual void addCustomContextMenuActions(ContextMenuHelper& cmh, Album* album); 346 347 /** 348 * Hook method to handle the custom context menu actions that were added 349 * with addCustomContextMenuActions. 350 * 351 * @param action the action that was chosen by the user, may be null if none 352 * of the custom actions were selected 353 * @param album the tag on which the context menu was requested. May be null 354 * if there was no 355 */ 356 virtual void handleCustomContextMenuAction(QAction* action, 357 const AlbumPointer<Album>& album); 358 359 // other stuff 360 361 void mousePressEvent(QMouseEvent* e) override; 362 363 void rowsInserted(const QModelIndex& index, int start, int end) override; 364 void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) override; 365 void startDrag(Qt::DropActions supportedActions) override; 366 void dragEnterEvent(QDragEnterEvent* e) override; 367 void dragMoveEvent(QDragMoveEvent* e) override; 368 void dragLeaveEvent(QDragLeaveEvent* e) override; 369 void dropEvent(QDropEvent* e) override; 370 371 virtual void middleButtonPressed(Album* a); 372 virtual QPixmap pixmapForDrag(const QStyleOptionViewItem& option, QList<QModelIndex> indexes); 373 374 void setAlbumFilterModel(AlbumFilterModel* const filterModel); 375 void setAlbumModel(AbstractSpecificAlbumModel* const model); 376 377 protected: 378 379 AbstractSpecificAlbumModel* m_albumModel; 380 AlbumFilterModel* m_albumFilterModel; 381 AlbumModelDragDropHandler* m_dragDropHandler; 382 383 int m_lastScrollBarValue; 384 bool m_checkOnMiddleClick; 385 bool m_restoreCheckState; 386 Flags m_flags; 387 388 private: 389 390 void saveStateRecursive(const QModelIndex& index, 391 QList<int>& selection, QList<int>& expansion); 392 393 /** 394 * Restores the state of the index and all sub-indexes if there is an entry 395 * for this index in stateStore. Every album that is restored is removed 396 * from the stateStore. 397 * 398 * @param index the index to start restoring 399 * @param stateStore states indexed by album id 400 */ 401 void restoreStateForHierarchy(const QModelIndex& index, 402 const QMap<int, Digikam::State>& stateStore); 403 404 /** 405 * Restore the state for this index. 406 */ 407 void restoreState(const QModelIndex& index, 408 const QMap<int, Digikam::State>& stateStore); 409 410 /** 411 * Creates the context menu. 412 * 413 * @param event the event that requested the menu 414 */ 415 void contextMenuEvent(QContextMenuEvent* event) override; 416 417 private Q_SLOTS: 418 419 /** 420 * Adapts the columns in between the given model indices to the content 421 * size. This can be connected to dataChanged. 422 * 423 * @param topLeft top left index of changed data 424 * @param bottomRight index of changed data 425 */ 426 void adaptColumnsOnDataChange(const QModelIndex& topLeft, const QModelIndex& bottomRight); 427 428 /** 429 * Adapt the column sizes to new contents. This can be connected to all 430 * signals indicating row changes. 431 * 432 * @param parent the parent index of changed rows 433 * @param start the start row changed under the parent 434 * @param end the end row changed under the parent 435 */ 436 void adaptColumnsOnRowChange(const QModelIndex& parent, int start, int end); 437 438 /** 439 * Adapts the column sizes if the layout changes. 440 */ 441 void adaptColumnsOnLayoutChange(); 442 443 /** 444 * This slot is used to ensure that after searching for entries the correct 445 * album is selected again. Therefore it tracks new selections. 446 */ 447 void currentAlbumChangedForBackupSelection(Album* currentAlbum); 448 449 /** 450 * This slots is used to fix bug 400960. 451 */ 452 void slotScrollBarValueChanged(int value); 453 void slotScrollBarActionTriggered(int action); 454 455 private: 456 457 class Private; 458 Private* d; 459 }; 460 461 } // namespace Digikam 462 463 Q_DECLARE_OPERATORS_FOR_FLAGS(Digikam::AbstractAlbumTreeView::Flags) 464 465 #endif // DIGIKAM_ABSTRACT_ALBUM_TREE_VIEW_H 466