#include "call_window.hpp" #include <QMenu> #include <QStatusBar> #include <QPushButton> #include <QHBoxLayout> #include <QVariant> #include "../stfl/stringutils.hpp" namespace cvv { namespace controller { class ViewController; } namespace gui { CallWindow::CallWindow(util::Reference<controller::ViewController> controller, size_t id) : id{ id }, controller{ controller } { initTabs(); initFooter(); setWindowTitle(QString("CVVisual | window no. %1").arg(id)); setMinimumWidth(600); setMinimumHeight(600); } void CallWindow::initTabs() { tabWidget = new TabWidget(this); tabWidget->setTabsClosable(true); tabWidget->setMovable(true); setCentralWidget(tabWidget); auto *flowButtons = new QHBoxLayout(); auto *flowButtonsWidget = new QWidget(this); tabWidget->setCornerWidget(flowButtonsWidget, Qt::TopLeftCorner); flowButtonsWidget->setLayout(flowButtons); flowButtons->setAlignment(Qt::AlignLeft | Qt::AlignTop); closeButton = new QPushButton("Close", this); flowButtons->addWidget(closeButton); closeButton->setStyleSheet( "QPushButton {background-color: red; color: white;}"); closeButton->setToolTip("Close this debugging application."); connect(closeButton, SIGNAL(clicked()), this, SLOT(closeApp())); fastForwardButton = new QPushButton(">>", this); flowButtons->addWidget(fastForwardButton); fastForwardButton->setStyleSheet( "QPushButton {background-color: yellow; color: blue;}"); fastForwardButton->setToolTip( "Fast forward until cvv::finalCall() gets called."); connect(fastForwardButton, SIGNAL(clicked()), this, SLOT(fastForward())); stepButton = new QPushButton("Step", this); flowButtons->addWidget(stepButton); stepButton->setStyleSheet( "QPushButton {background-color: green; color: white;}"); stepButton->setToolTip( "Resume program execution for a next debugging step."); connect(stepButton, SIGNAL(clicked()), this, SLOT(step())); flowButtons->setContentsMargins(0, 0, 0, 0); flowButtons->setSpacing(0); auto *tabBar = tabWidget->getTabBar(); tabBar->setElideMode(Qt::ElideRight); tabBar->setContextMenuPolicy(Qt::CustomContextMenu); connect(tabBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuRequested(QPoint))); connect(tabBar, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int))); } void CallWindow::initFooter() { leftFooter = new QLabel(); rightFooter = new QLabel(); QStatusBar *bar = statusBar(); bar->addPermanentWidget(leftFooter, 2); bar->addPermanentWidget(rightFooter, 2); } void CallWindow::showExitProgramButton() { stepButton->setVisible(false); fastForwardButton->setVisible(false); } void CallWindow::addTab(CallTab *tab) { tabMap[tab->getId()] = tab; QString name = QString("[%1] %2").arg(tab->getId()).arg(tab->getName()); int index = tabWidget->addTab(tab, stfl::shortenString(name, 20, true, true)); tabWidget->getTabBar()->setTabData(index, QVariant((int)tab->getId())); } size_t CallWindow::getId() { return id; } void CallWindow::removeTab(CallTab *tab) { tabMap.erase(tabMap.find(tab->getId())); int index = tabWidget->indexOf(tab); tabWidget->removeTab(index); } void CallWindow::removeTab(size_t tabId) { if (hasTab(tabId)) { removeTab(tabMap[tabId]); } } void CallWindow::showTab(CallTab *tab) { tabWidget->setCurrentWidget(tab); } void CallWindow::showTab(size_t tabId) { if (hasTab(tabId)) { showTab(tabMap[tabId]); } } void CallWindow::updateLeftFooter(QString newText) { leftFooter->setText(newText); } void CallWindow::updateRightFooter(QString newText) { rightFooter->setText(newText); } void CallWindow::step() { controller->resumeProgramExecution(); } void CallWindow::fastForward() { controller->setMode(controller::Mode::FAST_FORWARD); } void CallWindow::closeApp() { controller->setMode(controller::Mode::HIDE); } bool CallWindow::hasTab(size_t tabId) { return tabMap.count(tabId); } void CallWindow::contextMenuRequested(const QPoint &location) { controller->removeEmptyWindows(); auto tabBar = tabWidget->getTabBar(); int tabIndex = tabBar->tabAt(location); if (tabIndex == tabOffset - 1) return; QMenu *menu = new QMenu(this); connect(menu, SIGNAL(triggered(QAction *)), this, SLOT(contextMenuAction(QAction *))); auto windows = controller->getTabWindows(); menu->addAction(new QAction("Remove call", this)); menu->addAction(new QAction("Close tab", this)); menu->addAction(new QAction("Open in new window", this)); for (auto window : windows) { if (window->getId() != id) { menu->addAction(new QAction( QString("Open in '%1'").arg(window->windowTitle()), this)); } } currentContextMenuTabId = getCallTabIdByTabIndex(tabIndex); menu->popup(tabBar->mapToGlobal(location)); } void CallWindow::contextMenuAction(QAction *action) { if (currentContextMenuTabId == -1) { return; } auto text = action->text(); if (text == "Open in new window") { controller->moveCallTabToNewWindow(currentContextMenuTabId); } else if (text == "Remove call") { controller->removeCallTab(currentContextMenuTabId, true, true); } else if (text == "Close tab") { controller->removeCallTab(currentContextMenuTabId); } else { auto windows = controller->getTabWindows(); for (auto window : windows) { if (text == QString("Open in '%1'").arg(window->windowTitle())) { controller->moveCallTabToWindow( currentContextMenuTabId, window->getId()); break; } } } currentContextMenuTabId = -1; } size_t CallWindow::tabCount() { return tabMap.size(); } std::vector<size_t> CallWindow::getCallTabIds() { std::vector<size_t> ids{}; for (auto &elem : tabMap) { ids.push_back(elem.first); } return ids; } void CallWindow::closeEvent(QCloseEvent *event) { controller->removeWindowFromMaps(id); // FIXME: tabWidget is already freed sometimes: Use-after-free Bug tabWidget->clear(); for (auto &elem : tabMap) { controller->removeCallTab(elem.first, true); } event->accept(); } void CallWindow::tabCloseRequested(int index) { if (hasTabAtIndex(index)) { controller->removeCallTab(getCallTabIdByTabIndex(index)); } controller->removeEmptyWindows(); } size_t CallWindow::getCallTabIdByTabIndex(int index) { if (hasTabAtIndex(index)) { auto tabData = tabWidget->getTabBar()->tabData(index); bool ok = true; size_t callTabId = tabData.toInt(&ok); if (ok && tabMap.count(callTabId) > 0) { return callTabId; } } return 0; } bool CallWindow::hasTabAtIndex(int index) { auto tabData = tabWidget->getTabBar()->tabData(index); return tabData != 0 && !tabData.isNull() && tabData.isValid(); } } }