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/>.
23 #include <libsigrokdecode/libsigrokdecode.h>
30 #include <QApplication>
31 #include <QCloseEvent>
32 #include <QDockWidget>
33 #include <QHBoxLayout>
34 #include <QMessageBox>
38 #include "mainwindow.hpp"
40 #include "devicemanager.hpp"
42 #include "devices/hardwaredevice.hpp"
43 #include "dialogs/about.hpp"
44 #include "toolbars/mainbar.hpp"
45 #include "view/view.hpp"
46 #include "views/trace/standardbar.hpp"
50 #include <libsigrokcxx/libsigrokcxx.hpp>
52 using std::dynamic_pointer_cast
;
54 using std::make_shared
;
56 using std::shared_ptr
;
65 using toolbars::MainBar
;
67 const QString
MainWindow::WindowTitle
= tr("PulseView");
69 MainWindow::MainWindow(DeviceManager
&device_manager
,
70 string open_file_name
, string open_file_format
,
73 device_manager_(device_manager
),
74 session_selector_(this),
75 session_state_mapper_(this),
76 action_view_sticky_scrolling_(new QAction(this)),
77 action_view_coloured_bg_(new QAction(this)),
78 action_about_(new QAction(this)),
79 icon_red_(":/icons/status-red.svg"),
80 icon_green_(":/icons/status-green.svg"),
81 icon_grey_(":/icons/status-grey.svg")
83 qRegisterMetaType
<util::Timestamp
>("util::Timestamp");
84 qRegisterMetaType
<uint64_t>("uint64_t");
87 restore_ui_settings();
89 if (!open_file_name
.empty()) {
90 shared_ptr
<Session
> session
= add_session();
91 session
->load_init_file(open_file_name
, open_file_format
);
94 // Add empty default session if there aren't any sessions
95 if (sessions_
.size() == 0) {
96 shared_ptr
<Session
> session
= add_session();
98 map
<string
, string
> dev_info
;
99 shared_ptr
<devices::HardwareDevice
> other_device
, demo_device
;
101 // Use any available device that's not demo
102 for (shared_ptr
<devices::HardwareDevice
> dev
: device_manager_
.devices()) {
103 if (dev
->hardware_device()->driver()->name() == "demo") {
110 // ...and if there isn't any, just use demo then
111 session
->select_device(other_device
? other_device
: demo_device
);
115 MainWindow::~MainWindow()
117 while (!sessions_
.empty())
118 remove_session(sessions_
.front());
121 QAction
* MainWindow::action_view_sticky_scrolling() const
123 return action_view_sticky_scrolling_
;
126 QAction
* MainWindow::action_view_coloured_bg() const
128 return action_view_coloured_bg_
;
131 QAction
* MainWindow::action_about() const
133 return action_about_
;
136 shared_ptr
<views::ViewBase
> MainWindow::get_active_view() const
138 // If there's only one view, use it...
139 if (view_docks_
.size() == 1)
140 return view_docks_
.begin()->second
;
142 // ...otherwise find the dock widget the widget with focus is contained in
143 QObject
*w
= QApplication::focusWidget();
144 QDockWidget
*dock
= 0;
147 dock
= qobject_cast
<QDockWidget
*>(w
);
153 // Get the view contained in the dock widget
154 for (auto entry
: view_docks_
)
155 if (entry
.first
== dock
)
161 shared_ptr
<views::ViewBase
> MainWindow::add_view(const QString
&title
,
162 views::ViewType type
, Session
&session
)
164 QMainWindow
*main_window
= nullptr;
165 for (auto entry
: session_windows_
)
166 if (entry
.first
.get() == &session
)
167 main_window
= entry
.second
;
171 if (type
== views::ViewTypeTrace
) {
172 QDockWidget
* dock
= new QDockWidget(title
, main_window
);
173 dock
->setObjectName(title
);
174 main_window
->addDockWidget(Qt::TopDockWidgetArea
, dock
);
176 // Insert a QMainWindow into the dock widget to allow for a tool bar
177 QMainWindow
*dock_main
= new QMainWindow(dock
);
178 dock_main
->setWindowFlags(Qt::Widget
); // Remove Qt::Window flag
180 shared_ptr
<views::TraceView::View
> v
=
181 make_shared
<views::TraceView::View
>(session
, 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 if (type
== views::ViewTypeTrace
) {
199 connect(&session
, SIGNAL(trigger_event(util::Timestamp
)),
200 qobject_cast
<views::ViewBase
*>(v
.get()),
201 SLOT(trigger_event(util::Timestamp
)));
203 v
->enable_sticky_scrolling(action_view_sticky_scrolling_
->isChecked());
204 v
->enable_coloured_bg(action_view_coloured_bg_
->isChecked());
206 shared_ptr
<MainBar
> main_bar
= session
.main_bar();
208 /* Initial view, create the main bar */
209 main_bar
= make_shared
<MainBar
>(session
, this, v
.get());
210 dock_main
->addToolBar(main_bar
.get());
211 session
.set_main_bar(main_bar
);
213 connect(main_bar
.get(), SIGNAL(new_view(Session
*)),
214 this, SLOT(on_new_view(Session
*)));
216 main_bar
->action_view_show_cursors()->setChecked(v
->cursors_shown());
218 /* For the main view we need to prevent the dock widget from
219 * closing itself when its close button is clicked. This is
220 * so we can confirm with the user first. Regular views don't
222 close_btn
->disconnect(SIGNAL(clicked()), dock
, SLOT(close()));
224 /* Additional view, create a standard bar */
225 pv::views::trace::StandardBar
*standard_bar
=
226 new pv::views::trace::StandardBar(session
, this, v
.get());
227 dock_main
->addToolBar(standard_bar
);
229 standard_bar
->action_view_show_cursors()->setChecked(v
->cursors_shown());
239 void MainWindow::remove_view(shared_ptr
<views::ViewBase
> view
)
241 for (shared_ptr
<Session
> session
: sessions_
) {
242 if (!session
->has_view(view
))
245 // Find the dock the view is contained in and remove it
246 for (auto entry
: view_docks_
)
247 if (entry
.second
== view
) {
248 // Remove the view from the session
249 session
->deregister_view(view
);
251 // Remove the view from its parent; otherwise, Qt will
252 // call deleteLater() on it, which causes a double free
253 // since the shared_ptr in view_docks_ doesn't know
254 // that Qt keeps a pointer to the view around
257 // Delete the view's dock widget and all widgets inside it
258 entry
.first
->deleteLater();
260 // Remove the dock widget from the list and stop iterating
261 view_docks_
.erase(entry
.first
);
267 shared_ptr
<Session
> MainWindow::add_session()
269 static int last_session_id
= 1;
270 QString name
= tr("Untitled-%1").arg(last_session_id
++);
272 shared_ptr
<Session
> session
= make_shared
<Session
>(device_manager_
, name
);
274 connect(session
.get(), SIGNAL(add_view(const QString
&, views::ViewType
, Session
*)),
275 this, SLOT(on_add_view(const QString
&, views::ViewType
, Session
*)));
276 connect(session
.get(), SIGNAL(name_changed()),
277 this, SLOT(on_session_name_changed()));
278 session_state_mapper_
.setMapping(session
.get(), session
.get());
279 connect(session
.get(), SIGNAL(capture_state_changed(int)),
280 &session_state_mapper_
, SLOT(map()));
282 sessions_
.push_back(session
);
284 QMainWindow
*window
= new QMainWindow();
285 window
->setWindowFlags(Qt::Widget
); // Remove Qt::Window flag
286 session_windows_
[session
] = window
;
288 int index
= session_selector_
.addTab(window
, name
);
289 session_selector_
.setCurrentIndex(index
);
290 last_focused_session_
= session
;
292 window
->setDockNestingEnabled(true);
294 shared_ptr
<views::ViewBase
> main_view
=
295 add_view(name
, views::ViewTypeTrace
, *session
);
300 void MainWindow::remove_session(shared_ptr
<Session
> session
)
302 int h
= new_session_button_
->height();
304 for (shared_ptr
<views::ViewBase
> view
: session
->views())
307 QMainWindow
*window
= session_windows_
.at(session
);
308 session_selector_
.removeTab(session_selector_
.indexOf(window
));
310 session_windows_
.erase(session
);
312 if (last_focused_session_
== session
)
313 last_focused_session_
.reset();
315 sessions_
.remove_if([&](shared_ptr
<Session
> s
) {
316 return s
== session
; });
318 if (sessions_
.empty()) {
319 // When there are no more tabs, the height of the QTabWidget
320 // drops to zero. We must prevent this to keep the static
322 for (QWidget
*w
: static_tab_widget_
->findChildren
<QWidget
*>())
323 w
->setMinimumHeight(h
);
325 int margin
= static_tab_widget_
->layout()->contentsMargins().bottom();
326 static_tab_widget_
->setMinimumHeight(h
+ 2 * margin
);
327 session_selector_
.setMinimumHeight(h
+ 2 * margin
);
329 // Update the window title if there is no view left to
330 // generate focus change events
331 setWindowTitle(WindowTitle
);
335 void MainWindow::setup_ui()
337 setObjectName(QString::fromUtf8("MainWindow"));
339 setCentralWidget(&session_selector_
);
341 // Set the window icon
343 icon
.addFile(QString(":/icons/sigrok-logo-notext.png"));
346 action_view_sticky_scrolling_
->setCheckable(true);
347 action_view_sticky_scrolling_
->setChecked(true);
348 action_view_sticky_scrolling_
->setShortcut(QKeySequence(Qt::Key_S
));
349 action_view_sticky_scrolling_
->setObjectName(
350 QString::fromUtf8("actionViewStickyScrolling"));
351 action_view_sticky_scrolling_
->setText(tr("&Sticky Scrolling"));
353 action_view_coloured_bg_
->setCheckable(true);
354 action_view_coloured_bg_
->setChecked(true);
355 action_view_coloured_bg_
->setShortcut(QKeySequence(Qt::Key_B
));
356 action_view_coloured_bg_
->setObjectName(
357 QString::fromUtf8("actionViewColouredBg"));
358 action_view_coloured_bg_
->setText(tr("Use &Coloured Backgrounds"));
360 action_about_
->setObjectName(QString::fromUtf8("actionAbout"));
361 action_about_
->setToolTip(tr("&About..."));
363 // Set up the tab area
364 new_session_button_
= new QToolButton();
365 new_session_button_
->setIcon(QIcon::fromTheme("document-new",
366 QIcon(":/icons/document-new.png")));
367 new_session_button_
->setToolTip(tr("Create New Session"));
368 new_session_button_
->setAutoRaise(true);
370 run_stop_button_
= new QToolButton();
371 run_stop_button_
->setAutoRaise(true);
372 run_stop_button_
->setToolButtonStyle(Qt::ToolButtonTextBesideIcon
);
373 run_stop_button_
->setShortcut(QKeySequence(Qt::Key_Space
));
374 run_stop_button_
->setToolTip(tr("Start/Stop Acquisition"));
376 settings_button_
= new QToolButton();
377 settings_button_
->setIcon(QIcon::fromTheme("configure",
378 QIcon(":/icons/configure.png")));
379 settings_button_
->setToolTip(tr("Settings"));
380 settings_button_
->setAutoRaise(true);
382 QFrame
*separator1
= new QFrame();
383 separator1
->setFrameStyle(QFrame::VLine
| QFrame::Raised
);
384 QFrame
*separator2
= new QFrame();
385 separator2
->setFrameStyle(QFrame::VLine
| QFrame::Raised
);
387 QHBoxLayout
* layout
= new QHBoxLayout();
388 layout
->setContentsMargins(2, 2, 2, 2);
389 layout
->addWidget(new_session_button_
);
390 layout
->addWidget(separator1
);
391 layout
->addWidget(run_stop_button_
);
392 layout
->addWidget(separator2
);
393 layout
->addWidget(settings_button_
);
395 static_tab_widget_
= new QWidget();
396 static_tab_widget_
->setLayout(layout
);
398 session_selector_
.setCornerWidget(static_tab_widget_
, Qt::TopLeftCorner
);
399 session_selector_
.setTabsClosable(true);
401 connect(new_session_button_
, SIGNAL(clicked(bool)),
402 this, SLOT(on_new_session_clicked()));
403 connect(run_stop_button_
, SIGNAL(clicked(bool)),
404 this, SLOT(on_run_stop_clicked()));
405 connect(&session_state_mapper_
, SIGNAL(mapped(QObject
*)),
406 this, SLOT(on_capture_state_changed(QObject
*)));
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 std::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 (std::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_session_name_changed()
608 // Update the corresponding dock widget's name(s)
609 Session
*session
= qobject_cast
<Session
*>(QObject::sender());
612 for (shared_ptr
<views::ViewBase
> view
: session
->views()) {
613 // Get the dock that contains the view
614 for (auto entry
: view_docks_
)
615 if (entry
.second
== view
) {
616 entry
.first
->setObjectName(session
->name());
617 entry
.first
->setWindowTitle(session
->name());
621 // Update the tab widget by finding the main window and the tab from that
622 for (auto entry
: session_windows_
)
623 if (entry
.first
.get() == session
) {
624 QMainWindow
*window
= entry
.second
;
625 const int index
= session_selector_
.indexOf(window
);
626 session_selector_
.setTabText(index
, session
->name());
629 // Refresh window title if the affected session has focus
630 if (session
== last_focused_session_
.get())
631 setWindowTitle(session
->name() + " - " + WindowTitle
);
634 void MainWindow::on_capture_state_changed(QObject
*obj
)
636 Session
*caller
= qobject_cast
<Session
*>(obj
);
638 // Ignore if caller is not the currently focused session
639 // unless there is only one session
640 if ((sessions_
.size() > 1) && (caller
!= last_focused_session_
.get()))
643 int state
= caller
->get_capture_state();
645 const QIcon
*icons
[] = {&icon_grey_
, &icon_red_
, &icon_green_
};
646 run_stop_button_
->setIcon(*icons
[state
]);
647 run_stop_button_
->setText((state
== pv::Session::Stopped
) ?
648 tr("Run") : tr("Stop"));
651 void MainWindow::on_new_view(Session
*session
)
653 // We get a pointer and need a reference
654 for (std::shared_ptr
<Session
> s
: sessions_
)
655 if (s
.get() == session
)
656 add_view(session
->name(), views::ViewTypeTrace
, *s
);
659 void MainWindow::on_view_close_clicked()
661 // Find the dock widget that contains the close button that was clicked
662 QObject
*w
= QObject::sender();
663 QDockWidget
*dock
= 0;
666 dock
= qobject_cast
<QDockWidget
*>(w
);
672 // Get the view contained in the dock widget
673 shared_ptr
<views::ViewBase
> view
;
675 for (auto entry
: view_docks_
)
676 if (entry
.first
== dock
)
679 // Deregister the view
680 for (shared_ptr
<Session
> session
: sessions_
) {
681 if (!session
->has_view(view
))
684 // Also destroy the entire session if its main view is closing...
685 if (view
== session
->main_view()) {
686 // ...but only if data is saved or the user confirms closing
687 if (session
->data_saved() || (QMessageBox::question(this, tr("Confirmation"),
688 tr("This session contains unsaved data. Close it anyway?"),
689 QMessageBox::Yes
| QMessageBox::No
) == QMessageBox::Yes
))
690 remove_session(session
);
693 // All other views can be closed at any time as no data will be lost
698 void MainWindow::on_tab_changed(int index
)
700 shared_ptr
<Session
> session
= get_tab_session(index
);
703 on_focused_session_changed(session
);
706 void MainWindow::on_tab_close_requested(int index
)
708 shared_ptr
<Session
> session
= get_tab_session(index
);
712 if (session
->data_saved() || (QMessageBox::question(this, tr("Confirmation"),
713 tr("This session contains unsaved data. Close it anyway?"),
714 QMessageBox::Yes
| QMessageBox::No
) == QMessageBox::Yes
))
715 remove_session(session
);
718 void MainWindow::on_actionViewStickyScrolling_triggered()
720 shared_ptr
<views::ViewBase
> viewbase
= get_active_view();
721 views::TraceView::View
* view
=
722 qobject_cast
<views::TraceView::View
*>(viewbase
.get());
724 view
->enable_sticky_scrolling(action_view_sticky_scrolling_
->isChecked());
727 void MainWindow::on_actionViewColouredBg_triggered()
729 shared_ptr
<views::ViewBase
> viewbase
= get_active_view();
730 views::TraceView::View
* view
=
731 qobject_cast
<views::TraceView::View
*>(viewbase
.get());
733 view
->enable_coloured_bg(action_view_coloured_bg_
->isChecked());
736 void MainWindow::on_actionAbout_triggered()
738 dialogs::About
dlg(device_manager_
.context(), this);