]> sigrok.org Git - pulseview.git/blob - pv/mainwindow.cpp
662d68af2f29bdba5ceff5f68931855d18708026
[pulseview.git] / pv / mainwindow.cpp
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 #include <cassert>
21
22 #ifdef ENABLE_DECODE
23 #include <libsigrokdecode/libsigrokdecode.h>
24 #endif
25
26 #include <algorithm>
27 #include <iterator>
28
29 #include <QAction>
30 #include <QApplication>
31 #include <QCloseEvent>
32 #include <QDockWidget>
33 #include <QHBoxLayout>
34 #include <QMessageBox>
35 #include <QSettings>
36 #include <QWidget>
37 #include <QShortcut>
38
39 #include "mainwindow.hpp"
40
41 #include "devicemanager.hpp"
42 #include "util.hpp"
43 #include "devices/hardwaredevice.hpp"
44 #include "dialogs/about.hpp"
45 #include "toolbars/mainbar.hpp"
46 #include "view/view.hpp"
47 #include "views/trace/standardbar.hpp"
48
49 #include <stdint.h>
50 #include <stdarg.h>
51 #include <libsigrokcxx/libsigrokcxx.hpp>
52
53 using std::dynamic_pointer_cast;
54 using std::list;
55 using std::make_shared;
56 using std::map;
57 using std::shared_ptr;
58 using std::string;
59
60 namespace pv {
61
62 namespace view {
63 class ViewItem;
64 }
65
66 using toolbars::MainBar;
67
68 const QString MainWindow::WindowTitle = tr("PulseView");
69
70 MainWindow::MainWindow(DeviceManager &device_manager,
71         string open_file_name, string open_file_format,
72         QWidget *parent) :
73         QMainWindow(parent),
74         device_manager_(device_manager),
75         session_selector_(this),
76         session_state_mapper_(this),
77         action_view_sticky_scrolling_(new QAction(this)),
78         action_view_coloured_bg_(new QAction(this)),
79         action_about_(new QAction(this)),
80         icon_red_(":/icons/status-red.svg"),
81         icon_green_(":/icons/status-green.svg"),
82         icon_grey_(":/icons/status-grey.svg")
83 {
84         qRegisterMetaType<util::Timestamp>("util::Timestamp");
85         qRegisterMetaType<uint64_t>("uint64_t");
86
87         setup_ui();
88         restore_ui_settings();
89
90         if (!open_file_name.empty()) {
91                 shared_ptr<Session> session = add_session();
92                 session->load_init_file(open_file_name, open_file_format);
93         }
94
95         // Add empty default session if there aren't any sessions
96         if (sessions_.size() == 0) {
97                 shared_ptr<Session> session = add_session();
98
99                 map<string, string> dev_info;
100                 shared_ptr<devices::HardwareDevice> other_device, demo_device;
101
102                 // Use any available device that's not demo
103                 for (shared_ptr<devices::HardwareDevice> dev : device_manager_.devices()) {
104                         if (dev->hardware_device()->driver()->name() == "demo") {
105                                 demo_device = dev;
106                         } else {
107                                 other_device = dev;
108                         }
109                 }
110
111                 // ...and if there isn't any, just use demo then
112                 session->select_device(other_device ? other_device : demo_device);
113         }
114 }
115
116 MainWindow::~MainWindow()
117 {
118         while (!sessions_.empty())
119                 remove_session(sessions_.front());
120 }
121
122 QAction* MainWindow::action_view_sticky_scrolling() const
123 {
124         return action_view_sticky_scrolling_;
125 }
126
127 QAction* MainWindow::action_view_coloured_bg() const
128 {
129         return action_view_coloured_bg_;
130 }
131
132 QAction* MainWindow::action_about() const
133 {
134         return action_about_;
135 }
136
137 shared_ptr<views::ViewBase> MainWindow::get_active_view() const
138 {
139         // If there's only one view, use it...
140         if (view_docks_.size() == 1)
141                 return view_docks_.begin()->second;
142
143         // ...otherwise find the dock widget the widget with focus is contained in
144         QObject *w = QApplication::focusWidget();
145         QDockWidget *dock = 0;
146
147         while (w) {
148             dock = qobject_cast<QDockWidget*>(w);
149             if (dock)
150                 break;
151             w = w->parent();
152         }
153
154         // Get the view contained in the dock widget
155         for (auto entry : view_docks_)
156                 if (entry.first == dock)
157                         return entry.second;
158
159         return nullptr;
160 }
161
162 shared_ptr<views::ViewBase> MainWindow::add_view(const QString &title,
163         views::ViewType type, Session &session)
164 {
165         QMainWindow *main_window = nullptr;
166         for (auto entry : session_windows_)
167                 if (entry.first.get() == &session)
168                         main_window = entry.second;
169
170         assert(main_window);
171
172         if (type == views::ViewTypeTrace) {
173                 QDockWidget* dock = new QDockWidget(title, main_window);
174                 dock->setObjectName(title);
175                 main_window->addDockWidget(Qt::TopDockWidgetArea, dock);
176
177                 // Insert a QMainWindow into the dock widget to allow for a tool bar
178                 QMainWindow *dock_main = new QMainWindow(dock);
179                 dock_main->setWindowFlags(Qt::Widget);  // Remove Qt::Window flag
180
181                 shared_ptr<views::TraceView::View> v =
182                         make_shared<views::TraceView::View>(session, dock_main);
183                 view_docks_[dock] = v;
184                 session.register_view(v);
185
186                 dock_main->setCentralWidget(v.get());
187                 dock->setWidget(dock_main);
188
189                 dock->setFeatures(QDockWidget::DockWidgetMovable |
190                         QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetClosable);
191
192                 QAbstractButton *close_btn =
193                         dock->findChildren<QAbstractButton*>
194                                 ("qt_dockwidget_closebutton").front();
195
196                 connect(close_btn, SIGNAL(clicked(bool)),
197                         this, SLOT(on_view_close_clicked()));
198
199                 if (type == views::ViewTypeTrace) {
200                         connect(&session, SIGNAL(trigger_event(util::Timestamp)),
201                                 qobject_cast<views::ViewBase*>(v.get()),
202                                 SLOT(trigger_event(util::Timestamp)));
203
204                         v->enable_sticky_scrolling(action_view_sticky_scrolling_->isChecked());
205                         v->enable_coloured_bg(action_view_coloured_bg_->isChecked());
206
207                         shared_ptr<MainBar> main_bar = session.main_bar();
208                         if (!main_bar) {
209                                 /* Initial view, create the main bar */
210                                 main_bar = make_shared<MainBar>(session, this, v.get());
211                                 dock_main->addToolBar(main_bar.get());
212                                 session.set_main_bar(main_bar);
213
214                                 connect(main_bar.get(), SIGNAL(new_view(Session*)),
215                                         this, SLOT(on_new_view(Session*)));
216
217                                 main_bar->action_view_show_cursors()->setChecked(v->cursors_shown());
218
219                                 /* For the main view we need to prevent the dock widget from
220                                  * closing itself when its close button is clicked. This is
221                                  * so we can confirm with the user first. Regular views don't
222                                  * need this */
223                                 close_btn->disconnect(SIGNAL(clicked()), dock, SLOT(close()));
224                         } else {
225                                 /* Additional view, create a standard bar */
226                                 pv::views::trace::StandardBar *standard_bar =
227                                         new pv::views::trace::StandardBar(session, this, v.get());
228                                 dock_main->addToolBar(standard_bar);
229
230                                 standard_bar->action_view_show_cursors()->setChecked(v->cursors_shown());
231                         }
232                 }
233
234                 return v;
235         }
236
237         return nullptr;
238 }
239
240 void MainWindow::remove_view(shared_ptr<views::ViewBase> view)
241 {
242         for (shared_ptr<Session> session : sessions_) {
243                 if (!session->has_view(view))
244                         continue;
245
246                 // Find the dock the view is contained in and remove it
247                 for (auto entry : view_docks_)
248                         if (entry.second == view) {
249                                 // Remove the view from the session
250                                 session->deregister_view(view);
251
252                                 // Remove the view from its parent; otherwise, Qt will
253                                 // call deleteLater() on it, which causes a double free
254                                 // since the shared_ptr in view_docks_ doesn't know
255                                 // that Qt keeps a pointer to the view around
256                                 view->setParent(0);
257
258                                 // Delete the view's dock widget and all widgets inside it
259                                 entry.first->deleteLater();
260
261                                 // Remove the dock widget from the list and stop iterating
262                                 view_docks_.erase(entry.first);
263                                 break;
264                         }
265         }
266 }
267
268 shared_ptr<Session> MainWindow::add_session()
269 {
270         static int last_session_id = 1;
271         QString name = tr("Untitled-%1").arg(last_session_id++);
272
273         shared_ptr<Session> session = make_shared<Session>(device_manager_, name);
274
275         connect(session.get(), SIGNAL(add_view(const QString&, views::ViewType, Session*)),
276                 this, SLOT(on_add_view(const QString&, views::ViewType, Session*)));
277         connect(session.get(), SIGNAL(name_changed()),
278                 this, SLOT(on_session_name_changed()));
279         session_state_mapper_.setMapping(session.get(), session.get());
280         connect(session.get(), SIGNAL(capture_state_changed(int)),
281                 &session_state_mapper_, SLOT(map()));
282
283         sessions_.push_back(session);
284
285         QMainWindow *window = new QMainWindow();
286         window->setWindowFlags(Qt::Widget);  // Remove Qt::Window flag
287         session_windows_[session] = window;
288
289         int index = session_selector_.addTab(window, name);
290         session_selector_.setCurrentIndex(index);
291         last_focused_session_ = session;
292
293         window->setDockNestingEnabled(true);
294
295         shared_ptr<views::ViewBase> main_view =
296                 add_view(name, views::ViewTypeTrace, *session);
297
298         return session;
299 }
300
301 void MainWindow::remove_session(shared_ptr<Session> session)
302 {
303         int h = new_session_button_->height();
304
305         for (shared_ptr<views::ViewBase> view : session->views())
306                 remove_view(view);
307
308         QMainWindow *window = session_windows_.at(session);
309         session_selector_.removeTab(session_selector_.indexOf(window));
310
311         session_windows_.erase(session);
312
313         if (last_focused_session_ == session)
314                 last_focused_session_.reset();
315
316         sessions_.remove_if([&](shared_ptr<Session> s) {
317                 return s == session; });
318
319         if (sessions_.empty()) {
320                 // When there are no more tabs, the height of the QTabWidget
321                 // drops to zero. We must prevent this to keep the static
322                 // widgets visible
323                 for (QWidget *w : static_tab_widget_->findChildren<QWidget*>())
324                         w->setMinimumHeight(h);
325
326                 int margin = static_tab_widget_->layout()->contentsMargins().bottom();
327                 static_tab_widget_->setMinimumHeight(h + 2 * margin);
328                 session_selector_.setMinimumHeight(h + 2 * margin);
329
330                 // Update the window title if there is no view left to
331                 // generate focus change events
332                 setWindowTitle(WindowTitle);
333         }
334 }
335
336 void MainWindow::setup_ui()
337 {
338         setObjectName(QString::fromUtf8("MainWindow"));
339
340         setCentralWidget(&session_selector_);
341
342         // Set the window icon
343         QIcon icon;
344         icon.addFile(QString(":/icons/sigrok-logo-notext.png"));
345         setWindowIcon(icon);
346
347         action_view_sticky_scrolling_->setCheckable(true);
348         action_view_sticky_scrolling_->setChecked(true);
349         action_view_sticky_scrolling_->setShortcut(QKeySequence(Qt::Key_S));
350         action_view_sticky_scrolling_->setObjectName(
351                 QString::fromUtf8("actionViewStickyScrolling"));
352         action_view_sticky_scrolling_->setText(tr("&Sticky Scrolling"));
353
354         action_view_coloured_bg_->setCheckable(true);
355         action_view_coloured_bg_->setChecked(true);
356         action_view_coloured_bg_->setShortcut(QKeySequence(Qt::Key_B));
357         action_view_coloured_bg_->setObjectName(
358                 QString::fromUtf8("actionViewColouredBg"));
359         action_view_coloured_bg_->setText(tr("Use &Coloured Backgrounds"));
360
361         action_about_->setObjectName(QString::fromUtf8("actionAbout"));
362         action_about_->setToolTip(tr("&About..."));
363
364         // Set up the tab area
365         new_session_button_ = new QToolButton();
366         new_session_button_->setIcon(QIcon::fromTheme("document-new",
367                 QIcon(":/icons/document-new.png")));
368         new_session_button_->setToolTip(tr("Create New Session"));
369         new_session_button_->setAutoRaise(true);
370
371         run_stop_button_ = new QToolButton();
372         run_stop_button_->setAutoRaise(true);
373         run_stop_button_->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
374         run_stop_button_->setToolTip(tr("Start/Stop Acquisition"));
375
376         run_stop_shortcut_ = new QShortcut(QKeySequence(Qt::Key_Space), run_stop_button_, SLOT(click()));
377         run_stop_shortcut_->setAutoRepeat(false);
378
379         settings_button_ = new QToolButton();
380         settings_button_->setIcon(QIcon::fromTheme("configure",
381                 QIcon(":/icons/configure.png")));
382         settings_button_->setToolTip(tr("Settings"));
383         settings_button_->setAutoRaise(true);
384
385         QFrame *separator1 = new QFrame();
386         separator1->setFrameStyle(QFrame::VLine | QFrame::Raised);
387         QFrame *separator2 = new QFrame();
388         separator2->setFrameStyle(QFrame::VLine | QFrame::Raised);
389
390         QHBoxLayout* layout = new QHBoxLayout();
391         layout->setContentsMargins(2, 2, 2, 2);
392         layout->addWidget(new_session_button_);
393         layout->addWidget(separator1);
394         layout->addWidget(run_stop_button_);
395         layout->addWidget(separator2);
396         layout->addWidget(settings_button_);
397
398         static_tab_widget_ = new QWidget();
399         static_tab_widget_->setLayout(layout);
400
401         session_selector_.setCornerWidget(static_tab_widget_, Qt::TopLeftCorner);
402         session_selector_.setTabsClosable(true);
403
404         close_application_shortcut_ = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q), this, SLOT(close()));
405         close_application_shortcut_->setAutoRepeat(false);
406
407         close_current_tab_shortcut_ = new QShortcut(QKeySequence(Qt::CTRL + Qt::Key_W), this, SLOT(on_close_current_tab()));
408
409         connect(new_session_button_, SIGNAL(clicked(bool)),
410                 this, SLOT(on_new_session_clicked()));
411         connect(run_stop_button_, SIGNAL(clicked(bool)),
412                 this, SLOT(on_run_stop_clicked()));
413         connect(&session_state_mapper_, SIGNAL(mapped(QObject*)),
414                 this, SLOT(on_capture_state_changed(QObject*)));
415
416         connect(&session_selector_, SIGNAL(tabCloseRequested(int)),
417                 this, SLOT(on_tab_close_requested(int)));
418         connect(&session_selector_, SIGNAL(currentChanged(int)),
419                 this, SLOT(on_tab_changed(int)));
420
421
422         connect(static_cast<QApplication *>(QCoreApplication::instance()),
423                 SIGNAL(focusChanged(QWidget*, QWidget*)),
424                 this, SLOT(on_focus_changed()));
425 }
426
427 void MainWindow::save_ui_settings()
428 {
429         QSettings settings;
430         int id = 0;
431
432         settings.beginGroup("MainWindow");
433         settings.setValue("state", saveState());
434         settings.setValue("geometry", saveGeometry());
435         settings.endGroup();
436
437         for (shared_ptr<Session> session : sessions_) {
438                 // Ignore sessions using the demo device or no device at all
439                 if (session->device()) {
440                         shared_ptr<devices::HardwareDevice> device =
441                                 dynamic_pointer_cast< devices::HardwareDevice >
442                                 (session->device());
443
444                         if (device &&
445                                 device->hardware_device()->driver()->name() == "demo")
446                                 continue;
447
448                         settings.beginGroup("Session" + QString::number(id++));
449                         settings.remove("");  // Remove all keys in this group
450                         session->save_settings(settings);
451                         settings.endGroup();
452                 }
453         }
454
455         settings.setValue("sessions", id);
456 }
457
458 void MainWindow::restore_ui_settings()
459 {
460         QSettings settings;
461         int i, session_count;
462
463         settings.beginGroup("MainWindow");
464
465         if (settings.contains("geometry")) {
466                 restoreGeometry(settings.value("geometry").toByteArray());
467                 restoreState(settings.value("state").toByteArray());
468         } else
469                 resize(1000, 720);
470
471         settings.endGroup();
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
483 std::shared_ptr<Session> MainWindow::get_tab_session(int index) const
484 {
485         // Find the session that belongs to the tab's main window
486         for (auto entry : session_windows_)
487                 if (entry.second == session_selector_.widget(index))
488                         return entry.first;
489
490         return nullptr;
491 }
492
493 void MainWindow::closeEvent(QCloseEvent *event)
494 {
495         bool data_saved = true;
496
497         for (auto entry : session_windows_)
498                 if (!entry.first->data_saved())
499                         data_saved = false;
500
501         if (!data_saved && (QMessageBox::question(this, tr("Confirmation"),
502                 tr("There is unsaved data. Close anyway?"),
503                 QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) {
504                 event->ignore();
505         } else {
506                 save_ui_settings();
507                 event->accept();
508         }
509 }
510
511 QMenu* MainWindow::createPopupMenu()
512 {
513         return nullptr;
514 }
515
516 bool MainWindow::restoreState(const QByteArray &state, int version)
517 {
518         (void)state;
519         (void)version;
520
521         // Do nothing. We don't want Qt to handle this, or else it
522         // will try to restore all the dock widgets and create havoc.
523
524         return false;
525 }
526
527 void MainWindow::session_error(const QString text, const QString info_text)
528 {
529         QMetaObject::invokeMethod(this, "show_session_error",
530                 Qt::QueuedConnection, Q_ARG(QString, text),
531                 Q_ARG(QString, info_text));
532 }
533
534 void MainWindow::show_session_error(const QString text, const QString info_text)
535 {
536         QMessageBox msg(this);
537         msg.setText(text);
538         msg.setInformativeText(info_text);
539         msg.setStandardButtons(QMessageBox::Ok);
540         msg.setIcon(QMessageBox::Warning);
541         msg.exec();
542 }
543
544 void MainWindow::on_add_view(const QString &title, views::ViewType type,
545         Session *session)
546 {
547         // We get a pointer and need a reference
548         for (std::shared_ptr<Session> s : sessions_)
549                 if (s.get() == session)
550                         add_view(title, type, *s);
551 }
552
553 void MainWindow::on_focus_changed()
554 {
555         shared_ptr<views::ViewBase> view = get_active_view();
556
557         if (view) {
558                 for (shared_ptr<Session> session : sessions_) {
559                         if (session->has_view(view)) {
560                                 if (session != last_focused_session_) {
561                                         // Activate correct tab if necessary
562                                         shared_ptr<Session> tab_session = get_tab_session(
563                                                 session_selector_.currentIndex());
564                                         if (tab_session != session)
565                                                 session_selector_.setCurrentWidget(
566                                                         session_windows_.at(session));
567
568                                         on_focused_session_changed(session);
569                                 }
570
571                                 break;
572                         }
573                 }
574         }
575
576         if (sessions_.empty())
577                 setWindowTitle(WindowTitle);
578 }
579
580 void MainWindow::on_focused_session_changed(shared_ptr<Session> session)
581 {
582         last_focused_session_ = session;
583
584         setWindowTitle(session->name() + " - " + WindowTitle);
585
586         // Update the state of the run/stop button, too
587         on_capture_state_changed(session.get());
588 }
589
590 void MainWindow::on_new_session_clicked()
591 {
592         add_session();
593 }
594
595 void MainWindow::on_run_stop_clicked()
596 {
597         shared_ptr<Session> session = last_focused_session_;
598
599         if (!session)
600                 return;
601
602         switch (session->get_capture_state()) {
603         case Session::Stopped:
604                 session->start_capture([&](QString message) {
605                         session_error("Capture failed", message); });
606                 break;
607         case Session::AwaitingTrigger:
608         case Session::Running:
609                 session->stop_capture();
610                 break;
611         }
612 }
613
614 void MainWindow::on_session_name_changed()
615 {
616         // Update the corresponding dock widget's name(s)
617         Session *session = qobject_cast<Session*>(QObject::sender());
618         assert(session);
619
620         for (shared_ptr<views::ViewBase> view : session->views()) {
621                 // Get the dock that contains the view
622                 for (auto entry : view_docks_)
623                         if (entry.second == view) {
624                                 entry.first->setObjectName(session->name());
625                                 entry.first->setWindowTitle(session->name());
626                         }
627         }
628
629         // Update the tab widget by finding the main window and the tab from that
630         for (auto entry : session_windows_)
631                 if (entry.first.get() == session) {
632                         QMainWindow *window = entry.second;
633                         const int index = session_selector_.indexOf(window);
634                         session_selector_.setTabText(index, session->name());
635                 }
636
637         // Refresh window title if the affected session has focus
638         if (session == last_focused_session_.get())
639                 setWindowTitle(session->name() + " - " + WindowTitle);
640 }
641
642 void MainWindow::on_capture_state_changed(QObject *obj)
643 {
644         Session *caller = qobject_cast<Session*>(obj);
645
646         // Ignore if caller is not the currently focused session
647         // unless there is only one session
648         if ((sessions_.size() > 1) && (caller != last_focused_session_.get()))
649                 return;
650
651         int state = caller->get_capture_state();
652
653         const QIcon *icons[] = {&icon_grey_, &icon_red_, &icon_green_};
654         run_stop_button_->setIcon(*icons[state]);
655         run_stop_button_->setText((state == pv::Session::Stopped) ?
656                 tr("Run") : tr("Stop"));
657 }
658
659 void MainWindow::on_new_view(Session *session)
660 {
661         // We get a pointer and need a reference
662         for (std::shared_ptr<Session> s : sessions_)
663                 if (s.get() == session)
664                         add_view(session->name(), views::ViewTypeTrace, *s);
665 }
666
667 void MainWindow::on_view_close_clicked()
668 {
669         // Find the dock widget that contains the close button that was clicked
670         QObject *w = QObject::sender();
671         QDockWidget *dock = 0;
672
673         while (w) {
674             dock = qobject_cast<QDockWidget*>(w);
675             if (dock)
676                 break;
677             w = w->parent();
678         }
679
680         // Get the view contained in the dock widget
681         shared_ptr<views::ViewBase> view;
682
683         for (auto entry : view_docks_)
684                 if (entry.first == dock)
685                         view = entry.second;
686
687         // Deregister the view
688         for (shared_ptr<Session> session : sessions_) {
689                 if (!session->has_view(view))
690                         continue;
691
692                 // Also destroy the entire session if its main view is closing...
693                 if (view == session->main_view()) {
694                         // ...but only if data is saved or the user confirms closing
695                         if (session->data_saved() || (QMessageBox::question(this, tr("Confirmation"),
696                                 tr("This session contains unsaved data. Close it anyway?"),
697                                 QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes))
698                                 remove_session(session);
699                         break;
700                 } else
701                         // All other views can be closed at any time as no data will be lost
702                         remove_view(view);
703         }
704 }
705
706 void MainWindow::on_tab_changed(int index)
707 {
708         shared_ptr<Session> session = get_tab_session(index);
709
710         if (session)
711                 on_focused_session_changed(session);
712 }
713
714 void MainWindow::on_tab_close_requested(int index)
715 {
716         shared_ptr<Session> session = get_tab_session(index);
717
718         assert(session);
719
720         if (session->data_saved() || (QMessageBox::question(this, tr("Confirmation"),
721                 tr("This session contains unsaved data. Close it anyway?"),
722                 QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes))
723                 remove_session(session);
724 }
725
726 void MainWindow::on_actionViewStickyScrolling_triggered()
727 {
728         shared_ptr<views::ViewBase> viewbase = get_active_view();
729         views::TraceView::View* view =
730                 qobject_cast<views::TraceView::View*>(viewbase.get());
731         if (view)
732                 view->enable_sticky_scrolling(action_view_sticky_scrolling_->isChecked());
733 }
734
735 void MainWindow::on_actionViewColouredBg_triggered()
736 {
737         shared_ptr<views::ViewBase> viewbase = get_active_view();
738         views::TraceView::View* view =
739                         qobject_cast<views::TraceView::View*>(viewbase.get());
740         if (view)
741                 view->enable_coloured_bg(action_view_coloured_bg_->isChecked());
742 }
743
744 void MainWindow::on_actionAbout_triggered()
745 {
746         dialogs::About dlg(device_manager_.context(), this);
747         dlg.exec();
748 }
749
750 void MainWindow::on_close_current_tab()
751 {
752         int tab = session_selector_.currentIndex();
753
754         on_tab_close_requested(tab);
755 }
756
757 } // namespace pv