]> sigrok.org Git - pulseview.git/blob - pv/toolbars/mainbar.cpp
1875d3c415a34fab1cb8d2c0e65c5e486c16e7cd
[pulseview.git] / pv / toolbars / mainbar.cpp
1 /*
2  * This file is part of the PulseView project.
3  *
4  * Copyright (C) 2012-2015 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 <extdef.h>
22
23 #include <algorithm>
24 #include <cassert>
25
26 #include <QAction>
27 #include <QDebug>
28 #include <QFileDialog>
29 #include <QHelpEvent>
30 #include <QMenu>
31 #include <QMessageBox>
32 #include <QSettings>
33 #include <QToolTip>
34
35 #include "mainbar.hpp"
36
37 #include <boost/algorithm/string/join.hpp>
38
39 #include <pv/devicemanager.hpp>
40 #include <pv/devices/hardwaredevice.hpp>
41 #include <pv/devices/inputfile.hpp>
42 #include <pv/devices/sessionfile.hpp>
43 #include <pv/dialogs/connect.hpp>
44 #include <pv/dialogs/inputoutputoptions.hpp>
45 #include <pv/dialogs/storeprogress.hpp>
46 #include <pv/mainwindow.hpp>
47 #include <pv/popups/deviceoptions.hpp>
48 #include <pv/popups/channels.hpp>
49 #include <pv/util.hpp>
50 #include <pv/view/view.hpp>
51 #include <pv/widgets/exportmenu.hpp>
52 #include <pv/widgets/importmenu.hpp>
53 #ifdef ENABLE_DECODE
54 #include <pv/widgets/decodermenu.hpp>
55 #endif
56
57 #include <libsigrokcxx/libsigrokcxx.hpp>
58
59 using std::back_inserter;
60 using std::cerr;
61 using std::copy;
62 using std::endl;
63 using std::list;
64 using std::map;
65 using std::max;
66 using std::min;
67 using std::pair;
68 using std::shared_ptr;
69 using std::string;
70 using std::vector;
71
72 using sigrok::Capability;
73 using sigrok::ConfigKey;
74 using sigrok::Error;
75 using sigrok::InputFormat;
76 using sigrok::OutputFormat;
77
78 using boost::algorithm::join;
79
80 namespace pv {
81 namespace toolbars {
82
83 const uint64_t MainBar::MinSampleCount = 100ULL;
84 const uint64_t MainBar::MaxSampleCount = 1000000000000ULL;
85 const uint64_t MainBar::DefaultSampleCount = 1000000;
86
87 const char *MainBar::SettingOpenDirectory = "MainWindow/OpenDirectory";
88 const char *MainBar::SettingSaveDirectory = "MainWindow/SaveDirectory";
89
90 MainBar::MainBar(Session &session, MainWindow &main_window) :
91         QToolBar("Sampling Bar", &main_window),
92         action_open_(new QAction(this)),
93         action_save_as_(new QAction(this)),
94         action_save_selection_as_(new QAction(this)),
95         action_connect_(new QAction(this)),
96         action_view_zoom_in_(new QAction(this)),
97         action_view_zoom_out_(new QAction(this)),
98         action_view_zoom_fit_(new QAction(this)),
99         action_view_zoom_one_to_one_(new QAction(this)),
100         action_view_show_cursors_(new QAction(this)),
101         session_(session),
102         device_selector_(&main_window, session.device_manager(),
103                 action_connect_),
104         configure_button_(this),
105         configure_button_action_(nullptr),
106         channels_button_(this),
107         channels_button_action_(nullptr),
108         sample_count_(" samples", this),
109         sample_rate_("Hz", this),
110         updating_sample_rate_(false),
111         updating_sample_count_(false),
112         sample_count_supported_(false),
113         icon_red_(":/icons/status-red.svg"),
114         icon_green_(":/icons/status-green.svg"),
115         icon_grey_(":/icons/status-grey.svg"),
116         run_stop_button_(this),
117         run_stop_button_action_(nullptr),
118         menu_button_(this)
119 #ifdef ENABLE_DECODE
120         , menu_decoders_add_(new pv::widgets::DecoderMenu(this, true))
121 #endif
122 {
123         setObjectName(QString::fromUtf8("MainBar"));
124
125         setMovable(false);
126         setFloatable(false);
127         setContextMenuPolicy(Qt::PreventContextMenu);
128
129         // Actions
130         action_open_->setText(tr("&Open..."));
131         action_open_->setIcon(QIcon::fromTheme("document-open",
132                 QIcon(":/icons/document-open.png")));
133         action_open_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_O));
134         action_open_->setObjectName(QString::fromUtf8("actionOpen"));
135
136         action_save_as_->setText(tr("&Save As..."));
137         action_save_as_->setIcon(QIcon::fromTheme("document-save-as",
138                 QIcon(":/icons/document-save-as.png")));
139         action_save_as_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_S));
140         action_save_as_->setObjectName(QString::fromUtf8("actionSaveAs"));
141
142         action_save_selection_as_->setText(tr("Save Selected &Range As..."));
143         action_save_selection_as_->setIcon(QIcon::fromTheme("document-save-as",
144                 QIcon(":/icons/document-save-as.png")));
145         action_save_selection_as_->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_R));
146         action_save_selection_as_->setObjectName(QString::fromUtf8("actionSaveSelectionAs"));
147
148         widgets::ExportMenu *menu_file_export = new widgets::ExportMenu(this,
149                 session.device_manager().context());
150         menu_file_export->setTitle(tr("&Export"));
151         connect(menu_file_export,
152                 SIGNAL(format_selected(std::shared_ptr<sigrok::OutputFormat>)),
153                 this, SLOT(export_file(std::shared_ptr<sigrok::OutputFormat>)));
154
155         widgets::ImportMenu *menu_file_import = new widgets::ImportMenu(this,
156                 session.device_manager().context());
157         menu_file_import->setTitle(tr("&Import"));
158         connect(menu_file_import,
159                 SIGNAL(format_selected(std::shared_ptr<sigrok::InputFormat>)),
160                 this, SLOT(import_file(std::shared_ptr<sigrok::InputFormat>)));
161
162         action_connect_->setText(tr("&Connect to Device..."));
163         action_connect_->setObjectName(QString::fromUtf8("actionConnect"));
164
165         action_view_zoom_in_->setText(tr("Zoom &In"));
166         action_view_zoom_in_->setIcon(QIcon::fromTheme("zoom-in",
167                 QIcon(":/icons/zoom-in.png")));
168         // simply using Qt::Key_Plus shows no + in the menu
169         action_view_zoom_in_->setShortcut(QKeySequence::ZoomIn);
170         action_view_zoom_in_->setObjectName(
171                 QString::fromUtf8("actionViewZoomIn"));
172
173         action_view_zoom_out_->setText(tr("Zoom &Out"));
174         action_view_zoom_out_->setIcon(QIcon::fromTheme("zoom-out",
175                 QIcon(":/icons/zoom-out.png")));
176         action_view_zoom_out_->setShortcut(QKeySequence::ZoomOut);
177         action_view_zoom_out_->setObjectName(
178                 QString::fromUtf8("actionViewZoomOut"));
179
180         action_view_zoom_fit_->setCheckable(true);
181         action_view_zoom_fit_->setText(tr("Zoom to &Fit"));
182         action_view_zoom_fit_->setIcon(QIcon::fromTheme("zoom-fit",
183                 QIcon(":/icons/zoom-fit.png")));
184         action_view_zoom_fit_->setShortcut(QKeySequence(Qt::Key_F));
185         action_view_zoom_fit_->setObjectName(
186                 QString::fromUtf8("actionViewZoomFit"));
187
188         action_view_zoom_one_to_one_->setText(tr("Zoom to O&ne-to-One"));
189         action_view_zoom_one_to_one_->setIcon(QIcon::fromTheme("zoom-original",
190                 QIcon(":/icons/zoom-original.png")));
191         action_view_zoom_one_to_one_->setShortcut(QKeySequence(Qt::Key_O));
192         action_view_zoom_one_to_one_->setObjectName(
193                 QString::fromUtf8("actionViewZoomOneToOne"));
194
195         action_view_show_cursors_->setCheckable(true);
196         action_view_show_cursors_->setIcon(QIcon::fromTheme("show-cursors",
197                 QIcon(":/icons/show-cursors.svg")));
198         action_view_show_cursors_->setShortcut(QKeySequence(Qt::Key_C));
199         action_view_show_cursors_->setObjectName(
200                 QString::fromUtf8("actionViewShowCursors"));
201         action_view_show_cursors_->setText(tr("Show &Cursors"));
202
203         // Open button
204         QToolButton *const open_button = new QToolButton(this);
205
206         widgets::ImportMenu *import_menu = new widgets::ImportMenu(this,
207                 session.device_manager().context(), action_open_);
208         connect(import_menu,
209                 SIGNAL(format_selected(std::shared_ptr<sigrok::InputFormat>)),
210                 &main_window,
211                 SLOT(import_file(std::shared_ptr<sigrok::InputFormat>)));
212
213         open_button->setMenu(import_menu);
214         open_button->setDefaultAction(action_open_);
215         open_button->setPopupMode(QToolButton::MenuButtonPopup);
216
217         // Save button
218         QToolButton *const save_button = new QToolButton(this);
219
220         vector<QAction *> open_actions;
221         open_actions.push_back(action_save_as_);
222         open_actions.push_back(action_save_selection_as_);
223
224         widgets::ExportMenu *export_menu = new widgets::ExportMenu(this,
225                 session.device_manager().context(),
226                 open_actions);
227         connect(export_menu,
228                 SIGNAL(format_selected(std::shared_ptr<sigrok::OutputFormat>)),
229                 &main_window,
230                 SLOT(export_file(std::shared_ptr<sigrok::OutputFormat>)));
231
232         save_button->setMenu(export_menu);
233         save_button->setDefaultAction(action_save_as_);
234         save_button->setPopupMode(QToolButton::MenuButtonPopup);
235
236         // Device selector menu
237         connect(&device_selector_, SIGNAL(device_selected()),
238                 this, SLOT(on_device_selected()));
239         connect(&session, SIGNAL(device_changed()),
240                 this, SLOT(on_device_changed()));
241
242         // Setup the decoder button
243 #ifdef ENABLE_DECODE
244         menu_decoders_add_->setTitle(tr("&Add"));
245         connect(menu_decoders_add_, SIGNAL(decoder_selected(srd_decoder*)),
246                 this, SLOT(add_decoder(srd_decoder*)));
247
248         QToolButton *add_decoder_button = new QToolButton(this);
249         add_decoder_button->setIcon(QIcon::fromTheme("add-decoder",
250                 QIcon(":/icons/add-decoder.svg")));
251         add_decoder_button->setPopupMode(QToolButton::InstantPopup);
252         add_decoder_button->setMenu(menu_decoders_add_);
253 #endif
254
255         // Setup the toolbar
256         addWidget(open_button);
257         addWidget(save_button);
258         addSeparator();
259         addAction(action_view_zoom_in_);
260         addAction(action_view_zoom_out_);
261         addAction(action_view_zoom_fit_);
262         addAction(action_view_zoom_one_to_one_);
263         addSeparator();
264         addAction(action_view_show_cursors_);
265         addSeparator();
266
267         connect(&run_stop_button_, SIGNAL(clicked()),
268                 this, SLOT(on_run_stop()));
269         connect(&sample_count_, SIGNAL(value_changed()),
270                 this, SLOT(on_sample_count_changed()));
271         connect(&sample_rate_, SIGNAL(value_changed()),
272                 this, SLOT(on_sample_rate_changed()));
273
274         sample_count_.show_min_max_step(0, UINT64_MAX, 1);
275
276         set_capture_state(pv::Session::Stopped);
277
278         configure_button_.setIcon(QIcon::fromTheme("configure",
279                 QIcon(":/icons/configure.png")));
280
281         channels_button_.setIcon(QIcon::fromTheme("channels",
282                 QIcon(":/icons/channels.svg")));
283
284         run_stop_button_.setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
285
286         addWidget(&device_selector_);
287         configure_button_action_ = addWidget(&configure_button_);
288         channels_button_action_ = addWidget(&channels_button_);
289         addWidget(&sample_count_);
290         addWidget(&sample_rate_);
291         run_stop_button_action_ = addWidget(&run_stop_button_);
292 #ifdef ENABLE_DECODE
293         addSeparator();
294         addWidget(add_decoder_button);
295 #endif
296
297         QWidget *const spacer = new QWidget();
298         spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
299         addWidget(spacer);
300
301         addWidget(&menu_button_);
302
303         sample_count_.installEventFilter(this);
304         sample_rate_.installEventFilter(this);
305
306         // Setup session_ events
307         connect(&session_, SIGNAL(capture_state_changed(int)), this,
308                 SLOT(capture_state_changed(int)));
309         connect(&session_, SIGNAL(device_selected()), this,
310                 SLOT(device_selected()));
311
312         update_device_list();
313 }
314
315 Session &MainBar::session(void) const
316 {
317         return session_;
318 }
319
320 void MainBar::update_device_list()
321 {
322         DeviceManager &mgr = session_.device_manager();
323         shared_ptr<devices::Device> selected_device = session_.device();
324         list< shared_ptr<devices::Device> > devs;
325
326         copy(mgr.devices().begin(), mgr.devices().end(), back_inserter(devs));
327
328         if (std::find(devs.begin(), devs.end(), selected_device) == devs.end())
329                 devs.push_back(selected_device);
330
331         device_selector_.set_device_list(devs, selected_device);
332         update_device_config_widgets();
333 }
334
335
336 void MainBar::set_capture_state(pv::Session::capture_state state)
337 {
338         const QIcon *icons[] = {&icon_grey_, &icon_red_, &icon_green_};
339         run_stop_button_.setIcon(*icons[state]);
340         run_stop_button_.setText((state == pv::Session::Stopped) ?
341                 tr("Run") : tr("Stop"));
342         run_stop_button_.setShortcut(QKeySequence(Qt::Key_Space));
343
344         bool ui_enabled = (state == pv::Session::Stopped) ? true : false;
345
346         device_selector_.setEnabled(ui_enabled);
347         configure_button_.setEnabled(ui_enabled);
348         channels_button_.setEnabled(ui_enabled);
349         sample_count_.setEnabled(ui_enabled);
350         sample_rate_.setEnabled(ui_enabled);
351 }
352
353 void MainBar::reset_device_selector()
354 {
355         device_selector_.reset();
356 }
357
358 void MainBar::select_device(shared_ptr<devices::Device> device)
359 {
360         try {
361                 if (device)
362                         session_.set_device(device);
363                 else
364                         session_.set_default_device();
365         } catch (const QString &e) {
366                 QMessageBox msg(this);
367                 msg.setText(e);
368                 msg.setInformativeText(tr("Failed to Select Device"));
369                 msg.setStandardButtons(QMessageBox::Ok);
370                 msg.setIcon(QMessageBox::Warning);
371                 msg.exec();
372         }
373 }
374
375 void MainBar::load_init_file(const std::string &file_name,
376         const std::string &format)
377 {
378         shared_ptr<InputFormat> input_format;
379
380         DeviceManager& device_manager = session_.device_manager();
381
382         if (!format.empty()) {
383                 const map<string, shared_ptr<InputFormat> > formats =
384                         device_manager.context()->input_formats();
385                 const auto iter = find_if(formats.begin(), formats.end(),
386                         [&](const pair<string, shared_ptr<InputFormat> > f) {
387                                 return f.first == format; });
388                 if (iter == formats.end()) {
389                         cerr << "Unexpected input format: " << format << endl;
390                         return;
391                 }
392
393                 input_format = (*iter).second;
394         }
395
396         load_file(QString::fromStdString(file_name), input_format);
397 }
398
399 QAction* MainBar::action_open() const
400 {
401         return action_open_;
402 }
403
404 QAction* MainBar::action_save_as() const
405 {
406         return action_save_as_;
407 }
408
409 QAction* MainBar::action_save_selection_as() const
410 {
411         return action_save_selection_as_;
412 }
413
414 QAction* MainBar::action_connect() const
415 {
416         return action_connect_;
417 }
418
419 QAction* MainBar::action_view_zoom_in() const
420 {
421         return action_view_zoom_in_;
422 }
423
424 QAction* MainBar::action_view_zoom_out() const
425 {
426         return action_view_zoom_out_;
427 }
428
429 QAction* MainBar::action_view_zoom_fit() const
430 {
431         return action_view_zoom_fit_;
432 }
433
434 QAction* MainBar::action_view_zoom_one_to_one() const
435 {
436         return action_view_zoom_one_to_one_;
437 }
438
439 QAction* MainBar::action_view_show_cursors() const
440 {
441         return action_view_show_cursors_;
442 }
443
444 void MainBar::run_stop()
445 {
446         switch (session_.get_capture_state()) {
447         case Session::Stopped:
448                 session_.start_capture([&](QString message) {
449                         session_error("Capture failed", message); });
450                 break;
451         case Session::AwaitingTrigger:
452         case Session::Running:
453                 session_.stop_capture();
454                 break;
455         }
456 }
457
458 void MainBar::load_file(QString file_name,
459         std::shared_ptr<sigrok::InputFormat> format,
460         const std::map<std::string, Glib::VariantBase> &options)
461 {
462         DeviceManager& device_manager = session_.device_manager();
463
464         const QString errorMessage(
465                 QString("Failed to load file %1").arg(file_name));
466
467         try {
468                 if (format)
469                         session_.set_device(shared_ptr<devices::Device>(
470                                 new devices::InputFile(
471                                         device_manager.context(),
472                                         file_name.toStdString(),
473                                         format, options)));
474                 else
475                         session_.set_device(shared_ptr<devices::Device>(
476                                 new devices::SessionFile(
477                                         device_manager.context(),
478                                         file_name.toStdString())));
479         } catch (Error e) {
480                 show_session_error(tr("Failed to load ") + file_name, e.what());
481                 session_.set_default_device();
482                 update_device_list();
483                 return;
484         }
485
486         session_.set_name(QFileInfo(file_name).fileName());
487
488         update_device_list();
489
490         session_.start_capture([&, errorMessage](QString infoMessage) {
491                 session_error(errorMessage, infoMessage); });
492 }
493
494 void MainBar::update_sample_rate_selector()
495 {
496         Glib::VariantContainerBase gvar_dict;
497         GVariant *gvar_list;
498         const uint64_t *elements = nullptr;
499         gsize num_elements;
500         map< const ConfigKey*, std::set<Capability> > keys;
501
502         if (updating_sample_rate_) {
503                 sample_rate_.show_none();
504                 return;
505         }
506
507         const shared_ptr<devices::Device> device =
508                 device_selector_.selected_device();
509         if (!device)
510                 return;
511
512         assert(!updating_sample_rate_);
513         updating_sample_rate_ = true;
514
515         const shared_ptr<sigrok::Device> sr_dev = device->device();
516
517         if (sr_dev->config_check(ConfigKey::SAMPLERATE, Capability::LIST)) {
518                 gvar_dict = sr_dev->config_list(ConfigKey::SAMPLERATE);
519         } else {
520                 sample_rate_.show_none();
521                 updating_sample_rate_ = false;
522                 return;
523         }
524
525         if ((gvar_list = g_variant_lookup_value(gvar_dict.gobj(),
526                         "samplerate-steps", G_VARIANT_TYPE("at")))) {
527                 elements = (const uint64_t *)g_variant_get_fixed_array(
528                                 gvar_list, &num_elements, sizeof(uint64_t));
529
530                 const uint64_t min = elements[0];
531                 const uint64_t max = elements[1];
532                 const uint64_t step = elements[2];
533
534                 g_variant_unref(gvar_list);
535
536                 assert(min > 0);
537                 assert(max > 0);
538                 assert(max > min);
539                 assert(step > 0);
540
541                 if (step == 1)
542                         sample_rate_.show_125_list(min, max);
543                 else {
544                         // When the step is not 1, we cam't make a 1-2-5-10
545                         // list of sample rates, because we may not be able to
546                         // make round numbers. Therefore in this case, show a
547                         // spin box.
548                         sample_rate_.show_min_max_step(min, max, step);
549                 }
550         } else if ((gvar_list = g_variant_lookup_value(gvar_dict.gobj(),
551                         "samplerates", G_VARIANT_TYPE("at")))) {
552                 elements = (const uint64_t *)g_variant_get_fixed_array(
553                                 gvar_list, &num_elements, sizeof(uint64_t));
554                 sample_rate_.show_list(elements, num_elements);
555                 g_variant_unref(gvar_list);
556         }
557         updating_sample_rate_ = false;
558
559         update_sample_rate_selector_value();
560 }
561
562 void MainBar::update_sample_rate_selector_value()
563 {
564         if (updating_sample_rate_)
565                 return;
566
567         const shared_ptr<devices::Device> device =
568                 device_selector_.selected_device();
569         if (!device)
570                 return;
571
572         try {
573                 auto gvar = device->device()->config_get(ConfigKey::SAMPLERATE);
574                 uint64_t samplerate =
575                         Glib::VariantBase::cast_dynamic<Glib::Variant<guint64>>(gvar).get();
576                 assert(!updating_sample_rate_);
577                 updating_sample_rate_ = true;
578                 sample_rate_.set_value(samplerate);
579                 updating_sample_rate_ = false;
580         } catch (Error error) {
581                 qDebug() << "WARNING: Failed to get value of sample rate";
582                 return;
583         }
584 }
585
586 void MainBar::update_sample_count_selector()
587 {
588         if (updating_sample_count_)
589                 return;
590
591         const shared_ptr<devices::Device> device =
592                 device_selector_.selected_device();
593         if (!device)
594                 return;
595
596         const shared_ptr<sigrok::Device> sr_dev = device->device();
597
598         assert(!updating_sample_count_);
599         updating_sample_count_ = true;
600
601         if (!sample_count_supported_) {
602                 sample_count_.show_none();
603                 updating_sample_count_ = false;
604                 return;
605         }
606
607         uint64_t sample_count = sample_count_.value();
608         uint64_t min_sample_count = 0;
609         uint64_t max_sample_count = MaxSampleCount;
610
611         if (sample_count == 0)
612                 sample_count = DefaultSampleCount;
613
614         if (sr_dev->config_check(ConfigKey::LIMIT_SAMPLES, Capability::LIST)) {
615                 auto gvar = sr_dev->config_list(ConfigKey::LIMIT_SAMPLES);
616                 if (gvar.gobj())
617                         g_variant_get(gvar.gobj(), "(tt)",
618                                 &min_sample_count, &max_sample_count);
619         }
620
621         min_sample_count = min(max(min_sample_count, MinSampleCount),
622                 max_sample_count);
623
624         sample_count_.show_125_list(
625                 min_sample_count, max_sample_count);
626
627         if (sr_dev->config_check(ConfigKey::LIMIT_SAMPLES, Capability::GET)) {
628                 auto gvar = sr_dev->config_get(ConfigKey::LIMIT_SAMPLES);
629                 sample_count = g_variant_get_uint64(gvar.gobj());
630                 if (sample_count == 0)
631                         sample_count = DefaultSampleCount;
632                 sample_count = min(max(sample_count, MinSampleCount),
633                         max_sample_count);
634         }
635
636         sample_count_.set_value(sample_count);
637
638         updating_sample_count_ = false;
639 }
640
641 void MainBar::update_device_config_widgets()
642 {
643         using namespace pv::popups;
644
645         const shared_ptr<devices::Device> device =
646                 device_selector_.selected_device();
647
648         // Hide the widgets if no device is selected
649         channels_button_action_->setVisible(!!device);
650         run_stop_button_action_->setVisible(!!device);
651         if (!device) {
652                 configure_button_action_->setVisible(false);
653                 sample_count_.show_none();
654                 sample_rate_.show_none();
655                 return;
656         }
657
658         const shared_ptr<sigrok::Device> sr_dev = device->device();
659         if (!sr_dev)
660                 return;
661
662         // Update the configure popup
663         DeviceOptions *const opts = new DeviceOptions(sr_dev, this);
664         configure_button_action_->setVisible(
665                 !opts->binding().properties().empty());
666         configure_button_.set_popup(opts);
667
668         // Update the channels popup
669         Channels *const channels = new Channels(session_, this);
670         channels_button_.set_popup(channels);
671
672         // Update supported options.
673         sample_count_supported_ = false;
674
675         if (sr_dev->config_check(ConfigKey::LIMIT_SAMPLES, Capability::SET))
676                 sample_count_supported_ = true;
677
678         if (sr_dev->config_check(ConfigKey::LIMIT_FRAMES, Capability::SET)) {
679                 sr_dev->config_set(ConfigKey::LIMIT_FRAMES,
680                         Glib::Variant<guint64>::create(1));
681                         on_config_changed();
682         }
683
684         // Add notification of reconfigure events
685         disconnect(this, SLOT(on_config_changed()));
686         connect(&opts->binding(), SIGNAL(config_changed()),
687                 this, SLOT(on_config_changed()));
688
689         // Update sweep timing widgets.
690         update_sample_count_selector();
691         update_sample_rate_selector();
692 }
693
694 void MainBar::commit_sample_rate()
695 {
696         uint64_t sample_rate = 0;
697
698         const shared_ptr<devices::Device> device =
699                 device_selector_.selected_device();
700         if (!device)
701                 return;
702
703         const shared_ptr<sigrok::Device> sr_dev = device->device();
704
705         sample_rate = sample_rate_.value();
706         if (sample_rate == 0)
707                 return;
708
709         try {
710                 sr_dev->config_set(ConfigKey::SAMPLERATE,
711                         Glib::Variant<guint64>::create(sample_rate));
712                 update_sample_rate_selector();
713         } catch (Error error) {
714                 qDebug() << "Failed to configure samplerate.";
715                 return;
716         }
717
718         // Devices with built-in memory might impose limits on certain
719         // configurations, so let's check what sample count the driver
720         // lets us use now.
721         update_sample_count_selector();
722 }
723
724 void MainBar::commit_sample_count()
725 {
726         uint64_t sample_count = 0;
727
728         const shared_ptr<devices::Device> device =
729                 device_selector_.selected_device();
730         if (!device)
731                 return;
732
733         const shared_ptr<sigrok::Device> sr_dev = device->device();
734
735         sample_count = sample_count_.value();
736         if (sample_count_supported_) {
737                 try {
738                         sr_dev->config_set(ConfigKey::LIMIT_SAMPLES,
739                                 Glib::Variant<guint64>::create(sample_count));
740                         update_sample_count_selector();
741                 } catch (Error error) {
742                         qDebug() << "Failed to configure sample count.";
743                         return;
744                 }
745         }
746
747         // Devices with built-in memory might impose limits on certain
748         // configurations, so let's check what sample rate the driver
749         // lets us use now.
750         update_sample_rate_selector();
751 }
752
753 void MainBar::session_error(const QString text, const QString info_text)
754 {
755         QMetaObject::invokeMethod(this, "show_session_error",
756                 Qt::QueuedConnection, Q_ARG(QString, text),
757                 Q_ARG(QString, info_text));
758 }
759
760 void MainBar::show_session_error(const QString text, const QString info_text)
761 {
762         QMessageBox msg(this);
763         msg.setText(text);
764         msg.setInformativeText(info_text);
765         msg.setStandardButtons(QMessageBox::Ok);
766         msg.setIcon(QMessageBox::Warning);
767         msg.exec();
768 }
769
770 void MainBar::capture_state_changed(int state)
771 {
772         set_capture_state((pv::Session::capture_state)state);
773 }
774
775 void MainBar::add_decoder(srd_decoder *decoder)
776 {
777 #ifdef ENABLE_DECODE
778         assert(decoder);
779         session_.add_decoder(decoder);
780 #else
781         (void)decoder;
782 #endif
783 }
784
785 void MainBar::export_file(shared_ptr<OutputFormat> format,
786         bool selection_only)
787 {
788         using pv::dialogs::StoreProgress;
789
790         // Stop any currently running capture session
791         session_.stop_capture();
792
793         QSettings settings;
794         const QString dir = settings.value(SettingSaveDirectory).toString();
795
796         std::pair<uint64_t, uint64_t> sample_range;
797
798         // Selection only? Verify that the cursors are active and fetch their values
799         if (selection_only) {
800                 if (!session_.main_view()->cursors()->enabled()) {
801                         show_session_error(tr("Missing Cursors"), tr("You need to set the " \
802                                         "cursors before you can save the data enclosed by them " \
803                                         "to a session file (e.g. using ALT-V - Show Cursors)."));
804                         return;
805                 }
806
807                 const double samplerate = session_.get_samplerate();
808
809                 const pv::util::Timestamp& start_time = session_.main_view()->cursors()->first()->time();
810                 const pv::util::Timestamp& end_time = session_.main_view()->cursors()->second()->time();
811
812                 const uint64_t start_sample =
813                         std::max((double)0, start_time.convert_to<double>() * samplerate);
814                 const uint64_t end_sample = end_time.convert_to<double>() * samplerate;
815
816                 sample_range = std::make_pair(start_sample, end_sample);
817         } else {
818                 sample_range = std::make_pair(0, 0);
819         }
820
821         // Construct the filter
822         const vector<string> exts = format->extensions();
823         QString filter = tr("%1 files ").arg(
824                 QString::fromStdString(format->description()));
825
826         if (exts.empty())
827                 filter += "(*.*)";
828         else
829                 filter += QString("(*.%1);;%2 (*.*)").arg(
830                         QString::fromStdString(join(exts, ", *.")),
831                         tr("All Files"));
832
833         // Show the file dialog
834         const QString file_name = QFileDialog::getSaveFileName(
835                 this, tr("Save File"), dir, filter);
836
837         if (file_name.isEmpty())
838                 return;
839
840         const QString abs_path = QFileInfo(file_name).absolutePath();
841         settings.setValue(SettingSaveDirectory, abs_path);
842
843         // Show the options dialog
844         map<string, Glib::VariantBase> options;
845         if (!format->options().empty()) {
846                 dialogs::InputOutputOptions dlg(
847                         tr("Export %1").arg(QString::fromStdString(
848                                 format->description())),
849                         format->options(), this);
850                 if (!dlg.exec())
851                         return;
852                 options = dlg.options();
853         }
854
855         session_.set_name(QFileInfo(file_name).fileName());
856
857         StoreProgress *dlg = new StoreProgress(file_name, format, options,
858                 sample_range, session_, this);
859         dlg->run();
860 }
861
862 void MainBar::import_file(shared_ptr<InputFormat> format)
863 {
864         assert(format);
865
866         QSettings settings;
867         const QString dir = settings.value(SettingOpenDirectory).toString();
868
869         // Construct the filter
870         const vector<string> exts = format->extensions();
871         const QString filter = exts.empty() ? "" :
872                 tr("%1 files (*.%2)").arg(
873                         QString::fromStdString(format->description()),
874                         QString::fromStdString(join(exts, ", *.")));
875
876         // Show the file dialog
877         const QString file_name = QFileDialog::getOpenFileName(
878                 this, tr("Import File"), dir, tr(
879                         "%1 files (*.*);;All Files (*.*)").arg(
880                         QString::fromStdString(format->description())));
881
882         if (file_name.isEmpty())
883                 return;
884
885         // Show the options dialog
886         map<string, Glib::VariantBase> options;
887         if (!format->options().empty()) {
888                 dialogs::InputOutputOptions dlg(
889                         tr("Import %1").arg(QString::fromStdString(
890                                 format->description())),
891                         format->options(), this);
892                 if (!dlg.exec())
893                         return;
894                 options = dlg.options();
895         }
896
897         load_file(file_name, format, options);
898
899         const QString abs_path = QFileInfo(file_name).absolutePath();
900         settings.setValue(SettingOpenDirectory, abs_path);
901 }
902
903 void MainBar::on_device_selected()
904 {
905         shared_ptr<devices::Device> device = device_selector_.selected_device();
906         if (!device) {
907                 reset_device_selector();
908                 return;
909         }
910
911         select_device(device);
912 }
913
914 void MainBar::on_device_changed()
915 {
916         update_device_list();
917         update_device_config_widgets();
918 }
919
920 void MainBar::on_sample_count_changed()
921 {
922         if (!updating_sample_count_)
923                 commit_sample_count();
924 }
925
926 void MainBar::on_sample_rate_changed()
927 {
928         if (!updating_sample_rate_)
929                 commit_sample_rate();
930 }
931
932 void MainBar::on_run_stop()
933 {
934         commit_sample_count();
935         commit_sample_rate();   
936         run_stop();
937 }
938
939 void MainBar::on_config_changed()
940 {
941         commit_sample_count();
942         commit_sample_rate();   
943 }
944
945 void MainBar::on_actionOpen_triggered()
946 {
947         QSettings settings;
948         const QString dir = settings.value(SettingOpenDirectory).toString();
949
950         // Show the dialog
951         const QString file_name = QFileDialog::getOpenFileName(
952                 this, tr("Open File"), dir, tr(
953                         "Sigrok Sessions (*.sr);;"
954                         "All Files (*.*)"));
955
956         if (!file_name.isEmpty()) {
957                 load_file(file_name);
958
959                 const QString abs_path = QFileInfo(file_name).absolutePath();
960                 settings.setValue(SettingOpenDirectory, abs_path);
961         }
962 }
963
964 void MainBar::on_actionSaveAs_triggered()
965 {
966         export_file(session_.device_manager().context()->output_formats()["srzip"]);
967 }
968
969 void MainBar::on_actionSaveSelectionAs_triggered()
970 {
971         export_file(session_.device_manager().context()->output_formats()["srzip"], true);
972 }
973
974 void MainBar::on_actionConnect_triggered()
975 {
976         // Stop any currently running capture session
977         session_.stop_capture();
978
979         dialogs::Connect dlg(this, session_.device_manager());
980
981         // If the user selected a device, select it in the device list. Select the
982         // current device otherwise.
983         if (dlg.exec())
984                 select_device(dlg.get_selected_device());
985
986         update_device_list();
987 }
988
989 void MainBar::on_actionViewZoomIn_triggered()
990 {
991         session_.main_view()->zoom(1);
992 }
993
994 void MainBar::on_actionViewZoomOut_triggered()
995 {
996         session_.main_view()->zoom(-1);
997 }
998
999 void MainBar::on_actionViewZoomFit_triggered()
1000 {
1001         session_.main_view()->zoom_fit(action_view_zoom_fit_->isChecked());
1002 }
1003
1004 void MainBar::on_actionViewZoomOneToOne_triggered()
1005 {
1006         session_.main_view()->zoom_one_to_one();
1007 }
1008
1009 void MainBar::on_actionViewShowCursors_triggered()
1010 {
1011         const bool show = !session_.main_view()->cursors_shown();
1012         if (show)
1013                 session_.main_view()->centre_cursors();
1014
1015         session_.main_view()->show_cursors(show);
1016 }
1017
1018 bool MainBar::eventFilter(QObject *watched, QEvent *event)
1019 {
1020         if (sample_count_supported_ && (watched == &sample_count_ ||
1021                         watched == &sample_rate_) &&
1022                         (event->type() == QEvent::ToolTip)) {
1023                 auto sec = pv::util::Timestamp(sample_count_.value()) / sample_rate_.value();
1024                 QHelpEvent *help_event = static_cast<QHelpEvent*>(event);
1025
1026                 QString str = tr("Total sampling time: %1").arg(
1027                         pv::util::format_time_si(sec, pv::util::SIPrefix::unspecified, 0, "s", false));
1028                 QToolTip::showText(help_event->globalPos(), str);
1029
1030                 return true;
1031         }
1032
1033         return false;
1034 }
1035
1036 } // namespace toolbars
1037 } // namespace pv