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"