1 /*
2  * This file is part of the PulseView project.
3  *
4  * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifdef ENABLE_DECODE
21 #include <libsigrokdecode/libsigrokdecode.h>
22 #endif
23 
24 #include <algorithm>
25 #include <cassert>
26 #include <cstdarg>
27 #include <cstdint>
28 #include <iterator>
29 
30 #include <QAction>
31 #include <QApplication>
32 #include <QCloseEvent>
33 #include <QDebug>
34 #include <QDockWidget>
35 #include <QHBoxLayout>
36 #include <QMessageBox>
37 #include <QSettings>
38 #include <QShortcut>
39 #include <QWidget>
40 
41 #include "mainwindow.hpp"
42 
43 #include "application.hpp"
44 #include "devicemanager.hpp"
45 #include "devices/hardwaredevice.hpp"
46 #include "dialogs/settings.hpp"
47 #include "globalsettings.hpp"
48 #include "toolbars/mainbar.hpp"
49 #include "util.hpp"
50 #include "views/trace/view.hpp"
51 #include "views/trace/standardbar.hpp"
52 
53 #ifdef ENABLE_DECODE
54 #include "subwindows/decoder_selector/subwindow.hpp"
55 #include "views/decoder_binary/view.hpp"
56 #endif
57 
58 #include <libsigrokcxx/libsigrokcxx.hpp>
59 
60 using std::dynamic_pointer_cast;
61 using std::make_shared;
62 using std::shared_ptr;
63 using std::string;
64 
65 namespace pv {
66 
67 using toolbars::MainBar;
68 
69 const QString MainWindow::WindowTitle = tr("PulseView");
70 
MainWindow(DeviceManager & device_manager,QWidget * parent)71 MainWindow::MainWindow(DeviceManager &device_manager, QWidget *parent) :
72 	QMainWindow(parent),
73 	device_manager_(device_manager),
74 	session_selector_(this),
75 	icon_red_(":/icons/status-red.svg"),
76 	icon_green_(":/icons/status-green.svg"),
77 	icon_grey_(":/icons/status-grey.svg")
78 {
79 	setup_ui();
80 	restore_ui_settings();
81 }
82 
~MainWindow()83 MainWindow::~MainWindow()
84 {
85 	// Make sure we no longer hold any shared pointers to widgets after the
86 	// destructor finishes (goes for sessions and sub windows alike)
87 
88 	while (!sessions_.empty())
89 		remove_session(sessions_.front());
90 
91 	sub_windows_.clear();
92 }
93 
show_session_error(const QString text,const QString info_text)94 void MainWindow::show_session_error(const QString text, const QString info_text)
95 {
96 	// TODO Emulate noquote()
97 	qDebug() << "Notifying user of session error:" << info_text;
98 
99 	QMessageBox msg;
100 	msg.setText(text + "\n\n" + info_text);
101 	msg.setStandardButtons(QMessageBox::Ok);
102 	msg.setIcon(QMessageBox::Warning);
103 	msg.exec();
104 }
105 
get_active_view() const106 shared_ptr<views::ViewBase> MainWindow::get_active_view() const
107 {
108 	// If there's only one view, use it...
109 	if (view_docks_.size() == 1)
110 		return view_docks_.begin()->second;
111 
112 	// ...otherwise find the dock widget the widget with focus is contained in
113 	QObject *w = QApplication::focusWidget();
114 	QDockWidget *dock = nullptr;
115 
116 	while (w) {
117 		dock = qobject_cast<QDockWidget*>(w);
118 		if (dock)
119 			break;
120 		w = w->parent();
121 	}
122 
123 	// Get the view contained in the dock widget
124 	for (auto& entry : view_docks_)
125 		if (entry.first == dock)
126 			return entry.second;
127 
128 	return nullptr;
129 }
130 
add_view(views::ViewType type,Session & session)131 shared_ptr<views::ViewBase> MainWindow::add_view(views::ViewType type,
132 	Session &session)
133 {
134 	GlobalSettings settings;
135 	shared_ptr<views::ViewBase> v;
136 
137 	QMainWindow *main_window = nullptr;
138 	for (auto& entry : session_windows_)
139 		if (entry.first.get() == &session)
140 			main_window = entry.second;
141 
142 	assert(main_window);
143 
144 	shared_ptr<MainBar> main_bar = session.main_bar();
145 
146 	// Only use the view type in the name if it's not the main view
147 	QString title;
148 	if (main_bar)
149 		title = QString("%1 (%2)").arg(session.name(), views::ViewTypeNames[type]);
150 	else
151 		title = session.name();
152 
153 	QDockWidget* dock = new QDockWidget(title, main_window);
154 	dock->setObjectName(title);
155 	main_window->addDockWidget(Qt::TopDockWidgetArea, dock);
156 
157 	// Insert a QMainWindow into the dock widget to allow for a tool bar
158 	QMainWindow *dock_main = new QMainWindow(dock);
159 	dock_main->setWindowFlags(Qt::Widget);  // Remove Qt::Window flag
160 
161 	if (type == views::ViewTypeTrace)
162 		// This view will be the main view if there's no main bar yet
163 		v = make_shared<views::trace::View>(session, (main_bar ? false : true), dock_main);
164 #ifdef ENABLE_DECODE
165 	if (type == views::ViewTypeDecoderBinary)
166 		v = make_shared<views::decoder_binary::View>(session, false, dock_main);
167 #endif
168 
169 	if (!v)
170 		return nullptr;
171 
172 	view_docks_[dock] = v;
173 	session.register_view(v);
174 
175 	dock_main->setCentralWidget(v.get());
176 	dock->setWidget(dock_main);
177 
178 	dock->setContextMenuPolicy(Qt::PreventContextMenu);
179 	dock->setFeatures(QDockWidget::DockWidgetMovable |
180 		QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable);
181 
182 	QAbstractButton *close_btn =
183 		dock->findChildren<QAbstractButton*>("qt_dockwidget_closebutton")  // clazy:exclude=detaching-temporary
184 			.front();
185 
186 	connect(close_btn, SIGNAL(clicked(bool)),
187 		this, SLOT(on_view_close_clicked()));
188 
189 	connect(&session, SIGNAL(trigger_event(int, util::Timestamp)),
190 		qobject_cast<views::ViewBase*>(v.get()),
191 		SLOT(trigger_event(int, util::Timestamp)));
192 
193 	if (type == views::ViewTypeTrace) {
194 		views::trace::View *tv =
195 			qobject_cast<views::trace::View*>(v.get());
196 
197 		if (!main_bar) {
198 			/* Initial view, create the main bar */
199 			main_bar = make_shared<MainBar>(session, this, tv);
200 			dock_main->addToolBar(main_bar.get());
201 			session.set_main_bar(main_bar);
202 
203 			connect(main_bar.get(), SIGNAL(new_view(Session*, int)),
204 				this, SLOT(on_new_view(Session*, int)));
205 			connect(main_bar.get(), SIGNAL(show_decoder_selector(Session*)),
206 				this, SLOT(on_show_decoder_selector(Session*)));
207 
208 			main_bar->action_view_show_cursors()->setChecked(tv->cursors_shown());
209 
210 			/* For the main view we need to prevent the dock widget from
211 			 * closing itself when its close button is clicked. This is
212 			 * so we can confirm with the user first. Regular views don't
213 			 * need this */
214 			close_btn->disconnect(SIGNAL(clicked()), dock, SLOT(close()));
215 		} else {
216 			/* Additional view, create a standard bar */
217 			pv::views::trace::StandardBar *standard_bar =
218 				new pv::views::trace::StandardBar(session, this, tv);
219 			dock_main->addToolBar(standard_bar);
220 
221 			standard_bar->action_view_show_cursors()->setChecked(tv->cursors_shown());
222 		}
223 	}
224 
225 	v->setFocus();
226 
227 	return v;
228 }
229 
remove_view(shared_ptr<views::ViewBase> view)230 void MainWindow::remove_view(shared_ptr<views::ViewBase> view)
231 {
232 	for (shared_ptr<Session> session : sessions_) {
233 		if (!session->has_view(view))
234 			continue;
235 
236 		// Find the dock the view is contained in and remove it
237 		for (auto& entry : view_docks_)
238 			if (entry.second == view) {
239 				// Remove the view from the session
240 				session->deregister_view(view);
241 
242 				// Remove the view from its parent; otherwise, Qt will
243 				// call deleteLater() on it, which causes a double free
244 				// since the shared_ptr in view_docks_ doesn't know
245 				// that Qt keeps a pointer to the view around
246 				view->setParent(nullptr);
247 
248 				// Delete the view's dock widget and all widgets inside it
249 				entry.first->deleteLater();
250 
251 				// Remove the dock widget from the list and stop iterating
252 				view_docks_.erase(entry.first);
253 				break;
254 			}
255 	}
256 }
257 
add_subwindow(subwindows::SubWindowType type,Session & session)258 shared_ptr<subwindows::SubWindowBase> MainWindow::add_subwindow(
259 	subwindows::SubWindowType type, Session &session)
260 {
261 	GlobalSettings settings;
262 	shared_ptr<subwindows::SubWindowBase> w;
263 
264 	QMainWindow *main_window = nullptr;
265 	for (auto& entry : session_windows_)
266 		if (entry.first.get() == &session)
267 			main_window = entry.second;
268 
269 	assert(main_window);
270 
271 	QString title = "";
272 
273 	switch (type) {
274 #ifdef ENABLE_DECODE
275 		case subwindows::SubWindowTypeDecoderSelector:
276 			title = tr("Decoder Selector");
277 			break;
278 #endif
279 		default:
280 			break;
281 	}
282 
283 	QDockWidget* dock = new QDockWidget(title, main_window);
284 	dock->setObjectName(title);
285 	main_window->addDockWidget(Qt::TopDockWidgetArea, dock);
286 
287 	// Insert a QMainWindow into the dock widget to allow for a tool bar
288 	QMainWindow *dock_main = new QMainWindow(dock);
289 	dock_main->setWindowFlags(Qt::Widget);  // Remove Qt::Window flag
290 
291 #ifdef ENABLE_DECODE
292 	if (type == subwindows::SubWindowTypeDecoderSelector)
293 		w = make_shared<subwindows::decoder_selector::SubWindow>(session, dock_main);
294 #endif
295 
296 	if (!w)
297 		return nullptr;
298 
299 	sub_windows_[dock] = w;
300 	dock_main->setCentralWidget(w.get());
301 	dock->setWidget(dock_main);
302 
303 	dock->setContextMenuPolicy(Qt::PreventContextMenu);
304 	dock->setFeatures(QDockWidget::DockWidgetMovable |
305 		QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable);
306 
307 	QAbstractButton *close_btn =
308 		dock->findChildren<QAbstractButton*>  // clazy:exclude=detaching-temporary
309 			("qt_dockwidget_closebutton").front();
310 
311 	// Allow all subwindows to be closed via ESC.
312 	close_btn->setShortcut(QKeySequence(Qt::Key_Escape));
313 
314 	connect(close_btn, SIGNAL(clicked(bool)),
315 		this, SLOT(on_sub_window_close_clicked()));
316 
317 	if (w->has_toolbar())
318 		dock_main->addToolBar(w->create_toolbar(dock_main));
319 
320 	if (w->minimum_width() > 0)
321 		dock->setMinimumSize(w->minimum_width(), 0);
322 
323 	return w;
324 }
325 
add_session()326 shared_ptr<Session> MainWindow::add_session()
327 {
328 	static int last_session_id = 1;
329 	QString name = tr("Session %1").arg(last_session_id++);
330 
331 	shared_ptr<Session> session = make_shared<Session>(device_manager_, name);
332 
333 	connect(session.get(), SIGNAL(add_view(views::ViewType, Session*)),
334 		this, SLOT(on_add_view(views::ViewType, Session*)));
335 	connect(session.get(), SIGNAL(name_changed()),
336 		this, SLOT(on_session_name_changed()));
337 	connect(session.get(), SIGNAL(device_changed()),
338 		this, SLOT(on_session_device_changed()));
339 	connect(session.get(), SIGNAL(capture_state_changed(int)),
340 		this, SLOT(on_session_capture_state_changed(int)));
341 
342 	sessions_.push_back(session);
343 
344 	QMainWindow *window = new QMainWindow();
345 	window->setWindowFlags(Qt::Widget);  // Remove Qt::Window flag
346 	session_windows_[session] = window;
347 
348 	int index = session_selector_.addTab(window, name);
349 	session_selector_.setCurrentIndex(index);
350 	last_focused_session_ = session;
351 
352 	window->setDockNestingEnabled(true);
353 
354 	add_view(views::ViewTypeTrace, *session);
355 
356 	return session;
357 }
358 
remove_session(shared_ptr<Session> session)359 void MainWindow::remove_session(shared_ptr<Session> session)
360 {
361 	// Determine the height of the button before it collapses
362 	int h = new_session_button_->height();
363 
364 	// Stop capture while the session still exists so that the UI can be
365 	// updated in case we're currently running. If so, this will schedule a
366 	// call to our on_capture_state_changed() slot for the next run of the
367 	// event loop. We need to have this executed immediately or else it will
368 	// be dismissed since the session object will be deleted by the time we
369 	// leave this method and the event loop gets a chance to run again.
370 	session->stop_capture();
371 	QApplication::processEvents();
372 
373 	for (const shared_ptr<views::ViewBase>& view : session->views())
374 		remove_view(view);
375 
376 	QMainWindow *window = session_windows_.at(session);
377 	session_selector_.removeTab(session_selector_.indexOf(window));
378 
379 	session_windows_.erase(session);
380 
381 	if (last_focused_session_ == session)
382 		last_focused_session_.reset();
383 
384 	// Remove the session from our list of sessions (which also destroys it)
385 	sessions_.remove_if([&](shared_ptr<Session> s) {
386 		return s == session; });
387 
388 	if (sessions_.empty()) {
389 		// When there are no more tabs, the height of the QTabWidget
390 		// drops to zero. We must prevent this to keep the static
391 		// widgets visible
392 		for (QWidget *w : static_tab_widget_->findChildren<QWidget*>())  // clazy:exclude=range-loop
393 			w->setMinimumHeight(h);
394 
395 		int margin = static_tab_widget_->layout()->contentsMargins().bottom();
396 		static_tab_widget_->setMinimumHeight(h + 2 * margin);
397 		session_selector_.setMinimumHeight(h + 2 * margin);
398 
399 		// Update the window title if there is no view left to
400 		// generate focus change events
401 		setWindowTitle(WindowTitle);
402 	}
403 }
404 
add_session_with_file(string open_file_name,string open_file_format,string open_setup_file_name)405 void MainWindow::add_session_with_file(string open_file_name,
406 	string open_file_format, string open_setup_file_name)
407 {
408 	shared_ptr<Session> session = add_session();
409 	session->load_init_file(open_file_name, open_file_format, open_setup_file_name);
410 }
411 
add_default_session()412 void MainWindow::add_default_session()
413 {
414 	// Only add the default session if there would be no session otherwise
415 	if (sessions_.size() > 0)
416 		return;
417 
418 	shared_ptr<Session> session = add_session();
419 
420 	// Check the list of available devices. Prefer the one that was
421 	// found with user supplied scan specs (if applicable). Then try
422 	// one of the auto detected devices that are not the demo device.
423 	// Pick demo in the absence of "genuine" hardware devices.
424 	shared_ptr<devices::HardwareDevice> user_device, other_device, demo_device;
425 	for (const shared_ptr<devices::HardwareDevice>& dev : device_manager_.devices()) {
426 		if (dev == device_manager_.user_spec_device()) {
427 			user_device = dev;
428 		} else if (dev->hardware_device()->driver()->name() == "demo") {
429 			demo_device = dev;
430 		} else {
431 			other_device = dev;
432 		}
433 	}
434 	if (user_device)
435 		session->select_device(user_device);
436 	else if (other_device)
437 		session->select_device(other_device);
438 	else
439 		session->select_device(demo_device);
440 }
441 
save_sessions()442 void MainWindow::save_sessions()
443 {
444 	QSettings settings;
445 	int id = 0;
446 
447 	for (shared_ptr<Session>& session : sessions_) {
448 		// Ignore sessions using the demo device or no device at all
449 		if (session->device()) {
450 			shared_ptr<devices::HardwareDevice> device =
451 				dynamic_pointer_cast< devices::HardwareDevice >
452 				(session->device());
453 
454 			if (device &&
455 				device->hardware_device()->driver()->name() == "demo")
456 				continue;
457 
458 			settings.beginGroup("Session" + QString::number(id++));
459 			settings.remove("");  // Remove all keys in this group
460 			session->save_settings(settings);
461 			settings.endGroup();
462 		}
463 	}
464 
465 	settings.setValue("sessions", id);
466 }
467 
restore_sessions()468 void MainWindow::restore_sessions()
469 {
470 	QSettings settings;
471 	int i, session_count;
472 
473 	session_count = settings.value("sessions", 0).toInt();
474 
475 	for (i = 0; i < session_count; i++) {
476 		settings.beginGroup("Session" + QString::number(i));
477 		shared_ptr<Session> session = add_session();
478 		session->restore_settings(settings);
479 		settings.endGroup();
480 	}
481 }
482 
setup_ui()483 void MainWindow::setup_ui()
484 {
485 	setObjectName(QString::fromUtf8("MainWindow"));
486 
487 	setCentralWidget(&session_selector_);
488 
489 	// Set the window icon
490 	QIcon icon;
491 	icon.addFile(QString(":/icons/pulseview.png"));
492 	setWindowIcon(icon);
493 
494 	// Set up keyboard shortcuts that affect all views at once
495 	view_sticky_scrolling_shortcut_ = new QShortcut(QKeySequence(Qt::Key_S), this, SLOT(on_view_sticky_scrolling_shortcut()));
496 	view_sticky_scrolling_shortcut_->setAutoRepeat(false);
497 
498 	view_show_sampling_points_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Period), this, SLOT(on_view_show_sampling_points_shortcut()));
499 	view_show_sampling_points_shortcut_->setAutoRepeat(false);
500 
501 	view_show_analog_minor_grid_shortcut_ = new QShortcut(QKeySequence(Qt::Key_G), this, SLOT(on_view_show_analog_minor_grid_shortcut()));
502 	view_show_analog_minor_grid_shortcut_->setAutoRepeat(false);
503 
504 	view_colored_bg_shortcut_ = new QShortcut(QKeySequence(Qt::Key_B), this, SLOT(on_view_colored_bg_shortcut()));
505 	view_colored_bg_shortcut_->setAutoRepeat(false);
506 
507 	// Set up the tab area
508 	new_session_button_ = new QToolButton();
509 	new_session_button_->setIcon(QIcon::fromTheme("document-new",
510 		QIcon(":/icons/document-new.png")));
511 	new_session_button_->setToolTip(tr("Create New Session"));
512 	new_session_button_->setAutoRaise(true);
513 
514 	run_stop_button_ = new QToolButton();
515 	run_stop_button_->setAutoRaise(true);
516 	run_stop_button_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
517 	run_stop_button_->setToolTip(tr("Start/Stop Acquisition"));
518 
519 	run_stop_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Space), run_stop_button_, SLOT(click()));
520 	run_stop_shortcut_->setAutoRepeat(false);
521 
522 	settings_button_ = new QToolButton();
523 	settings_button_->setIcon(QIcon::fromTheme("preferences-system",
524 		QIcon(":/icons/preferences-system.png")));
525 	settings_button_->setToolTip(tr("Settings"));
526 	settings_button_->setAutoRaise(true);
527 
528 	QFrame *separator1 = new QFrame();
529 	separator1->setFrameStyle(QFrame::VLine | QFrame::Raised);
530 	QFrame *separator2 = new QFrame();
531 	separator2->setFrameStyle(QFrame::VLine | QFrame::Raised);
532 
533 	QHBoxLayout* layout = new QHBoxLayout();
534 	layout->setContentsMargins(2, 2, 2, 2);
535 	layout->addWidget(new_session_button_);
536 	layout->addWidget(separator1);
537 	layout->addWidget(run_stop_button_);
538 	layout->addWidget(separator2);
539 	layout->addWidget(settings_button_);
540 
541 	static_tab_widget_ = new QWidget();
542 	static_tab_widget_->setLayout(layout);
543 
544 	session_selector_.setCornerWidget(static_tab_widget_, Qt::TopLeftCorner);
545 	session_selector_.setTabsClosable(true);
546 
547 	close_application_shortcut_ = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this, SLOT(close()));
548 	close_application_shortcut_->setAutoRepeat(false);
549 
550 	close_current_tab_shortcut_ = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), this, SLOT(on_close_current_tab()));
551 
552 	connect(new_session_button_, SIGNAL(clicked(bool)),
553 		this, SLOT(on_new_session_clicked()));
554 	connect(run_stop_button_, SIGNAL(clicked(bool)),
555 		this, SLOT(on_run_stop_clicked()));
556 	connect(settings_button_, SIGNAL(clicked(bool)),
557 		this, SLOT(on_settings_clicked()));
558 
559 	connect(&session_selector_, SIGNAL(tabCloseRequested(int)),
560 		this, SLOT(on_tab_close_requested(int)));
561 	connect(&session_selector_, SIGNAL(currentChanged(int)),
562 		this, SLOT(on_tab_changed(int)));
563 
564 
565 	connect(static_cast<QApplication *>(QCoreApplication::instance()),
566 		SIGNAL(focusChanged(QWidget*, QWidget*)),
567 		this, SLOT(on_focus_changed()));
568 }
569 
update_acq_button(Session * session)570 void MainWindow::update_acq_button(Session *session)
571 {
572 	int state;
573 	QString run_caption;
574 
575 	if (session) {
576 		state = session->get_capture_state();
577 		run_caption = session->using_file_device() ? tr("Reload") : tr("Run");
578 	} else {
579 		state = Session::Stopped;
580 		run_caption = tr("Run");
581 	}
582 
583 	const QIcon *icons[] = {&icon_grey_, &icon_red_, &icon_green_};
584 	run_stop_button_->setIcon(*icons[state]);
585 	run_stop_button_->setText((state == pv::Session::Stopped) ?
586 		run_caption : tr("Stop"));
587 }
588 
save_ui_settings()589 void MainWindow::save_ui_settings()
590 {
591 	QSettings settings;
592 
593 	settings.beginGroup("MainWindow");
594 	settings.setValue("state", saveState());
595 	settings.setValue("geometry", saveGeometry());
596 	settings.endGroup();
597 }
598 
restore_ui_settings()599 void MainWindow::restore_ui_settings()
600 {
601 	QSettings settings;
602 
603 	settings.beginGroup("MainWindow");
604 
605 	if (settings.contains("geometry")) {
606 		restoreGeometry(settings.value("geometry").toByteArray());
607 		restoreState(settings.value("state").toByteArray());
608 	} else
609 		resize(1000, 720);
610 
611 	settings.endGroup();
612 }
613 
get_tab_session(int index) const614 shared_ptr<Session> MainWindow::get_tab_session(int index) const
615 {
616 	// Find the session that belongs to the tab's main window
617 	for (auto& entry : session_windows_)
618 		if (entry.second == session_selector_.widget(index))
619 			return entry.first;
620 
621 	return nullptr;
622 }
623 
closeEvent(QCloseEvent * event)624 void MainWindow::closeEvent(QCloseEvent *event)
625 {
626 	bool data_saved = true;
627 
628 	for (auto& entry : session_windows_)
629 		if (!entry.first->data_saved())
630 			data_saved = false;
631 
632 	if (!data_saved && (QMessageBox::question(this, tr("Confirmation"),
633 		tr("There is unsaved data. Close anyway?"),
634 		QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) {
635 		event->ignore();
636 	} else {
637 		save_ui_settings();
638 		save_sessions();
639 		event->accept();
640 	}
641 }
642 
createPopupMenu()643 QMenu* MainWindow::createPopupMenu()
644 {
645 	return nullptr;
646 }
647 
restoreState(const QByteArray & state,int version)648 bool MainWindow::restoreState(const QByteArray &state, int version)
649 {
650 	(void)state;
651 	(void)version;
652 
653 	// Do nothing. We don't want Qt to handle this, or else it
654 	// will try to restore all the dock widgets and create havoc.
655 
656 	return false;
657 }
658 
on_add_view(views::ViewType type,Session * session)659 void MainWindow::on_add_view(views::ViewType type, Session *session)
660 {
661 	// We get a pointer and need a reference
662 	for (shared_ptr<Session>& s : sessions_)
663 		if (s.get() == session)
664 			add_view(type, *s);
665 }
666 
on_focus_changed()667 void MainWindow::on_focus_changed()
668 {
669 	shared_ptr<views::ViewBase> view = get_active_view();
670 
671 	if (view) {
672 		for (shared_ptr<Session> session : sessions_) {
673 			if (session->has_view(view)) {
674 				if (session != last_focused_session_) {
675 					// Activate correct tab if necessary
676 					shared_ptr<Session> tab_session = get_tab_session(
677 						session_selector_.currentIndex());
678 					if (tab_session != session)
679 						session_selector_.setCurrentWidget(
680 							session_windows_.at(session));
681 
682 					on_focused_session_changed(session);
683 				}
684 
685 				break;
686 			}
687 		}
688 	}
689 
690 	if (sessions_.empty())
691 		setWindowTitle(WindowTitle);
692 }
693 
on_focused_session_changed(shared_ptr<Session> session)694 void MainWindow::on_focused_session_changed(shared_ptr<Session> session)
695 {
696 	last_focused_session_ = session;
697 
698 	setWindowTitle(session->name() + " - " + WindowTitle);
699 
700 	// Update the state of the run/stop button, too
701 	update_acq_button(session.get());
702 }
703 
on_new_session_clicked()704 void MainWindow::on_new_session_clicked()
705 {
706 	add_session();
707 }
708 
on_run_stop_clicked()709 void MainWindow::on_run_stop_clicked()
710 {
711 	shared_ptr<Session> session = last_focused_session_;
712 
713 	if (!session)
714 		return;
715 
716 	switch (session->get_capture_state()) {
717 	case Session::Stopped:
718 		session->start_capture([&](QString message) {
719 			show_session_error("Capture failed", message); });
720 		break;
721 	case Session::AwaitingTrigger:
722 	case Session::Running:
723 		session->stop_capture();
724 		break;
725 	}
726 }
727 
on_settings_clicked()728 void MainWindow::on_settings_clicked()
729 {
730 	dialogs::Settings dlg(device_manager_);
731 	dlg.exec();
732 }
733 
on_session_name_changed()734 void MainWindow::on_session_name_changed()
735 {
736 	// Update the corresponding dock widget's name(s)
737 	Session *session = qobject_cast<Session*>(QObject::sender());
738 	assert(session);
739 
740 	for (const shared_ptr<views::ViewBase>& view : session->views()) {
741 		// Get the dock that contains the view
742 		for (auto& entry : view_docks_)
743 			if (entry.second == view) {
744 				entry.first->setObjectName(session->name());
745 				entry.first->setWindowTitle(session->name());
746 			}
747 	}
748 
749 	// Update the tab widget by finding the main window and the tab from that
750 	for (auto& entry : session_windows_)
751 		if (entry.first.get() == session) {
752 			QMainWindow *window = entry.second;
753 			const int index = session_selector_.indexOf(window);
754 			session_selector_.setTabText(index, session->name());
755 		}
756 
757 	// Refresh window title if the affected session has focus
758 	if (session == last_focused_session_.get())
759 		setWindowTitle(session->name() + " - " + WindowTitle);
760 }
761 
on_session_device_changed()762 void MainWindow::on_session_device_changed()
763 {
764 	Session *session = qobject_cast<Session*>(QObject::sender());
765 	assert(session);
766 
767 	// Ignore if caller is not the currently focused session
768 	// unless there is only one session
769 	if ((sessions_.size() > 1) && (session != last_focused_session_.get()))
770 		return;
771 
772 	update_acq_button(session);
773 }
774 
on_session_capture_state_changed(int state)775 void MainWindow::on_session_capture_state_changed(int state)
776 {
777 	(void)state;
778 
779 	Session *session = qobject_cast<Session*>(QObject::sender());
780 	assert(session);
781 
782 	// Ignore if caller is not the currently focused session
783 	// unless there is only one session
784 	if ((sessions_.size() > 1) && (session != last_focused_session_.get()))
785 		return;
786 
787 	update_acq_button(session);
788 }
789 
on_new_view(Session * session,int view_type)790 void MainWindow::on_new_view(Session *session, int view_type)
791 {
792 	// We get a pointer and need a reference
793 	for (shared_ptr<Session>& s : sessions_)
794 		if (s.get() == session)
795 			add_view((views::ViewType)view_type, *s);
796 }
797 
on_view_close_clicked()798 void MainWindow::on_view_close_clicked()
799 {
800 	// Find the dock widget that contains the close button that was clicked
801 	QObject *w = QObject::sender();
802 	QDockWidget *dock = nullptr;
803 
804 	while (w) {
805 	    dock = qobject_cast<QDockWidget*>(w);
806 	    if (dock)
807 	        break;
808 	    w = w->parent();
809 	}
810 
811 	// Get the view contained in the dock widget
812 	shared_ptr<views::ViewBase> view;
813 
814 	for (auto& entry : view_docks_)
815 		if (entry.first == dock)
816 			view = entry.second;
817 
818 	// Deregister the view
819 	for (shared_ptr<Session> session : sessions_) {
820 		if (!session->has_view(view))
821 			continue;
822 
823 		// Also destroy the entire session if its main view is closing...
824 		if (view == session->main_view()) {
825 			// ...but only if data is saved or the user confirms closing
826 			if (session->data_saved() || (QMessageBox::question(this, tr("Confirmation"),
827 				tr("This session contains unsaved data. Close it anyway?"),
828 				QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes))
829 				remove_session(session);
830 			break;
831 		} else
832 			// All other views can be closed at any time as no data will be lost
833 			remove_view(view);
834 	}
835 }
836 
on_tab_changed(int index)837 void MainWindow::on_tab_changed(int index)
838 {
839 	shared_ptr<Session> session = get_tab_session(index);
840 
841 	if (session)
842 		on_focused_session_changed(session);
843 }
844 
on_tab_close_requested(int index)845 void MainWindow::on_tab_close_requested(int index)
846 {
847 	shared_ptr<Session> session = get_tab_session(index);
848 
849 	if (!session)
850 		return;
851 
852 	if (session->data_saved() || (QMessageBox::question(this, tr("Confirmation"),
853 		tr("This session contains unsaved data. Close it anyway?"),
854 		QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes))
855 		remove_session(session);
856 
857 	if (sessions_.empty())
858 		update_acq_button(nullptr);
859 }
860 
on_show_decoder_selector(Session * session)861 void MainWindow::on_show_decoder_selector(Session *session)
862 {
863 #ifdef ENABLE_DECODE
864 	// Close dock widget if it's already showing and return
865 	for (auto& entry : sub_windows_) {
866 		QDockWidget* dock = entry.first;
867 		shared_ptr<subwindows::SubWindowBase> decoder_selector =
868 			dynamic_pointer_cast<subwindows::decoder_selector::SubWindow>(entry.second);
869 
870 		if (decoder_selector && (&decoder_selector->session() == session)) {
871 			sub_windows_.erase(dock);
872 			dock->close();
873 			return;
874 		}
875 	}
876 
877 	// We get a pointer and need a reference
878 	for (shared_ptr<Session>& s : sessions_)
879 		if (s.get() == session)
880 			add_subwindow(subwindows::SubWindowTypeDecoderSelector, *s);
881 #endif
882 }
883 
on_sub_window_close_clicked()884 void MainWindow::on_sub_window_close_clicked()
885 {
886 	// Find the dock widget that contains the close button that was clicked
887 	QObject *w = QObject::sender();
888 	QDockWidget *dock = nullptr;
889 
890 	while (w) {
891 	    dock = qobject_cast<QDockWidget*>(w);
892 	    if (dock)
893 	        break;
894 	    w = w->parent();
895 	}
896 
897 	sub_windows_.erase(dock);
898 	dock->close();
899 
900 	// Restore focus to the last used main view
901 	if (last_focused_session_)
902 		last_focused_session_->main_view()->setFocus();
903 }
904 
on_view_colored_bg_shortcut()905 void MainWindow::on_view_colored_bg_shortcut()
906 {
907 	GlobalSettings settings;
908 
909 	bool state = settings.value(GlobalSettings::Key_View_ColoredBG).toBool();
910 	settings.setValue(GlobalSettings::Key_View_ColoredBG, !state);
911 }
912 
on_view_sticky_scrolling_shortcut()913 void MainWindow::on_view_sticky_scrolling_shortcut()
914 {
915 	GlobalSettings settings;
916 
917 	bool state = settings.value(GlobalSettings::Key_View_StickyScrolling).toBool();
918 	settings.setValue(GlobalSettings::Key_View_StickyScrolling, !state);
919 }
920 
on_view_show_sampling_points_shortcut()921 void MainWindow::on_view_show_sampling_points_shortcut()
922 {
923 	GlobalSettings settings;
924 
925 	bool state = settings.value(GlobalSettings::Key_View_ShowSamplingPoints).toBool();
926 	settings.setValue(GlobalSettings::Key_View_ShowSamplingPoints, !state);
927 }
928 
on_view_show_analog_minor_grid_shortcut()929 void MainWindow::on_view_show_analog_minor_grid_shortcut()
930 {
931 	GlobalSettings settings;
932 
933 	bool state = settings.value(GlobalSettings::Key_View_ShowAnalogMinorGrid).toBool();
934 	settings.setValue(GlobalSettings::Key_View_ShowAnalogMinorGrid, !state);
935 }
936 
on_close_current_tab()937 void MainWindow::on_close_current_tab()
938 {
939 	int tab = session_selector_.currentIndex();
940 
941 	on_tab_close_requested(tab);
942 }
943 
944 } // namespace pv
945