1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtWidgets module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qundostack.h" |
41 | #include "qundoview.h" |
42 | |
43 | #if QT_CONFIG(undogroup) |
44 | #include "qundogroup.h" |
45 | #endif |
46 | #include <QtCore/qabstractitemmodel.h> |
47 | #include <QtCore/qpointer.h> |
48 | #include <QtGui/qicon.h> |
49 | #include <private/qlistview_p.h> |
50 | |
51 | QT_BEGIN_NAMESPACE |
52 | |
53 | class QUndoModel : public QAbstractItemModel |
54 | { |
55 | Q_OBJECT |
56 | public: |
57 | QUndoModel(QObject *parent = nullptr); |
58 | |
59 | QUndoStack *stack() const; |
60 | |
61 | virtual QModelIndex index(int row, int column, |
62 | const QModelIndex &parent = QModelIndex()) const override; |
63 | virtual QModelIndex parent(const QModelIndex &child) const override; |
64 | virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; |
65 | virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override; |
66 | virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; |
67 | |
68 | QModelIndex selectedIndex() const; |
69 | QItemSelectionModel *selectionModel() const; |
70 | |
71 | QString emptyLabel() const; |
72 | void setEmptyLabel(const QString &label); |
73 | |
74 | void setCleanIcon(const QIcon &icon); |
75 | QIcon cleanIcon() const; |
76 | |
77 | public slots: |
78 | void setStack(QUndoStack *stack); |
79 | |
80 | private slots: |
81 | void stackChanged(); |
82 | void stackDestroyed(QObject *obj); |
83 | void setStackCurrentIndex(const QModelIndex &index); |
84 | |
85 | private: |
86 | QUndoStack *m_stack; |
87 | QItemSelectionModel *m_sel_model; |
88 | QString m_emty_label; |
89 | QIcon m_clean_icon; |
90 | }; |
91 | |
92 | QUndoModel::QUndoModel(QObject *parent) |
93 | : QAbstractItemModel(parent) |
94 | { |
95 | m_stack = nullptr; |
96 | m_sel_model = new QItemSelectionModel(this, this); |
97 | connect(sender: m_sel_model, SIGNAL(currentChanged(QModelIndex,QModelIndex)), |
98 | receiver: this, SLOT(setStackCurrentIndex(QModelIndex))); |
99 | m_emty_label = tr(s: "<empty>" ); |
100 | } |
101 | |
102 | QItemSelectionModel *QUndoModel::selectionModel() const |
103 | { |
104 | return m_sel_model; |
105 | } |
106 | |
107 | QUndoStack *QUndoModel::stack() const |
108 | { |
109 | return m_stack; |
110 | } |
111 | |
112 | void QUndoModel::setStack(QUndoStack *stack) |
113 | { |
114 | if (m_stack == stack) |
115 | return; |
116 | |
117 | if (m_stack != nullptr) { |
118 | disconnect(sender: m_stack, SIGNAL(cleanChanged(bool)), receiver: this, SLOT(stackChanged())); |
119 | disconnect(sender: m_stack, SIGNAL(indexChanged(int)), receiver: this, SLOT(stackChanged())); |
120 | disconnect(sender: m_stack, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(stackDestroyed(QObject*))); |
121 | } |
122 | m_stack = stack; |
123 | if (m_stack != nullptr) { |
124 | connect(sender: m_stack, SIGNAL(cleanChanged(bool)), receiver: this, SLOT(stackChanged())); |
125 | connect(sender: m_stack, SIGNAL(indexChanged(int)), receiver: this, SLOT(stackChanged())); |
126 | connect(sender: m_stack, SIGNAL(destroyed(QObject*)), receiver: this, SLOT(stackDestroyed(QObject*))); |
127 | } |
128 | |
129 | stackChanged(); |
130 | } |
131 | |
132 | void QUndoModel::stackDestroyed(QObject *obj) |
133 | { |
134 | if (obj != m_stack) |
135 | return; |
136 | m_stack = nullptr; |
137 | |
138 | stackChanged(); |
139 | } |
140 | |
141 | void QUndoModel::stackChanged() |
142 | { |
143 | beginResetModel(); |
144 | endResetModel(); |
145 | m_sel_model->setCurrentIndex(index: selectedIndex(), command: QItemSelectionModel::ClearAndSelect); |
146 | } |
147 | |
148 | void QUndoModel::setStackCurrentIndex(const QModelIndex &index) |
149 | { |
150 | if (m_stack == nullptr) |
151 | return; |
152 | |
153 | if (index == selectedIndex()) |
154 | return; |
155 | |
156 | if (index.column() != 0) |
157 | return; |
158 | |
159 | m_stack->setIndex(index.row()); |
160 | } |
161 | |
162 | QModelIndex QUndoModel::selectedIndex() const |
163 | { |
164 | return m_stack == nullptr ? QModelIndex() : createIndex(arow: m_stack->index(), acolumn: 0); |
165 | } |
166 | |
167 | QModelIndex QUndoModel::index(int row, int column, const QModelIndex &parent) const |
168 | { |
169 | if (m_stack == nullptr) |
170 | return QModelIndex(); |
171 | |
172 | if (parent.isValid()) |
173 | return QModelIndex(); |
174 | |
175 | if (column != 0) |
176 | return QModelIndex(); |
177 | |
178 | if (row < 0 || row > m_stack->count()) |
179 | return QModelIndex(); |
180 | |
181 | return createIndex(arow: row, acolumn: column); |
182 | } |
183 | |
184 | QModelIndex QUndoModel::parent(const QModelIndex&) const |
185 | { |
186 | return QModelIndex(); |
187 | } |
188 | |
189 | int QUndoModel::rowCount(const QModelIndex &parent) const |
190 | { |
191 | if (m_stack == nullptr) |
192 | return 0; |
193 | |
194 | if (parent.isValid()) |
195 | return 0; |
196 | |
197 | return m_stack->count() + 1; |
198 | } |
199 | |
200 | int QUndoModel::columnCount(const QModelIndex&) const |
201 | { |
202 | return 1; |
203 | } |
204 | |
205 | QVariant QUndoModel::data(const QModelIndex &index, int role) const |
206 | { |
207 | if (m_stack == nullptr) |
208 | return QVariant(); |
209 | |
210 | if (index.column() != 0) |
211 | return QVariant(); |
212 | |
213 | if (index.row() < 0 || index.row() > m_stack->count()) |
214 | return QVariant(); |
215 | |
216 | if (role == Qt::DisplayRole) { |
217 | if (index.row() == 0) |
218 | return m_emty_label; |
219 | return m_stack->text(idx: index.row() - 1); |
220 | } else if (role == Qt::DecorationRole) { |
221 | if (index.row() == m_stack->cleanIndex() && !m_clean_icon.isNull()) |
222 | return m_clean_icon; |
223 | return QVariant(); |
224 | } |
225 | |
226 | return QVariant(); |
227 | } |
228 | |
229 | QString QUndoModel::emptyLabel() const |
230 | { |
231 | return m_emty_label; |
232 | } |
233 | |
234 | void QUndoModel::setEmptyLabel(const QString &label) |
235 | { |
236 | m_emty_label = label; |
237 | stackChanged(); |
238 | } |
239 | |
240 | void QUndoModel::setCleanIcon(const QIcon &icon) |
241 | { |
242 | m_clean_icon = icon; |
243 | stackChanged(); |
244 | } |
245 | |
246 | QIcon QUndoModel::cleanIcon() const |
247 | { |
248 | return m_clean_icon; |
249 | } |
250 | |
251 | /*! |
252 | \class QUndoView |
253 | \brief The QUndoView class displays the contents of a QUndoStack. |
254 | \since 4.2 |
255 | |
256 | \ingroup advanced |
257 | \inmodule QtWidgets |
258 | |
259 | QUndoView is a QListView which displays the list of commands pushed on an undo stack. |
260 | The most recently executed command is always selected. Selecting a different command |
261 | results in a call to QUndoStack::setIndex(), rolling the state of the document |
262 | backwards or forward to the new command. |
263 | |
264 | The stack can be set explicitly with setStack(). Alternatively, a QUndoGroup object can |
265 | be set with setGroup(). The view will then update itself automatically whenever the |
266 | active stack of the group changes. |
267 | |
268 | \image qundoview.png |
269 | */ |
270 | |
271 | class QUndoViewPrivate : public QListViewPrivate |
272 | { |
273 | Q_DECLARE_PUBLIC(QUndoView) |
274 | public: |
275 | QUndoViewPrivate() : |
276 | #if QT_CONFIG(undogroup) |
277 | group(nullptr), |
278 | #endif |
279 | model(nullptr) {} |
280 | |
281 | #if QT_CONFIG(undogroup) |
282 | QPointer<QUndoGroup> group; |
283 | #endif |
284 | QUndoModel *model; |
285 | |
286 | void init(); |
287 | }; |
288 | |
289 | void QUndoViewPrivate::init() |
290 | { |
291 | Q_Q(QUndoView); |
292 | |
293 | model = new QUndoModel(q); |
294 | q->setModel(model); |
295 | q->setSelectionModel(model->selectionModel()); |
296 | } |
297 | |
298 | /*! |
299 | Constructs a new view with parent \a parent. |
300 | */ |
301 | |
302 | QUndoView::QUndoView(QWidget *parent) |
303 | : QListView(*new QUndoViewPrivate(), parent) |
304 | { |
305 | Q_D(QUndoView); |
306 | d->init(); |
307 | } |
308 | |
309 | /*! |
310 | Constructs a new view with parent \a parent and sets the observed stack to \a stack. |
311 | */ |
312 | |
313 | QUndoView::QUndoView(QUndoStack *stack, QWidget *parent) |
314 | : QListView(*new QUndoViewPrivate(), parent) |
315 | { |
316 | Q_D(QUndoView); |
317 | d->init(); |
318 | setStack(stack); |
319 | } |
320 | |
321 | #if QT_CONFIG(undogroup) |
322 | |
323 | /*! |
324 | Constructs a new view with parent \a parent and sets the observed group to \a group. |
325 | |
326 | The view will update itself autmiatically whenever the active stack of the group changes. |
327 | */ |
328 | |
329 | QUndoView::QUndoView(QUndoGroup *group, QWidget *parent) |
330 | : QListView(*new QUndoViewPrivate(), parent) |
331 | { |
332 | Q_D(QUndoView); |
333 | d->init(); |
334 | setGroup(group); |
335 | } |
336 | |
337 | #endif // QT_CONFIG(undogroup) |
338 | |
339 | /*! |
340 | Destroys this view. |
341 | */ |
342 | |
343 | QUndoView::~QUndoView() |
344 | { |
345 | } |
346 | |
347 | /*! |
348 | Returns the stack currently displayed by this view. If the view is looking at a |
349 | QUndoGroup, this the group's active stack. |
350 | |
351 | \sa setStack(), setGroup() |
352 | */ |
353 | |
354 | QUndoStack *QUndoView::stack() const |
355 | { |
356 | Q_D(const QUndoView); |
357 | return d->model->stack(); |
358 | } |
359 | |
360 | /*! |
361 | Sets the stack displayed by this view to \a stack. If \a stack is \nullptr, |
362 | the view will be empty. |
363 | |
364 | If the view was previously looking at a QUndoGroup, the group is set to \nullptr. |
365 | |
366 | \sa stack(), setGroup() |
367 | */ |
368 | |
369 | void QUndoView::setStack(QUndoStack *stack) |
370 | { |
371 | Q_D(QUndoView); |
372 | #if QT_CONFIG(undogroup) |
373 | setGroup(nullptr); |
374 | #endif |
375 | d->model->setStack(stack); |
376 | } |
377 | |
378 | #if QT_CONFIG(undogroup) |
379 | |
380 | /*! |
381 | Sets the group displayed by this view to \a group. If \a group is \nullptr, |
382 | the view will be empty. |
383 | |
384 | The view will update itself automatically whenever the active stack of the group changes. |
385 | |
386 | \sa group(), setStack() |
387 | */ |
388 | |
389 | void QUndoView::setGroup(QUndoGroup *group) |
390 | { |
391 | Q_D(QUndoView); |
392 | |
393 | if (d->group == group) |
394 | return; |
395 | |
396 | if (d->group != nullptr) { |
397 | disconnect(sender: d->group, SIGNAL(activeStackChanged(QUndoStack*)), |
398 | receiver: d->model, SLOT(setStack(QUndoStack*))); |
399 | } |
400 | |
401 | d->group = group; |
402 | |
403 | if (d->group != nullptr) { |
404 | connect(sender: d->group, SIGNAL(activeStackChanged(QUndoStack*)), |
405 | receiver: d->model, SLOT(setStack(QUndoStack*))); |
406 | d->model->setStack(d->group->activeStack()); |
407 | } else { |
408 | d->model->setStack(nullptr); |
409 | } |
410 | } |
411 | |
412 | /*! |
413 | Returns the group displayed by this view. |
414 | |
415 | If the view is not looking at group, this function returns \nullptr. |
416 | |
417 | \sa setGroup(), setStack() |
418 | */ |
419 | |
420 | QUndoGroup *QUndoView::group() const |
421 | { |
422 | Q_D(const QUndoView); |
423 | return d->group; |
424 | } |
425 | |
426 | #endif // QT_CONFIG(undogroup) |
427 | |
428 | /*! |
429 | \property QUndoView::emptyLabel |
430 | \brief the label used for the empty state. |
431 | |
432 | The empty label is the topmost element in the list of commands, which represents |
433 | the state of the document before any commands were pushed on the stack. The default |
434 | is the string "<empty>". |
435 | */ |
436 | |
437 | void QUndoView::setEmptyLabel(const QString &label) |
438 | { |
439 | Q_D(QUndoView); |
440 | d->model->setEmptyLabel(label); |
441 | } |
442 | |
443 | QString QUndoView::emptyLabel() const |
444 | { |
445 | Q_D(const QUndoView); |
446 | return d->model->emptyLabel(); |
447 | } |
448 | |
449 | /*! |
450 | \property QUndoView::cleanIcon |
451 | \brief the icon used to represent the clean state. |
452 | |
453 | A stack may have a clean state set with QUndoStack::setClean(). This is usually |
454 | the state of the document at the point it was saved. QUndoView can display an |
455 | icon in the list of commands to show the clean state. If this property is |
456 | a null icon, no icon is shown. The default value is the null icon. |
457 | */ |
458 | |
459 | void QUndoView::setCleanIcon(const QIcon &icon) |
460 | { |
461 | Q_D(const QUndoView); |
462 | d->model->setCleanIcon(icon); |
463 | |
464 | } |
465 | |
466 | QIcon QUndoView::cleanIcon() const |
467 | { |
468 | Q_D(const QUndoView); |
469 | return d->model->cleanIcon(); |
470 | } |
471 | |
472 | QT_END_NAMESPACE |
473 | |
474 | #include "qundoview.moc" |
475 | #include "moc_qundoview.cpp" |
476 | |