]> sigrok.org Git - pulseview.git/blob - pv/mainwindow.cpp
Introduce MainWindow::get_active_view()
[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, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
19  */
20
21 #include <cassert>
22
23 #ifdef ENABLE_DECODE
24 #include <libsigrokdecode/libsigrokdecode.h>
25 #endif
26
27 #include <algorithm>
28 #include <iterator>
29
30 #include <boost/algorithm/string/join.hpp>
31
32 #include <QAction>
33 #include <QApplication>
34 #include <QButtonGroup>
35 #include <QCloseEvent>
36 #include <QFileDialog>
37 #include <QMessageBox>
38 #include <QMenu>
39 #include <QMenuBar>
40 #include <QSettings>
41 #include <QStatusBar>
42 #include <QVBoxLayout>
43 #include <QWidget>
44
45 #include <QDockWidget>
46
47 #include "mainwindow.hpp"
48
49 #include "devicemanager.hpp"
50 #include "util.hpp"
51 #include "data/segment.hpp"
52 #include "devices/hardwaredevice.hpp"
53 #include "devices/inputfile.hpp"
54 #include "devices/sessionfile.hpp"
55 #include "dialogs/about.hpp"
56 #include "dialogs/connect.hpp"
57 #include "dialogs/inputoutputoptions.hpp"
58 #include "dialogs/storeprogress.hpp"
59 #include "toolbars/mainbar.hpp"
60 #include "view/logicsignal.hpp"
61 #include "view/view.hpp"
62 #include "widgets/exportmenu.hpp"
63 #include "widgets/importmenu.hpp"
64 #ifdef ENABLE_DECODE
65 #include "widgets/decodermenu.hpp"
66 #endif
67 #include "widgets/hidingmenubar.hpp"
68
69 #include <inttypes.h>
70 #include <stdint.h>
71 #include <stdarg.h>
72 #include <glib.h>
73 #include <libsigrokcxx/libsigrokcxx.hpp>
74
75 using std::cerr;
76 using std::endl;
77 using std::list;
78 using std::make_shared;
79 using std::map;
80 using std::max;
81 using std::pair;
82 using std::shared_ptr;
83 using std::string;
84 using std::vector;
85
86 using boost::algorithm::join;
87
88 using sigrok::Error;
89 using sigrok::OutputFormat;
90 using sigrok::InputFormat;
91
92 namespace pv {
93
94 namespace view {
95 class ViewItem;
96 }
97
98 const char *MainWindow::SettingOpenDirectory = "MainWindow/OpenDirectory";
99 const char *MainWindow::SettingSaveDirectory = "MainWindow/SaveDirectory";
100
101 MainWindow::MainWindow(DeviceManager &device_manager,
102         string open_file_name, string open_file_format,
103         QWidget *parent) :
104         QMainWindow(parent),
105         device_manager_(device_manager),
106         session_(device_manager),
107         action_open_(new QAction(this)),
108         action_save_as_(new QAction(this)),
109         action_save_selection_as_(new QAction(this)),
110         action_connect_(new QAction(this)),
111         action_quit_(new QAction(this)),
112         action_view_zoom_in_(new QAction(this)),
113         action_view_zoom_out_(new QAction(this)),
114         action_view_zoom_fit_(new QAction(this)),
115         action_view_zoom_one_to_one_(new QAction(this)),
116         action_view_sticky_scrolling_(new QAction(this)),
117         action_view_coloured_bg_(new QAction(this)),
118         action_view_show_cursors_(new QAction(this)),
119         action_about_(new QAction(this))
120 #ifdef ENABLE_DECODE
121         , menu_decoders_add_(new pv::widgets::DecoderMenu(this, true))
122 #endif
123 {
124         qRegisterMetaType<util::Timestamp>("util::Timestamp");
125
126         setup_ui();
127         restore_ui_settings();
128         if (open_file_name.empty())
129                 select_init_device();
130         else
131                 load_init_file(open_file_name, open_file_format);
132 }
133
134 QAction* MainWindow::action_open() const
135 {
136         return action_open_;
137 }
138
139 QAction* MainWindow::action_save_as() const
140 {
141         return action_save_as_;
142 }
143
144 QAction* MainWindow::action_save_selection_as() const
145 {
146         return action_save_selection_as_;
147 }
148
149 QAction* MainWindow::action_connect() const
150 {
151         return action_connect_;
152 }
153
154 QAction* MainWindow::action_quit() const
155 {
156         return action_quit_;
157 }
158
159 QAction* MainWindow::action_view_zoom_in() const
160 {
161         return action_view_zoom_in_;
162 }
163
164 QAction* MainWindow::action_view_zoom_out() const
165 {
166         return action_view_zoom_out_;
167 }
168
169 QAction* MainWindow::action_view_zoom_fit() const
170 {
171         return action_view_zoom_fit_;
172 }
173
174 QAction* MainWindow::action_view_zoom_one_to_one() const
175 {
176         return action_view_zoom_one_to_one_;
177 }
178
179 QAction* MainWindow::action_view_sticky_scrolling() const
180 {
181         return action_view_sticky_scrolling_;
182 }
183
184 QAction* MainWindow::action_view_coloured_bg() const
185 {
186         return action_view_coloured_bg_;
187 }
188
189 QAction* MainWindow::action_view_show_cursors() const
190 {
191         return action_view_show_cursors_;
192 }
193
194 QAction* MainWindow::action_about() const
195 {
196         return action_about_;
197 }
198
199 #ifdef ENABLE_DECODE
200 QMenu* MainWindow::menu_decoder_add() const
201 {
202         return menu_decoders_add_;
203 }
204 #endif
205
206 shared_ptr<pv::view::View> MainWindow::get_active_view() const
207 {
208         // If there's only one view, use it...
209         if (view_docks_.size() == 1)
210                 return view_docks_.begin()->second;
211
212         // ...otherwise find the dock widget the widget with focus is contained in
213         QObject *w = QApplication::focusWidget();
214         QDockWidget *dock = 0;
215
216         while (w) {
217             dock = qobject_cast<QDockWidget*>(w);
218             if (dock)
219                 break;
220             w = w->parent();
221         }
222
223         // Get the view contained in the dock widget
224         for (auto entry : view_docks_)
225                 if (entry.first.get() == dock)
226                         return entry.second;
227
228         return shared_ptr<pv::view::View>();
229 }
230
231 void MainWindow::run_stop()
232 {
233         switch (session_.get_capture_state()) {
234         case Session::Stopped:
235                 session_.start_capture([&](QString message) {
236                         session_error("Capture failed", message); });
237                 break;
238         case Session::AwaitingTrigger:
239         case Session::Running:
240                 session_.stop_capture();
241                 break;
242         }
243 }
244
245 void MainWindow::select_device(shared_ptr<devices::Device> device)
246 {
247         try {
248                 if (device)
249                         session_.set_device(device);
250                 else
251                         session_.set_default_device();
252         } catch (const QString &e) {
253                 QMessageBox msg(this);
254                 msg.setText(e);
255                 msg.setInformativeText(tr("Failed to Select Device"));
256                 msg.setStandardButtons(QMessageBox::Ok);
257                 msg.setIcon(QMessageBox::Warning);
258                 msg.exec();
259         }
260 }
261
262 void MainWindow::export_file(shared_ptr<OutputFormat> format,
263         bool selection_only)
264 {
265         using pv::dialogs::StoreProgress;
266
267         // Make sure there's a view selected to pull the data from
268         shared_ptr<pv::view::View> view = get_active_view();
269         if (!view) {
270                 show_session_error(tr("No View Selected"), tr("Please click on the " \
271                                 "view whose data you want to save and try again."));
272                 return;
273         }
274
275         // Stop any currently running capture session
276         session_.stop_capture();
277
278         QSettings settings;
279         const QString dir = settings.value(SettingSaveDirectory).toString();
280
281         std::pair<uint64_t, uint64_t> sample_range;
282
283         // Selection only? Verify that the cursors are active and fetch their values
284         if (selection_only) {
285                 if (!view->cursors()->enabled()) {
286                         show_session_error(tr("Missing Cursors"), tr("You need to set the " \
287                                         "cursors before you can save the data enclosed by them " \
288                                         "to a session file (e.g. using ALT-V - Show Cursors)."));
289                         return;
290                 }
291
292                 const double samplerate = session_.get_samplerate();
293
294                 const pv::util::Timestamp& start_time = view->cursors()->first()->time();
295                 const pv::util::Timestamp& end_time = view->cursors()->second()->time();
296
297                 const uint64_t start_sample =
298                         std::max((double)0, start_time.convert_to<double>() * samplerate);
299                 const uint64_t end_sample = end_time.convert_to<double>() * samplerate;
300
301                 sample_range = std::make_pair(start_sample, end_sample);
302         } else {
303                 sample_range = std::make_pair(0, 0);
304         }
305
306         // Construct the filter
307         const vector<string> exts = format->extensions();
308         QString filter = tr("%1 files ").arg(
309                 QString::fromStdString(format->description()));
310
311         if (exts.empty())
312                 filter += "(*.*)";
313         else
314                 filter += QString("(*.%1);;%2 (*.*)").arg(
315                         QString::fromStdString(join(exts, ", *.")),
316                         tr("All Files"));
317
318         // Show the file dialog
319         const QString file_name = QFileDialog::getSaveFileName(
320                 this, tr("Save File"), dir, filter);
321
322         if (file_name.isEmpty())
323                 return;
324
325         const QString abs_path = QFileInfo(file_name).absolutePath();
326         settings.setValue(SettingSaveDirectory, abs_path);
327
328         // Show the options dialog
329         map<string, Glib::VariantBase> options;
330         if (!format->options().empty()) {
331                 dialogs::InputOutputOptions dlg(
332                         tr("Export %1").arg(QString::fromStdString(
333                                 format->description())),
334                         format->options(), this);
335                 if (!dlg.exec())
336                         return;
337                 options = dlg.options();
338         }
339
340         StoreProgress *dlg = new StoreProgress(file_name, format, options,
341                 sample_range, session_, this);
342         dlg->run();
343 }
344
345 void MainWindow::import_file(shared_ptr<InputFormat> format)
346 {
347         assert(format);
348
349         QSettings settings;
350         const QString dir = settings.value(SettingOpenDirectory).toString();
351
352         // Construct the filter
353         const vector<string> exts = format->extensions();
354         const QString filter = exts.empty() ? "" :
355                 tr("%1 files (*.%2)").arg(
356                         QString::fromStdString(format->description()),
357                         QString::fromStdString(join(exts, ", *.")));
358
359         // Show the file dialog
360         const QString file_name = QFileDialog::getOpenFileName(
361                 this, tr("Import File"), dir, tr(
362                         "%1 files (*.*);;All Files (*.*)").arg(
363                         QString::fromStdString(format->description())));
364
365         if (file_name.isEmpty())
366                 return;
367
368         // Show the options dialog
369         map<string, Glib::VariantBase> options;
370         if (!format->options().empty()) {
371                 dialogs::InputOutputOptions dlg(
372                         tr("Import %1").arg(QString::fromStdString(
373                                 format->description())),
374                         format->options(), this);
375                 if (!dlg.exec())
376                         return;
377                 options = dlg.options();
378         }
379
380         load_file(file_name, format, options);
381
382         const QString abs_path = QFileInfo(file_name).absolutePath();
383         settings.setValue(SettingOpenDirectory, abs_path);
384 }
385
386 void MainWindow::setup_ui()
387 {
388         setObjectName(QString::fromUtf8("MainWindow"));
389
390         // Set the window icon
391         QIcon icon;
392         icon.addFile(QString(":/icons/sigrok-logo-notext.svg"));
393         setWindowIcon(icon);
394
395         // Set up the initial view
396         shared_ptr<pv::view::View> view = make_shared<pv::view::View>(session_, this);
397         shared_ptr<QDockWidget> dock = make_shared<QDockWidget>(tr("Untitled"), this);
398         dock->setWidget(view.get());
399         addDockWidget(Qt::TopDockWidgetArea, dock.get());
400         view_docks_[dock] = view;
401
402         // Setup the menu bar
403         pv::widgets::HidingMenuBar *const menu_bar =
404                 new pv::widgets::HidingMenuBar(this);
405
406         // File Menu
407         QMenu *const menu_file = new QMenu;
408         menu_file->setTitle(tr("&File"));
409
410         action_open_->setText(tr("&Open..."));
411         action_open_->setIcon(QIcon::fromTheme("document-open",
412                 QIcon(":/icons/document-open.png")));
413         action_open_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_O));
414         action_open_->setObjectName(QString::fromUtf8("actionOpen"));
415         menu_file->addAction(action_open_);
416
417         action_save_as_->setText(tr("&Save As..."));
418         action_save_as_->setIcon(QIcon::fromTheme("document-save-as",
419                 QIcon(":/icons/document-save-as.png")));
420         action_save_as_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S));
421         action_save_as_->setObjectName(QString::fromUtf8("actionSaveAs"));
422         menu_file->addAction(action_save_as_);
423
424         action_save_selection_as_->setText(tr("Save Selected &Range As..."));
425         action_save_selection_as_->setIcon(QIcon::fromTheme("document-save-as",
426                 QIcon(":/icons/document-save-as.png")));
427         action_save_selection_as_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R));
428         action_save_selection_as_->setObjectName(QString::fromUtf8("actionSaveSelectionAs"));
429         menu_file->addAction(action_save_selection_as_);
430
431         menu_file->addSeparator();
432
433         widgets::ExportMenu *menu_file_export = new widgets::ExportMenu(this,
434                 device_manager_.context());
435         menu_file_export->setTitle(tr("&Export"));
436         connect(menu_file_export,
437                 SIGNAL(format_selected(std::shared_ptr<sigrok::OutputFormat>)),
438                 this, SLOT(export_file(std::shared_ptr<sigrok::OutputFormat>)));
439         menu_file->addAction(menu_file_export->menuAction());
440
441         widgets::ImportMenu *menu_file_import = new widgets::ImportMenu(this,
442                 device_manager_.context());
443         menu_file_import->setTitle(tr("&Import"));
444         connect(menu_file_import,
445                 SIGNAL(format_selected(std::shared_ptr<sigrok::InputFormat>)),
446                 this, SLOT(import_file(std::shared_ptr<sigrok::InputFormat>)));
447         menu_file->addAction(menu_file_import->menuAction());
448
449         menu_file->addSeparator();
450
451         action_connect_->setText(tr("&Connect to Device..."));
452         action_connect_->setObjectName(QString::fromUtf8("actionConnect"));
453         menu_file->addAction(action_connect_);
454
455         menu_file->addSeparator();
456
457         action_quit_->setText(tr("&Quit"));
458         action_quit_->setIcon(QIcon::fromTheme("application-exit",
459                 QIcon(":/icons/application-exit.png")));
460         action_quit_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_Q));
461         action_quit_->setObjectName(QString::fromUtf8("actionQuit"));
462         menu_file->addAction(action_quit_);
463
464         // View Menu
465         QMenu *menu_view = new QMenu;
466         menu_view->setTitle(tr("&View"));
467
468         action_view_zoom_in_->setText(tr("Zoom &In"));
469         action_view_zoom_in_->setIcon(QIcon::fromTheme("zoom-in",
470                 QIcon(":/icons/zoom-in.png")));
471         // simply using Qt::Key_Plus shows no + in the menu
472         action_view_zoom_in_->setShortcut(QKeySequence::ZoomIn);
473         action_view_zoom_in_->setObjectName(
474                 QString::fromUtf8("actionViewZoomIn"));
475         menu_view->addAction(action_view_zoom_in_);
476
477         action_view_zoom_out_->setText(tr("Zoom &Out"));
478         action_view_zoom_out_->setIcon(QIcon::fromTheme("zoom-out",
479                 QIcon(":/icons/zoom-out.png")));
480         action_view_zoom_out_->setShortcut(QKeySequence::ZoomOut);
481         action_view_zoom_out_->setObjectName(
482                 QString::fromUtf8("actionViewZoomOut"));
483         menu_view->addAction(action_view_zoom_out_);
484
485         action_view_zoom_fit_->setCheckable(true);
486         action_view_zoom_fit_->setText(tr("Zoom to &Fit"));
487         action_view_zoom_fit_->setIcon(QIcon::fromTheme("zoom-fit",
488                 QIcon(":/icons/zoom-fit.png")));
489         action_view_zoom_fit_->setShortcut(QKeySequence(Qt::Key_F));
490         action_view_zoom_fit_->setObjectName(
491                 QString::fromUtf8("actionViewZoomFit"));
492         menu_view->addAction(action_view_zoom_fit_);
493
494         action_view_zoom_one_to_one_->setText(tr("Zoom to O&ne-to-One"));
495         action_view_zoom_one_to_one_->setIcon(QIcon::fromTheme("zoom-original",
496                 QIcon(":/icons/zoom-original.png")));
497         action_view_zoom_one_to_one_->setShortcut(QKeySequence(Qt::Key_O));
498         action_view_zoom_one_to_one_->setObjectName(
499                 QString::fromUtf8("actionViewZoomOneToOne"));
500         menu_view->addAction(action_view_zoom_one_to_one_);
501
502         menu_view->addSeparator();
503
504         action_view_sticky_scrolling_->setCheckable(true);
505         action_view_sticky_scrolling_->setChecked(true);
506         action_view_sticky_scrolling_->setShortcut(QKeySequence(Qt::Key_S));
507         action_view_sticky_scrolling_->setObjectName(
508                 QString::fromUtf8("actionViewStickyScrolling"));
509         action_view_sticky_scrolling_->setText(tr("&Sticky Scrolling"));
510         menu_view->addAction(action_view_sticky_scrolling_);
511
512         // TODO: Refactor this into a "new view" method
513         if (view)
514                 view->enable_sticky_scrolling(action_view_sticky_scrolling_->isChecked());
515
516         menu_view->addSeparator();
517
518         action_view_coloured_bg_->setCheckable(true);
519         action_view_coloured_bg_->setChecked(true);
520         action_view_coloured_bg_->setShortcut(QKeySequence(Qt::Key_B));
521         action_view_coloured_bg_->setObjectName(
522                 QString::fromUtf8("actionViewColouredBg"));
523         action_view_coloured_bg_->setText(tr("Use &coloured backgrounds"));
524         menu_view->addAction(action_view_coloured_bg_);
525
526         // TODO: Refactor this into a "new view" method
527         if (view)
528                 view->enable_coloured_bg(action_view_coloured_bg_->isChecked());
529
530         menu_view->addSeparator();
531
532         action_view_show_cursors_->setCheckable(true);
533         action_view_show_cursors_->setIcon(QIcon::fromTheme("show-cursors",
534                 QIcon(":/icons/show-cursors.svg")));
535         action_view_show_cursors_->setShortcut(QKeySequence(Qt::Key_C));
536         action_view_show_cursors_->setObjectName(
537                 QString::fromUtf8("actionViewShowCursors"));
538         action_view_show_cursors_->setText(tr("Show &Cursors"));
539         menu_view->addAction(action_view_show_cursors_);
540
541         // TODO: Refactor this into a "new view" method
542         if (view)
543                 action_view_show_cursors_->setChecked(view->cursors_shown());
544
545         // Decoders Menu
546 #ifdef ENABLE_DECODE
547         QMenu *const menu_decoders = new QMenu;
548         menu_decoders->setTitle(tr("&Decoders"));
549
550         menu_decoders_add_->setTitle(tr("&Add"));
551         connect(menu_decoders_add_, SIGNAL(decoder_selected(srd_decoder*)),
552                 this, SLOT(add_decoder(srd_decoder*)));
553
554         menu_decoders->addMenu(menu_decoders_add_);
555 #endif
556
557         // Help Menu
558         QMenu *const menu_help = new QMenu;
559         menu_help->setTitle(tr("&Help"));
560
561         action_about_->setObjectName(QString::fromUtf8("actionAbout"));
562         action_about_->setText(tr("&About..."));
563         menu_help->addAction(action_about_);
564
565         menu_bar->addAction(menu_file->menuAction());
566         menu_bar->addAction(menu_view->menuAction());
567 #ifdef ENABLE_DECODE
568         menu_bar->addAction(menu_decoders->menuAction());
569 #endif
570         menu_bar->addAction(menu_help->menuAction());
571
572         setMenuBar(menu_bar);
573         QMetaObject::connectSlotsByName(this);
574
575         // Also add all actions to the main window for always-enabled hotkeys
576         for (QAction* action : menu_bar->actions())
577                 this->addAction(action);
578
579         // Setup the toolbar
580         main_bar_ = new toolbars::MainBar(session_, *this);
581
582         // Populate the device list and select the initially selected device
583         update_device_list();
584
585         addToolBar(main_bar_);
586
587         // Set the title
588         setWindowTitle(tr("PulseView"));
589
590         // Setup session_ events
591         connect(&session_, SIGNAL(capture_state_changed(int)), this,
592                 SLOT(capture_state_changed(int)));
593         connect(&session_, SIGNAL(device_selected()), this,
594                 SLOT(device_selected()));
595
596         // TODO: Refactor this into a "new view" method
597         if (view) {
598                 connect(&session_, SIGNAL(trigger_event(util::Timestamp)), view.get(),
599                         SLOT(trigger_event(util::Timestamp)));
600                 connect(view.get(), SIGNAL(sticky_scrolling_changed(bool)), this,
601                         SLOT(sticky_scrolling_changed(bool)));
602                 connect(view.get(), SIGNAL(always_zoom_to_fit_changed(bool)), this,
603                         SLOT(always_zoom_to_fit_changed(bool)));
604         }
605 }
606
607 void MainWindow::select_init_device()
608 {
609         QSettings settings;
610         map<string, string> dev_info;
611         list<string> key_list;
612         shared_ptr<devices::HardwareDevice> device;
613
614         // Re-select last used device if possible but only if it's not demo
615         settings.beginGroup("Device");
616         key_list.push_back("vendor");
617         key_list.push_back("model");
618         key_list.push_back("version");
619         key_list.push_back("serial_num");
620         key_list.push_back("connection_id");
621
622         for (string key : key_list) {
623                 const QString k = QString::fromStdString(key);
624                 if (!settings.contains(k))
625                         continue;
626
627                 const string value = settings.value(k).toString().toStdString();
628                 if (!value.empty())
629                         dev_info.insert(std::make_pair(key, value));
630         }
631
632         if (dev_info.count("model") > 0)
633                 if (dev_info.at("model").find("Demo device") == std::string::npos)
634                         device = device_manager_.find_device_from_info(dev_info);
635
636         // When we can't find a device similar to the one we used last
637         // time and there is at least one device aside from demo, use it
638         if (!device) {
639                 for (shared_ptr<devices::HardwareDevice> dev : device_manager_.devices()) {
640                         dev_info = device_manager_.get_device_info(dev);
641
642                         if (dev_info.count("model") > 0)
643                                 if (dev_info.at("model").find("Demo device") == std::string::npos) {
644                                         device = dev;
645                                         break;
646                                 }
647                 }
648         }
649
650         select_device(device);
651         update_device_list();
652
653         settings.endGroup();
654 }
655
656 void MainWindow::load_init_file(const std::string &file_name,
657         const std::string &format)
658 {
659         shared_ptr<InputFormat> input_format;
660
661         if (!format.empty()) {
662                 const map<string, shared_ptr<InputFormat> > formats =
663                         device_manager_.context()->input_formats();
664                 const auto iter = find_if(formats.begin(), formats.end(),
665                         [&](const pair<string, shared_ptr<InputFormat> > f) {
666                                 return f.first == format; });
667                 if (iter == formats.end()) {
668                         cerr << "Unexpected input format: " << format << endl;
669                         return;
670                 }
671
672                 input_format = (*iter).second;
673         }
674
675         load_file(QString::fromStdString(file_name), input_format);
676 }
677
678
679 void MainWindow::save_ui_settings()
680 {
681         QSettings settings;
682
683         map<string, string> dev_info;
684         list<string> key_list;
685
686         settings.beginGroup("MainWindow");
687         settings.setValue("state", saveState());
688         settings.setValue("geometry", saveGeometry());
689         settings.endGroup();
690
691         if (session_.device()) {
692                 settings.beginGroup("Device");
693                 key_list.push_back("vendor");
694                 key_list.push_back("model");
695                 key_list.push_back("version");
696                 key_list.push_back("serial_num");
697                 key_list.push_back("connection_id");
698
699                 dev_info = device_manager_.get_device_info(
700                         session_.device());
701
702                 for (string key : key_list) {
703                         if (dev_info.count(key))
704                                 settings.setValue(QString::fromUtf8(key.c_str()),
705                                                 QString::fromUtf8(dev_info.at(key).c_str()));
706                         else
707                                 settings.remove(QString::fromUtf8(key.c_str()));
708                 }
709
710                 settings.endGroup();
711         }
712 }
713
714 void MainWindow::restore_ui_settings()
715 {
716         QSettings settings;
717
718         settings.beginGroup("MainWindow");
719
720         if (settings.contains("geometry")) {
721                 restoreGeometry(settings.value("geometry").toByteArray());
722                 restoreState(settings.value("state").toByteArray());
723         } else
724                 resize(1000, 720);
725
726         settings.endGroup();
727 }
728
729 void MainWindow::session_error(
730         const QString text, const QString info_text)
731 {
732         QMetaObject::invokeMethod(this, "show_session_error",
733                 Qt::QueuedConnection, Q_ARG(QString, text),
734                 Q_ARG(QString, info_text));
735 }
736
737 void MainWindow::update_device_list()
738 {
739         main_bar_->update_device_list();
740 }
741
742 void MainWindow::load_file(QString file_name,
743         std::shared_ptr<sigrok::InputFormat> format,
744         const std::map<std::string, Glib::VariantBase> &options)
745 {
746         const QString errorMessage(
747                 QString("Failed to load file %1").arg(file_name));
748
749         try {
750                 if (format)
751                         session_.set_device(shared_ptr<devices::Device>(
752                                 new devices::InputFile(
753                                         device_manager_.context(),
754                                         file_name.toStdString(),
755                                         format, options)));
756                 else
757                         session_.set_device(shared_ptr<devices::Device>(
758                                 new devices::SessionFile(
759                                         device_manager_.context(),
760                                         file_name.toStdString())));
761         } catch (Error e) {
762                 show_session_error(tr("Failed to load ") + file_name, e.what());
763                 session_.set_default_device();
764                 update_device_list();
765                 return;
766         }
767
768         update_device_list();
769
770         session_.start_capture([&, errorMessage](QString infoMessage) {
771                 session_error(errorMessage, infoMessage); });
772 }
773
774 void MainWindow::closeEvent(QCloseEvent *event)
775 {
776         save_ui_settings();
777         event->accept();
778 }
779
780 void MainWindow::keyReleaseEvent(QKeyEvent *event)
781 {
782         if (event->key() == Qt::Key_Alt) {
783                 menuBar()->setHidden(!menuBar()->isHidden());
784                 menuBar()->setFocus();
785         }
786         QMainWindow::keyReleaseEvent(event);
787 }
788
789 void MainWindow::show_session_error(
790         const QString text, const QString info_text)
791 {
792         QMessageBox msg(this);
793         msg.setText(text);
794         msg.setInformativeText(info_text);
795         msg.setStandardButtons(QMessageBox::Ok);
796         msg.setIcon(QMessageBox::Warning);
797         msg.exec();
798 }
799
800 void MainWindow::on_actionOpen_triggered()
801 {
802         QSettings settings;
803         const QString dir = settings.value(SettingOpenDirectory).toString();
804
805         // Show the dialog
806         const QString file_name = QFileDialog::getOpenFileName(
807                 this, tr("Open File"), dir, tr(
808                         "Sigrok Sessions (*.sr);;"
809                         "All Files (*.*)"));
810
811         if (!file_name.isEmpty()) {
812                 load_file(file_name);
813
814                 const QString abs_path = QFileInfo(file_name).absolutePath();
815                 settings.setValue(SettingOpenDirectory, abs_path);
816         }
817 }
818
819 void MainWindow::on_actionSaveAs_triggered()
820 {
821         export_file(device_manager_.context()->output_formats()["srzip"]);
822 }
823
824 void MainWindow::on_actionSaveSelectionAs_triggered()
825 {
826         export_file(device_manager_.context()->output_formats()["srzip"], true);
827 }
828
829 void MainWindow::on_actionConnect_triggered()
830 {
831         // Stop any currently running capture session
832         session_.stop_capture();
833
834         dialogs::Connect dlg(this, device_manager_);
835
836         // If the user selected a device, select it in the device list. Select the
837         // current device otherwise.
838         if (dlg.exec())
839                 select_device(dlg.get_selected_device());
840
841         update_device_list();
842 }
843
844 void MainWindow::on_actionQuit_triggered()
845 {
846         close();
847 }
848
849 void MainWindow::on_actionViewZoomIn_triggered()
850 {
851         shared_ptr<pv::view::View> view = get_active_view();
852         if (view)
853                 view->zoom(1);
854 }
855
856 void MainWindow::on_actionViewZoomOut_triggered()
857 {
858         shared_ptr<pv::view::View> view = get_active_view();
859         if (view)
860                 view->zoom(-1);
861 }
862
863 void MainWindow::on_actionViewZoomFit_triggered()
864 {
865         shared_ptr<pv::view::View> view = get_active_view();
866         if (view)
867                 view->zoom_fit(action_view_zoom_fit_->isChecked());
868 }
869
870 void MainWindow::on_actionViewZoomOneToOne_triggered()
871 {
872         shared_ptr<pv::view::View> view = get_active_view();
873         if (view)
874                 view->zoom_one_to_one();
875 }
876
877 void MainWindow::on_actionViewStickyScrolling_triggered()
878 {
879         shared_ptr<pv::view::View> view = get_active_view();
880         if (view)
881                 view->enable_sticky_scrolling(action_view_sticky_scrolling_->isChecked());
882 }
883
884 void MainWindow::on_actionViewColouredBg_triggered()
885 {
886         shared_ptr<pv::view::View> view = get_active_view();
887         if (view)
888                 view->enable_coloured_bg(action_view_coloured_bg_->isChecked());
889 }
890
891 void MainWindow::on_actionViewShowCursors_triggered()
892 {
893         shared_ptr<pv::view::View> view = get_active_view();
894         if (!view)
895                 return;
896
897         const bool show = !view->cursors_shown();
898         if (show)
899                 view->centre_cursors();
900
901         view->show_cursors(show);
902 }
903
904 void MainWindow::on_actionAbout_triggered()
905 {
906         dialogs::About dlg(device_manager_.context(), this);
907         dlg.exec();
908 }
909
910 void MainWindow::sticky_scrolling_changed(bool state)
911 {
912         action_view_sticky_scrolling_->setChecked(state);
913 }
914
915 void MainWindow::always_zoom_to_fit_changed(bool state)
916 {
917         action_view_zoom_fit_->setChecked(state);
918 }
919
920 void MainWindow::add_decoder(srd_decoder *decoder)
921 {
922 #ifdef ENABLE_DECODE
923         assert(decoder);
924         session_.add_decoder(decoder);
925 #else
926         (void)decoder;
927 #endif
928 }
929
930 void MainWindow::capture_state_changed(int state)
931 {
932         main_bar_->set_capture_state((pv::Session::capture_state)state);
933 }
934
935 void MainWindow::device_selected()
936 {
937         // Set the title to include the device/file name
938         const shared_ptr<devices::Device> device = session_.device();
939
940         if (!device) {
941                 main_bar_->reset_device_selector();
942                 return;
943         }
944
945         const string display_name = device->display_name(device_manager_);
946         setWindowTitle(tr("%1 - PulseView").arg(display_name.c_str()));
947 }
948
949 } // namespace pv