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