| 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 | |