2 * This file is part of the PulseView project.
4 * Copyright (C) 2012 Joel Holdsworth <joel@airwebreathe.org.uk>
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.
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.
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
24 #include <libsigrokdecode/libsigrokdecode.h>
30 #include <boost/algorithm/string/join.hpp>
33 #include <QApplication>
34 #include <QButtonGroup>
35 #include <QCloseEvent>
36 #include <QFileDialog>
37 #include <QMessageBox>
42 #include <QVBoxLayout>
45 #include "mainwindow.hpp"
47 #include "devicemanager.hpp"
48 #include "devices/hardwaredevice.hpp"
49 #include "devices/inputfile.hpp"
50 #include "devices/sessionfile.hpp"
51 #include "dialogs/about.hpp"
52 #include "dialogs/connect.hpp"
53 #include "dialogs/inputoutputoptions.hpp"
54 #include "dialogs/storeprogress.hpp"
55 #include "toolbars/mainbar.hpp"
56 #include "view/logicsignal.hpp"
57 #include "view/view.hpp"
58 #include "widgets/exportmenu.hpp"
59 #include "widgets/importmenu.hpp"
61 #include "widgets/decodermenu.hpp"
63 #include "widgets/hidingmenubar.hpp"
69 #include <libsigrokcxx/libsigrokcxx.hpp>
73 using std::shared_ptr
;
77 using boost::algorithm::join
;
80 using sigrok::OutputFormat
;
81 using sigrok::InputFormat
;
89 const char *MainWindow::SettingOpenDirectory
= "MainWindow/OpenDirectory";
90 const char *MainWindow::SettingSaveDirectory
= "MainWindow/SaveDirectory";
92 MainWindow::MainWindow(DeviceManager
&device_manager
,
93 const char *open_file_name
,
96 device_manager_(device_manager
),
97 session_(device_manager
),
98 action_open_(new QAction(this)),
99 action_save_as_(new QAction(this)),
100 action_connect_(new QAction(this)),
101 action_quit_(new QAction(this)),
102 action_view_zoom_in_(new QAction(this)),
103 action_view_zoom_out_(new QAction(this)),
104 action_view_zoom_fit_(new QAction(this)),
105 action_view_zoom_one_to_one_(new QAction(this)),
106 action_view_show_cursors_(new QAction(this)),
107 action_about_(new QAction(this))
109 , menu_decoders_add_(new pv::widgets::DecoderMenu(this, true))
113 restore_ui_settings();
114 if (open_file_name
) {
115 const QString
s(QString::fromUtf8(open_file_name
));
116 QMetaObject::invokeMethod(this, "load_file",
117 Qt::QueuedConnection
,
122 QAction
* MainWindow::action_open() const
127 QAction
* MainWindow::action_save_as() const
129 return action_save_as_
;
132 QAction
* MainWindow::action_connect() const
134 return action_connect_
;
137 QAction
* MainWindow::action_quit() const
142 QAction
* MainWindow::action_view_zoom_in() const
144 return action_view_zoom_in_
;
147 QAction
* MainWindow::action_view_zoom_out() const
149 return action_view_zoom_out_
;
152 QAction
* MainWindow::action_view_zoom_fit() const
154 return action_view_zoom_fit_
;
157 QAction
* MainWindow::action_view_zoom_one_to_one() const
159 return action_view_zoom_one_to_one_
;
162 QAction
* MainWindow::action_view_show_cursors() const
164 return action_view_show_cursors_
;
167 QAction
* MainWindow::action_about() const
169 return action_about_
;
173 QMenu
* MainWindow::menu_decoder_add() const
175 return menu_decoders_add_
;
179 void MainWindow::run_stop()
181 switch(session_
.get_capture_state()) {
182 case Session::Stopped
:
183 session_
.start_capture([&](QString message
) {
184 session_error("Capture failed", message
); });
187 case Session::AwaitingTrigger
:
188 case Session::Running
:
189 session_
.stop_capture();
194 void MainWindow::select_device(shared_ptr
<devices::Device
> device
)
197 session_
.set_device(device
);
198 } catch(const QString
&e
) {
199 QMessageBox
msg(this);
201 msg
.setInformativeText(tr("Failed to Select Device"));
202 msg
.setStandardButtons(QMessageBox::Ok
);
203 msg
.setIcon(QMessageBox::Warning
);
208 void MainWindow::export_file(shared_ptr
<OutputFormat
> format
)
210 using pv::dialogs::StoreProgress
;
212 // Stop any currently running capture session
213 session_
.stop_capture();
216 const QString dir
= settings
.value(SettingSaveDirectory
).toString();
218 // Construct the filter
219 const vector
<string
> exts
= format
->extensions();
220 QString filter
= tr("%1 files ").arg(
221 QString::fromStdString(format
->description()));
226 filter
+= QString("(*.%1);;%2 (*.*)").arg(
227 QString::fromStdString(join(exts
, ", *."))).arg(
230 // Show the file dialog
231 const QString file_name
= QFileDialog::getSaveFileName(
232 this, tr("Save File"), dir
, filter
);
234 if (file_name
.isEmpty())
237 const QString abs_path
= QFileInfo(file_name
).absolutePath();
238 settings
.setValue(SettingSaveDirectory
, abs_path
);
240 // Show the options dialog
241 map
<string
, Glib::VariantBase
> options
;
242 if (!format
->options().empty()) {
243 dialogs::InputOutputOptions
dlg(
244 tr("Export %1").arg(QString::fromStdString(
245 format
->description())),
246 format
->options(), this);
249 options
= dlg
.options();
252 StoreProgress
*dlg
= new StoreProgress(file_name
, format
, options
,
257 void MainWindow::import_file(shared_ptr
<InputFormat
> format
)
262 const QString dir
= settings
.value(SettingOpenDirectory
).toString();
264 // Construct the filter
265 const vector
<string
> exts
= format
->extensions();
266 const QString filter
= exts
.empty() ? "" :
267 tr("%1 files (*.%2)").arg(
268 QString::fromStdString(format
->description())).arg(
269 QString::fromStdString(join(exts
, ", *.")));
271 // Show the file dialog
272 const QString file_name
= QFileDialog::getOpenFileName(
273 this, tr("Import File"), dir
, tr(
274 "%1 files (*.*);;All Files (*.*)").arg(
275 QString::fromStdString(format
->description())));
277 if (file_name
.isEmpty())
280 // Show the options dialog
281 map
<string
, Glib::VariantBase
> options
;
282 if (!format
->options().empty()) {
283 dialogs::InputOutputOptions
dlg(
284 tr("Import %1").arg(QString::fromStdString(
285 format
->description())),
286 format
->options(), this);
289 options
= dlg
.options();
292 load_file(file_name
, format
, options
);
294 const QString abs_path
= QFileInfo(file_name
).absolutePath();
295 settings
.setValue(SettingOpenDirectory
, abs_path
);
298 void MainWindow::setup_ui()
300 setObjectName(QString::fromUtf8("MainWindow"));
302 // Set the window icon
304 icon
.addFile(QString(":/icons/sigrok-logo-notext.svg"));
307 // Setup the central widget
308 central_widget_
= new QWidget(this);
309 vertical_layout_
= new QVBoxLayout(central_widget_
);
310 vertical_layout_
->setSpacing(6);
311 vertical_layout_
->setContentsMargins(0, 0, 0, 0);
312 setCentralWidget(central_widget_
);
314 view_
= new pv::view::View(session_
, this);
316 vertical_layout_
->addWidget(view_
);
318 // Setup the menu bar
319 pv::widgets::HidingMenuBar
*const menu_bar
=
320 new pv::widgets::HidingMenuBar(this);
323 QMenu
*const menu_file
= new QMenu
;
324 menu_file
->setTitle(tr("&File"));
326 action_open_
->setText(tr("&Open..."));
327 action_open_
->setIcon(QIcon::fromTheme("document-open",
328 QIcon(":/icons/document-open.png")));
329 action_open_
->setShortcut(QKeySequence(Qt::CTRL
+ Qt::Key_O
));
330 action_open_
->setObjectName(QString::fromUtf8("actionOpen"));
331 menu_file
->addAction(action_open_
);
333 action_save_as_
->setText(tr("&Save As..."));
334 action_save_as_
->setIcon(QIcon::fromTheme("document-save-as",
335 QIcon(":/icons/document-save-as.png")));
336 action_save_as_
->setShortcut(QKeySequence(Qt::CTRL
+ Qt::Key_S
));
337 action_save_as_
->setObjectName(QString::fromUtf8("actionSaveAs"));
338 menu_file
->addAction(action_save_as_
);
340 menu_file
->addSeparator();
342 widgets::ExportMenu
*menu_file_export
= new widgets::ExportMenu(this,
343 device_manager_
.context());
344 menu_file_export
->setTitle(tr("&Export"));
345 connect(menu_file_export
,
346 SIGNAL(format_selected(std::shared_ptr
<sigrok::OutputFormat
>)),
347 this, SLOT(export_file(std::shared_ptr
<sigrok::OutputFormat
>)));
348 menu_file
->addAction(menu_file_export
->menuAction());
350 widgets::ImportMenu
*menu_file_import
= new widgets::ImportMenu(this,
351 device_manager_
.context());
352 menu_file_import
->setTitle(tr("&Import"));
353 connect(menu_file_import
,
354 SIGNAL(format_selected(std::shared_ptr
<sigrok::InputFormat
>)),
355 this, SLOT(import_file(std::shared_ptr
<sigrok::InputFormat
>)));
356 menu_file
->addAction(menu_file_import
->menuAction());
358 menu_file
->addSeparator();
360 action_connect_
->setText(tr("&Connect to Device..."));
361 action_connect_
->setObjectName(QString::fromUtf8("actionConnect"));
362 menu_file
->addAction(action_connect_
);
364 menu_file
->addSeparator();
366 action_quit_
->setText(tr("&Quit"));
367 action_quit_
->setIcon(QIcon::fromTheme("application-exit",
368 QIcon(":/icons/application-exit.png")));
369 action_quit_
->setShortcut(QKeySequence(Qt::CTRL
+ Qt::Key_Q
));
370 action_quit_
->setObjectName(QString::fromUtf8("actionQuit"));
371 menu_file
->addAction(action_quit_
);
374 QMenu
*menu_view
= new QMenu
;
375 menu_view
->setTitle(tr("&View"));
377 action_view_zoom_in_
->setText(tr("Zoom &In"));
378 action_view_zoom_in_
->setIcon(QIcon::fromTheme("zoom-in",
379 QIcon(":/icons/zoom-in.png")));
380 // simply using Qt::Key_Plus shows no + in the menu
381 action_view_zoom_in_
->setShortcut(QKeySequence::ZoomIn
);
382 action_view_zoom_in_
->setObjectName(
383 QString::fromUtf8("actionViewZoomIn"));
384 menu_view
->addAction(action_view_zoom_in_
);
386 action_view_zoom_out_
->setText(tr("Zoom &Out"));
387 action_view_zoom_out_
->setIcon(QIcon::fromTheme("zoom-out",
388 QIcon(":/icons/zoom-out.png")));
389 action_view_zoom_out_
->setShortcut(QKeySequence::ZoomOut
);
390 action_view_zoom_out_
->setObjectName(
391 QString::fromUtf8("actionViewZoomOut"));
392 menu_view
->addAction(action_view_zoom_out_
);
394 action_view_zoom_fit_
->setText(tr("Zoom to &Fit"));
395 action_view_zoom_fit_
->setIcon(QIcon::fromTheme("zoom-fit",
396 QIcon(":/icons/zoom-fit.png")));
397 action_view_zoom_fit_
->setShortcut(QKeySequence(Qt::Key_F
));
398 action_view_zoom_fit_
->setObjectName(
399 QString::fromUtf8("actionViewZoomFit"));
400 menu_view
->addAction(action_view_zoom_fit_
);
402 action_view_zoom_one_to_one_
->setText(tr("Zoom to O&ne-to-One"));
403 action_view_zoom_one_to_one_
->setIcon(QIcon::fromTheme("zoom-original",
404 QIcon(":/icons/zoom-original.png")));
405 action_view_zoom_one_to_one_
->setShortcut(QKeySequence(Qt::Key_O
));
406 action_view_zoom_one_to_one_
->setObjectName(
407 QString::fromUtf8("actionViewZoomOneToOne"));
408 menu_view
->addAction(action_view_zoom_one_to_one_
);
410 menu_view
->addSeparator();
412 action_view_show_cursors_
->setCheckable(true);
413 action_view_show_cursors_
->setChecked(view_
->cursors_shown());
414 action_view_show_cursors_
->setIcon(QIcon::fromTheme("show-cursors",
415 QIcon(":/icons/show-cursors.svg")));
416 action_view_show_cursors_
->setShortcut(QKeySequence(Qt::Key_C
));
417 action_view_show_cursors_
->setObjectName(
418 QString::fromUtf8("actionViewShowCursors"));
419 action_view_show_cursors_
->setText(tr("Show &Cursors"));
420 menu_view
->addAction(action_view_show_cursors_
);
424 QMenu
*const menu_decoders
= new QMenu
;
425 menu_decoders
->setTitle(tr("&Decoders"));
427 menu_decoders_add_
->setTitle(tr("&Add"));
428 connect(menu_decoders_add_
, SIGNAL(decoder_selected(srd_decoder
*)),
429 this, SLOT(add_decoder(srd_decoder
*)));
431 menu_decoders
->addMenu(menu_decoders_add_
);
435 QMenu
*const menu_help
= new QMenu
;
436 menu_help
->setTitle(tr("&Help"));
438 action_about_
->setObjectName(QString::fromUtf8("actionAbout"));
439 action_about_
->setText(tr("&About..."));
440 menu_help
->addAction(action_about_
);
442 menu_bar
->addAction(menu_file
->menuAction());
443 menu_bar
->addAction(menu_view
->menuAction());
445 menu_bar
->addAction(menu_decoders
->menuAction());
447 menu_bar
->addAction(menu_help
->menuAction());
449 setMenuBar(menu_bar
);
450 QMetaObject::connectSlotsByName(this);
453 main_bar_
= new toolbars::MainBar(session_
, *this);
455 // Populate the device list and select the initially selected device
456 update_device_list();
458 addToolBar(main_bar_
);
461 setWindowTitle(tr("PulseView"));
463 // Setup session_ events
464 connect(&session_
, SIGNAL(capture_state_changed(int)), this,
465 SLOT(capture_state_changed(int)));
466 connect(&session_
, SIGNAL(device_selected()), this,
467 SLOT(device_selected()));
470 void MainWindow::save_ui_settings()
474 map
<string
, string
> dev_info
;
475 list
<string
> key_list
;
477 settings
.beginGroup("MainWindow");
478 settings
.setValue("state", saveState());
479 settings
.setValue("geometry", saveGeometry());
482 if (session_
.device()) {
483 settings
.beginGroup("Device");
484 key_list
.push_back("vendor");
485 key_list
.push_back("model");
486 key_list
.push_back("version");
487 key_list
.push_back("serial_num");
488 key_list
.push_back("connection_id");
490 dev_info
= device_manager_
.get_device_info(
493 for (string key
: key_list
) {
495 if (dev_info
.count(key
))
496 settings
.setValue(QString::fromUtf8(key
.c_str()),
497 QString::fromUtf8(dev_info
.at(key
).c_str()));
499 settings
.remove(QString::fromUtf8(key
.c_str()));
506 void MainWindow::restore_ui_settings()
510 map
<string
, string
> dev_info
;
511 list
<string
> key_list
;
514 settings
.beginGroup("MainWindow");
516 if (settings
.contains("geometry")) {
517 restoreGeometry(settings
.value("geometry").toByteArray());
518 restoreState(settings
.value("state").toByteArray());
524 // Re-select last used device if possible.
525 settings
.beginGroup("Device");
526 key_list
.push_back("vendor");
527 key_list
.push_back("model");
528 key_list
.push_back("version");
529 key_list
.push_back("serial_num");
530 key_list
.push_back("connection_id");
532 for (string key
: key_list
) {
533 if (!settings
.contains(QString::fromUtf8(key
.c_str())))
536 value
= settings
.value(QString::fromUtf8(key
.c_str())).toString().toStdString();
538 if (value
.size() > 0)
539 dev_info
.insert(std::make_pair(key
, value
));
542 const shared_ptr
<devices::HardwareDevice
> device
=
543 device_manager_
.find_device_from_info(dev_info
);
545 select_device(device
);
546 update_device_list();
552 void MainWindow::session_error(
553 const QString text
, const QString info_text
)
555 QMetaObject::invokeMethod(this, "show_session_error",
556 Qt::QueuedConnection
, Q_ARG(QString
, text
),
557 Q_ARG(QString
, info_text
));
560 void MainWindow::update_device_list()
562 main_bar_
->update_device_list();
565 void MainWindow::closeEvent(QCloseEvent
*event
)
571 void MainWindow::keyReleaseEvent(QKeyEvent
*event
)
573 if (event
->key() == Qt::Key_Alt
) {
574 menuBar()->setHidden(!menuBar()->isHidden());
575 menuBar()->setFocus();
577 QMainWindow::keyReleaseEvent(event
);
580 void MainWindow::load_file(QString file_name
,
581 std::shared_ptr
<sigrok::InputFormat
> format
,
582 const std::map
<std::string
, Glib::VariantBase
> &options
)
584 const QString
errorMessage(
585 QString("Failed to load file %1").arg(file_name
));
586 const QString infoMessage
;
590 session_
.set_device(shared_ptr
<devices::Device
>(
591 new devices::InputFile(
592 device_manager_
.context(),
593 file_name
.toStdString(),
596 session_
.set_device(shared_ptr
<devices::Device
>(
597 new devices::SessionFile(
598 device_manager_
.context(),
599 file_name
.toStdString())));
601 show_session_error(tr("Failed to load ") + file_name
, e
.what());
602 session_
.set_default_device();
603 update_device_list();
607 update_device_list();
609 session_
.start_capture([&, errorMessage
, infoMessage
](QString
) {
610 session_error(errorMessage
, infoMessage
); });
613 void MainWindow::show_session_error(
614 const QString text
, const QString info_text
)
616 QMessageBox
msg(this);
618 msg
.setInformativeText(info_text
);
619 msg
.setStandardButtons(QMessageBox::Ok
);
620 msg
.setIcon(QMessageBox::Warning
);
624 void MainWindow::on_actionOpen_triggered()
627 const QString dir
= settings
.value(SettingOpenDirectory
).toString();
630 const QString file_name
= QFileDialog::getOpenFileName(
631 this, tr("Open File"), dir
, tr(
632 "Sigrok Sessions (*.sr);;"
635 if (!file_name
.isEmpty()) {
636 load_file(file_name
);
638 const QString abs_path
= QFileInfo(file_name
).absolutePath();
639 settings
.setValue(SettingOpenDirectory
, abs_path
);
643 void MainWindow::on_actionSaveAs_triggered()
645 export_file(device_manager_
.context()->output_formats()["srzip"]);
648 void MainWindow::on_actionConnect_triggered()
650 // Stop any currently running capture session
651 session_
.stop_capture();
653 dialogs::Connect
dlg(this, device_manager_
);
655 // If the user selected a device, select it in the device list. Select the
656 // current device otherwise.
658 select_device(dlg
.get_selected_device());
660 update_device_list();
663 void MainWindow::on_actionQuit_triggered()
668 void MainWindow::on_actionViewZoomIn_triggered()
673 void MainWindow::on_actionViewZoomOut_triggered()
678 void MainWindow::on_actionViewZoomFit_triggered()
683 void MainWindow::on_actionViewZoomOneToOne_triggered()
685 view_
->zoom_one_to_one();
688 void MainWindow::on_actionViewShowCursors_triggered()
692 const bool show
= !view_
->cursors_shown();
694 view_
->centre_cursors();
696 view_
->show_cursors(show
);
699 void MainWindow::on_actionAbout_triggered()
701 dialogs::About
dlg(device_manager_
.context(), this);
705 void MainWindow::add_decoder(srd_decoder
*decoder
)
709 session_
.add_decoder(decoder
);
715 void MainWindow::capture_state_changed(int state
)
717 main_bar_
->set_capture_state((pv::Session::capture_state
)state
);
720 void MainWindow::device_selected()
722 // Set the title to include the device/file name
723 const shared_ptr
<devices::Device
> device
= session_
.device();
727 const string display_name
= device
->display_name(device_manager_
);
728 setWindowTitle(tr("%1 - PulseView").arg(display_name
.c_str()));