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