1 | // SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. |
2 | // |
3 | // SPDX-License-Identifier: GPL-3.0-or-later |
4 | |
5 | #include "taskwindow.h" |
6 | #include "taskmodel.h" |
7 | #include "taskfiltermodel.h" |
8 | #include "timelinewidget.h" |
9 | |
10 | #include <QDir> |
11 | #include <QPainter> |
12 | #include <QStyledItemDelegate> |
13 | #include <QMenu> |
14 | #include <QLabel> |
15 | #include <QLineEdit> |
16 | #include <QToolButton> |
17 | #include <QScrollBar> |
18 | #include <QVBoxLayout> |
19 | #include <QPushButton> |
20 | #include <QComboBox> |
21 | #include <QListView> |
22 | #include <QTextLayout> |
23 | #include <QTextLine> |
24 | #include <QtDebug> |
25 | |
26 | namespace { |
27 | const int ELLIPSIS_GRADIENT_WIDTH = 16; |
28 | } |
29 | |
30 | namespace ReverseDebugger { |
31 | namespace Internal { |
32 | |
33 | class TaskView : public QListView |
34 | { |
35 | public: |
36 | TaskView(QWidget *parent = nullptr); |
37 | ~TaskView(); |
38 | void resizeEvent(QResizeEvent *e); |
39 | void contextMenuEvent(QContextMenuEvent*); |
40 | private: |
41 | }; |
42 | |
43 | class TaskWidget : public QWidget |
44 | { |
45 | // Q_OBJECT |
46 | public: |
47 | TaskWidget(QWidget* parent = nullptr) |
48 | : QWidget(parent) |
49 | { |
50 | } |
51 | ~TaskWidget() |
52 | { |
53 | } |
54 | |
55 | void setup(TimelineWidget *timeline, TaskView *listview) |
56 | { |
57 | QVBoxLayout *layout = new QVBoxLayout(this); |
58 | layout->setMargin(0); |
59 | layout->setSpacing(0); |
60 | setLayout(layout); |
61 | layout->addWidget(timeline); |
62 | layout->addWidget(listview); |
63 | } |
64 | }; |
65 | |
66 | class TaskDelegate : public QStyledItemDelegate |
67 | { |
68 | Q_OBJECT |
69 | |
70 | friend class TaskView; // for using Positions::minimumSize() |
71 | |
72 | public: |
73 | TaskDelegate(QObject * parent = nullptr); |
74 | ~TaskDelegate(); |
75 | void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; |
76 | QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; |
77 | |
78 | // TaskView uses this method if the size of the taskview changes |
79 | void emitSizeHintChanged(const QModelIndex &index); |
80 | |
81 | public slots: |
82 | void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); |
83 | |
84 | private: |
85 | void generateGradientPixmap(int width, int height, QColor color, bool selected) const; |
86 | |
87 | mutable int m_cachedHeight; |
88 | mutable QFont m_cachedFont; |
89 | |
90 | /* |
91 | Collapsed: |
92 | +----------------------------------------------------------------------------------------------------+ |
93 | | TASKICONAREA TEXTAREA TIMEAREA DURATIONAREA RETURNAREA TIDAREA THREADSAREA| |
94 | +----------------------------------------------------------------------------------------------------+ |
95 | |
96 | Expanded: |
97 | +----------------------------------------------------------------------------------------------------+ |
98 | | TASKICONICON TEXTAREA TIMEAREA DURATIONAREA RETURNAREA TIDAREA THREADSAREA| |
99 | | more text -------------------------------------------------------------------------> | |
100 | +----------------------------------------------------------------------------------------------------+ |
101 | */ |
102 | class Positions |
103 | { |
104 | public: |
105 | Positions(const QStyleOptionViewItem &options, TaskModel *model) : |
106 | totalWidth(options.rect.width()), |
107 | maxLineLength(model->getSizeOfLineNumber(options.font)), |
108 | iTop(options.rect.top()), |
109 | iBottom(options.rect.bottom()) |
110 | { |
111 | fontHeight = QFontMetrics(options.font).height(); |
112 | } |
113 | |
114 | int top() const { return iTop + ITEM_MARGIN; } |
115 | int left() const { return ITEM_MARGIN; } |
116 | int right() const { return totalWidth - ITEM_MARGIN; } |
117 | int bottom() const { return iBottom; } |
118 | int firstLineHeight() const { return fontHeight + 1; } |
119 | static int minimumHeight() { return taskIconHeight() + 2 * ITEM_MARGIN; } |
120 | |
121 | int taskIconLeft() const { return left(); } |
122 | static int taskIconWidth() { return TASK_ICON_SIZE; } |
123 | static int taskIconHeight() { return TASK_ICON_SIZE; } |
124 | int taskIconRight() const { return taskIconLeft() + taskIconWidth(); } |
125 | QRect taskIcon() const { return QRect(taskIconLeft(), top(), taskIconWidth(), taskIconHeight()); } |
126 | |
127 | int textAreaLeft() const { return taskIconRight() + ITEM_SPACING; } |
128 | int textAreaWidth() const { return textAreaRight() - textAreaLeft(); } |
129 | int textAreaRight() const { return timeAreaLeft() - ITEM_SPACING; } |
130 | QRect textArea() const { return QRect(textAreaLeft(), top(), textAreaWidth(), firstLineHeight()); } |
131 | |
132 | int timeAreaLeft() const { return timeAreaRight() - timeAreaWidth(); } |
133 | int timeAreaWidth() const { return maxLineLength*5; } |
134 | int timeAreaRight() const { return durationAreaLeft() - ITEM_SPACING; } |
135 | QRect timeArea() const { return QRect(timeAreaLeft(), top(), timeAreaWidth(), firstLineHeight()); } |
136 | |
137 | int durationAreaLeft() const { return durationAreaRight() - durationAreaWidth(); } |
138 | int durationAreaWidth() const { return maxLineLength*2; } |
139 | int durationAreaRight() const { return returnAreaLeft() - ITEM_SPACING; } |
140 | QRect durationArea() const { return QRect(durationAreaLeft(), top(), durationAreaWidth(), firstLineHeight()); } |
141 | |
142 | int returnAreaLeft() const { return returnAreaRight() - returnAreaWidth(); } |
143 | int returnAreaWidth() const { return maxLineLength*3; } |
144 | int returnAreaRight() const { return tidAreaLeft() - ITEM_SPACING; } |
145 | QRect returnArea() const { return QRect(returnAreaLeft(), top(), returnAreaWidth(), firstLineHeight()); } |
146 | |
147 | int tidAreaLeft() const { return tidAreaRight() - tidAreaWidth(); } |
148 | int tidAreaWidth() const { return maxLineLength; } |
149 | int tidAreaRight() const { return threadsAreaLeft() - ITEM_SPACING; } |
150 | QRect tidArea() const { return QRect(tidAreaLeft(), top(), tidAreaWidth(), firstLineHeight()); } |
151 | |
152 | int threadsAreaLeft() const { return threadsAreaRight() - threadsAreaWidth(); } |
153 | int threadsAreaWidth() const { return maxLineLength; } |
154 | int threadsAreaRight() const { return right(); } |
155 | QRect threadsArea() const { return QRect(threadsAreaLeft(), top(), threadsAreaWidth(), firstLineHeight()); } |
156 | |
157 | private: |
158 | int totalWidth = 0; |
159 | int maxLineLength = 0; |
160 | int iTop = 0; |
161 | int iBottom = 0; |
162 | int fontHeight = 0; |
163 | |
164 | static const int TASK_ICON_SIZE = 16; |
165 | static const int ITEM_MARGIN = 2; |
166 | static const int ITEM_SPACING = 2 * ITEM_MARGIN; |
167 | }; |
168 | }; |
169 | |
170 | TaskView::TaskView(QWidget *parent) |
171 | : QListView(parent) |
172 | { |
173 | setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
174 | setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); |
175 | |
176 | QFontMetrics fm(font()); |
177 | int vStepSize = fm.height() + 3; |
178 | if (vStepSize < TaskDelegate::Positions::minimumHeight()) |
179 | vStepSize = TaskDelegate::Positions::minimumHeight(); |
180 | |
181 | verticalScrollBar()->setSingleStep(vStepSize); |
182 | } |
183 | |
184 | TaskView::~TaskView() |
185 | { } |
186 | |
187 | void TaskView::resizeEvent(QResizeEvent *e) |
188 | { |
189 | Q_UNUSED(e) |
190 | static_cast<TaskDelegate *>(itemDelegate())->emitSizeHintChanged(selectionModel()->currentIndex()); |
191 | } |
192 | |
193 | void TaskView::(QContextMenuEvent* event) |
194 | { |
195 | // disable right context-menu |
196 | Q_UNUSED(event) |
197 | } |
198 | |
199 | ///// |
200 | // TaskWindow |
201 | ///// |
202 | |
203 | #define TIMELINE_WIDGET_HEIGHT 60 |
204 | #define STR_CURRENT_EVENT tr(" Current Event [") |
205 | |
206 | class TaskWindowPrivate |
207 | { |
208 | public: |
209 | Internal::TaskModel *taskModel = nullptr; |
210 | Internal::TaskFilterModel *filter = nullptr; |
211 | Internal::TaskView *listview = nullptr; |
212 | Internal::TimelineWidget *timeline = nullptr; |
213 | Internal::TaskWidget *widget = nullptr; |
214 | QMenu * = nullptr; |
215 | QToolButton *categoriesButton = nullptr; |
216 | QLabel* eventLabel = nullptr; |
217 | QLineEdit* commandLine = nullptr; |
218 | QComboBox* sortCombo = nullptr; |
219 | QPushButton* zoomIn = nullptr; |
220 | QPushButton* zoomOut = nullptr; |
221 | QPushButton* zoomFit = nullptr; |
222 | QPushButton* backward = nullptr; |
223 | QPushButton* forward = nullptr; |
224 | QMenu * = nullptr; |
225 | QList<QAction *> actions; |
226 | int currentEvent = -1; |
227 | }; |
228 | |
229 | TaskWindow::TaskWindow() |
230 | : d(new TaskWindowPrivate) |
231 | { |
232 | setupUi(); |
233 | } |
234 | |
235 | TaskWindow::~TaskWindow() |
236 | { |
237 | delete d->widget; |
238 | delete d->filter; |
239 | delete d->taskModel; |
240 | delete d; |
241 | } |
242 | |
243 | void TaskWindow::delayedInitialization() |
244 | { |
245 | } |
246 | |
247 | int TaskWindow::taskCount(const QString &category) const |
248 | { |
249 | return d->taskModel->taskCount(category); |
250 | } |
251 | |
252 | QWidget *TaskWindow::outputWidget() const |
253 | { |
254 | return d->widget; |
255 | } |
256 | |
257 | QList<QWidget *> TaskWindow::toolBarWidgets() const |
258 | { |
259 | return QList<QWidget*>() << d->categoriesButton |
260 | << d->zoomIn |
261 | << d->zoomOut |
262 | << d->zoomFit |
263 | << d->backward |
264 | << d->forward |
265 | << d->sortCombo |
266 | << d->commandLine |
267 | << d->eventLabel; |
268 | } |
269 | |
270 | int TaskWindow::priorityInStatusBar() const |
271 | { |
272 | return 90; |
273 | } |
274 | |
275 | void TaskWindow::clearContents() |
276 | { |
277 | // clear all tasks in all displays |
278 | // Yeah we are that special |
279 | d->taskModel->clearTasks(); |
280 | d->eventLabel->setText(STR_CURRENT_EVENT + QLatin1String("...]" )); |
281 | emit tasksCleared(); |
282 | } |
283 | |
284 | void TaskWindow::visibilityChanged(bool visible) |
285 | { |
286 | if (visible) |
287 | delayedInitialization(); |
288 | } |
289 | |
290 | bool TaskWindow::canFocus() const |
291 | { |
292 | return d->filter->rowCount(); |
293 | } |
294 | |
295 | bool TaskWindow::hasFocus() const |
296 | { |
297 | return d->listview->window()->focusWidget() == d->listview; |
298 | } |
299 | |
300 | void TaskWindow::setFocus() |
301 | { |
302 | if (d->filter->rowCount()) { |
303 | d->listview->setFocus(); |
304 | if (d->listview->currentIndex() == QModelIndex()) |
305 | d->listview->setCurrentIndex(d->filter->index(0,0, QModelIndex())); |
306 | } |
307 | } |
308 | |
309 | bool TaskWindow::canNavigate() const |
310 | { |
311 | return true; |
312 | } |
313 | |
314 | bool TaskWindow::canNext() const |
315 | { |
316 | return d->filter->rowCount(); |
317 | } |
318 | |
319 | bool TaskWindow::canPrevious() const |
320 | { |
321 | return d->filter->rowCount(); |
322 | } |
323 | |
324 | void TaskWindow::goToNext() |
325 | { |
326 | if (d->currentEvent + 1 < d->taskModel->rowCount()) |
327 | goTo(d->currentEvent + 1); |
328 | } |
329 | |
330 | void TaskWindow::goToPrev() |
331 | { |
332 | if (d->currentEvent > 0) goTo(d->currentEvent - 1); |
333 | } |
334 | |
335 | void TaskWindow::goTo(int index) |
336 | { |
337 | Task task = d->taskModel->task(index); |
338 | d->eventLabel->setText( STR_CURRENT_EVENT + |
339 | task.description + QLatin1Char(']')); |
340 | d->currentEvent = index; |
341 | emit coredumpChanged(index); |
342 | } |
343 | |
344 | void TaskWindow::addTask(const Task &task) |
345 | { |
346 | d->taskModel->addTask(task); |
347 | |
348 | emit tasksChanged(); |
349 | navigateStateChanged(); |
350 | } |
351 | |
352 | void TaskWindow::removeTask(const Task &task) |
353 | { |
354 | d->taskModel->removeTask(task); |
355 | |
356 | emit tasksChanged(); |
357 | navigateStateChanged(); |
358 | } |
359 | |
360 | void TaskWindow::addCategory(const QString &categoryId, const QString &displayName, bool visible) |
361 | { |
362 | d->taskModel->addCategory(categoryId, displayName); |
363 | if (!visible) { |
364 | QList<QString> filters = d->filter->filteredCategories(); |
365 | filters += categoryId; |
366 | d->filter->setFilteredCategories(filters); |
367 | d->timeline->setFilteredCategories(filters); |
368 | } |
369 | } |
370 | |
371 | void TaskWindow::updateTimeline(void *timeline, int count) |
372 | { |
373 | d->timeline->setData(this, timeline, count); |
374 | |
375 | if (!timeline || !count) { |
376 | d->taskModel->clearTasks(); |
377 | d->eventLabel->setText(STR_CURRENT_EVENT + QLatin1String("...]" )); |
378 | } |
379 | } |
380 | |
381 | static bool get_range(const QString& range, int* begin, int* end) |
382 | { |
383 | bool ok; |
384 | int pos = range.indexOf(QLatin1Char(',')); |
385 | if (pos > 0) { |
386 | *begin = range.left(pos).toInt(&ok, 10); |
387 | if (!ok) return false; |
388 | *end = range.mid(pos+1).toInt(&ok, 10); |
389 | if (!ok) return false; |
390 | } |
391 | else { |
392 | *begin = range.toInt(&ok, 10); |
393 | if (!ok) return false; |
394 | *end = *begin; |
395 | } |
396 | |
397 | return (*begin >= 0 && *begin <= *end); |
398 | } |
399 | |
400 | void TaskWindow::execCommand(void) |
401 | { |
402 | int begin = -1, end = -1; |
403 | QString cmd = d->commandLine->text(); |
404 | |
405 | if (0 == cmd.indexOf(QLatin1String("sys " ))) { |
406 | if (get_range(cmd.mid(4), &begin, &end)) { |
407 | d->filter->setEventRange(begin, end); |
408 | d->timeline->setEventRange(begin, end); |
409 | } |
410 | } |
411 | else if (0 == cmd.indexOf(QLatin1String("sig " ))) { |
412 | if (get_range(cmd.mid(4), &begin, &end)) { |
413 | d->filter->setEventRange( |
414 | begin + DUMP_REASON_signal, |
415 | end + DUMP_REASON_signal); |
416 | d->timeline->setEventRange( |
417 | begin + DUMP_REASON_signal, |
418 | end + DUMP_REASON_signal); |
419 | } |
420 | } |
421 | else if (0 == cmd.indexOf(QLatin1String("x11 " ))) { |
422 | if (get_range(cmd.mid(4), &begin, &end)) { |
423 | d->filter->setEventRange( |
424 | begin + DUMP_REASON_x11, |
425 | end + DUMP_REASON_x11); |
426 | d->timeline->setEventRange( |
427 | begin + DUMP_REASON_x11, |
428 | end + DUMP_REASON_x11); |
429 | } |
430 | } |
431 | else if (0 == cmd.indexOf(QLatin1String("dbus " ))) { |
432 | if (get_range(cmd.mid(5), &begin, &end)) { |
433 | d->filter->setEventRange( |
434 | begin + DUMP_REASON_dbus, |
435 | end + DUMP_REASON_dbus); |
436 | d->timeline->setEventRange( |
437 | begin + DUMP_REASON_dbus, |
438 | end + DUMP_REASON_dbus); |
439 | } |
440 | } |
441 | else if (0 == cmd.indexOf(QLatin1String("list " ))) { |
442 | if (get_range(cmd.mid(5), &begin, &end)) { |
443 | d->filter->setEventIndexRange(begin, end); |
444 | d->timeline->setEventIndexRange(begin, end); |
445 | } |
446 | } |
447 | else if (0 == cmd.indexOf(QLatin1String("tid " ))) { |
448 | if (get_range(cmd.mid(4), &begin, &end)) { |
449 | d->filter->setEventTid(begin); |
450 | d->timeline->setEventTid(begin); |
451 | } |
452 | } |
453 | else if (cmd.isEmpty()) { |
454 | d->filter->setEventIndexRange(-1, -1); |
455 | d->timeline->setEventIndexRange(-1, -1); |
456 | } |
457 | } |
458 | |
459 | void TaskWindow::sortEvent(int index) |
460 | { |
461 | d->filter->setSortType(index); |
462 | } |
463 | |
464 | void TaskWindow::setupUi() |
465 | { |
466 | d->taskModel = new Internal::TaskModel(this); |
467 | d->filter = new Internal::TaskFilterModel(d->taskModel); |
468 | d->listview = new Internal::TaskView; |
469 | |
470 | d->listview->setModel(d->filter); |
471 | d->listview->setFrameStyle(QFrame::NoFrame); |
472 | d->listview->setWindowTitle(tr("Events list" )); |
473 | d->listview->setSelectionMode(QAbstractItemView::SingleSelection); |
474 | Internal::TaskDelegate *tld = new Internal::TaskDelegate(this); |
475 | d->listview->setItemDelegate(tld); |
476 | d->listview->setContextMenuPolicy(Qt::DefaultContextMenu); |
477 | d->listview->setAttribute(Qt::WA_MacShowFocusRect, false); |
478 | |
479 | connect(d->listview->selectionModel(), &QItemSelectionModel::currentChanged, |
480 | tld, &TaskDelegate::currentChanged); |
481 | |
482 | connect(d->listview->selectionModel(), &QItemSelectionModel::currentChanged, |
483 | this, &TaskWindow::currentChanged); |
484 | connect(d->listview, &QAbstractItemView::activated, |
485 | this, &TaskWindow::triggerDefaultHandler); |
486 | connect(d->listview, &QAbstractItemView::clicked, |
487 | this, &TaskWindow::clickItem); |
488 | |
489 | d->contextMenu = new QMenu(d->listview); |
490 | |
491 | d->widget = new TaskWidget(); |
492 | d->listview->setParent(d->widget); |
493 | d->timeline = new TimelineWidget(d->widget); |
494 | d->timeline->setMinimumHeight(TIMELINE_WIDGET_HEIGHT); |
495 | d->widget->setup(d->timeline, d->listview); |
496 | d->taskModel->setTimelinePtr(d->timeline); // mozart added. |
497 | |
498 | d->sortCombo = new QComboBox(); |
499 | d->sortCombo->addItem(tr("sort by index" )); |
500 | d->sortCombo->addItem(tr("sort by duration" )); |
501 | d->sortCombo->addItem(tr("sort by result" )); |
502 | d->sortCombo->addItem(tr("sort by number of threads" )); |
503 | connect(d->sortCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(sortEvent(int))); |
504 | |
505 | d->commandLine = new QLineEdit(); |
506 | d->commandLine->setPlaceholderText("[sys sig x11] begin[,end]" ); |
507 | connect(d->commandLine, SIGNAL(returnPressed()), this, SLOT(execCommand())); |
508 | |
509 | d->zoomIn = new QPushButton(tr("zoom in" )); |
510 | connect(d->zoomIn, &QAbstractButton::clicked, d->timeline, &TimelineWidget::zoomIn); |
511 | |
512 | d->zoomOut = new QPushButton(tr("zoom out" )); |
513 | connect(d->zoomOut, &QAbstractButton::clicked, d->timeline, &TimelineWidget::zoomOut); |
514 | |
515 | d->zoomFit = new QPushButton(tr("zoom fit" )); |
516 | connect(d->zoomFit, &QAbstractButton::clicked, d->timeline, &TimelineWidget::zoomFit); |
517 | |
518 | d->zoomIn->setFlat(true); |
519 | d->zoomOut->setFlat(true); |
520 | d->zoomFit->setFlat(true); |
521 | |
522 | d->backward = new QPushButton(); |
523 | auto backIcon = QIcon(":/resource/images/backward_press@2x" ); |
524 | d->backward->setIcon(backIcon); |
525 | d->backward->setFlat(true); |
526 | d->backward->setToolTip(tr("Previous Item" )); |
527 | d->backward->setFixedSize(backIcon.actualSize(backIcon.availableSizes().first())); |
528 | |
529 | connect(d->backward, &QAbstractButton::clicked, this, &TaskWindow::goToNext); |
530 | |
531 | d->forward = new QPushButton(); |
532 | auto forwardIcon = QIcon(":/resource/images/forward_press@2x" ); |
533 | d->forward->setIcon(forwardIcon); |
534 | d->forward->setFlat(true); |
535 | d->forward->setToolTip(tr("Next Item" )); |
536 | d->forward->setFixedSize(backIcon.actualSize(backIcon.availableSizes().first())); |
537 | connect(d->forward, &QAbstractButton::clicked, this, &TaskWindow::goToPrev); |
538 | |
539 | d->eventLabel = new QLabel(STR_CURRENT_EVENT + QLatin1String("...]" )); |
540 | d->categoriesButton = new QToolButton; |
541 | d->categoriesButton->setIcon(QIcon(":/resource/images/filter_normal" )); |
542 | d->categoriesButton->setToolTip(tr("Filter by categories" )); |
543 | d->categoriesButton->setProperty("noArrow" , true); |
544 | d->categoriesButton->setAutoRaise(true); |
545 | d->categoriesButton->setPopupMode(QToolButton::InstantPopup); |
546 | |
547 | d->categoriesMenu = new QMenu(d->categoriesButton); |
548 | connect(d->categoriesMenu, &QMenu::aboutToShow, this, &TaskWindow::updateCategoriesMenu); |
549 | |
550 | d->categoriesButton->setMenu(d->categoriesMenu); |
551 | |
552 | connect(d->filter, &TaskFilterModel::rowsRemoved, |
553 | [this]() { emit setBadgeNumber(d->filter->rowCount()); }); |
554 | connect(d->filter, &TaskFilterModel::rowsInserted, |
555 | [this]() { emit setBadgeNumber(d->filter->rowCount()); }); |
556 | connect(d->filter, &TaskFilterModel::modelReset, |
557 | [this]() { emit setBadgeNumber(d->filter->rowCount()); }); |
558 | } |
559 | |
560 | void TaskWindow::showTask(unsigned int id) |
561 | { |
562 | int sourceRow = d->taskModel->rowForId(id); |
563 | QModelIndex sourceIdx = d->taskModel->index(sourceRow, 0); |
564 | QModelIndex filterIdx = d->filter->mapFromSource(sourceIdx); |
565 | d->listview->setCurrentIndex(filterIdx); |
566 | // popup(Core::IOutputPane::ModeSwitch); |
567 | } |
568 | |
569 | void TaskWindow::openTask(unsigned int id) |
570 | { |
571 | int sourceRow = d->taskModel->rowForId(id); |
572 | QModelIndex sourceIdx = d->taskModel->index(sourceRow, 0); |
573 | QModelIndex filterIdx = d->filter->mapFromSource(sourceIdx); |
574 | triggerDefaultHandler(filterIdx); |
575 | } |
576 | |
577 | void TaskWindow::clearTasks(const QString &categoryId) |
578 | { |
579 | d->taskModel->clearTasks(categoryId); |
580 | |
581 | emit tasksChanged(); |
582 | emit tasksCleared(); |
583 | navigateStateChanged(); |
584 | } |
585 | |
586 | void TaskWindow::setCategoryVisibility(const QString &categoryId, bool visible) |
587 | { |
588 | if (categoryId.isEmpty()) |
589 | return; |
590 | |
591 | QList<QString> categories = d->filter->filteredCategories(); |
592 | |
593 | if (visible) |
594 | categories.removeOne(categoryId); |
595 | else |
596 | categories.append(categoryId); |
597 | |
598 | d->filter->setFilteredCategories(categories); |
599 | d->timeline->setFilteredCategories(categories); |
600 | } |
601 | |
602 | void TaskWindow::currentChanged(const QModelIndex &index) |
603 | { |
604 | qDebug() << "currentChanged " << index.row(); |
605 | } |
606 | |
607 | void TaskWindow::saveSettings() |
608 | { |
609 | // do something. |
610 | } |
611 | |
612 | void TaskWindow::loadSettings() |
613 | { |
614 | // do something. |
615 | } |
616 | |
617 | void TaskWindow::triggerDefaultHandler(const QModelIndex &index) |
618 | { |
619 | bool ok; |
620 | int i = 0; |
621 | Task task = d->filter->task(index); |
622 | for (; i < task.description.size(); ++i) { |
623 | if (task.description[i] > QLatin1Char('9')) { |
624 | break; |
625 | } |
626 | } |
627 | |
628 | if (i > 0) { |
629 | i = task.description.left(i).toInt(&ok, 10); |
630 | if (ok) { |
631 | d->eventLabel->setText( STR_CURRENT_EVENT + |
632 | task.description + QLatin1Char(']')); |
633 | d->currentEvent = i; |
634 | emit coredumpChanged(i); |
635 | } |
636 | } |
637 | } |
638 | |
639 | void TaskWindow::clickItem(const QModelIndex &index) |
640 | { |
641 | qDebug() << "clickItem " << index.row(); |
642 | } |
643 | |
644 | void TaskWindow::actionTriggered() |
645 | { |
646 | // do something. |
647 | } |
648 | |
649 | void TaskWindow::() |
650 | { |
651 | typedef QMap<QString, QString>::ConstIterator NameToIdsConstIt; |
652 | |
653 | d->categoriesMenu->clear(); |
654 | |
655 | const QList<QString> filteredCategories = d->filter->filteredCategories(); |
656 | |
657 | QMap<QString, QString> nameToIds; |
658 | foreach (QString categoryId, d->taskModel->categoryIds()) |
659 | nameToIds.insert(d->taskModel->categoryDisplayName(categoryId), categoryId); |
660 | |
661 | const NameToIdsConstIt cend = nameToIds.constEnd(); |
662 | for (NameToIdsConstIt it = nameToIds.constBegin(); it != cend; ++it) { |
663 | const QString &displayName = it.key(); |
664 | const QString categoryId = it.value(); |
665 | QAction *action = new QAction(d->categoriesMenu); |
666 | action->setCheckable(true); |
667 | action->setText(displayName); |
668 | action->setChecked(!filteredCategories.contains(categoryId)); |
669 | connect(action, &QAction::triggered, this, [this, action, categoryId] { |
670 | setCategoryVisibility(categoryId, action->isChecked()); |
671 | }); |
672 | d->categoriesMenu->addAction(action); |
673 | } |
674 | } |
675 | |
676 | ///// |
677 | // Delegate |
678 | ///// |
679 | TaskDelegate::TaskDelegate(QObject *parent) : |
680 | QStyledItemDelegate(parent), |
681 | m_cachedHeight(0) |
682 | { } |
683 | |
684 | TaskDelegate::~TaskDelegate() |
685 | { |
686 | } |
687 | |
688 | QSize TaskDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const |
689 | { |
690 | QStyleOptionViewItem opt = option; |
691 | initStyleOption(&opt, index); |
692 | |
693 | const QAbstractItemView * view = qobject_cast<const QAbstractItemView *>(opt.widget); |
694 | const bool selected = (view->selectionModel()->currentIndex() == index); |
695 | QSize s; |
696 | s.setWidth(option.rect.width()); |
697 | |
698 | if (!selected && option.font == m_cachedFont && m_cachedHeight > 0) { |
699 | s.setHeight(m_cachedHeight); |
700 | return s; |
701 | } |
702 | |
703 | QFontMetrics fm(option.font); |
704 | int fontHeight = fm.height(); |
705 | int fontLeading = fm.leading(); |
706 | |
707 | TaskModel *model = static_cast<TaskFilterModel *>(view->model())->taskModel(); |
708 | Positions positions(option, model); |
709 | |
710 | if (selected) { |
711 | QString description = index.data(TaskModel::Description).toString(); |
712 | description += QLatin1Char('\n'); |
713 | description += index.data(TaskModel::ExtraInfo).toString(); |
714 | // Layout the description |
715 | int leading = fontLeading; |
716 | int height = 0; |
717 | description.replace(QLatin1Char('\n'), QChar::LineSeparator); |
718 | QTextLayout tl(description); |
719 | // TODO: tl.setAdditionalFormats(...); |
720 | tl.beginLayout(); |
721 | while (true) { |
722 | QTextLine line = tl.createLine(); |
723 | if (!line.isValid()) |
724 | break; |
725 | line.setLineWidth(positions.textAreaWidth()); |
726 | height += leading; |
727 | line.setPosition(QPoint(0, height)); |
728 | height += static_cast<int>(line.height()); |
729 | } |
730 | tl.endLayout(); |
731 | |
732 | s.setHeight(height + leading + fontHeight + 3); |
733 | } else { |
734 | s.setHeight(fontHeight + 3); |
735 | } |
736 | if (s.height() < positions.minimumHeight()) |
737 | s.setHeight(positions.minimumHeight()); |
738 | |
739 | if (!selected) { |
740 | m_cachedHeight = s.height(); |
741 | m_cachedFont = option.font; |
742 | } |
743 | |
744 | return s; |
745 | } |
746 | |
747 | void TaskDelegate::emitSizeHintChanged(const QModelIndex &index) |
748 | { |
749 | emit sizeHintChanged(index); |
750 | } |
751 | |
752 | void TaskDelegate::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) |
753 | { |
754 | emit sizeHintChanged(current); |
755 | emit sizeHintChanged(previous); |
756 | } |
757 | |
758 | void TaskDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const |
759 | { |
760 | QStyleOptionViewItem opt = option; |
761 | initStyleOption(&opt, index); |
762 | painter->save(); |
763 | |
764 | QFontMetrics fm(opt.font); |
765 | QColor backgroundColor; |
766 | QColor textColor; |
767 | |
768 | const QAbstractItemView * view = qobject_cast<const QAbstractItemView *>(opt.widget); |
769 | bool selected = view->selectionModel()->currentIndex() == index; |
770 | |
771 | if (selected) { |
772 | painter->setBrush(opt.palette.highlight().color()); |
773 | backgroundColor = opt.palette.highlight().color(); |
774 | } else { |
775 | painter->setBrush(opt.palette.window().color()); |
776 | backgroundColor = opt.palette.window().color(); |
777 | } |
778 | painter->setPen(Qt::NoPen); |
779 | painter->drawRect(opt.rect); |
780 | |
781 | // Set Text Color |
782 | if (selected) |
783 | textColor = opt.palette.highlightedText().color(); |
784 | else |
785 | textColor = opt.palette.text().color(); |
786 | |
787 | painter->setPen(textColor); |
788 | |
789 | TaskModel *model = static_cast<TaskFilterModel *>(view->model())->taskModel(); |
790 | Positions positions(opt, model); |
791 | |
792 | // Paint TaskIconArea: |
793 | QIcon icon = index.data(TaskModel::Icon).value<QIcon>(); |
794 | painter->drawPixmap(positions.left(), positions.top(), |
795 | icon.pixmap(positions.taskIconWidth(), positions.taskIconHeight())); |
796 | |
797 | // Paint TextArea: |
798 | if (!selected) { |
799 | // in small mode we lay out differently |
800 | QString bottom = index.data(TaskModel::Description).toString(); |
801 | painter->setClipRect(positions.textArea()); |
802 | painter->drawText(positions.textAreaLeft(), positions.top() + fm.ascent(), bottom); |
803 | if (fm.horizontalAdvance(bottom) > positions.textAreaWidth()) { |
804 | // draw a gradient to mask the text |
805 | int gradientStart = positions.textAreaRight() - ELLIPSIS_GRADIENT_WIDTH + 1; |
806 | QLinearGradient lg(gradientStart, 0, gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0); |
807 | lg.setColorAt(0, Qt::transparent); |
808 | lg.setColorAt(1, backgroundColor); |
809 | painter->fillRect(gradientStart, positions.top(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg); |
810 | } |
811 | } else { |
812 | // Description |
813 | QString description = index.data(TaskModel::Description).toString(); |
814 | description += QLatin1Char('\n'); |
815 | description += index.data(TaskModel::ExtraInfo).toString(); |
816 | |
817 | // Layout the description |
818 | int leading = fm.leading(); |
819 | int height = 0; |
820 | description.replace(QLatin1Char('\n'), QChar::LineSeparator); |
821 | QTextLayout tl(description); |
822 | // TODO: tl.setAdditionalFormats(...); |
823 | tl.beginLayout(); |
824 | while (true) { |
825 | QTextLine line = tl.createLine(); |
826 | if (!line.isValid()) |
827 | break; |
828 | line.setLineWidth(positions.textAreaWidth()); |
829 | height += leading; |
830 | line.setPosition(QPoint(0, height)); |
831 | height += static_cast<int>(line.height()); |
832 | } |
833 | tl.endLayout(); |
834 | tl.draw(painter, QPoint(positions.textAreaLeft(), positions.top())); |
835 | |
836 | QColor mix; |
837 | mix.setRgb( static_cast<int>(0.7 * textColor.red() + 0.3 * backgroundColor.red()), |
838 | static_cast<int>(0.7 * textColor.green() + 0.3 * backgroundColor.green()), |
839 | static_cast<int>(0.7 * textColor.blue() + 0.3 * backgroundColor.blue())); |
840 | painter->setPen(mix); |
841 | } |
842 | painter->setPen(textColor); |
843 | |
844 | // Paint time |
845 | double curtime = index.data(TaskModel::Time).toDouble(); |
846 | time_t seconds = static_cast<time_t>(curtime/1000.0); |
847 | struct tm* cur_tm = localtime(&seconds); |
848 | QString result; |
849 | if (cur_tm) { |
850 | result = QString::asprintf("%d/%02d/%02d %02d:%02d:%02d.%03d" , |
851 | cur_tm->tm_year + 1900, cur_tm->tm_mon + 1, cur_tm->tm_mday, |
852 | cur_tm->tm_hour, cur_tm->tm_min, cur_tm->tm_sec, |
853 | int(curtime - seconds*1000.0)); |
854 | } |
855 | int realWidth = fm.horizontalAdvance(result); |
856 | painter->setClipRect(positions.timeArea()); |
857 | painter->drawText(qMin(positions.timeAreaLeft(), positions.timeAreaRight() - realWidth), |
858 | positions.top() + fm.ascent(), result); |
859 | |
860 | // Paint duration |
861 | double duration = index.data(TaskModel::Duration).toDouble(); |
862 | result = QString::asprintf("%.3f ms" , duration); |
863 | realWidth = fm.horizontalAdvance(result); |
864 | painter->setClipRect(positions.durationArea()); |
865 | painter->drawText(qMin(positions.durationAreaLeft(), positions.durationAreaRight() - realWidth), |
866 | positions.top() + fm.ascent(), result); |
867 | |
868 | // Paint syscall return |
869 | long syscall = index.data(TaskModel::Return).toLongLong(); |
870 | if (syscall < 0xffff) { |
871 | result = QString::asprintf("%ld" , syscall); |
872 | } |
873 | else { |
874 | result = QString::asprintf("%p" , (void*)syscall); |
875 | } |
876 | realWidth = fm.horizontalAdvance(result); |
877 | painter->setClipRect(positions.returnArea()); |
878 | painter->drawText(qMin(positions.returnAreaLeft(), positions.returnAreaRight() - realWidth), |
879 | positions.top() + fm.ascent(), result); |
880 | if (realWidth > positions.returnAreaWidth()) { |
881 | // draw a gradient to mask the text |
882 | int gradientStart = positions.returnAreaLeft() - 1; |
883 | QLinearGradient lg(gradientStart + ELLIPSIS_GRADIENT_WIDTH, 0, gradientStart, 0); |
884 | lg.setColorAt(0, Qt::transparent); |
885 | lg.setColorAt(1, backgroundColor); |
886 | painter->fillRect(gradientStart, positions.top(), ELLIPSIS_GRADIENT_WIDTH, positions.firstLineHeight(), lg); |
887 | } |
888 | |
889 | // Paint current tid |
890 | int tid = index.data(TaskModel::Tid).toInt(); |
891 | result = QString::number(tid); |
892 | painter->setClipRect(positions.tidArea()); |
893 | realWidth = fm.horizontalAdvance(result); |
894 | painter->drawText(positions.tidAreaRight() - realWidth, positions.top() + fm.ascent(), result); |
895 | |
896 | // Paint thread num |
897 | int threads = index.data(TaskModel::ThreadNum).toInt(); |
898 | result = QString::number(threads); |
899 | painter->setClipRect(positions.threadsArea()); |
900 | realWidth = fm.horizontalAdvance(result); |
901 | painter->drawText(positions.threadsAreaRight() - realWidth, positions.top() + fm.ascent(), result); |
902 | |
903 | painter->setClipRect(opt.rect); |
904 | |
905 | // Separator lines |
906 | painter->setPen(QColor::fromRgb(150,150,150)); |
907 | painter->drawLine(0, opt.rect.bottom(), opt.rect.right(), opt.rect.bottom()); |
908 | painter->restore(); |
909 | } |
910 | |
911 | #include "taskwindow.moc" |
912 | } // namespace Internal |
913 | } // namespace ReverseDebugger |
914 | |