1 #include "call_window.hpp"
2
3 #include <QMenu>
4 #include <QStatusBar>
5 #include <QPushButton>
6 #include <QHBoxLayout>
7 #include <QVariant>
8
9 #include "../stfl/stringutils.hpp"
10
11 namespace cvv
12 {
13
14 namespace controller
15 {
16 class ViewController;
17 }
18
19 namespace gui
20 {
21
CallWindow(util::Reference<controller::ViewController> controller,size_t id)22 CallWindow::CallWindow(util::Reference<controller::ViewController> controller,
23 size_t id)
24 : id{ id }, controller{ controller }
25 {
26 initTabs();
27 initFooter();
28 setWindowTitle(QString("CVVisual | window no. %1").arg(id));
29 setMinimumWidth(600);
30 setMinimumHeight(600);
31 }
32
initTabs()33 void CallWindow::initTabs()
34 {
35 tabWidget = new TabWidget(this);
36 tabWidget->setTabsClosable(true);
37 tabWidget->setMovable(true);
38 setCentralWidget(tabWidget);
39
40 auto *flowButtons = new QHBoxLayout();
41 auto *flowButtonsWidget = new QWidget(this);
42 tabWidget->setCornerWidget(flowButtonsWidget, Qt::TopLeftCorner);
43 flowButtonsWidget->setLayout(flowButtons);
44 flowButtons->setAlignment(Qt::AlignLeft | Qt::AlignTop);
45 closeButton = new QPushButton("Close", this);
46 flowButtons->addWidget(closeButton);
47 closeButton->setStyleSheet(
48 "QPushButton {background-color: red; color: white;}");
49 closeButton->setToolTip("Close this debugging application.");
50 connect(closeButton, SIGNAL(clicked()), this, SLOT(closeApp()));
51 fastForwardButton = new QPushButton(">>", this);
52 flowButtons->addWidget(fastForwardButton);
53 fastForwardButton->setStyleSheet(
54 "QPushButton {background-color: yellow; color: blue;}");
55 fastForwardButton->setToolTip(
56 "Fast forward until cvv::finalCall() gets called.");
57 connect(fastForwardButton, SIGNAL(clicked()), this,
58 SLOT(fastForward()));
59 stepButton = new QPushButton("Step", this);
60 flowButtons->addWidget(stepButton);
61 stepButton->setStyleSheet(
62 "QPushButton {background-color: green; color: white;}");
63 stepButton->setToolTip(
64 "Resume program execution for a next debugging step.");
65 connect(stepButton, SIGNAL(clicked()), this, SLOT(step()));
66 flowButtons->setContentsMargins(0, 0, 0, 0);
67 flowButtons->setSpacing(0);
68
69 auto *tabBar = tabWidget->getTabBar();
70 tabBar->setElideMode(Qt::ElideRight);
71 tabBar->setContextMenuPolicy(Qt::CustomContextMenu);
72 connect(tabBar, SIGNAL(customContextMenuRequested(QPoint)), this,
73 SLOT(contextMenuRequested(QPoint)));
74 connect(tabBar, SIGNAL(tabCloseRequested(int)), this,
75 SLOT(tabCloseRequested(int)));
76 }
77
initFooter()78 void CallWindow::initFooter()
79 {
80 leftFooter = new QLabel();
81 rightFooter = new QLabel();
82 QStatusBar *bar = statusBar();
83 bar->addPermanentWidget(leftFooter, 2);
84 bar->addPermanentWidget(rightFooter, 2);
85 }
86
showExitProgramButton()87 void CallWindow::showExitProgramButton()
88 {
89 stepButton->setVisible(false);
90 fastForwardButton->setVisible(false);
91 }
92
addTab(CallTab * tab)93 void CallWindow::addTab(CallTab *tab)
94 {
95 tabMap[tab->getId()] = tab;
96 QString name = QString("[%1] %2").arg(tab->getId()).arg(tab->getName());
97 int index =
98 tabWidget->addTab(tab, stfl::shortenString(name, 20, true, true));
99 tabWidget->getTabBar()->setTabData(index, QVariant((int)tab->getId()));
100 }
101
getId()102 size_t CallWindow::getId()
103 {
104 return id;
105 }
106
removeTab(CallTab * tab)107 void CallWindow::removeTab(CallTab *tab)
108 {
109 tabMap.erase(tabMap.find(tab->getId()));
110 int index = tabWidget->indexOf(tab);
111 tabWidget->removeTab(index);
112 }
113
removeTab(size_t tabId)114 void CallWindow::removeTab(size_t tabId)
115 {
116 if (hasTab(tabId))
117 {
118 removeTab(tabMap[tabId]);
119 }
120 }
121
showTab(CallTab * tab)122 void CallWindow::showTab(CallTab *tab)
123 {
124 tabWidget->setCurrentWidget(tab);
125 }
126
showTab(size_t tabId)127 void CallWindow::showTab(size_t tabId)
128 {
129 if (hasTab(tabId))
130 {
131 showTab(tabMap[tabId]);
132 }
133 }
134
updateLeftFooter(QString newText)135 void CallWindow::updateLeftFooter(QString newText)
136 {
137 leftFooter->setText(newText);
138 }
139
updateRightFooter(QString newText)140 void CallWindow::updateRightFooter(QString newText)
141 {
142 rightFooter->setText(newText);
143 }
144
step()145 void CallWindow::step()
146 {
147 controller->resumeProgramExecution();
148 }
149
fastForward()150 void CallWindow::fastForward()
151 {
152 controller->setMode(controller::Mode::FAST_FORWARD);
153 }
154
closeApp()155 void CallWindow::closeApp()
156 {
157 controller->setMode(controller::Mode::HIDE);
158 }
159
hasTab(size_t tabId)160 bool CallWindow::hasTab(size_t tabId)
161 {
162 return tabMap.count(tabId);
163 }
164
contextMenuRequested(const QPoint & location)165 void CallWindow::contextMenuRequested(const QPoint &location)
166 {
167 controller->removeEmptyWindows();
168 auto tabBar = tabWidget->getTabBar();
169 int tabIndex = tabBar->tabAt(location);
170 if (tabIndex == tabOffset - 1)
171 return;
172 QMenu *menu = new QMenu(this);
173 connect(menu, SIGNAL(triggered(QAction *)), this,
174 SLOT(contextMenuAction(QAction *)));
175 auto windows = controller->getTabWindows();
176 menu->addAction(new QAction("Remove call", this));
177 menu->addAction(new QAction("Close tab", this));
178 menu->addAction(new QAction("Open in new window", this));
179 for (auto window : windows)
180 {
181 if (window->getId() != id)
182 {
183 menu->addAction(new QAction(
184 QString("Open in '%1'").arg(window->windowTitle()),
185 this));
186 }
187 }
188 currentContextMenuTabId = getCallTabIdByTabIndex(tabIndex);
189 menu->popup(tabBar->mapToGlobal(location));
190 }
191
contextMenuAction(QAction * action)192 void CallWindow::contextMenuAction(QAction *action)
193 {
194 if (currentContextMenuTabId == -1)
195 {
196 return;
197 }
198 auto text = action->text();
199 if (text == "Open in new window")
200 {
201 controller->moveCallTabToNewWindow(currentContextMenuTabId);
202 }
203 else if (text == "Remove call")
204 {
205 controller->removeCallTab(currentContextMenuTabId, true, true);
206 }
207 else if (text == "Close tab")
208 {
209 controller->removeCallTab(currentContextMenuTabId);
210 }
211 else
212 {
213 auto windows = controller->getTabWindows();
214 for (auto window : windows)
215 {
216 if (text ==
217 QString("Open in '%1'").arg(window->windowTitle()))
218 {
219 controller->moveCallTabToWindow(
220 currentContextMenuTabId, window->getId());
221 break;
222 }
223 }
224 }
225 currentContextMenuTabId = -1;
226 }
227
tabCount()228 size_t CallWindow::tabCount()
229 {
230 return tabMap.size();
231 }
232
getCallTabIds()233 std::vector<size_t> CallWindow::getCallTabIds()
234 {
235 std::vector<size_t> ids{};
236 for (auto &elem : tabMap)
237 {
238 ids.push_back(elem.first);
239 }
240 return ids;
241 }
242
closeEvent(QCloseEvent * event)243 void CallWindow::closeEvent(QCloseEvent *event)
244 {
245 controller->removeWindowFromMaps(id);
246 // FIXME: tabWidget is already freed sometimes: Use-after-free Bug
247 tabWidget->clear();
248 for (auto &elem : tabMap)
249 {
250 controller->removeCallTab(elem.first, true);
251 }
252 event->accept();
253 }
254
tabCloseRequested(int index)255 void CallWindow::tabCloseRequested(int index)
256 {
257 if (hasTabAtIndex(index))
258 {
259 controller->removeCallTab(getCallTabIdByTabIndex(index));
260 }
261 controller->removeEmptyWindows();
262 }
263
getCallTabIdByTabIndex(int index)264 size_t CallWindow::getCallTabIdByTabIndex(int index)
265 {
266 if (hasTabAtIndex(index))
267 {
268 auto tabData = tabWidget->getTabBar()->tabData(index);
269 bool ok = true;
270 size_t callTabId = tabData.toInt(&ok);
271 if (ok && tabMap.count(callTabId) > 0)
272 {
273 return callTabId;
274 }
275 }
276 return 0;
277 }
278
hasTabAtIndex(int index)279 bool CallWindow::hasTabAtIndex(int index)
280 {
281 auto tabData = tabWidget->getTabBar()->tabData(index);
282 return tabData != 0 && !tabData.isNull() && tabData.isValid();
283 }
284 }
285 }
286