File indexing completed on 2025-01-05 05:23:48
0001 /* 0002 This file is part of the Okteta Kasten Framework, made within the KDE community. 0003 0004 SPDX-FileCopyrightText: 2009, 2010, 2012 Alex Richardson <alex.richardson@gmx.de> 0005 SPDX-FileCopyrightText: 2009 Friedrich W. H. Kossebau <kossebau@kde.org> 0006 0007 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0008 */ 0009 0010 #include "structureview.hpp" 0011 0012 // controller 0013 #include "structuretreemodel.hpp" 0014 #include "structurestool.hpp" 0015 #include "structuresmanager.hpp" 0016 #include "structureviewitemdelegate.hpp" 0017 #include <structureslogging.hpp> 0018 // settings 0019 #include "structureviewpreferences.hpp" 0020 #include "settings/structureviewsettingswidget.hpp" 0021 #include "settings/structuresmanagerview.hpp" 0022 #include "settings/structureaddremovewidget.hpp" 0023 0024 #include "datatypes/datainformation.hpp" 0025 #include "script/scriptloggerview.hpp" 0026 0027 // KF 0028 #include <KComboBox> 0029 #include <KStandardAction> 0030 #include <KLocalizedString> 0031 #include <KConfigDialog> 0032 // Qt 0033 #include <QDialog> 0034 #include <QDialogButtonBox> 0035 #include <QHBoxLayout> 0036 #include <QVBoxLayout> 0037 #include <QTreeView> 0038 #include <QPushButton> 0039 #include <QHeaderView> 0040 #include <QFocusEvent> 0041 #include <QMenu> 0042 #include <QToolBar> 0043 #include <QMimeData> 0044 #include <QApplication> 0045 #include <QClipboard> 0046 #include <QAction> 0047 #include <QIcon> 0048 // #include <QAbstractItemModelTester> 0049 0050 namespace Kasten { 0051 0052 StructureView::StructureView(StructuresTool* tool, QWidget* parent) 0053 : QWidget(parent) 0054 , mTool(tool) 0055 , mDelegate(new StructureViewItemDelegate(this)) 0056 , mStructTreeViewFocusChild(nullptr) 0057 { 0058 auto* baseLayout = new QVBoxLayout(this); 0059 setLayout(baseLayout); 0060 baseLayout->setContentsMargins(0, 0, 0, 0); 0061 baseLayout->setSpacing(0); 0062 0063 // table 0064 mStructureTreeModel = new StructureTreeModel(mTool, this); 0065 // mModeltester = new QAbstractItemModelTester(mStructureTreeModel, this); 0066 mStructTreeView = new QTreeView(this); 0067 mStructTreeView->setObjectName(QStringLiteral("StructTree")); 0068 mStructTreeView->setRootIsDecorated(true); 0069 mStructTreeView->setAlternatingRowColors(true); 0070 mStructTreeView->setItemsExpandable(true); 0071 mStructTreeView->setUniformRowHeights(true); 0072 mStructTreeView->setAllColumnsShowFocus(true); 0073 mStructTreeView->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed); 0074 mStructTreeView->setItemDelegate(mDelegate); 0075 mStructTreeView->setDragEnabled(true); 0076 mStructTreeView->setDragDropMode(QAbstractItemView::DragOnly); 0077 mStructTreeView->setSortingEnabled(false); 0078 mStructTreeView->setModel(mStructureTreeModel); 0079 mStructTreeView->setHeaderHidden(false); 0080 mStructTreeView->setSortingEnabled(false); 0081 mStructTreeView->setContextMenuPolicy(Qt::CustomContextMenu); 0082 mStructTreeView->installEventFilter(this); 0083 QHeaderView* header = mStructTreeView->header(); 0084 header->setSectionResizeMode(QHeaderView::Interactive); 0085 connect(mStructTreeView, &QWidget::customContextMenuRequested, 0086 this, &StructureView::onCustomContextMenuRequested); 0087 0088 baseLayout->addWidget(mStructTreeView, 10); 0089 0090 // settings 0091 auto* actionsToolBar = new QToolBar(this); 0092 actionsToolBar->setToolButtonStyle(Qt::ToolButtonFollowStyle); 0093 0094 QIcon validateIcon = QIcon::fromTheme(QStringLiteral("document-sign")); 0095 mValidateAction = 0096 actionsToolBar->addAction(validateIcon, i18nc("@action:button", "Validate"), 0097 mTool, &StructuresTool::validateAllStructures); 0098 const QString validationToolTip = i18nc("@info:tooltip", "Validate all structures."); 0099 mValidateAction->setToolTip(validationToolTip); 0100 mValidateAction->setEnabled(false); // no point validating without file open 0101 connect(mTool, &StructuresTool::byteArrayModelChanged, 0102 this, &StructureView::onByteArrayModelChanged); 0103 // TODO also disable the button if the structure has no validatable members 0104 0105 mLockStructureAction = 0106 actionsToolBar->addAction(QString(), 0107 this, &StructureView::onLockButtonClicked); 0108 mLockStructureAction->setCheckable(true); 0109 mLockStructureAction->setChecked(false); 0110 mLockStructureAction->setEnabled(false); // won't work at beginning 0111 onLockButtonToggled(false); 0112 connect(mLockStructureAction, &QAction::toggled, this, &StructureView::onLockButtonToggled); 0113 0114 auto* stretcher = new QWidget(this); 0115 stretcher->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); 0116 actionsToolBar->addWidget(stretcher); 0117 0118 QIcon console = QIcon::fromTheme(QStringLiteral("utilities-terminal")); 0119 mScriptConsoleAction = 0120 actionsToolBar->addAction(console, i18nc("@action:button", "Script console"), 0121 this, &StructureView::openScriptConsole); 0122 mScriptConsoleAction->setToolTip(i18nc("@info:tooltip", "Open script console.")); 0123 0124 QIcon settings = QIcon::fromTheme(QStringLiteral("configure")); 0125 mSettingsAction = 0126 actionsToolBar->addAction(settings, i18nc("@action:button", "Settings"), 0127 this, &StructureView::openSettingsDlg); 0128 const QString settingsTooltip = i18nc("@info:tooltip", "Open settings."); 0129 mSettingsAction->setToolTip(settingsTooltip); 0130 0131 baseLayout->addWidget(actionsToolBar); 0132 0133 connect(mStructTreeView->selectionModel(), 0134 &QItemSelectionModel::currentRowChanged, 0135 this, &StructureView::onCurrentRowChanged); 0136 } 0137 0138 StructureView::~StructureView() = default; 0139 0140 StructuresTool* StructureView::tool() const 0141 { 0142 return mTool; 0143 } 0144 0145 void StructureView::openSettingsDlg() 0146 { 0147 // An instance of your dialog could be already created and could be cached, 0148 // in which case you want to display the cached dialog instead of creating 0149 // another one 0150 if (KConfigDialog::showDialog(QStringLiteral("Structures Tool Settings"))) { 0151 return; 0152 } 0153 0154 // KConfigDialog didn't find an instance of this dialog, so lets create it : 0155 auto* dialog = new KConfigDialog(this, QStringLiteral("Structures Tool Settings"), 0156 StructureViewPreferences::self()); 0157 0158 auto* displaySettings = new StructureViewSettingsWidget(); 0159 KPageWidgetItem* displ = dialog->addPage(displaySettings, i18n("Value Display"), 0160 QStringLiteral("configure")); 0161 0162 // cannot use StructuresManagerView directly as page even if the only element 0163 // because KConfigDialogManager only scans the children of the page for kcfg_ elements 0164 auto* structSelectionPage = new QWidget(); 0165 auto* hbox = new QHBoxLayout(); 0166 hbox->setContentsMargins(0, 0, 0, 0); 0167 structSelectionPage->setLayout(hbox); 0168 auto* structureSettings = new StructuresManagerView(mTool, this); 0169 structureSettings->setObjectName(QStringLiteral("kcfg_LoadedStructures")); 0170 hbox->addWidget(structureSettings); 0171 dialog->addPage(structSelectionPage, i18n("Structures management"), 0172 QStringLiteral("preferences-plugin")); 0173 0174 // User edited the configuration - update your local copies of the configuration data 0175 connect(dialog, &KConfigDialog::settingsChanged, mTool, &StructuresTool::setEnabledStructuresInView); 0176 0177 // TODO: kconfig_compiler signals work now, use those signals and not the generic KConfigDialog::settingsChanged 0178 dialog->setCurrentPage(displ); 0179 dialog->show(); 0180 } 0181 0182 bool StructureView::eventFilter(QObject* object, QEvent* event) 0183 { 0184 if (object == mStructTreeView) { 0185 if (event->type() == QEvent::FocusIn) { 0186 const QModelIndex current = mStructTreeView->selectionModel()->currentIndex(); 0187 0188 if (current.isValid()) { 0189 mTool->mark(current); 0190 } else { 0191 mTool->unmark(); 0192 } 0193 } else if (event->type() == QEvent::FocusOut) { 0194 QWidget* treeViewFocusWidget = mStructTreeView->focusWidget(); 0195 const bool subChildHasFocus = (treeViewFocusWidget != mStructTreeView); 0196 if (subChildHasFocus) { 0197 mStructTreeViewFocusChild = treeViewFocusWidget; 0198 mStructTreeViewFocusChild->installEventFilter(this); 0199 } else { 0200 mTool->unmark(); 0201 } 0202 } 0203 } else if (object == mStructTreeViewFocusChild) { 0204 // TODO: it is only assumed the edit widget will be removed if it loses the focus 0205 if (event->type() == QEvent::FocusOut) { 0206 if (!mStructTreeView->hasFocus()) { 0207 mTool->unmark(); 0208 } 0209 mStructTreeViewFocusChild->removeEventFilter(this); 0210 mStructTreeViewFocusChild = nullptr; 0211 } 0212 } 0213 0214 return QWidget::eventFilter(object, event); 0215 } 0216 0217 void StructureView::setLockButtonState(const QModelIndex& current) 0218 { 0219 // qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "setLockButtonState() for" << current; 0220 0221 mLockStructureAction->setEnabled(mTool->canStructureBeLocked(current)); 0222 mLockStructureAction->setChecked(mTool->isStructureLocked(current)); 0223 } 0224 0225 void StructureView::onCurrentRowChanged(const QModelIndex& current, const QModelIndex& previous) 0226 { 0227 Q_UNUSED(previous) 0228 if (current.isValid() && mTool->byteArrayModel()) { 0229 mTool->mark(current); 0230 } else { 0231 mTool->unmark(); 0232 } 0233 setLockButtonState(current); 0234 } 0235 0236 void StructureView::onLockButtonClicked(bool checked) 0237 { 0238 // qCDebug(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "Lock button toggled"; 0239 0240 const QModelIndex current = mStructTreeView->selectionModel()->currentIndex(); 0241 if (!current.isValid()) { 0242 qCWarning(LOG_KASTEN_OKTETA_CONTROLLERS_STRUCTURES) << "it should not be possible to toggle this button when current index is invalid!"; 0243 return; 0244 } 0245 0246 if (checked) { 0247 mTool->lockStructure(current); 0248 } else { 0249 mTool->unlockStructure(current); 0250 } 0251 } 0252 0253 void StructureView::onLockButtonToggled(bool structureLocked) 0254 { 0255 if (structureLocked) { 0256 mLockStructureAction->setIcon(QIcon::fromTheme(QStringLiteral("object-locked"))); 0257 mLockStructureAction->setText(i18nc("@action:button" 0258 " unlock the starting offset of the current structure", "Unlock")); 0259 mLockStructureAction->setToolTip(i18nc("@info:tooltip", 0260 "Unlock selected structure, i.e. the starting offset is" 0261 " always set to the current cursor position.")); 0262 } else { 0263 mLockStructureAction->setIcon(QIcon::fromTheme(QStringLiteral("object-unlocked"))); 0264 mLockStructureAction->setText(i18nc("@action:button" 0265 " unlock the starting offset of the current structure", "Lock")); 0266 mLockStructureAction->setToolTip(i18nc("@info:tooltip", 0267 "Lock selected structure to current offset.")); 0268 } 0269 } 0270 0271 void StructureView::editData() 0272 { 0273 auto* action = static_cast<QAction*>(sender()); 0274 const QModelIndex index = action->data().value<QModelIndex>(); 0275 0276 mStructTreeView->setCurrentIndex(index); 0277 mStructTreeView->edit(index); 0278 } 0279 0280 void StructureView::selectBytesInView() 0281 { 0282 auto* action = static_cast<QAction*>(sender()); 0283 const QModelIndex index = action->data().value<QModelIndex>(); 0284 0285 mTool->selectBytesInView(index); 0286 } 0287 0288 void StructureView::copyToClipboard() 0289 { 0290 auto* action = static_cast<QAction*>(sender()); 0291 const QModelIndex index = action->data().value<QModelIndex>(); 0292 QMimeData* mimeData = mStructTreeView->model()->mimeData({index}); 0293 0294 QApplication::clipboard()->setMimeData(mimeData); 0295 } 0296 0297 void StructureView::openScriptConsole() 0298 { 0299 auto* dialog = new QDialog(this); 0300 dialog->setWindowTitle(i18nc("@title:window", "Structures Script Console")); 0301 auto* layout = new QVBoxLayout; 0302 auto* dialogButtonBox = new QDialogButtonBox; 0303 QPushButton* closeButton = dialogButtonBox->addButton(QDialogButtonBox::Close); 0304 connect(closeButton, &QPushButton::clicked, dialog, &QDialog::accept); 0305 layout->addWidget(new ScriptLoggerView(mTool->allData())); 0306 layout->addWidget(dialogButtonBox); 0307 dialog->setLayout(layout); 0308 dialog->show(); 0309 } 0310 0311 void StructureView::onByteArrayModelChanged(Okteta::AbstractByteArrayModel* model) 0312 { 0313 const bool validModel = (model != nullptr); 0314 const QModelIndex current = mStructTreeView->currentIndex(); 0315 setLockButtonState(current); 0316 mValidateAction->setEnabled(validModel); 0317 } 0318 0319 void StructureView::onCustomContextMenuRequested(QPoint pos) 0320 { 0321 const QModelIndex index = mStructTreeView->indexAt(pos); 0322 if (!index.isValid()) { 0323 return; 0324 } 0325 const auto* data = index.data(StructureTreeModel::DataInformationRole).value<DataInformation*>(); 0326 if (!data) { 0327 return; 0328 } 0329 if (!data->wasAbleToRead()) { 0330 return; 0331 } 0332 0333 auto* menu = new QMenu(this); 0334 0335 const QModelIndex valueIndex = index.siblingAtColumn(DataInformation::ColumnValue); 0336 if (valueIndex.flags() & Qt::ItemIsEditable) { 0337 auto* editAction = new QAction(QIcon::fromTheme(QStringLiteral("document-edit")), 0338 i18nc("@action:inmenu", "Edit"), this); 0339 connect(editAction, &QAction::triggered, 0340 this, &StructureView::editData); 0341 editAction->setData(valueIndex); 0342 menu->addAction(editAction); 0343 } 0344 0345 // TODO: split into explicit "Copy As Data" and "Copy As Text" 0346 auto* copyAction = KStandardAction::copy(this, &StructureView::copyToClipboard, this); 0347 copyAction->setShortcut(QKeySequence()); 0348 copyAction->setData(index); 0349 menu->addAction(copyAction); 0350 0351 // TODO: reusing string due to string freeze 0352 auto* selectAction = new QAction(QIcon::fromTheme(QStringLiteral("select-rectangular")), 0353 i18nc("@action:button", "&Select"), this); 0354 connect(selectAction, &QAction::triggered, 0355 this, &StructureView::selectBytesInView); 0356 selectAction->setData(index); 0357 menu->addAction(selectAction); 0358 0359 menu->popup(mStructTreeView->viewport()->mapToGlobal(pos)); 0360 } 0361 0362 } 0363 0364 #include "moc_structureview.cpp"