1 /*
2 This file is part of the Okteta Kasten Framework, made within the KDE community.
3
4 SPDX-FileCopyrightText: 2009, 2010, 2012 Alex Richardson <alex.richardson@gmx.de>
5 SPDX-FileCopyrightText: 2009 Friedrich W. H. Kossebau <kossebau@kde.org>
6
7 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
8 */
9
10 #include "structureview.hpp"
11
12 // controller
13 #include "structuretreemodel.hpp"
14 #include "structurestool.hpp"
15 #include "structuresmanager.hpp"
16 #include "structureviewitemdelegate.hpp"
17 #include "structlogging.hpp"
18 // settings
19 #include "structureviewpreferences.h"
20 #include "settings/structureviewsettingswidget.hpp"
21 #include "settings/structuresmanagerview.hpp"
22 #include "settings/structureaddremovewidget.hpp"
23
24 #include "script/scriptutils.hpp"
25 #include "script/scriptloggerview.hpp"
26
27 // #include "modeltest.hpp"
28
29 // KF
30 #include <KComboBox>
31 #include <KLocalizedString>
32 #include <KConfigDialog>
33 // Qt
34 #include <QDialog>
35 #include <QDialogButtonBox>
36 #include <QLabel>
37 #include <QLayout>
38 #include <QTreeView>
39 #include <QPushButton>
40 #include <QHeaderView>
41 #include <QFocusEvent>
42
43 namespace Kasten {
44
StructureView(StructuresTool * tool,QWidget * parent)45 StructureView::StructureView(StructuresTool* tool, QWidget* parent)
46 : QWidget(parent)
47 , mTool(tool)
48 , mDelegate(new StructureViewItemDelegate(this))
49 , mStructTreeViewFocusChild(nullptr)
50 {
51 QBoxLayout* baseLayout = new QVBoxLayout(this);
52 setLayout(baseLayout);
53 baseLayout->setContentsMargins(0, 0, 0, 0);
54 // table
55 mStructureTreeModel = new StructureTreeModel(mTool, this);
56 // mModeltest = new ModelTest(mStructureTreeModel, this);
57 mStructTreeView = new QTreeView(this);
58 mStructTreeView->setObjectName(QStringLiteral("StructTree"));
59 mStructTreeView->setRootIsDecorated(true);
60 mStructTreeView->setAlternatingRowColors(true);
61 mStructTreeView->setItemsExpandable(true);
62 mStructTreeView->setUniformRowHeights(true);
63 mStructTreeView->setAllColumnsShowFocus(true);
64 mStructTreeView->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed);
65 mStructTreeView->setItemDelegate(mDelegate);
66 mStructTreeView->setDragEnabled(false);
67 mStructTreeView->setSortingEnabled(false);
68 mStructTreeView->setModel(mStructureTreeModel);
69 mStructTreeView->setHeaderHidden(false);
70 mStructTreeView->setSortingEnabled(false);
71 mStructTreeView->installEventFilter(this);
72 QHeaderView* header = mStructTreeView->header();
73 header->setSectionResizeMode(QHeaderView::Interactive);
74
75 baseLayout->addWidget(mStructTreeView, 10);
76
77 // settings
78 QBoxLayout* settingsLayout = new QHBoxLayout();
79 settingsLayout->setContentsMargins(0, 0, 0, 0);
80
81 baseLayout->addLayout(settingsLayout);
82
83 QIcon validateIcon = QIcon::fromTheme(QStringLiteral("document-sign"));
84 mValidateButton = new QPushButton(validateIcon, i18nc("@action:button", "Validate"), this);
85 const QString validationToolTip = i18nc("@info:tooltip", "Validate all structures.");
86 mValidateButton->setToolTip(validationToolTip);
87 mValidateButton->setEnabled(false); // no point validating without file open
88 connect(mValidateButton, &QPushButton::clicked, mTool, &StructuresTool::validateAllStructures);
89 connect(mTool, &StructuresTool::byteArrayModelChanged,
90 this, &StructureView::onByteArrayModelChanged);
91 // TODO also disable the button if the structure has no validatable members
92 settingsLayout->addWidget(mValidateButton);
93
94 mLockStructureButton = new QPushButton(this);
95 mLockStructureButton->setCheckable(true);
96 setLockButtonState(false);
97 mLockStructureButton->setEnabled(false); // won't work at beginning
98 connect(mLockStructureButton, &QPushButton::toggled, this, &StructureView::lockButtonToggled);
99
100 settingsLayout->addWidget(mLockStructureButton);
101
102 settingsLayout->addStretch(); // stretch before the settings button
103
104 QIcon console = QIcon::fromTheme(QStringLiteral("utilities-terminal"));
105 mScriptConsoleButton = new QPushButton(console, i18nc("@action:button", "Script console"), this);
106 mScriptConsoleButton->setToolTip(i18nc("@info:tooltip", "Open script console."));
107 connect(mScriptConsoleButton, &QPushButton::pressed, this, &StructureView::openScriptConsole);
108 settingsLayout->addWidget(mScriptConsoleButton);
109
110 QIcon settings = QIcon::fromTheme(QStringLiteral("configure"));
111 mSettingsButton = new QPushButton(settings, i18nc("@action:button", "Settings"), this);
112 const QString settingsTooltip = i18nc("@info:tooltip", "Open settings.");
113 mSettingsButton->setToolTip(settingsTooltip);
114 connect(mSettingsButton, &QPushButton::pressed, this, &StructureView::openSettingsDlg);
115 settingsLayout->addWidget(mSettingsButton);
116
117 connect(mStructTreeView->selectionModel(),
118 &QItemSelectionModel::currentRowChanged,
119 this, &StructureView::onCurrentRowChanged);
120
121 connect(mTool, &StructuresTool::cursorIndexChanged, this, &StructureView::onCursorIndexChange);
122 }
123
124 StructureView::~StructureView() = default;
125
tool() const126 StructuresTool* StructureView::tool() const
127 {
128 return mTool;
129 }
130
onCursorIndexChange()131 void StructureView::onCursorIndexChange()
132 {
133 QModelIndex idx = mStructTreeView->currentIndex();
134 if (idx.isValid()) {
135 mTool->mark(idx);
136 }
137 }
138
openSettingsDlg()139 void StructureView::openSettingsDlg()
140 {
141 // An instance of your dialog could be already created and could be cached,
142 // in which case you want to display the cached dialog instead of creating
143 // another one
144 if (KConfigDialog::showDialog(QStringLiteral("Structures Tool Settings"))) {
145 return;
146 }
147
148 // KConfigDialog didn't find an instance of this dialog, so lets create it :
149 KConfigDialog* dialog = new KConfigDialog(this, QStringLiteral("Structures Tool Settings"),
150 StructureViewPreferences::self());
151
152 auto* displaySettings = new StructureViewSettingsWidget();
153 KPageWidgetItem* displ = dialog->addPage(displaySettings, i18n("Value Display"),
154 QStringLiteral("configure"));
155
156 // cannot use StructuresManagerView directly as page even if the only element
157 // because KConfigDialogManager only scans the children of the page for kcfg_ elements
158 QWidget* structSelectionPage = new QWidget();
159 auto* hbox = new QHBoxLayout();
160 hbox->setContentsMargins(0, 0, 0, 0);
161 structSelectionPage->setLayout(hbox);
162 auto* structureSettings = new StructuresManagerView(mTool, this);
163 structureSettings->setObjectName(QStringLiteral("kcfg_LoadedStructures"));
164 hbox->addWidget(structureSettings);
165 dialog->addPage(structSelectionPage, i18n("Structures management"),
166 QStringLiteral("preferences-plugin"));
167
168 // User edited the configuration - update your local copies of the configuration data
169 connect(dialog, &KConfigDialog::settingsChanged, mTool, &StructuresTool::setSelectedStructuresInView);
170
171 // TODO: kconfig_compiler signals work now, use those signals and not the generic KConfigDialog::settingsChanged
172 dialog->setCurrentPage(displ);
173 dialog->show();
174 }
175
eventFilter(QObject * object,QEvent * event)176 bool StructureView::eventFilter(QObject* object, QEvent* event)
177 {
178 if (object == mStructTreeView) {
179 if (event->type() == QEvent::FocusIn) {
180 const QModelIndex current = mStructTreeView->selectionModel()->currentIndex();
181
182 if (current.isValid()) {
183 mTool->mark(current);
184 } else {
185 mTool->unmark();
186 }
187
188 setLockButtonState(current);
189 } else if (event->type() == QEvent::FocusOut) {
190 QWidget* treeViewFocusWidget = mStructTreeView->focusWidget();
191 const bool subChildHasFocus = (treeViewFocusWidget != mStructTreeView);
192 if (subChildHasFocus) {
193 mStructTreeViewFocusChild = treeViewFocusWidget;
194 mStructTreeViewFocusChild->installEventFilter(this);
195 } else {
196 mTool->unmark();
197 }
198 }
199 } else if (object == mStructTreeViewFocusChild) {
200 // TODO: it is only assumed the edit widget will be removed if it loses the focus
201 if (event->type() == QEvent::FocusOut) {
202 if (!mStructTreeView->hasFocus()) {
203 mTool->unmark();
204 }
205 mStructTreeViewFocusChild->removeEventFilter(this);
206 mStructTreeViewFocusChild = nullptr;
207 }
208 }
209
210 return QWidget::eventFilter(object, event);
211 }
212
setLockButtonState(const QModelIndex & current)213 void StructureView::setLockButtonState(const QModelIndex& current)
214 {
215 // qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "setLockButtonState() for" << current;
216
217 // we don't want the toggled signal here, only when the user clicks the button!
218 QSignalBlocker block(mLockStructureButton);
219 setLockButtonState(mTool->isStructureLocked(current));
220 mLockStructureButton->setEnabled(mTool->canStructureBeLocked(current));
221 }
222
onCurrentRowChanged(const QModelIndex & current,const QModelIndex & previous)223 void StructureView::onCurrentRowChanged(const QModelIndex& current, const QModelIndex& previous)
224 {
225 Q_UNUSED(previous)
226 if (current.isValid() && mTool->byteArrayModel()) {
227 mTool->mark(current);
228 setLockButtonState(current);
229 } else {
230 mTool->unmark();
231 }
232 }
233
lockButtonToggled()234 void StructureView::lockButtonToggled()
235 {
236 // qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "Lock button toggled";
237
238 setLockButtonState(mLockStructureButton->isChecked());
239 const QModelIndex current = mStructTreeView->selectionModel()->currentIndex();
240 if (!current.isValid()) {
241 qCWarning(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "it should not be possible to toggle this button when current index is invalid!";
242 return;
243 }
244
245 if (mLockStructureButton->isChecked()) {
246 mTool->lockStructure(current);
247 } else {
248 mTool->unlockStructure(current);
249 }
250 }
251
setLockButtonState(bool structureLocked)252 void StructureView::setLockButtonState(bool structureLocked)
253 {
254 if (structureLocked) {
255 mLockStructureButton->setIcon(QIcon::fromTheme(QStringLiteral("object-locked")));
256 mLockStructureButton->setText(i18nc("@action:button"
257 " unlock the starting offset of the current structure", "Unlock"));
258 mLockStructureButton->setToolTip(i18nc("@info:tooltip",
259 "Unlock selected structure, i.e. the starting offset is"
260 " always set to the current cursor position."));
261 } else {
262 mLockStructureButton->setIcon(QIcon::fromTheme(QStringLiteral("object-unlocked")));
263 mLockStructureButton->setText(i18nc("@action:button"
264 " unlock the starting offset of the current structure", "Lock"));
265 mLockStructureButton->setToolTip(i18nc("@info:tooltip",
266 "Lock selected structure to current offset."));
267 }
268 mLockStructureButton->setChecked(structureLocked);
269 }
270
openScriptConsole()271 void StructureView::openScriptConsole()
272 {
273 QDialog* dialog = new QDialog(this);
274 dialog->setWindowTitle(i18nc("@title:window", "Structures Script Console"));
275 auto* layout = new QVBoxLayout;
276 auto* dialogButtonBox = new QDialogButtonBox;
277 QPushButton* closeButton = dialogButtonBox->addButton(QDialogButtonBox::Close);
278 connect(closeButton, &QPushButton::clicked, dialog, &QDialog::accept);
279 layout->addWidget(new ScriptLoggerView(mTool->allData()));
280 layout->addWidget(dialogButtonBox);
281 dialog->setLayout(layout);
282 dialog->show();
283 }
284
onByteArrayModelChanged(Okteta::AbstractByteArrayModel * model)285 void StructureView::onByteArrayModelChanged(Okteta::AbstractByteArrayModel* model)
286 {
287 const bool validModel = (model != nullptr);
288 QModelIndex current = mStructTreeView->currentIndex();
289 mLockStructureButton->setEnabled(mTool->canStructureBeLocked(current));
290 setLockButtonState(mTool->isStructureLocked(current));
291 mValidateButton->setEnabled(validModel);
292 }
293
294 }
295