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 examples of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:BSD$ |
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 | ** BSD License Usage |
18 | ** Alternatively, you may use this file under the terms of the BSD license |
19 | ** as follows: |
20 | ** |
21 | ** "Redistribution and use in source and binary forms, with or without |
22 | ** modification, are permitted provided that the following conditions are |
23 | ** met: |
24 | ** * Redistributions of source code must retain the above copyright |
25 | ** notice, this list of conditions and the following disclaimer. |
26 | ** * Redistributions in binary form must reproduce the above copyright |
27 | ** notice, this list of conditions and the following disclaimer in |
28 | ** the documentation and/or other materials provided with the |
29 | ** distribution. |
30 | ** * Neither the name of The Qt Company Ltd nor the names of its |
31 | ** contributors may be used to endorse or promote products derived |
32 | ** from this software without specific prior written permission. |
33 | ** |
34 | ** |
35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." |
46 | ** |
47 | ** $QT_END_LICENSE$ |
48 | ** |
49 | ****************************************************************************/ |
50 | |
51 | #include "settingstree.h" |
52 | #include "variantdelegate.h" |
53 | |
54 | #include <QApplication> |
55 | #include <QHeaderView> |
56 | #include <QScreen> |
57 | #include <QSettings> |
58 | |
59 | SettingsTree::SettingsTree(QWidget *parent) |
60 | : QTreeWidget(parent), |
61 | m_typeChecker(new TypeChecker) |
62 | { |
63 | setItemDelegate(new VariantDelegate(m_typeChecker, this)); |
64 | |
65 | setHeaderLabels({tr(s: "Setting" ), tr(s: "Type" ), tr(s: "Value" )}); |
66 | header()->setSectionResizeMode(logicalIndex: 0, mode: QHeaderView::ResizeToContents); |
67 | header()->setSectionResizeMode(logicalIndex: 1, mode: QHeaderView::ResizeToContents); |
68 | header()->setSectionResizeMode(logicalIndex: 2, mode: QHeaderView::Stretch); |
69 | |
70 | refreshTimer.setInterval(2000); |
71 | |
72 | groupIcon.addPixmap(pixmap: style()->standardPixmap(standardPixmap: QStyle::SP_DirClosedIcon), |
73 | mode: QIcon::Normal, state: QIcon::Off); |
74 | groupIcon.addPixmap(pixmap: style()->standardPixmap(standardPixmap: QStyle::SP_DirOpenIcon), |
75 | mode: QIcon::Normal, state: QIcon::On); |
76 | keyIcon.addPixmap(pixmap: style()->standardPixmap(standardPixmap: QStyle::SP_FileIcon)); |
77 | |
78 | connect(sender: &refreshTimer, signal: &QTimer::timeout, receiver: this, slot: &SettingsTree::maybeRefresh); |
79 | } |
80 | |
81 | SettingsTree::~SettingsTree() = default; |
82 | |
83 | void SettingsTree::setSettingsObject(const SettingsPtr &newSettings) |
84 | { |
85 | settings = newSettings; |
86 | clear(); |
87 | |
88 | if (settings.isNull()) { |
89 | refreshTimer.stop(); |
90 | } else { |
91 | refresh(); |
92 | if (autoRefresh) |
93 | refreshTimer.start(); |
94 | } |
95 | } |
96 | |
97 | QSize SettingsTree::sizeHint() const |
98 | { |
99 | const QRect availableGeometry = screen()->availableGeometry(); |
100 | return QSize(availableGeometry.width() * 2 / 3, availableGeometry.height() * 2 / 3); |
101 | } |
102 | |
103 | void SettingsTree::setAutoRefresh(bool autoRefresh) |
104 | { |
105 | this->autoRefresh = autoRefresh; |
106 | if (!settings.isNull()) { |
107 | if (autoRefresh) { |
108 | maybeRefresh(); |
109 | refreshTimer.start(); |
110 | } else { |
111 | refreshTimer.stop(); |
112 | } |
113 | } |
114 | } |
115 | |
116 | void SettingsTree::setFallbacksEnabled(bool enabled) |
117 | { |
118 | if (!settings.isNull()) { |
119 | settings->setFallbacksEnabled(enabled); |
120 | refresh(); |
121 | } |
122 | } |
123 | |
124 | void SettingsTree::maybeRefresh() |
125 | { |
126 | if (state() != EditingState) |
127 | refresh(); |
128 | } |
129 | |
130 | void SettingsTree::refresh() |
131 | { |
132 | if (settings.isNull()) |
133 | return; |
134 | |
135 | disconnect(sender: this, signal: &QTreeWidget::itemChanged, |
136 | receiver: this, slot: &SettingsTree::updateSetting); |
137 | |
138 | settings->sync(); |
139 | updateChildItems(parent: nullptr); |
140 | |
141 | connect(sender: this, signal: &QTreeWidget::itemChanged, |
142 | receiver: this, slot: &SettingsTree::updateSetting); |
143 | } |
144 | |
145 | bool SettingsTree::event(QEvent *event) |
146 | { |
147 | if (event->type() == QEvent::WindowActivate) { |
148 | if (isActiveWindow() && autoRefresh) |
149 | maybeRefresh(); |
150 | } |
151 | return QTreeWidget::event(e: event); |
152 | } |
153 | |
154 | void SettingsTree::updateSetting(QTreeWidgetItem *item) |
155 | { |
156 | QString key = item->text(column: 0); |
157 | QTreeWidgetItem *ancestor = item->parent(); |
158 | while (ancestor) { |
159 | key.prepend(s: ancestor->text(column: 0) + QLatin1Char('/')); |
160 | ancestor = ancestor->parent(); |
161 | } |
162 | |
163 | settings->setValue(key, value: item->data(column: 2, role: Qt::UserRole)); |
164 | if (autoRefresh) |
165 | refresh(); |
166 | } |
167 | |
168 | void SettingsTree::updateChildItems(QTreeWidgetItem *parent) |
169 | { |
170 | int dividerIndex = 0; |
171 | |
172 | const QStringList childGroups = settings->childGroups(); |
173 | for (const QString &group : childGroups) { |
174 | QTreeWidgetItem *child; |
175 | int childIndex = findChild(parent, text: group, startIndex: dividerIndex); |
176 | if (childIndex != -1) { |
177 | child = childAt(parent, index: childIndex); |
178 | child->setText(column: 1, atext: QString()); |
179 | child->setText(column: 2, atext: QString()); |
180 | child->setData(column: 2, role: Qt::UserRole, value: QVariant()); |
181 | moveItemForward(parent, oldIndex: childIndex, newIndex: dividerIndex); |
182 | } else { |
183 | child = createItem(text: group, parent, index: dividerIndex); |
184 | } |
185 | child->setIcon(column: 0, aicon: groupIcon); |
186 | ++dividerIndex; |
187 | |
188 | settings->beginGroup(prefix: group); |
189 | updateChildItems(parent: child); |
190 | settings->endGroup(); |
191 | } |
192 | |
193 | const QStringList childKeys = settings->childKeys(); |
194 | for (const QString &key : childKeys) { |
195 | QTreeWidgetItem *child; |
196 | int childIndex = findChild(parent, text: key, startIndex: 0); |
197 | |
198 | if (childIndex == -1 || childIndex >= dividerIndex) { |
199 | if (childIndex != -1) { |
200 | child = childAt(parent, index: childIndex); |
201 | for (int i = 0; i < child->childCount(); ++i) |
202 | delete childAt(parent: child, index: i); |
203 | moveItemForward(parent, oldIndex: childIndex, newIndex: dividerIndex); |
204 | } else { |
205 | child = createItem(text: key, parent, index: dividerIndex); |
206 | } |
207 | child->setIcon(column: 0, aicon: keyIcon); |
208 | ++dividerIndex; |
209 | } else { |
210 | child = childAt(parent, index: childIndex); |
211 | } |
212 | |
213 | QVariant value = settings->value(key); |
214 | if (value.userType() == QMetaType::UnknownType) { |
215 | child->setText(column: 1, atext: "Invalid" ); |
216 | } else { |
217 | if (value.type() == QVariant::String) { |
218 | const QString stringValue = value.toString(); |
219 | if (m_typeChecker->boolExp.match(subject: stringValue).hasMatch()) { |
220 | value.setValue(stringValue.compare(s: "true" , cs: Qt::CaseInsensitive) == 0); |
221 | } else if (m_typeChecker->signedIntegerExp.match(subject: stringValue).hasMatch()) |
222 | value.setValue(stringValue.toInt()); |
223 | } |
224 | |
225 | child->setText(column: 1, atext: value.typeName()); |
226 | } |
227 | child->setText(column: 2, atext: VariantDelegate::displayText(value)); |
228 | child->setData(column: 2, role: Qt::UserRole, value); |
229 | } |
230 | |
231 | while (dividerIndex < childCount(parent)) |
232 | delete childAt(parent, index: dividerIndex); |
233 | } |
234 | |
235 | QTreeWidgetItem *SettingsTree::createItem(const QString &text, |
236 | QTreeWidgetItem *parent, int index) |
237 | { |
238 | QTreeWidgetItem *after = nullptr; |
239 | if (index != 0) |
240 | after = childAt(parent, index: index - 1); |
241 | |
242 | QTreeWidgetItem *item; |
243 | if (parent) |
244 | item = new QTreeWidgetItem(parent, after); |
245 | else |
246 | item = new QTreeWidgetItem(this, after); |
247 | |
248 | item->setText(column: 0, atext: text); |
249 | item->setFlags(item->flags() | Qt::ItemIsEditable); |
250 | return item; |
251 | } |
252 | |
253 | QTreeWidgetItem *SettingsTree::childAt(QTreeWidgetItem *parent, int index) const |
254 | { |
255 | return (parent ? parent->child(index) : topLevelItem(index)); |
256 | } |
257 | |
258 | int SettingsTree::childCount(QTreeWidgetItem *parent) const |
259 | { |
260 | return (parent ? parent->childCount() : topLevelItemCount()); |
261 | } |
262 | |
263 | int SettingsTree::findChild(QTreeWidgetItem *parent, const QString &text, |
264 | int startIndex) const |
265 | { |
266 | for (int i = startIndex; i < childCount(parent); ++i) { |
267 | if (childAt(parent, index: i)->text(column: 0) == text) |
268 | return i; |
269 | } |
270 | return -1; |
271 | } |
272 | |
273 | void SettingsTree::moveItemForward(QTreeWidgetItem *parent, int oldIndex, |
274 | int newIndex) |
275 | { |
276 | for (int i = 0; i < oldIndex - newIndex; ++i) |
277 | delete childAt(parent, index: newIndex); |
278 | } |
279 | |