1 /*
2     This file is part of the Okteta Kasten module, made within the KDE community.
3 
4     SPDX-FileCopyrightText: 2006-2009 Friedrich W. H. Kossebau <kossebau@kde.org>
5 
6     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7 */
8 
9 #include "podtableview.hpp"
10 
11 // controller
12 #include "podtablemodel.hpp"
13 #include "poddelegate.hpp"
14 #include "poddecodertool.hpp"
15 // KF
16 #include <KComboBox>
17 #include <KLocalizedString>
18 #include <KMessageBox>
19 // Qt
20 #include <QLabel>
21 #include <QLayout>
22 #include <QCheckBox>
23 #include <QTreeView>
24 #include <QHeaderView>
25 #include <QFocusEvent>
26 #include <QFontMetrics>
27 
28 namespace Kasten {
29 
PODTableView(PODDecoderTool * tool,QWidget * parent)30 PODTableView::PODTableView(PODDecoderTool* tool, QWidget* parent)
31     : QWidget(parent)
32     , mTool(tool)
33 {
34     QBoxLayout* baseLayout = new QVBoxLayout(this);
35     baseLayout->setContentsMargins(0, 0, 0, 0);
36 
37     // table
38     mPODTableModel = new PODTableModel(mTool, this);
39     mPODTableView = new QTreeView(this);
40     mPODTableView->setObjectName(QStringLiteral("PODTable"));
41     mPODTableView->setRootIsDecorated(false);
42     mPODTableView->setAlternatingRowColors(true);
43     mPODTableView->setItemsExpandable(false);
44     mPODTableView->setUniformRowHeights(true);
45     mPODTableView->setAllColumnsShowFocus(true);
46     mPODTableView->setItemDelegate(new PODDelegate(mTool, this));
47     mPODTableView->setEditTriggers(QAbstractItemView::EditKeyPressed | QAbstractItemView::DoubleClicked);
48     mPODTableView->setDragEnabled(true);
49     mPODTableView->setSortingEnabled(false);
50     mPODTableView->setModel(mPODTableModel);
51     mPODTableView->installEventFilter(this);
52     QHeaderView* header = mPODTableView->header();
53     header->setSectionResizeMode(QHeaderView::Interactive);
54     header->setStretchLastSection(false);
55     connect(mPODTableView->selectionModel(),
56             &QItemSelectionModel::currentRowChanged,
57             this, &PODTableView::onCurrentRowChanged);
58 
59     baseLayout->addWidget(mPODTableView, 10);
60 
61     // settings
62     QBoxLayout* settingsLayout = new QHBoxLayout();
63     settingsLayout->setContentsMargins(0, 0, 0, 0);
64 
65     mByteOrderSelection = new KComboBox(this);
66     mByteOrderSelection->addItem(i18nc("@item:inlistbox", "Big-endian"));     // add first for index
67     mByteOrderSelection->addItem(i18nc("@item:inlistbox", "Little-endian"));  // add second for index
68     mByteOrderSelection->setCurrentIndex(mTool->byteOrder());
69     connect(mByteOrderSelection, QOverload<int>::of(&KComboBox::activated),
70             mTool, &PODDecoderTool::setByteOrder);
71     const QString byteOrderToolTip =
72         i18nc("@info:tooltip",
73               "The byte order to use for decoding the bytes.");
74     mByteOrderSelection->setToolTip(byteOrderToolTip);
75     settingsLayout->addWidget(mByteOrderSelection);
76 
77     QLabel* unsignedAsHexLabel = new QLabel(i18nc("@option:check", "Unsigned as hexadecimal:"), this);
78     settingsLayout->addWidget(unsignedAsHexLabel);
79 
80     mUnsignedAsHexCheck = new QCheckBox(this);
81     mUnsignedAsHexCheck->setChecked(mTool->isUnsignedAsHex());
82     connect(mUnsignedAsHexCheck, &QCheckBox::toggled,
83             mTool, &PODDecoderTool::setUnsignedAsHex);
84     unsignedAsHexLabel->setBuddy(mUnsignedAsHexCheck);
85     const QString unsignedAsHexToolTip =
86         i18nc("@info:tooltip",
87               "Sets whether the values of the unsigned integer types are shown as hexadecimal instead of as decimal.");
88     unsignedAsHexLabel->setToolTip(unsignedAsHexToolTip);
89     mUnsignedAsHexCheck->setToolTip(unsignedAsHexToolTip);
90     settingsLayout->addWidget(mUnsignedAsHexCheck);
91     settingsLayout->addStretch();
92 
93     baseLayout->addLayout(settingsLayout);
94 
95     mTool->setDifferentSizeDialog(this);
96 
97     // resize to fit width of contents
98     // this is much (!) faster than using setResizeMode(QHeaderView::ResizeToContents)
99     QFont f;
100     QFontMetrics metrics(f);
101     // ideally we should check the width of the longest translated string, but this should be wide enough for most
102     // anyway this is just an initial setting and the width can be changed manually
103 #if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
104     header->resizeSection(0, metrics.horizontalAdvance(QStringLiteral("Hexadecimal 8-bit")) + 30);
105     header->resizeSection(1, metrics.horizontalAdvance(QStringLiteral("1.01234567890123456789e-111")) + 15);
106 #else
107     header->resizeSection(0, metrics.width(QStringLiteral("Hexadecimal 8-bit")) + 30);
108     header->resizeSection(1, metrics.width(QStringLiteral("1.01234567890123456789e-111")) + 15);
109 #endif
110 }
111 
112 PODTableView::~PODTableView() = default;
113 
query(int newValueSize,int oldValueSize,int sizeLeft)114 Answer PODTableView::query(int newValueSize, int oldValueSize, int sizeLeft)
115 {
116     Q_UNUSED(sizeLeft);
117 
118     Answer answer;
119 
120     int messageBoxAnswer;
121     if (newValueSize < oldValueSize) {
122         const QString message =
123             xi18nc("@info",
124                    "The new value needs <emphasis>fewer</emphasis> bytes (%1 instead of %2).<nl/>"
125                    "Keep the unused bytes or remove them?", newValueSize, oldValueSize);
126 
127         const KGuiItem keepGuiItem =
128             KGuiItem(i18nc("@action:button keep the unused bytes",
129                            "&Keep"),
130                      QString(),
131                      i18nc("@info:tooltip",
132                            "Keep the unused bytes with their old values."));
133 
134         messageBoxAnswer = KMessageBox::warningYesNoCancel(this, message, mTool->title(),
135                                                            keepGuiItem,
136                                                            KStandardGuiItem::remove());
137     } else {
138         const QString message =
139             xi18nc("@info",
140                    "The new value needs <emphasis>more</emphasis> bytes (%1 instead of %2).<nl/>"
141                    "Overwrite the following bytes or insert new ones as needed?", newValueSize, oldValueSize);
142 
143         messageBoxAnswer = KMessageBox::warningYesNoCancel(this, message, mTool->title(),
144                                                            KStandardGuiItem::overwrite(),
145                                                            KStandardGuiItem::insert());
146     }
147 
148     answer = (messageBoxAnswer == KMessageBox::Yes) ? Overwrite :
149              (messageBoxAnswer == KMessageBox::No) ?  AdaptSize :
150                                                       Cancel;
151     return answer;
152 }
153 
eventFilter(QObject * object,QEvent * event)154 bool PODTableView::eventFilter(QObject* object, QEvent* event)
155 {
156     if (object == mPODTableView) {
157         if (event->type() == QEvent::FocusIn) {
158             const QModelIndex current = mPODTableView->selectionModel()->currentIndex();
159             const int podId = current.row();
160             if (current.isValid() && mTool->isApplyable() && !mTool->value(podId).isNull()) {
161                 mTool->markPOD(podId);
162             }
163         } else if (event->type() == QEvent::FocusOut) {
164             QWidget* tableViewFocusWidget = mPODTableView->focusWidget();
165             const bool subChildHasFocus = (tableViewFocusWidget != mPODTableView);
166             if (subChildHasFocus) {
167                 mPODTableViewFocusChild = tableViewFocusWidget;
168                 mPODTableViewFocusChild->installEventFilter(this);
169             } else if (mTool->isApplyable()) {
170                 mTool->unmarkPOD();
171             }
172         }
173     } else if (object == mPODTableViewFocusChild) {
174         // TODO: it is only assumed the edit widget will be removed if it loses the focus
175         if (event->type() == QEvent::FocusOut) {
176             if (!mPODTableView->hasFocus() && mTool->isApplyable()) {
177                 mTool->unmarkPOD();
178             }
179             mPODTableViewFocusChild->removeEventFilter(this);
180             mPODTableViewFocusChild = nullptr;
181         }
182     }
183 
184     return QWidget::eventFilter(object, event);
185 }
186 
onCurrentRowChanged(const QModelIndex & current,const QModelIndex & previous)187 void PODTableView::onCurrentRowChanged(const QModelIndex& current, const QModelIndex& previous)
188 {
189     Q_UNUSED(previous)
190 
191     if (!mTool->isApplyable()) {
192         return;
193     }
194 
195     const int podId = current.row();
196     if (current.isValid() && !mTool->value(podId).isNull()) {
197         mTool->markPOD(podId);
198     } else {
199         mTool->unmarkPOD();
200     }
201 }
202 
203 }
204