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

source code of qtbase/src/widgets/util/qundoview.cpp