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, see <http://www.gnu.org/licenses/>.
21 #include <libsigrokdecode/libsigrokdecode.h>
31 #include <QApplication>
32 #include <QCloseEvent>
33 #include <QDockWidget>
34 #include <QHBoxLayout>
35 #include <QMessageBox>
40 #include "mainwindow.hpp"
42 #include "devicemanager.hpp"
43 #include "devices/hardwaredevice.hpp"
44 #include "dialogs/settings.hpp"
45 #include "globalsettings.hpp"
46 #include "toolbars/mainbar.hpp"
48 #include "view/view.hpp"
49 #include "views/trace/standardbar.hpp"
51 #include <libsigrokcxx/libsigrokcxx.hpp>
54 using std::dynamic_pointer_cast
;
55 using std::make_shared
;
57 using std::placeholders::_1
;
58 using std::shared_ptr
;
67 using toolbars::MainBar
;
69 const QString
MainWindow::WindowTitle
= tr("PulseView");
71 MainWindow::MainWindow(DeviceManager
&device_manager
,
72 string open_file_name
, string open_file_format
,
75 device_manager_(device_manager
),
76 session_selector_(this),
77 session_state_mapper_(this),
78 icon_red_(":/icons/status-red.svg"),
79 icon_green_(":/icons/status-green.svg"),
80 icon_grey_(":/icons/status-grey.svg")
82 qRegisterMetaType
<util::Timestamp
>("util::Timestamp");
83 qRegisterMetaType
<uint64_t>("uint64_t");
85 GlobalSettings::register_change_handler(GlobalSettings::Key_View_ColouredBG
,
86 bind(&MainWindow::on_settingViewColouredBg_changed
, this, _1
));
88 GlobalSettings::register_change_handler(GlobalSettings::Key_View_ShowSamplingPoints
,
89 bind(&MainWindow::on_settingViewShowSamplingPoints_changed
, this, _1
));
92 restore_ui_settings();
94 if (!open_file_name
.empty()) {
95 shared_ptr
<Session
> session
= add_session();
96 session
->load_init_file(open_file_name
, open_file_format
);
99 // Add empty default session if there aren't any sessions
100 if (sessions_
.size() == 0) {
101 shared_ptr
<Session
> session
= add_session();
103 map
<string
, string
> dev_info
;
104 shared_ptr
<devices::HardwareDevice
> other_device
, demo_device
;
106 // Use any available device that's not demo
107 for (shared_ptr
<devices::HardwareDevice
> dev
: device_manager_
.devices()) {
108 if (dev
->hardware_device()->driver()->name() == "demo") {
115 // ...and if there isn't any, just use demo then
116 session
->select_device(other_device
? other_device
: demo_device
);
120 MainWindow::~MainWindow()
122 while (!sessions_
.empty())
123 remove_session(sessions_
.front());
126 shared_ptr
<views::ViewBase
> MainWindow::get_active_view() const
128 // If there's only one view, use it...
129 if (view_docks_
.size() == 1)
130 return view_docks_
.begin()->second
;
132 // ...otherwise find the dock widget the widget with focus is contained in
133 QObject
*w
= QApplication::focusWidget();
134 QDockWidget
*dock
= nullptr;
137 dock
= qobject_cast
<QDockWidget
*>(w
);
143 // Get the view contained in the dock widget
144 for (auto entry
: view_docks_
)
145 if (entry
.first
== dock
)
151 shared_ptr
<views::ViewBase
> MainWindow::add_view(const QString
&title
,
152 views::ViewType type
, Session
&session
)
154 GlobalSettings settings
;
155 shared_ptr
<views::ViewBase
> v
;
157 QMainWindow
*main_window
= nullptr;
158 for (auto entry
: session_windows_
)
159 if (entry
.first
.get() == &session
)
160 main_window
= entry
.second
;
164 shared_ptr
<MainBar
> main_bar
= session
.main_bar();
166 QDockWidget
* dock
= new QDockWidget(title
, main_window
);
167 dock
->setObjectName(title
);
168 main_window
->addDockWidget(Qt::TopDockWidgetArea
, dock
);
170 // Insert a QMainWindow into the dock widget to allow for a tool bar
171 QMainWindow
*dock_main
= new QMainWindow(dock
);
172 dock_main
->setWindowFlags(Qt::Widget
); // Remove Qt::Window flag
174 if (type
== views::ViewTypeTrace
)
175 // This view will be the main view if there's no main bar yet
176 v
= make_shared
<views::TraceView::View
>(session
,
177 (main_bar
? false : true), dock_main
);
182 view_docks_
[dock
] = v
;
183 session
.register_view(v
);
185 dock_main
->setCentralWidget(v
.get());
186 dock
->setWidget(dock_main
);
188 dock
->setFeatures(QDockWidget::DockWidgetMovable
|
189 QDockWidget::DockWidgetFloatable
| QDockWidget::DockWidgetClosable
);
191 QAbstractButton
*close_btn
=
192 dock
->findChildren
<QAbstractButton
*>
193 ("qt_dockwidget_closebutton").front();
195 connect(close_btn
, SIGNAL(clicked(bool)),
196 this, SLOT(on_view_close_clicked()));
198 connect(&session
, SIGNAL(trigger_event(util::Timestamp
)),
199 qobject_cast
<views::ViewBase
*>(v
.get()),
200 SLOT(trigger_event(util::Timestamp
)));
202 if (type
== views::ViewTypeTrace
) {
203 views::TraceView::View
*tv
=
204 qobject_cast
<views::TraceView::View
*>(v
.get());
206 tv
->enable_coloured_bg(settings
.value(GlobalSettings::Key_View_ColouredBG
).toBool());
207 tv
->enable_show_sampling_points(settings
.value(GlobalSettings::Key_View_ShowSamplingPoints
).toBool());
210 /* Initial view, create the main bar */
211 main_bar
= make_shared
<MainBar
>(session
, this, tv
);
212 dock_main
->addToolBar(main_bar
.get());
213 session
.set_main_bar(main_bar
);
215 connect(main_bar
.get(), SIGNAL(new_view(Session
*)),
216 this, SLOT(on_new_view(Session
*)));
218 main_bar
->action_view_show_cursors()->setChecked(tv
->cursors_shown());
220 /* For the main view we need to prevent the dock widget from
221 * closing itself when its close button is clicked. This is
222 * so we can confirm with the user first. Regular views don't
224 close_btn
->disconnect(SIGNAL(clicked()), dock
, SLOT(close()));
226 /* Additional view, create a standard bar */
227 pv::views::trace::StandardBar
*standard_bar
=
228 new pv::views::trace::StandardBar(session
, this, tv
);
229 dock_main
->addToolBar(standard_bar
);
231 standard_bar
->action_view_show_cursors()->setChecked(tv
->cursors_shown());
238 void MainWindow::remove_view(shared_ptr
<views::ViewBase
> view
)
240 for (shared_ptr
<Session
> session
: sessions_
) {
241 if (!session
->has_view(view
))
244 // Find the dock the view is contained in and remove it
245 for (auto entry
: view_docks_
)
246 if (entry
.second
== view
) {
247 // Remove the view from the session
248 session
->deregister_view(view
);
250 // Remove the view from its parent; otherwise, Qt will
251 // call deleteLater() on it, which causes a double free
252 // since the shared_ptr in view_docks_ doesn't know
253 // that Qt keeps a pointer to the view around
254 view
->setParent(nullptr);
256 // Delete the view's dock widget and all widgets inside it
257 entry
.first
->deleteLater();
259 // Remove the dock widget from the list and stop iterating
260 view_docks_
.erase(entry
.first
);
266 shared_ptr
<Session
> MainWindow::add_session()
268 static int last_session_id
= 1;
269 QString name
= tr("Session %1").arg(last_session_id
++);
271 shared_ptr
<Session
> session
= make_shared
<Session
>(device_manager_
, name
);
273 connect(session
.get(), SIGNAL(add_view(const QString
&, views::ViewType
, Session
*)),
274 this, SLOT(on_add_view(const QString
&, views::ViewType
, Session
*)));
275 connect(session
.get(), SIGNAL(name_changed()),
276 this, SLOT(on_session_name_changed()));
277 session_state_mapper_
.setMapping(session
.get(), session
.get());
278 connect(session
.get(), SIGNAL(capture_state_changed(int)),
279 &session_state_mapper_
, SLOT(map()));
281 sessions_
.push_back(session
);
283 QMainWindow
*window
= new QMainWindow();
284 window
->setWindowFlags(Qt::Widget
); // Remove Qt::Window flag
285 session_windows_
[session
] = window
;
287 int index
= session_selector_
.addTab(window
, name
);
288 session_selector_
.setCurrentIndex(index
);
289 last_focused_session_
= session
;
291 window
->setDockNestingEnabled(true);
293 shared_ptr
<views::ViewBase
> main_view
=
294 add_view(name
, views::ViewTypeTrace
, *session
);
299 void MainWindow::remove_session(shared_ptr
<Session
> session
)
301 int h
= new_session_button_
->height();
303 for (shared_ptr
<views::ViewBase
> view
: session
->views())
306 QMainWindow
*window
= session_windows_
.at(session
);
307 session_selector_
.removeTab(session_selector_
.indexOf(window
));
309 session_windows_
.erase(session
);
311 if (last_focused_session_
== session
)
312 last_focused_session_
.reset();
314 sessions_
.remove_if([&](shared_ptr
<Session
> s
) {
315 return s
== session
; });
317 if (sessions_
.empty()) {
318 // When there are no more tabs, the height of the QTabWidget
319 // drops to zero. We must prevent this to keep the static
321 for (QWidget
*w
: static_tab_widget_
->findChildren
<QWidget
*>())
322 w
->setMinimumHeight(h
);
324 int margin
= static_tab_widget_
->layout()->contentsMargins().bottom();
325 static_tab_widget_
->setMinimumHeight(h
+ 2 * margin
);
326 session_selector_
.setMinimumHeight(h
+ 2 * margin
);
328 // Update the window title if there is no view left to
329 // generate focus change events
330 setWindowTitle(WindowTitle
);
334 void MainWindow::setup_ui()
336 setObjectName(QString::fromUtf8("MainWindow"));
338 setCentralWidget(&session_selector_
);
340 // Set the window icon
342 icon
.addFile(QString(":/icons/sigrok-logo-notext.png"));
345 view_sticky_scrolling_shortcut_
= new QShortcut(QKeySequence(Qt::Key_S
), this, SLOT(on_view_sticky_scrolling_shortcut()));
346 view_sticky_scrolling_shortcut_
->setAutoRepeat(false);
348 view_show_sampling_points_shortcut_
= new QShortcut(QKeySequence(Qt::Key_Period
), this, SLOT(on_view_show_sampling_points_shortcut()));
349 view_show_sampling_points_shortcut_
->setAutoRepeat(false);
351 view_coloured_bg_shortcut_
= new QShortcut(QKeySequence(Qt::Key_B
), this, SLOT(on_view_coloured_bg_shortcut()));
352 view_coloured_bg_shortcut_
->setAutoRepeat(false);
354 // Set up the tab area
355 new_session_button_
= new QToolButton();
356 new_session_button_
->setIcon(QIcon::fromTheme("document-new",
357 QIcon(":/icons/document-new.png")));
358 new_session_button_
->setToolTip(tr("Create New Session"));
359 new_session_button_
->setAutoRaise(true);
361 run_stop_button_
= new QToolButton();
362 run_stop_button_
->setAutoRaise(true);
363 run_stop_button_
->setToolButtonStyle(Qt::ToolButtonTextBesideIcon
);
364 run_stop_button_
->setToolTip(tr("Start/Stop Acquisition"));
366 run_stop_shortcut_
= new QShortcut(QKeySequence(Qt::Key_Space
), run_stop_button_
, SLOT(click()));
367 run_stop_shortcut_
->setAutoRepeat(false);
369 settings_button_
= new QToolButton();
370 settings_button_
->setIcon(QIcon::fromTheme("configure",
371 QIcon(":/icons/configure.png")));
372 settings_button_
->setToolTip(tr("Settings"));
373 settings_button_
->setAutoRaise(true);
375 QFrame
*separator1
= new QFrame();
376 separator1
->setFrameStyle(QFrame::VLine
| QFrame::Raised
);
377 QFrame
*separator2
= new QFrame();
378 separator2
->setFrameStyle(QFrame::VLine
| QFrame::Raised
);
380 QHBoxLayout
* layout
= new QHBoxLayout();
381 layout
->setContentsMargins(2, 2, 2, 2);
382 layout
->addWidget(new_session_button_
);
383 layout
->addWidget(separator1
);
384 layout
->addWidget(run_stop_button_
);
385 layout
->addWidget(separator2
);
386 layout
->addWidget(settings_button_
);
388 static_tab_widget_
= new QWidget();
389 static_tab_widget_
->setLayout(layout
);
391 session_selector_
.setCornerWidget(static_tab_widget_
, Qt::TopLeftCorner
);
392 session_selector_
.setTabsClosable(true);
394 close_application_shortcut_
= new QShortcut(QKeySequence(Qt::CTRL
+ Qt::Key_Q
), this, SLOT(close()));
395 close_application_shortcut_
->setAutoRepeat(false);
397 close_current_tab_shortcut_
= new QShortcut(QKeySequence(Qt::CTRL
+ Qt::Key_W
), this, SLOT(on_close_current_tab()));
399 connect(new_session_button_
, SIGNAL(clicked(bool)),
400 this, SLOT(on_new_session_clicked()));
401 connect(run_stop_button_
, SIGNAL(clicked(bool)),
402 this, SLOT(on_run_stop_clicked()));
403 connect(&session_state_mapper_
, SIGNAL(mapped(QObject
*)),
404 this, SLOT(on_capture_state_changed(QObject
*)));
405 connect(settings_button_
, SIGNAL(clicked(bool)),
406 this, SLOT(on_settings_clicked()));
408 connect(&session_selector_
, SIGNAL(tabCloseRequested(int)),
409 this, SLOT(on_tab_close_requested(int)));
410 connect(&session_selector_
, SIGNAL(currentChanged(int)),
411 this, SLOT(on_tab_changed(int)));
414 connect(static_cast<QApplication
*>(QCoreApplication::instance()),
415 SIGNAL(focusChanged(QWidget
*, QWidget
*)),
416 this, SLOT(on_focus_changed()));
419 void MainWindow::save_ui_settings()
424 settings
.beginGroup("MainWindow");
425 settings
.setValue("state", saveState());
426 settings
.setValue("geometry", saveGeometry());
429 for (shared_ptr
<Session
> session
: sessions_
) {
430 // Ignore sessions using the demo device or no device at all
431 if (session
->device()) {
432 shared_ptr
<devices::HardwareDevice
> device
=
433 dynamic_pointer_cast
< devices::HardwareDevice
>
437 device
->hardware_device()->driver()->name() == "demo")
440 settings
.beginGroup("Session" + QString::number(id
++));
441 settings
.remove(""); // Remove all keys in this group
442 session
->save_settings(settings
);
447 settings
.setValue("sessions", id
);
450 void MainWindow::restore_ui_settings()
453 int i
, session_count
;
455 settings
.beginGroup("MainWindow");
457 if (settings
.contains("geometry")) {
458 restoreGeometry(settings
.value("geometry").toByteArray());
459 restoreState(settings
.value("state").toByteArray());
465 session_count
= settings
.value("sessions", 0).toInt();
467 for (i
= 0; i
< session_count
; i
++) {
468 settings
.beginGroup("Session" + QString::number(i
));
469 shared_ptr
<Session
> session
= add_session();
470 session
->restore_settings(settings
);
475 shared_ptr
<Session
> MainWindow::get_tab_session(int index
) const
477 // Find the session that belongs to the tab's main window
478 for (auto entry
: session_windows_
)
479 if (entry
.second
== session_selector_
.widget(index
))
485 void MainWindow::closeEvent(QCloseEvent
*event
)
487 bool data_saved
= true;
489 for (auto entry
: session_windows_
)
490 if (!entry
.first
->data_saved())
493 if (!data_saved
&& (QMessageBox::question(this, tr("Confirmation"),
494 tr("There is unsaved data. Close anyway?"),
495 QMessageBox::Yes
| QMessageBox::No
) == QMessageBox::No
)) {
503 QMenu
* MainWindow::createPopupMenu()
508 bool MainWindow::restoreState(const QByteArray
&state
, int version
)
513 // Do nothing. We don't want Qt to handle this, or else it
514 // will try to restore all the dock widgets and create havoc.
519 void MainWindow::session_error(const QString text
, const QString info_text
)
521 QMetaObject::invokeMethod(this, "show_session_error",
522 Qt::QueuedConnection
, Q_ARG(QString
, text
),
523 Q_ARG(QString
, info_text
));
526 void MainWindow::show_session_error(const QString text
, const QString info_text
)
528 QMessageBox
msg(this);
530 msg
.setInformativeText(info_text
);
531 msg
.setStandardButtons(QMessageBox::Ok
);
532 msg
.setIcon(QMessageBox::Warning
);
536 void MainWindow::on_add_view(const QString
&title
, views::ViewType type
,
539 // We get a pointer and need a reference
540 for (shared_ptr
<Session
> s
: sessions_
)
541 if (s
.get() == session
)
542 add_view(title
, type
, *s
);
545 void MainWindow::on_focus_changed()
547 shared_ptr
<views::ViewBase
> view
= get_active_view();
550 for (shared_ptr
<Session
> session
: sessions_
) {
551 if (session
->has_view(view
)) {
552 if (session
!= last_focused_session_
) {
553 // Activate correct tab if necessary
554 shared_ptr
<Session
> tab_session
= get_tab_session(
555 session_selector_
.currentIndex());
556 if (tab_session
!= session
)
557 session_selector_
.setCurrentWidget(
558 session_windows_
.at(session
));
560 on_focused_session_changed(session
);
568 if (sessions_
.empty())
569 setWindowTitle(WindowTitle
);
572 void MainWindow::on_focused_session_changed(shared_ptr
<Session
> session
)
574 last_focused_session_
= session
;
576 setWindowTitle(session
->name() + " - " + WindowTitle
);
578 // Update the state of the run/stop button, too
579 on_capture_state_changed(session
.get());
582 void MainWindow::on_new_session_clicked()
587 void MainWindow::on_run_stop_clicked()
589 shared_ptr
<Session
> session
= last_focused_session_
;
594 switch (session
->get_capture_state()) {
595 case Session::Stopped
:
596 session
->start_capture([&](QString message
) {
597 session_error("Capture failed", message
); });
599 case Session::AwaitingTrigger
:
600 case Session::Running
:
601 session
->stop_capture();
606 void MainWindow::on_settings_clicked()
608 dialogs::Settings
dlg(device_manager_
);
612 void MainWindow::on_session_name_changed()
614 // Update the corresponding dock widget's name(s)
615 Session
*session
= qobject_cast
<Session
*>(QObject::sender());
618 for (shared_ptr
<views::ViewBase
> view
: session
->views()) {
619 // Get the dock that contains the view
620 for (auto entry
: view_docks_
)
621 if (entry
.second
== view
) {
622 entry
.first
->setObjectName(session
->name());
623 entry
.first
->setWindowTitle(session
->name());
627 // Update the tab widget by finding the main window and the tab from that
628 for (auto entry
: session_windows_
)
629 if (entry
.first
.get() == session
) {
630 QMainWindow
*window
= entry
.second
;
631 const int index
= session_selector_
.indexOf(window
);
632 session_selector_
.setTabText(index
, session
->name());
635 // Refresh window title if the affected session has focus
636 if (session
== last_focused_session_
.get())
637 setWindowTitle(session
->name() + " - " + WindowTitle
);
640 void MainWindow::on_capture_state_changed(QObject
*obj
)
642 Session
*caller
= qobject_cast
<Session
*>(obj
);
644 // Ignore if caller is not the currently focused session
645 // unless there is only one session
646 if ((sessions_
.size() > 1) && (caller
!= last_focused_session_
.get()))
649 int state
= caller
->get_capture_state();
651 const QIcon
*icons
[] = {&icon_grey_
, &icon_red_
, &icon_green_
};
652 run_stop_button_
->setIcon(*icons
[state
]);
653 run_stop_button_
->setText((state
== pv::Session::Stopped
) ?
654 tr("Run") : tr("Stop"));
657 void MainWindow::on_new_view(Session
*session
)
659 // We get a pointer and need a reference
660 for (shared_ptr
<Session
> s
: sessions_
)
661 if (s
.get() == session
)
662 add_view(session
->name(), views::ViewTypeTrace
, *s
);
665 void MainWindow::on_view_close_clicked()
667 // Find the dock widget that contains the close button that was clicked
668 QObject
*w
= QObject::sender();
669 QDockWidget
*dock
= nullptr;
672 dock
= qobject_cast
<QDockWidget
*>(w
);
678 // Get the view contained in the dock widget
679 shared_ptr
<views::ViewBase
> view
;
681 for (auto entry
: view_docks_
)
682 if (entry
.first
== dock
)
685 // Deregister the view
686 for (shared_ptr
<Session
> session
: sessions_
) {
687 if (!session
->has_view(view
))
690 // Also destroy the entire session if its main view is closing...
691 if (view
== session
->main_view()) {
692 // ...but only if data is saved or the user confirms closing
693 if (session
->data_saved() || (QMessageBox::question(this, tr("Confirmation"),
694 tr("This session contains unsaved data. Close it anyway?"),
695 QMessageBox::Yes
| QMessageBox::No
) == QMessageBox::Yes
))
696 remove_session(session
);
699 // All other views can be closed at any time as no data will be lost
704 void MainWindow::on_tab_changed(int index
)
706 shared_ptr
<Session
> session
= get_tab_session(index
);
709 on_focused_session_changed(session
);
712 void MainWindow::on_tab_close_requested(int index
)
714 shared_ptr
<Session
> session
= get_tab_session(index
);
718 if (session
->data_saved() || (QMessageBox::question(this, tr("Confirmation"),
719 tr("This session contains unsaved data. Close it anyway?"),
720 QMessageBox::Yes
| QMessageBox::No
) == QMessageBox::Yes
))
721 remove_session(session
);
724 void MainWindow::on_view_coloured_bg_shortcut()
726 GlobalSettings settings
;
728 bool state
= settings
.value(GlobalSettings::Key_View_ColouredBG
).toBool();
729 settings
.setValue(GlobalSettings::Key_View_ColouredBG
, !state
);
732 void MainWindow::on_view_sticky_scrolling_shortcut()
734 GlobalSettings settings
;
736 bool state
= settings
.value(GlobalSettings::Key_View_StickyScrolling
).toBool();
737 settings
.setValue(GlobalSettings::Key_View_StickyScrolling
, !state
);
740 void MainWindow::on_view_show_sampling_points_shortcut()
742 GlobalSettings settings
;
744 bool state
= settings
.value(GlobalSettings::Key_View_ShowSamplingPoints
).toBool();
745 settings
.setValue(GlobalSettings::Key_View_ShowSamplingPoints
, !state
);
748 void MainWindow::on_settingViewColouredBg_changed(const QVariant new_value
)
750 bool state
= new_value
.toBool();
752 for (auto entry
: view_docks_
) {
753 shared_ptr
<views::ViewBase
> viewbase
= entry
.second
;
755 // Only trace views have this setting
756 views::TraceView::View
* view
=
757 qobject_cast
<views::TraceView::View
*>(viewbase
.get());
759 view
->enable_coloured_bg(state
);
763 void MainWindow::on_settingViewShowSamplingPoints_changed(const QVariant new_value
)
765 bool state
= new_value
.toBool();
767 for (auto entry
: view_docks_
) {
768 shared_ptr
<views::ViewBase
> viewbase
= entry
.second
;
770 // Only trace views have this setting
771 views::TraceView::View
* view
=
772 qobject_cast
<views::TraceView::View
*>(viewbase
.get());
774 view
->enable_show_sampling_points(state
);
778 void MainWindow::on_close_current_tab()
780 int tab
= session_selector_
.currentIndex();
782 on_tab_close_requested(tab
);