1 #include "FlagsWidget.h"
2 #include "ui_FlagsWidget.h"
3 #include "core/MainWindow.h"
4 #include "common/Helpers.h"
5 
6 #include <QComboBox>
7 #include <QMenu>
8 #include <QShortcut>
9 #include <QTreeWidget>
10 #include <QStandardItemModel>
11 #include <QInputDialog>
12 
FlagsModel(QList<FlagDescription> * flags,QObject * parent)13 FlagsModel::FlagsModel(QList<FlagDescription> *flags, QObject *parent)
14     : AddressableItemModel<QAbstractListModel>(parent),
15       flags(flags)
16 {
17 }
18 
rowCount(const QModelIndex &) const19 int FlagsModel::rowCount(const QModelIndex &) const
20 {
21     return flags->count();
22 }
23 
columnCount(const QModelIndex &) const24 int FlagsModel::columnCount(const QModelIndex &) const
25 {
26     return Columns::COUNT;
27 }
28 
data(const QModelIndex & index,int role) const29 QVariant FlagsModel::data(const QModelIndex &index, int role) const
30 {
31     if (index.row() >= flags->count())
32         return QVariant();
33 
34     const FlagDescription &flag = flags->at(index.row());
35 
36     switch (role) {
37     case Qt::DisplayRole:
38         switch (index.column()) {
39         case SIZE:
40             return RSizeString(flag.size);
41         case OFFSET:
42             return RAddressString(flag.offset);
43         case NAME:
44             return flag.name;
45         case REALNAME:
46             return flag.realname;
47         case COMMENT:
48             return Core()->getCommentAt(flag.offset);
49         default:
50             return QVariant();
51         }
52     case FlagDescriptionRole:
53         return QVariant::fromValue(flag);
54     default:
55         return QVariant();
56     }
57 }
58 
headerData(int section,Qt::Orientation,int role) const59 QVariant FlagsModel::headerData(int section, Qt::Orientation, int role) const
60 {
61     switch (role) {
62     case Qt::DisplayRole:
63         switch (section) {
64         case SIZE:
65             return tr("Size");
66         case OFFSET:
67             return tr("Offset");
68         case NAME:
69             return tr("Name");
70         case REALNAME:
71             return tr("Real Name");
72         case COMMENT:
73             return tr("Comment");
74         default:
75             return QVariant();
76         }
77     default:
78         return QVariant();
79     }
80 }
81 
address(const QModelIndex & index) const82 RVA FlagsModel::address(const QModelIndex &index) const
83 {
84     const FlagDescription &flag = flags->at(index.row());
85     return flag.offset;
86 }
87 
name(const QModelIndex & index) const88 QString FlagsModel::name(const QModelIndex &index) const
89 {
90     const FlagDescription &flag = flags->at(index.row());
91     return flag.name;
92 }
93 
description(QModelIndex index) const94 const FlagDescription *FlagsModel::description(QModelIndex index) const
95 {
96     if (index.row() < flags->size()) {
97         return &flags->at(index.row());
98     }
99     return nullptr;
100 }
101 
FlagsSortFilterProxyModel(FlagsModel * source_model,QObject * parent)102 FlagsSortFilterProxyModel::FlagsSortFilterProxyModel(FlagsModel *source_model, QObject *parent)
103     : AddressableFilterProxyModel(source_model, parent)
104 {
105 }
106 
filterAcceptsRow(int row,const QModelIndex & parent) const107 bool FlagsSortFilterProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const
108 {
109     QModelIndex index = sourceModel()->index(row, 0, parent);
110     FlagDescription flag = index.data(FlagsModel::FlagDescriptionRole).value<FlagDescription>();
111     return flag.name.contains(filterRegExp()) || flag.realname.contains(filterRegExp());
112 }
113 
lessThan(const QModelIndex & left,const QModelIndex & right) const114 bool FlagsSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
115 {
116     auto source = static_cast<FlagsModel *>(sourceModel());
117     auto left_flag = source->description(left);
118     auto right_flag = source->description(right);
119 
120     switch (left.column()) {
121     case FlagsModel::SIZE:
122         if (left_flag->size != right_flag->size)
123             return left_flag->size < right_flag->size;
124     // fallthrough
125     case FlagsModel::OFFSET:
126         if (left_flag->offset != right_flag->offset)
127             return left_flag->offset < right_flag->offset;
128     // fallthrough
129     case FlagsModel::NAME:
130         return left_flag->name < right_flag->name;
131 
132     case FlagsModel::REALNAME:
133         return left_flag->realname < right_flag->realname;
134 
135     case FlagsModel::COMMENT:
136         return Core()->getCommentAt(left_flag->offset) < Core()->getCommentAt(right_flag->offset);
137 
138     default:
139         break;
140     }
141 
142     // fallback
143     return left_flag->offset < right_flag->offset;
144 }
145 
146 
FlagsWidget(MainWindow * main)147 FlagsWidget::FlagsWidget(MainWindow *main) :
148     CutterDockWidget(main),
149     ui(new Ui::FlagsWidget),
150     main(main),
151     tree(new CutterTreeWidget(this))
152 {
153     ui->setupUi(this);
154 
155     // Add Status Bar footer
156     tree->addStatusBar(ui->verticalLayout);
157 
158     flags_model = new FlagsModel(&flags, this);
159     flags_proxy_model = new FlagsSortFilterProxyModel(flags_model, this);
160     connect(ui->filterLineEdit, &QLineEdit::textChanged,
161             flags_proxy_model, &QSortFilterProxyModel::setFilterWildcard);
162     ui->flagsTreeView->setMainWindow(mainWindow);
163     ui->flagsTreeView->setModel(flags_proxy_model);
164     ui->flagsTreeView->sortByColumn(FlagsModel::OFFSET, Qt::AscendingOrder);
165 
166     // Ctrl-F to move the focus to the Filter search box
167     QShortcut *searchShortcut = new QShortcut(QKeySequence::Find, this);
168     connect(searchShortcut, &QShortcut::activated, ui->filterLineEdit, [this]() { ui->filterLineEdit->setFocus(); });
169     searchShortcut->setContext(Qt::WidgetWithChildrenShortcut);
170 
171     // Esc to clear the filter entry
172     QShortcut *clearShortcut = new QShortcut(QKeySequence(Qt::Key_Escape), this);
173     connect(clearShortcut, &QShortcut::activated, [this] {
174         if (ui->filterLineEdit->text().isEmpty()) {
175             ui->flagsTreeView->setFocus();
176         } else {
177             ui->filterLineEdit->setText("");
178         }
179     });
180     clearShortcut->setContext(Qt::WidgetWithChildrenShortcut);
181 
182     connect(ui->filterLineEdit, &QLineEdit::textChanged, this, [this] {
183         tree->showItemsNumber(flags_proxy_model->rowCount());
184     });
185 
186     setScrollMode();
187 
188     connect(Core(), &CutterCore::flagsChanged, this, &FlagsWidget::flagsChanged);
189     connect(Core(), &CutterCore::codeRebased, this, &FlagsWidget::flagsChanged);
190     connect(Core(), &CutterCore::refreshAll, this, &FlagsWidget::refreshFlagspaces);
191     connect(Core(), &CutterCore::commentsChanged, this, [this]() {
192         qhelpers::emitColumnChanged(flags_model, FlagsModel::COMMENT);
193     });
194 
195     auto menu = ui->flagsTreeView->getItemContextMenu();
196     menu->addSeparator();
197     menu->addAction(ui->actionRename);
198     menu->addAction(ui->actionDelete);
199     addAction(ui->actionRename);
200     addAction(ui->actionDelete);
201 }
202 
~FlagsWidget()203 FlagsWidget::~FlagsWidget() {}
204 
on_flagspaceCombo_currentTextChanged(const QString & arg1)205 void FlagsWidget::on_flagspaceCombo_currentTextChanged(const QString &arg1)
206 {
207     Q_UNUSED(arg1);
208 
209     refreshFlags();
210 }
211 
on_actionRename_triggered()212 void FlagsWidget::on_actionRename_triggered()
213 {
214     FlagDescription flag = ui->flagsTreeView->selectionModel()->currentIndex().data(
215                                FlagsModel::FlagDescriptionRole).value<FlagDescription>();
216 
217     bool ok;
218     QString newName = QInputDialog::getText(this, tr("Rename flag %1").arg(flag.name),
219                             tr("Flag name:"), QLineEdit::Normal, flag.name, &ok);
220     if (ok && !newName.isEmpty()) {
221         Core()->renameFlag(flag.name, newName);
222     }
223 }
224 
on_actionDelete_triggered()225 void FlagsWidget::on_actionDelete_triggered()
226 {
227     FlagDescription flag = ui->flagsTreeView->selectionModel()->currentIndex().data(
228                                FlagsModel::FlagDescriptionRole).value<FlagDescription>();
229     Core()->delFlag(flag.name);
230 }
231 
flagsChanged()232 void FlagsWidget::flagsChanged()
233 {
234     refreshFlagspaces();
235 }
236 
refreshFlagspaces()237 void FlagsWidget::refreshFlagspaces()
238 {
239     int cur_idx = ui->flagspaceCombo->currentIndex();
240     if (cur_idx < 0)
241         cur_idx = 0;
242 
243     disableFlagRefresh = true; // prevent duplicate flag refresh caused by flagspaceCombo modifications
244     ui->flagspaceCombo->clear();
245     ui->flagspaceCombo->addItem(tr("(all)"));
246 
247     for (const FlagspaceDescription &i : Core()->getAllFlagspaces()) {
248         ui->flagspaceCombo->addItem(i.name, QVariant::fromValue(i));
249     }
250 
251     if (cur_idx > 0)
252         ui->flagspaceCombo->setCurrentIndex(cur_idx);
253     disableFlagRefresh = false;
254 
255     refreshFlags();
256 }
257 
refreshFlags()258 void FlagsWidget::refreshFlags()
259 {
260     if (disableFlagRefresh) {
261         return;
262     }
263     QString flagspace;
264 
265     QVariant flagspace_data = ui->flagspaceCombo->currentData();
266     if (flagspace_data.isValid())
267         flagspace = flagspace_data.value<FlagspaceDescription>().name;
268 
269 
270     flags_model->beginResetModel();
271     flags = Core()->getAllFlags(flagspace);
272     flags_model->endResetModel();
273 
274     tree->showItemsNumber(flags_proxy_model->rowCount());
275 
276     // TODO: this is not a very good place for the following:
277     QStringList flagNames;
278     for (const FlagDescription &i : flags)
279         flagNames.append(i.name);
280     main->refreshOmniBar(flagNames);
281 }
282 
setScrollMode()283 void FlagsWidget::setScrollMode()
284 {
285     qhelpers::setVerticalScrollMode(ui->flagsTreeView);
286 }
287