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