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 
QUndoModel(QObject * parent)92 QUndoModel::QUndoModel(QObject *parent)
93     : QAbstractItemModel(parent)
94 {
95     m_stack = nullptr;
96     m_sel_model = new QItemSelectionModel(this, this);
97     connect(m_sel_model, SIGNAL(currentChanged(QModelIndex,QModelIndex)),
98             this, SLOT(setStackCurrentIndex(QModelIndex)));
99     m_emty_label = tr("<empty>");
100 }
101 
selectionModel() const102 QItemSelectionModel *QUndoModel::selectionModel() const
103 {
104     return m_sel_model;
105 }
106 
stack() const107 QUndoStack *QUndoModel::stack() const
108 {
109     return m_stack;
110 }
111 
setStack(QUndoStack * stack)112 void QUndoModel::setStack(QUndoStack *stack)
113 {
114     if (m_stack == stack)
115         return;
116 
117     if (m_stack != nullptr) {
118         disconnect(m_stack, SIGNAL(cleanChanged(bool)), this, SLOT(stackChanged()));
119         disconnect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(stackChanged()));
120         disconnect(m_stack, SIGNAL(destroyed(QObject*)), this, SLOT(stackDestroyed(QObject*)));
121     }
122     m_stack = stack;
123     if (m_stack != nullptr) {
124         connect(m_stack, SIGNAL(cleanChanged(bool)), this, SLOT(stackChanged()));
125         connect(m_stack, SIGNAL(indexChanged(int)), this, SLOT(stackChanged()));
126         connect(m_stack, SIGNAL(destroyed(QObject*)), this, SLOT(stackDestroyed(QObject*)));
127     }
128 
129     stackChanged();
130 }
131 
stackDestroyed(QObject * obj)132 void QUndoModel::stackDestroyed(QObject *obj)
133 {
134     if (obj != m_stack)
135         return;
136     m_stack = nullptr;
137 
138     stackChanged();
139 }
140 
stackChanged()141 void QUndoModel::stackChanged()
142 {
143     beginResetModel();
144     endResetModel();
145     m_sel_model->setCurrentIndex(selectedIndex(), QItemSelectionModel::ClearAndSelect);
146 }
147 
setStackCurrentIndex(const QModelIndex & index)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 
selectedIndex() const162 QModelIndex QUndoModel::selectedIndex() const
163 {
164     return m_stack == nullptr ? QModelIndex() : createIndex(m_stack->index(), 0);
165 }
166 
index(int row,int column,const QModelIndex & parent) const167 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(row, column);
182 }
183 
parent(const QModelIndex &) const184 QModelIndex QUndoModel::parent(const QModelIndex&) const
185 {
186     return QModelIndex();
187 }
188 
rowCount(const QModelIndex & parent) const189 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 
columnCount(const QModelIndex &) const200 int QUndoModel::columnCount(const QModelIndex&) const
201 {
202     return 1;
203 }
204 
data(const QModelIndex & index,int role) const205 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(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 
emptyLabel() const229 QString QUndoModel::emptyLabel() const
230 {
231     return m_emty_label;
232 }
233 
setEmptyLabel(const QString & label)234 void QUndoModel::setEmptyLabel(const QString &label)
235 {
236     m_emty_label = label;
237     stackChanged();
238 }
239 
setCleanIcon(const QIcon & icon)240 void QUndoModel::setCleanIcon(const QIcon &icon)
241 {
242     m_clean_icon = icon;
243     stackChanged();
244 }
245 
cleanIcon() const246 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:
QUndoViewPrivate()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 
init()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 
QUndoView(QWidget * parent)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 
QUndoView(QUndoStack * stack,QWidget * parent)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 
QUndoView(QUndoGroup * group,QWidget * parent)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 
~QUndoView()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 
stack() const354 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 
setStack(QUndoStack * stack)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 
setGroup(QUndoGroup * group)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(d->group, SIGNAL(activeStackChanged(QUndoStack*)),
398                 d->model, SLOT(setStack(QUndoStack*)));
399     }
400 
401     d->group = group;
402 
403     if (d->group != nullptr) {
404         connect(d->group, SIGNAL(activeStackChanged(QUndoStack*)),
405                 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 
group() const420 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 
setEmptyLabel(const QString & label)437 void QUndoView::setEmptyLabel(const QString &label)
438 {
439     Q_D(QUndoView);
440     d->model->setEmptyLabel(label);
441 }
442 
emptyLabel() const443 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 
setCleanIcon(const QIcon & icon)459 void QUndoView::setCleanIcon(const QIcon &icon)
460 {
461     Q_D(const QUndoView);
462     d->model->setCleanIcon(icon);
463 
464 }
465 
cleanIcon() const466 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