1 /*
2     Scan Tailor - Interactive post-processing tool for scanned pages.
3     Copyright (C)  Joseph Artsimovich <joseph.artsimovich@gmail.com>
4 
5     This program is free software: you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation, either version 3 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #ifndef THUMBNAILSEQUENCE_H_
20 #define THUMBNAILSEQUENCE_H_
21 
22 #include <QObject>
23 #include <memory>
24 #include <set>
25 #include <vector>
26 #include "BeforeOrAfter.h"
27 #include "FlagOps.h"
28 #include "NonCopyable.h"
29 #include "PageOrderProvider.h"
30 #include "PageRange.h"
31 #include "intrusive_ptr.h"
32 
33 class QGraphicsItem;
34 class QGraphicsView;
35 class PageId;
36 class ImageId;
37 class PageInfo;
38 class PageSequence;
39 class ThumbnailFactory;
40 class QSizeF;
41 class QRectF;
42 class QPoint;
43 
44 class ThumbnailSequence : public QObject {
45   Q_OBJECT
46   DECLARE_NON_COPYABLE(ThumbnailSequence)
47 
48  public:
49   enum SelectionAction { KEEP_SELECTION, RESET_SELECTION };
50 
51   enum SelectionFlags {
52     DEFAULT_SELECTION_FLAGS = 0,
53 
54     /** Indicates the item was selected by a user action, rather than programmatically. */
55     SELECTED_BY_USER = 1 << 0,
56 
57     /**
58      * Indicates that the request to make this item a selection leader was redundant,
59      * as it's already a selection leader.
60      */
61     REDUNDANT_SELECTION = 1 << 1,
62 
63     /**
64      * This flag is set when Ctrl-clicking the current selection leader while other
65      * selected items exist.  In this case, the leader will become unselected, and
66      * one of the other selected items will be promoted to a selection leader.
67      * In these circumstances, scrolling to make the new selection leader visible
68      * is undesireable.
69      */
70     AVOID_SCROLLING_TO = 1 << 2
71   };
72 
73   explicit ThumbnailSequence(const QSizeF& max_logical_thumb_size);
74 
75   ~ThumbnailSequence() override;
76 
77   void setThumbnailFactory(intrusive_ptr<ThumbnailFactory> factory);
78 
79   void attachView(QGraphicsView* view);
80 
81   /**
82    * \brief Re-populate the list of thumbnails.
83    *
84    * \param pages Pages to put in the sequence.
85    * \param selection_action Whether to keep the selection, provided
86    *        selected item(s) are still present in the new list of pages.
87    * \param order_provider The source of ordering information.  It will
88    *        be preserved until the next reset() call and will be taken
89    *        into account by other methods, like invalidateThumbnail()
90    *        and insert().  A null order provider indicates to keep the
91    *        order of ProjectPages.
92    */
93   void reset(const PageSequence& pages,
94              SelectionAction selection_action,
95              intrusive_ptr<const PageOrderProvider> order_provider = nullptr);
96 
97   /** Returns the current page order provider, which may be null. */
98   intrusive_ptr<const PageOrderProvider> pageOrderProvider() const;
99 
100   PageSequence toPageSequence() const;
101 
102   /**
103    * \brief Updates position of all the thumbnails in the view.
104    *
105    * \note This function doesn't update thumbnails appearance.
106    */
107   void updateSceneItemsPos();
108 
109   /**
110    * \brief Updates appearance and possibly position of a thumbnail.
111    *
112    * If thumbnail's size or position have changed and this thumbnail
113    * is a selection leader, newSelectionLeader() signal will be emitted
114    * with REDUNDANT_SELECTION flag set.
115    *
116    * \note This function assumes the thumbnail specified by page_id
117    *       is the only thumbnail at incorrect position.  If you do
118    *       something that changes the logical position of more than
119    *       one thumbnail at once, use invalidateAllThumbnails()
120    *       instead of sequentially calling invalidateThumbnail().
121    */
122   void invalidateThumbnail(const PageId& page_id);
123 
124   /**
125    * This signature differs from invalidateThumbnail(PageId) in that
126    * it will cause PageInfo stored by ThumbnailSequence to be updated.
127    */
128   void invalidateThumbnail(const PageInfo& page_info);
129 
130   /**
131    * \brief Updates appearance of all thumbnails and possibly their order.
132    *
133    * Whether or not order will be updated depends on whether an order provider
134    * was specified by the most recent reset() call.
135    */
136   void invalidateAllThumbnails();
137 
138   /**
139    * \brief Makes the item a selection leader, and unselects other items.
140    *
141    * \param page_id The page to select.
142    * \param selection_action Whether to keep the selection, provided
143    *        selected item(s) are still present in the new list of pages.
144    * \return true on success, false if the requested page wasn't found.
145    *
146    * On success, the newSelectionLeader() signal is emitted, possibly
147    * with REDUNDANT_SELECTION flag set, in case our page was already the
148    * selection leader.
149    */
150   bool setSelection(const PageId& page_id, SelectionAction selection_action = RESET_SELECTION);
151 
152   /**
153    * \brief Returns the current selection leader.
154    *
155    * A null PageInfo is returned if no items are currently selected.
156    */
157   PageInfo selectionLeader() const;
158 
159   /**
160    * \brief Returns the page immediately preceding the given one.
161    *
162    * A null PageInfo is returned if the given page wasn't found or
163    * there are no pages preceding it.
164    */
165   PageInfo prevPage(const PageId& reference_page) const;
166 
167   /**
168    * \brief Returns the page immediately following the given one.
169    *
170    * A null PageInfo is returned if the given page wasn't found or
171    * there are no pages following it.
172    */
173   PageInfo nextPage(const PageId& reference_page) const;
174 
175   /**
176    * \brief Returns the selected page preceding the given one.
177    *
178    * A null PageInfo is returned if the given page wasn't found or
179    * there are no pages preceding it.
180    */
181   PageInfo prevSelectedPage(const PageId& reference_page) const;
182 
183   /**
184    * \brief Returns the selected page following the given one.
185    *
186    * A null PageInfo is returned if the given page wasn't found or
187    * there are no pages following it.
188    */
189   PageInfo nextSelectedPage(const PageId& reference_page) const;
190 
191   /**
192    * \brief Returns the first page in the sequence.
193    *
194    * A null PageInfo is returned if the sequence is empty.
195    */
196   PageInfo firstPage() const;
197 
198   /**
199    * \brief Returns the last page in the sequence.
200    *
201    * A null PageInfo is returned if the sequence is empty.
202    */
203   PageInfo lastPage() const;
204 
205   /**
206    * \brief Inserts a page before the first page with matching ImageId.
207    *
208    * If no order provider was specified by the previous reset() call,
209    * we won't allow inserting a page between two halves of another page,
210    * to be compatible with what reset() does.  Otherwise, the new
211    * page will be inserted at a correct position according to the current
212    * order provider.  In this case \p before_or_after doesn't really matter.
213    *
214    * If there are no pages with matching ImageId, the new page won't
215    * be inserted, unless the request is to insert BEFORE a null ImageId(),
216    * which would cause insertion at the end.
217    */
218   void insert(const PageInfo& new_page, BeforeOrAfter before_or_after, const ImageId& image);
219 
220   void removePages(const std::set<PageId>& pages);
221 
222   /**
223    * \brief The bounding rectangle in scene coordinates of the selection leader.
224    *
225    * Returns a null rectangle if no item is currently selected.
226    */
227   QRectF selectionLeaderSceneRect() const;
228 
229   std::set<PageId> selectedItems() const;
230 
231   std::vector<PageRange> selectedRanges() const;
232 
233   const QSizeF& getMaxLogicalThumbSize() const;
234 
235   void setMaxLogicalThumbSize(const QSizeF& size);
236 
237  signals:
238 
239   void newSelectionLeader(const PageInfo& page_info, const QRectF& thumb_rect, ThumbnailSequence::SelectionFlags flags);
240 
241   /**
242    * Emitted when a user right-clicks on a page thumbnail.
243    */
244   void pageContextMenuRequested(const PageInfo& page_info, const QPoint& screen_pos, bool selected);
245 
246   /**
247    * Emitted when a user right clicks on area below the last page.
248    * In the absence of any pages, all the area is considered to be
249    * below the last page.
250    */
251   void pastLastPageContextMenuRequested(const QPoint& screen_pos);
252 
253  private:
254   class Item;
255   class Impl;
256   class GraphicsScene;
257   class PlaceholderThumb;
258   class LabelGroup;
259   class CompositeItem;
260 
261   void emitNewSelectionLeader(const PageInfo& page_info, const CompositeItem* composite, SelectionFlags flags);
262 
263   std::unique_ptr<Impl> m_impl;
264 };
265 
266 
267 DEFINE_FLAG_OPS(ThumbnailSequence::SelectionFlags)
268 
269 #endif  // ifndef THUMBNAILSEQUENCE_H_
270