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 "qundoview.h"
41
42#if QT_CONFIG(undogroup)
43#include <QtGui/qundogroup.h>
44#endif
45#include <QtGui/qundostack.h>
46#include <QtCore/qabstractitemmodel.h>
47#include <QtCore/qpointer.h>
48#include <QtGui/qicon.h>
49#include <private/qlistview_p.h>
50
51QT_BEGIN_NAMESPACE
52
53class QUndoModel : public QAbstractItemModel
54{
55 Q_OBJECT
56public:
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
77public slots:
78 void setStack(QUndoStack *stack);
79
80private slots:
81 void stackChanged();
82 void stackDestroyed(QObject *obj);
83 void setStackCurrentIndex(const QModelIndex &index);
84
85private:
86 QUndoStack *m_stack;
87 QItemSelectionModel *m_sel_model;
88 QString m_emty_label;
89 QIcon m_clean_icon;
90};
91
92QUndoModel::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
102QItemSelectionModel *QUndoModel::selectionModel() const
103{
104 return m_sel_model;
105}
106
107QUndoStack *QUndoModel::stack() const
108{
109 return m_stack;
110}
111
112void 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
132void QUndoModel::stackDestroyed(QObject *obj)
133{
134 if (obj != m_stack)
135 return;
136 m_stack = nullptr;
137
138 stackChanged();
139}
140
141void QUndoModel::stackChanged()
142{
143 beginResetModel();
144 endResetModel();
145 m_sel_model->setCurrentIndex(selectedIndex(), QItemSelectionModel::ClearAndSelect);
146}
147
148void 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
162QModelIndex QUndoModel::selectedIndex() const
163{
164 return m_stack == nullptr ? QModelIndex() : createIndex(m_stack->index(), 0);
165}
166
167QModelIndex 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
184QModelIndex QUndoModel::parent(const QModelIndex&) const
185{
186 return QModelIndex();
187}
188
189int 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
200int QUndoModel::columnCount(const QModelIndex&) const
201{
202 return 1;
203}
204
205QVariant 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
229QString QUndoModel::emptyLabel() const
230{
231 return m_emty_label;
232}
233
234void QUndoModel::setEmptyLabel(const QString &label)
235{
236 m_emty_label = label;
237 stackChanged();
238}
239
240void QUndoModel::setCleanIcon(const QIcon &icon)
241{
242 m_clean_icon = icon;
243 stackChanged();
244}
245
246QIcon 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
271class QUndoViewPrivate : public QListViewPrivate
272{
273 Q_DECLARE_PUBLIC(QUndoView)
274public:
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
289void 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
302QUndoView::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
313QUndoView::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
329QUndoView::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
343QUndoView::~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
354QUndoStack *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
369void 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
389void 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
420QUndoGroup *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
437void QUndoView::setEmptyLabel(const QString &label)
438{
439 Q_D(QUndoView);
440 d->model->setEmptyLabel(label);
441}
442
443QString 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
459void QUndoView::setCleanIcon(const QIcon &icon)
460{
461 Q_D(const QUndoView);
462 d->model->setCleanIcon(icon);
463
464}
465
466QIcon QUndoView::cleanIcon() const
467{
468 Q_D(const QUndoView);
469 return d->model->cleanIcon();
470}
471
472QT_END_NAMESPACE
473
474#include "qundoview.moc"
475#include "moc_qundoview.cpp"
476