diff --git a/src/kdefrontend/MainWin.cpp b/src/kdefrontend/MainWin.cpp index 0bcd87cff..77c1a7770 100644 --- a/src/kdefrontend/MainWin.cpp +++ b/src/kdefrontend/MainWin.cpp @@ -1,1885 +1,1886 @@ /*************************************************************************** File : MainWin.cc Project : LabPlot Description : Main window of the application -------------------------------------------------------------------- Copyright : (C) 2009-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2008-2015 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "MainWin.h" #include "backend/core/Project.h" #include "backend/core/Folder.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Workbook.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include "backend/worksheet/Worksheet.h" #include "backend/datasources/LiveDataSource.h" #include "backend/datasources/projects/OriginProjectParser.h" #ifdef HAVE_CANTOR_LIBS #include "backend/cantorWorksheet/CantorWorksheet.h" #endif #include "backend/datapicker/Datapicker.h" #include "backend/note/Note.h" #include "backend/lib/macros.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #endif #include "commonfrontend/core/PartMdiView.h" #include "commonfrontend/ProjectExplorer.h" #include "commonfrontend/matrix/MatrixView.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include "commonfrontend/worksheet/WorksheetView.h" #ifdef HAVE_CANTOR_LIBS #include "commonfrontend/cantorWorksheet/CantorWorksheetView.h" #endif #include "commonfrontend/datapicker/DatapickerView.h" #include "commonfrontend/datapicker/DatapickerImageView.h" #include "commonfrontend/note/NoteView.h" #include "kdefrontend/datasources/ImportFileDialog.h" #include "kdefrontend/datasources/ImportProjectDialog.h" #include "kdefrontend/datasources/ImportSQLDatabaseDialog.h" #include "kdefrontend/dockwidgets/ProjectDock.h" #include "kdefrontend/HistoryDialog.h" #include "kdefrontend/SettingsDialog.h" #include "kdefrontend/GuiObserver.h" #include "kdefrontend/widgets/FITSHeaderEditDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_CANTOR_LIBS #include #endif /*! \class MainWin \brief Main application window. \ingroup kdefrontend */ MainWin::MainWin(QWidget *parent, const QString& filename) : KXmlGuiWindow(parent), m_currentSubWindow(0), m_project(0), m_aspectTreeModel(0), m_projectExplorer(0), m_projectExplorerDock(0), m_propertiesDock(0), m_currentAspect(0), m_currentFolder(0), m_suppressCurrentSubWindowChangedEvent(false), m_closing(false), m_autoSaveActive(false), m_visibilityMenu(0), m_newMenu(0), m_editMenu(0), axisDock(0), notesDock(0), cartesianPlotDock(0), cartesianPlotLegendDock(0), columnDock(0), m_liveDataDock(0), matrixDock(0), spreadsheetDock(0), projectDock(0), xyCurveDock(0), xyEquationCurveDock(0), xyDataReductionCurveDock(0), xyDifferentiationCurveDock(0), xyIntegrationCurveDock(0), xyInterpolationCurveDock(0), xySmoothCurveDock(0), xyFitCurveDock(0), xyFourierFilterCurveDock(0), xyFourierTransformCurveDock(0), histogramDock(0), worksheetDock(0), textLabelDock(0), customPointDock(0), datapickerImageDock(0), datapickerCurveDock(0), #ifdef HAVE_CANTOR_LIBS cantorWorksheetDock(0), #endif m_guiObserver(0) { initGUI(filename); setAcceptDrops(true); //restore the geometry KConfigGroup group = KSharedConfig::openConfig()->group("MainWin"); restoreGeometry(group.readEntry("geometry", QByteArray())); } MainWin::~MainWin() { //save the recent opened files m_recentProjectsAction->saveEntries( KSharedConfig::openConfig()->group("Recent Files") ); KConfigGroup group = KSharedConfig::openConfig()->group("MainWin"); group.writeEntry("geometry", saveGeometry()); KSharedConfig::openConfig()->sync(); if (m_project != 0) { m_mdiArea->closeAllSubWindows(); disconnect(m_project, 0, this, 0); delete m_project; } if (m_aspectTreeModel) delete m_aspectTreeModel; if (m_guiObserver) delete m_guiObserver; } void MainWin::showPresenter() { Worksheet* w = activeWorksheet(); if (w) { WorksheetView* view = dynamic_cast(w->view()); view->presenterMode(); } else { //currently active object is not a worksheet but we're asked to start in the presenter mode //determine the first available worksheet and show it in the presenter mode QVector worksheets = m_project->children(); if (worksheets.size()>0) { WorksheetView* view = qobject_cast(worksheets.first()->view()); view->presenterMode(); } else { QMessageBox::information(this, i18n("Presenter Mode"), i18n("No worksheets are available in the project. The presenter mode will not be started.")); } } } AspectTreeModel* MainWin::model() const { return m_aspectTreeModel; } Project* MainWin::project() const { return m_project; } void MainWin::initGUI(const QString& fileName) { m_mdiArea = new QMdiArea; setCentralWidget(m_mdiArea); connect(m_mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)), this, SLOT(handleCurrentSubWindowChanged(QMdiSubWindow*))); statusBar()->showMessage(i18nc("%1 is the LabPlot version", "Welcome to LabPlot %1", QLatin1String(LVERSION))); initActions(); #ifdef Q_OS_DARWIN setupGUI(Default, QLatin1String("/Applications/labplot2.app/Contents/Resources/labplot2ui.rc")); #else setupGUI(Default, QLatin1String("labplot2ui.rc")); #endif //all toolbars created via the KXMLGUI framework are locked on default: // * on the very first program start, unlock all toolbars // * on later program starts, set stored lock status //Furthermore, we want to show icons only after the first program start. KConfigGroup groupMain = KSharedConfig::openConfig()->group("MainWindow"); if (groupMain.exists()) { //KXMLGUI framework automatically stores "Disabled" for the key "ToolBarsMovable" //in case the toolbars are locked -> load this value const QString& str = groupMain.readEntry(QLatin1String("ToolBarsMovable"), ""); bool locked = (str == QLatin1String("Disabled")); KToolBar::setToolBarsLocked(locked); } else { //first start KToolBar::setToolBarsLocked(false); //show icons only for (auto* container : factory()->containers(QLatin1String("ToolBar"))) { QToolBar* toolbar = dynamic_cast(container); if (toolbar) toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly); } } initMenus(); QToolBar* mainToolBar = qobject_cast(factory()->container("main_toolbar", this)); if (!mainToolBar) { QMessageBox::critical(this, i18n("GUI configuration file not found"), i18n("labplot2ui.rc file was not found. Please check your installation.")); //TODO: the application is not really usable if the rc file was not found. We should quit the application. The following line crashes //the application because of the splash screen. We need to find another solution. // QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); //call close as soon as we enter the eventloop return; } QToolButton* tbImport = new QToolButton(mainToolBar); tbImport->setPopupMode(QToolButton::MenuButtonPopup); tbImport->setMenu(m_importMenu); tbImport->setDefaultAction(m_importFileAction); mainToolBar->addWidget(tbImport); qobject_cast(factory()->container("import", this))->setIcon(QIcon::fromTheme("document-import")); setWindowIcon(QIcon::fromTheme("LabPlot2", QGuiApplication::windowIcon())); setAttribute( Qt::WA_DeleteOnClose ); //make the status bar of a fixed size in order to avoid height changes when placing a ProgressBar there. QFont font; font.setFamily(font.defaultFamily()); QFontMetrics fm(font); statusBar()->setFixedHeight(fm.height()+5); //load recently used projects m_recentProjectsAction->loadEntries( KSharedConfig::openConfig()->group("Recent Files") ); //set the view mode of the mdi area KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); int viewMode = group.readEntry("ViewMode", 0); if (viewMode == 1) { m_mdiArea->setViewMode(QMdiArea::TabbedView); int tabPosition = group.readEntry("TabPosition", 0); m_mdiArea->setTabPosition(QTabWidget::TabPosition(tabPosition)); m_mdiArea->setTabsClosable(true); m_mdiArea->setTabsMovable(true); m_tileWindows->setVisible(false); m_cascadeWindows->setVisible(false); } //auto-save m_autoSaveActive = group.readEntry("AutoSave", 0); int interval = group.readEntry("AutoSaveInterval", 1); interval = interval*60*1000; m_autoSaveTimer.setInterval(interval); connect(&m_autoSaveTimer, SIGNAL(timeout()), this, SLOT(autoSaveProject())); if (!fileName.isEmpty()) { if (Project::isLabPlotProject(fileName) || OriginProjectParser::isOriginProject(fileName)) { QTimer::singleShot(0, this, [=] () { openProject(fileName); }); } else { newProject(); QTimer::singleShot(0, this, [=] () { importFileDialog(fileName); }); } } else { //There is no file to open. Depending on the settings do nothing, //create a new project or open the last used project. int load = group.readEntry("LoadOnStart", 0); if (load == 1) //create new project newProject(); else if (load == 2) { //create new project with a worksheet newProject(); newWorksheet(); } else if (load == 3) { //open last used project if (!m_recentProjectsAction->urls().isEmpty()) { QDEBUG("TO OPEN m_recentProjectsAction->urls() =" << m_recentProjectsAction->urls().first()); openRecentProject( m_recentProjectsAction->urls().constFirst() ); } } } updateGUIOnProjectChanges(); } void MainWin::initActions() { // ******************** File-menu ******************************* //add some standard actions KStandardAction::openNew(this, SLOT(newProject()),actionCollection()); KStandardAction::open(this, SLOT(openProject()),actionCollection()); m_recentProjectsAction = KStandardAction::openRecent(this, SLOT(openRecentProject(QUrl)),actionCollection()); m_closeAction = KStandardAction::close(this, SLOT(closeProject()),actionCollection()); m_saveAction = KStandardAction::save(this, SLOT(saveProject()),actionCollection()); m_saveAsAction = KStandardAction::saveAs(this, SLOT(saveProjectAs()),actionCollection()); m_printAction = KStandardAction::print(this, SLOT(print()),actionCollection()); m_printPreviewAction = KStandardAction::printPreview(this, SLOT(printPreview()),actionCollection()); KStandardAction::fullScreen(this, SLOT(toggleFullScreen()), this, actionCollection()); //New Folder/Workbook/Spreadsheet/Matrix/Worksheet/Datasources m_newWorkbookAction = new QAction(QIcon::fromTheme("labplot-workbook-new"),i18n("Workbook"),this); actionCollection()->addAction("new_workbook", m_newWorkbookAction); connect(m_newWorkbookAction, SIGNAL(triggered()), SLOT(newWorkbook())); m_newDatapickerAction = new QAction(QIcon::fromTheme("color-picker-black"), i18n("Datapicker"), this); actionCollection()->addAction("new_datapicker", m_newDatapickerAction); connect(m_newDatapickerAction, SIGNAL(triggered()), SLOT(newDatapicker())); m_newSpreadsheetAction = new QAction(QIcon::fromTheme("labplot-spreadsheet-new"),i18n("Spreadsheet"),this); // m_newSpreadsheetAction->setShortcut(Qt::CTRL+Qt::Key_Equal); actionCollection()->addAction("new_spreadsheet", m_newSpreadsheetAction); connect(m_newSpreadsheetAction, SIGNAL(triggered()), SLOT(newSpreadsheet())); m_newMatrixAction = new QAction(QIcon::fromTheme("labplot-matrix-new"),i18n("Matrix"),this); // m_newMatrixAction->setShortcut(Qt::CTRL+Qt::Key_Equal); actionCollection()->addAction("new_matrix", m_newMatrixAction); connect(m_newMatrixAction, SIGNAL(triggered()), SLOT(newMatrix())); m_newWorksheetAction= new QAction(QIcon::fromTheme("labplot-worksheet-new"),i18n("Worksheet"),this); // m_newWorksheetAction->setShortcut(Qt::ALT+Qt::Key_X); actionCollection()->addAction("new_worksheet", m_newWorksheetAction); connect(m_newWorksheetAction, SIGNAL(triggered()), SLOT(newWorksheet())); m_newNotesAction= new QAction(QIcon::fromTheme("document-new"),i18n("Note"),this); actionCollection()->addAction("new_notes", m_newNotesAction); connect(m_newNotesAction, SIGNAL(triggered()), SLOT(newNotes())); // m_newScriptAction = new QAction(QIcon::fromTheme("insert-text"),i18n("Note/Script"),this); // actionCollection()->addAction("new_script", m_newScriptAction); // connect(m_newScriptAction, SIGNAL(triggered()),SLOT(newScript())); m_newFolderAction = new QAction(QIcon::fromTheme("folder-new"),i18n("Folder"),this); actionCollection()->addAction("new_folder", m_newFolderAction); connect(m_newFolderAction, SIGNAL(triggered()), SLOT(newFolder())); //"New file datasources" m_newLiveDataSourceAction = new QAction(QIcon::fromTheme("application-octet-stream"),i18n("Live Data Source"),this); actionCollection()->addAction("new_live_datasource", m_newLiveDataSourceAction); connect(m_newLiveDataSourceAction, SIGNAL(triggered()), this, SLOT(newLiveDataSourceActionTriggered())); //Import/Export m_importFileAction = new QAction(QIcon::fromTheme("document-import"), i18n("From File"), this); actionCollection()->setDefaultShortcut(m_importFileAction, Qt::CTRL+Qt::SHIFT+Qt::Key_I); actionCollection()->addAction("import_file", m_importFileAction); connect(m_importFileAction, SIGNAL(triggered()), SLOT(importFileDialog())); m_importSqlAction = new QAction(QIcon::fromTheme("document-import-database"), i18n("From SQL Database"), this); actionCollection()->addAction("import_sql", m_importSqlAction); connect(m_importSqlAction, SIGNAL(triggered()),SLOT(importSqlDialog())); m_importLabPlotAction = new QAction(QIcon::fromTheme("document-import"), i18n("LabPlot Project"), this); actionCollection()->addAction("import_labplot", m_importLabPlotAction); connect(m_importLabPlotAction, SIGNAL(triggered()),SLOT(importProjectDialog())); #ifdef HAVE_LIBORIGIN m_importOpjAction = new QAction(QIcon::fromTheme("document-import-database"), i18n("Origin Project (OPJ)"), this); actionCollection()->addAction("import_opj", m_importOpjAction); connect(m_importOpjAction, SIGNAL(triggered()),SLOT(importProjectDialog())); #endif m_exportAction = new QAction(QIcon::fromTheme("document-export"), i18n("Export"), this); actionCollection()->setDefaultShortcut(m_exportAction, Qt::CTRL+Qt::SHIFT+Qt::Key_E); actionCollection()->addAction("export", m_exportAction); connect(m_exportAction, SIGNAL(triggered()), SLOT(exportDialog())); m_editFitsFileAction = new QAction(i18n("FITS Metadata Editor"), this); actionCollection()->addAction("edit_fits", m_editFitsFileAction); connect(m_editFitsFileAction, SIGNAL(triggered()), SLOT(editFitsFileDialog())); // Edit //Undo/Redo-stuff m_undoAction = KStandardAction::undo(this, SLOT(undo()), actionCollection()); m_redoAction = KStandardAction::redo(this, SLOT(redo()), actionCollection()); m_historyAction = new QAction(QIcon::fromTheme("view-history"), i18n("Undo/Redo History"),this); actionCollection()->addAction("history", m_historyAction); connect(m_historyAction, SIGNAL(triggered()), SLOT(historyDialog())); // TODO: more menus // Appearance // Analysis: see WorksheetView.cpp // Drawing // Script //Windows QAction* action = new QAction(i18n("&Close"), this); actionCollection()->setDefaultShortcut(action, Qt::CTRL+Qt::Key_U); action->setStatusTip(i18n("Close the active window")); actionCollection()->addAction("close window", action); connect(action, SIGNAL(triggered()), m_mdiArea, SLOT(closeActiveSubWindow())); action = new QAction(i18n("Close &All"), this); action->setStatusTip(i18n("Close all the windows")); actionCollection()->addAction("close all windows", action); connect(action, SIGNAL(triggered()), m_mdiArea, SLOT(closeAllSubWindows())); m_tileWindows = new QAction(i18n("&Tile"), this); m_tileWindows->setStatusTip(i18n("Tile the windows")); actionCollection()->addAction("tile windows", m_tileWindows); connect(m_tileWindows, SIGNAL(triggered()), m_mdiArea, SLOT(tileSubWindows())); m_cascadeWindows = new QAction(i18n("&Cascade"), this); m_cascadeWindows->setStatusTip(i18n("Cascade the windows")); actionCollection()->addAction("cascade windows", m_cascadeWindows); connect(m_cascadeWindows, SIGNAL(triggered()), m_mdiArea, SLOT(cascadeSubWindows())); action = new QAction(QIcon::fromTheme("go-next-view"), i18n("Ne&xt"), this); action->setStatusTip(i18n("Move the focus to the next window")); actionCollection()->addAction("next window", action); connect(action, SIGNAL(triggered()), m_mdiArea, SLOT(activateNextSubWindow())); action = new QAction(QIcon::fromTheme("go-previous-view"), i18n("Pre&vious"), this); action->setStatusTip(i18n("Move the focus to the previous window")); actionCollection()->addAction("previous window", action); connect(action, SIGNAL(triggered()), m_mdiArea, SLOT(activatePreviousSubWindow())); //"Standard actions" KStandardAction::preferences(this, SLOT(settingsDialog()), actionCollection()); KStandardAction::quit(this, SLOT(close()), actionCollection()); //Actions for window visibility QActionGroup* windowVisibilityActions = new QActionGroup(this); windowVisibilityActions->setExclusive(true); m_visibilityFolderAction = new QAction(QIcon::fromTheme("folder"), i18n("Current &Folder Only"), windowVisibilityActions); m_visibilityFolderAction->setCheckable(true); m_visibilityFolderAction->setData(Project::folderOnly); m_visibilitySubfolderAction = new QAction(QIcon::fromTheme("folder-documents"), i18n("Current Folder and &Subfolders"), windowVisibilityActions); m_visibilitySubfolderAction->setCheckable(true); m_visibilitySubfolderAction->setData(Project::folderAndSubfolders); m_visibilityAllAction = new QAction(i18n("&All"), windowVisibilityActions); m_visibilityAllAction->setCheckable(true); m_visibilityAllAction->setData(Project::allMdiWindows); connect(windowVisibilityActions, SIGNAL(triggered(QAction*)), this, SLOT(setMdiWindowVisibility(QAction*))); //Actions for hiding/showing the dock widgets QActionGroup * docksActions = new QActionGroup(this); docksActions->setExclusive(false); m_toggleProjectExplorerDockAction = new QAction(QIcon::fromTheme("view-list-tree"), i18n("Project Explorer"), docksActions); m_toggleProjectExplorerDockAction->setCheckable(true); m_toggleProjectExplorerDockAction->setChecked(true); actionCollection()->addAction("toggle_project_explorer_dock", m_toggleProjectExplorerDockAction); m_togglePropertiesDockAction = new QAction(QIcon::fromTheme("view-list-details"), i18n("Properties Explorer"), docksActions); m_togglePropertiesDockAction->setCheckable(true); m_togglePropertiesDockAction->setChecked(true); actionCollection()->addAction("toggle_properties_explorer_dock", m_togglePropertiesDockAction); connect(docksActions, SIGNAL(triggered(QAction*)), this, SLOT(toggleDockWidget(QAction*))); } void MainWin::initMenus() { //menu for adding new aspects m_newMenu = new QMenu(i18n("Add New"), this); m_newMenu->setIcon(QIcon::fromTheme("document-new")); m_newMenu->addAction(m_newFolderAction); m_newMenu->addAction(m_newWorkbookAction); m_newMenu->addAction(m_newSpreadsheetAction); m_newMenu->addAction(m_newMatrixAction); m_newMenu->addAction(m_newWorksheetAction); m_newMenu->addAction(m_newNotesAction); m_newMenu->addAction(m_newDatapickerAction); m_newMenu->addSeparator(); m_newMenu->addAction(m_newLiveDataSourceAction); //import menu m_importMenu = new QMenu(this); m_importMenu->setIcon(QIcon::fromTheme("document-import")); m_importMenu ->addAction(m_importFileAction); m_importMenu ->addAction(m_importSqlAction); m_importMenu->addSeparator(); m_importMenu->addAction(m_importLabPlotAction); #ifdef HAVE_LIBORIGIN m_importMenu ->addAction(m_importOpjAction); #endif #ifdef HAVE_CANTOR_LIBS m_newMenu->addSeparator(); m_newCantorWorksheetMenu = new QMenu(i18n("CAS Worksheet")); m_newCantorWorksheetMenu->setIcon(QIcon::fromTheme("archive-insert")); //"Adding Cantor backends to menue and context menu" QStringList m_availableBackend = Cantor::Backend::listAvailableBackends(); if(m_availableBackend.count() > 0) { unplugActionList(QLatin1String("backends_list")); QList newBackendActions; for (Cantor::Backend* backend : Cantor::Backend::availableBackends()) { if (!backend->isEnabled()) continue; QAction* action = new QAction(QIcon::fromTheme(backend->icon()), backend->name(),this); action->setData(backend->name()); newBackendActions << action; m_newCantorWorksheetMenu->addAction(action); } connect(m_newCantorWorksheetMenu, SIGNAL(triggered(QAction*)), this, SLOT(newCantorWorksheet(QAction*))); plugActionList(QLatin1String("backends_list"), newBackendActions); } m_newMenu->addMenu(m_newCantorWorksheetMenu); #else delete this->guiFactory()->container("cas_worksheet", this); delete this->guiFactory()->container("new_cas_worksheet", this); delete this->guiFactory()->container("cas_worksheet_toolbar", this); #endif //menu subwindow visibility policy m_visibilityMenu = new QMenu(i18n("Window Visibility Policy"), this); m_visibilityMenu->setIcon(QIcon::fromTheme("window-duplicate")); m_visibilityMenu ->addAction(m_visibilityFolderAction); m_visibilityMenu ->addAction(m_visibilitySubfolderAction); m_visibilityMenu ->addAction(m_visibilityAllAction); //menu for editing files m_editMenu = new QMenu(i18n("Edit"), this); m_editMenu->addAction(m_editFitsFileAction); KColorSchemeManager schemeManager; KActionMenu* schemesMenu = schemeManager.createSchemeSelectionMenu(i18n("Color Theme"), this); schemesMenu->menu()->setTitle(i18n("Color Theme")); schemesMenu->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-color"))); QMenu* settingsMenu = dynamic_cast(factory()->container("settings", this)); if (settingsMenu) settingsMenu->insertMenu(settingsMenu->actions().constFirst(), schemesMenu->menu()); //set the action for the current color scheme checked KConfigGroup generalGlobalsGroup = KSharedConfig::openConfig(QLatin1String("kdeglobals"))->group("General"); QString defaultSchemeName = generalGlobalsGroup.readEntry("ColorScheme", QStringLiteral("Breeze")); KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); QString schemeName = group.readEntry("ColorScheme", defaultSchemeName); for (auto* action : schemesMenu->menu()->actions()) { if (action->text() == schemeName) { action->setChecked(true); break; } } connect(schemesMenu->menu(), &QMenu::triggered, this, &MainWin::colorSchemeChanged); } void MainWin::colorSchemeChanged(QAction* action) { QString schemeName = KLocalizedString::removeAcceleratorMarker(action->text()); //background of the mdi area is not updated on theme changes, do it here. KColorSchemeManager schemeManager; QModelIndex index = schemeManager.indexForScheme(schemeName); const QPalette& palette = KColorScheme::createApplicationPalette( KSharedConfig::openConfig(index.data(Qt::UserRole).toString()) ); const QBrush& brush = palette.brush(QPalette::Dark); m_mdiArea->setBackground(brush); //save the selected color scheme KConfigGroup group = KSharedConfig::openConfig()->group(QLatin1String("Settings_General")); group.writeEntry("ColorScheme", schemeName); group.sync(); } /*! Asks to save the project if it was modified. \return \c true if the project still needs to be saved ("cancel" clicked), \c false otherwise. */ bool MainWin::warnModified() { if (m_project->hasChanged()) { int want_save = KMessageBox::warningYesNoCancel( this, i18n("The current project %1 has been modified. Do you want to save it?", m_project->name()), i18n("Save Project")); switch (want_save) { case KMessageBox::Yes: return !saveProject(); case KMessageBox::No: break; case KMessageBox::Cancel: return true; } } return false; } /*! * updates the state of actions, menus and toolbars (enabled or disabled) * on project changes (project closes and opens) */ void MainWin::updateGUIOnProjectChanges() { if (m_closing) return; KXMLGUIFactory* factory = this->guiFactory(); if (factory->container("worksheet", this) == NULL) { //no worksheet menu found, most probably labplot2ui.rc //was not properly installed -> return here in order not to crash return; } //disable all menus if there is no project bool b = (m_project == 0); m_saveAction->setEnabled(!b); m_saveAsAction->setEnabled(!b); m_printAction->setEnabled(!b); m_printPreviewAction->setEnabled(!b); m_importFileAction->setEnabled(!b); m_importSqlAction->setEnabled(!b); #ifdef HAVE_LIBORIGIN m_importOpjAction->setEnabled(!b); #endif m_exportAction->setEnabled(!b); m_newWorkbookAction->setEnabled(!b); m_newSpreadsheetAction->setEnabled(!b); m_newMatrixAction->setEnabled(!b); m_newWorksheetAction->setEnabled(!b); m_newDatapickerAction->setEnabled(!b); m_closeAction->setEnabled(!b); m_toggleProjectExplorerDockAction->setEnabled(!b); m_togglePropertiesDockAction->setEnabled(!b); if (!m_mdiArea->currentSubWindow()) { factory->container("spreadsheet", this)->setEnabled(false); factory->container("matrix", this)->setEnabled(false); factory->container("worksheet", this)->setEnabled(false); factory->container("analysis", this)->setEnabled(false); factory->container("datapicker", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->hide(); factory->container("worksheet_toolbar", this)->hide(); factory->container("cartesian_plot_toolbar", this)->hide(); // factory->container("histogram_toolbar",this)->hide(); // factory->container("barchart_toolbar",this)->hide(); factory->container("datapicker_toolbar", this)->hide(); #ifdef HAVE_CANTOR_LIBS factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->hide(); #endif } factory->container("new", this)->setEnabled(!b); factory->container("edit", this)->setEnabled(!b); factory->container("import", this)->setEnabled(!b); if (b) setCaption("LabPlot2"); else setCaption(m_project->name()); // undo/redo actions are disabled in both cases - when the project is closed or opened m_undoAction->setEnabled(false); m_redoAction->setEnabled(false); } /* * updates the state of actions, menus and toolbars (enabled or disabled) * depending on the currently active window (worksheet or spreadsheet). */ void MainWin::updateGUI() { if (m_project->isLoading()) return; if (m_closing) return; KXMLGUIFactory* factory = this->guiFactory(); if (factory->container("worksheet", this) == NULL) { //no worksheet menu found, most probably labplot2ui.rc //was not properly installed -> return here in order not to crash return; } if (!m_mdiArea->currentSubWindow()) { factory->container("spreadsheet", this)->setEnabled(false); factory->container("matrix", this)->setEnabled(false); factory->container("worksheet", this)->setEnabled(false); factory->container("analysis", this)->setEnabled(false); factory->container("datapicker", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->hide(); factory->container("worksheet_toolbar", this)->hide(); // factory->container("histogram_toolbar",this)->hide(); // factory->container("barchart_toolbar",this)->hide(); factory->container("cartesian_plot_toolbar", this)->hide(); factory->container("datapicker_toolbar", this)->hide(); #ifdef HAVE_CANTOR_LIBS factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->hide(); #endif return; } //Handle the Worksheet-object Worksheet* w = this->activeWorksheet(); if (w != 0) { //enable worksheet related menus factory->container("worksheet", this)->setEnabled(true); factory->container("analysis", this)->setEnabled(true); //TODO factory->container("drawing", this)->setEnabled(true); //disable spreadsheet and matrix related menus factory->container("spreadsheet", this)->setEnabled(false); factory->container("matrix", this)->setEnabled(false); //populate worksheet menu WorksheetView* view=qobject_cast(w->view()); QMenu* menu = qobject_cast(factory->container("worksheet", this)); menu->clear(); view->createContextMenu(menu); //populate analysis menu menu = qobject_cast(factory->container("analysis", this)); menu->clear(); view->createAnalysisMenu(menu); //populate worksheet-toolbar QToolBar* toolbar = qobject_cast(factory->container("worksheet_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); //populate the toolbar for cartesian plots toolbar=qobject_cast(factory->container("cartesian_plot_toolbar", this)); toolbar->clear(); view->fillCartesianPlotToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); //hide the spreadsheet toolbar factory->container("spreadsheet_toolbar", this)->setVisible(false); } else { factory->container("worksheet", this)->setEnabled(false); factory->container("analysis", this)->setEnabled(false); // factory->container("drawing", this)->setEnabled(false); factory->container("worksheet_toolbar", this)->setEnabled(false); factory->container("cartesian_plot_toolbar", this)->setEnabled(false); } //Handle the Spreadsheet-object const Spreadsheet* spreadsheet = this->activeSpreadsheet(); if (spreadsheet) { //enable spreadsheet related menus factory->container("spreadsheet", this)->setEnabled(true); //populate spreadsheet-menu SpreadsheetView* view = qobject_cast(spreadsheet->view()); QMenu* menu = qobject_cast(factory->container("spreadsheet", this)); menu->clear(); view->createContextMenu(menu); //populate spreadsheet-toolbar QToolBar* toolbar = qobject_cast(factory->container("spreadsheet_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); toolbar->setEnabled(true); } else { factory->container("spreadsheet", this)->setEnabled(false); factory->container("spreadsheet_toolbar", this)->setEnabled(false); } //Handle the Matrix-object const Matrix* matrix = this->activeMatrix(); if (matrix) { factory->container("matrix", this)->setEnabled(true); //populate matrix-menu MatrixView* view = qobject_cast(matrix->view()); QMenu* menu = qobject_cast(factory->container("matrix", this)); menu->clear(); view->createContextMenu(menu); } else factory->container("matrix", this)->setEnabled(false); #ifdef HAVE_CANTOR_LIBS CantorWorksheet* cantorworksheet = this->activeCantorWorksheet(); if(cantorworksheet) { // enable Cantor Worksheet related menues factory->container("cas_worksheet", this)->setEnabled(true); CantorWorksheetView* view=qobject_cast(cantorworksheet->view()); QMenu* menu=qobject_cast(factory->container("cas_worksheet", this)); menu->clear(); view->createContextMenu(menu); QToolBar* toolbar=qobject_cast(factory->container("cas_worksheet_toolbar", this)); toolbar->setVisible(true); toolbar->clear(); view->fillToolBar(toolbar); } else { //no Cantor worksheet selected -> deactivate Cantor worksheet related menu and toolbar factory->container("cas_worksheet", this)->setEnabled(false); factory->container("cas_worksheet_toolbar", this)->setVisible(false); } #endif const Datapicker* datapicker = this->activeDatapicker(); if (datapicker) { factory->container("datapicker", this)->setEnabled(true); //populate datapicker-menu DatapickerView* view = qobject_cast(datapicker->view()); QMenu* menu = qobject_cast(factory->container("datapicker", this)); menu->clear(); view->createContextMenu(menu); //populate spreadsheet-toolbar QToolBar* toolbar = qobject_cast(factory->container("datapicker_toolbar", this)); toolbar->clear(); view->fillToolBar(toolbar); toolbar->setVisible(true); } else { factory->container("datapicker", this)->setEnabled(false); factory->container("datapicker_toolbar", this)->setVisible(false); } } /*! creates a new empty project. Returns \c true, if a new project was created. */ bool MainWin::newProject() { //close the current project, if available if (!closeProject()) return false; QApplication::processEvents(QEventLoop::AllEvents, 100); if (m_project) delete m_project; if (m_aspectTreeModel) delete m_aspectTreeModel; m_project = new Project(); m_currentAspect = m_project; m_currentFolder = m_project; KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); Project::MdiWindowVisibility vis = Project::MdiWindowVisibility(group.readEntry("MdiWindowVisibility", 0)); m_project->setMdiWindowVisibility( vis ); if (vis == Project::folderOnly) m_visibilityFolderAction->setChecked(true); else if (vis == Project::folderAndSubfolders) m_visibilitySubfolderAction->setChecked(true); else m_visibilityAllAction->setChecked(true); m_aspectTreeModel = new AspectTreeModel(m_project, this); //newProject is called for the first time, there is no project explorer yet //-> initialize the project explorer, the GUI-observer and the dock widgets. if (m_projectExplorer == 0) { m_projectExplorerDock = new QDockWidget(this); m_projectExplorerDock->setObjectName("projectexplorer"); m_projectExplorerDock->setWindowTitle(i18nc("@title:window", "Project Explorer")); addDockWidget(Qt::LeftDockWidgetArea, m_projectExplorerDock); m_projectExplorer = new ProjectExplorer(m_projectExplorerDock); m_projectExplorerDock->setWidget(m_projectExplorer); connect(m_projectExplorer, SIGNAL(currentAspectChanged(AbstractAspect*)), this, SLOT(handleCurrentAspectChanged(AbstractAspect*))); connect(m_projectExplorerDock, SIGNAL(visibilityChanged(bool)), SLOT(projectExplorerDockVisibilityChanged(bool))); //Properties dock m_propertiesDock = new QDockWidget(this); m_propertiesDock->setObjectName("aspect_properties_dock"); m_propertiesDock->setWindowTitle(i18nc("@title:window", "Properties")); addDockWidget(Qt::RightDockWidgetArea, m_propertiesDock); QScrollArea* sa = new QScrollArea(m_propertiesDock); stackedWidget = new QStackedWidget(sa); sa->setWidget(stackedWidget); sa->setWidgetResizable(true); m_propertiesDock->setWidget(sa); connect(m_propertiesDock, SIGNAL(visibilityChanged(bool)), SLOT(propertiesDockVisibilityChanged(bool))); //GUI-observer; m_guiObserver = new GuiObserver(this); } m_projectExplorer->setModel(m_aspectTreeModel); m_projectExplorer->setProject(m_project); m_projectExplorer->setCurrentAspect(m_project); m_projectExplorerDock->show(); m_propertiesDock->show(); updateGUIOnProjectChanges(); connect(m_project, SIGNAL(aspectAdded(const AbstractAspect*)), this, SLOT(handleAspectAdded(const AbstractAspect*))); connect(m_project, SIGNAL(aspectRemoved(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*)), this, SLOT(handleAspectRemoved(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*))); connect(m_project, SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(handleAspectAboutToBeRemoved(const AbstractAspect*))); connect(m_project, SIGNAL(statusInfo(QString)), statusBar(), SLOT(showMessage(QString))); connect(m_project, SIGNAL(changed()), this, SLOT(projectChanged())); connect(m_project, SIGNAL(requestProjectContextMenu(QMenu*)), this, SLOT(createContextMenu(QMenu*))); connect(m_project, SIGNAL(requestFolderContextMenu(const Folder*,QMenu*)), this, SLOT(createFolderContextMenu(const Folder*,QMenu*))); connect(m_project, SIGNAL(mdiWindowVisibilityChanged()), this, SLOT(updateMdiWindowVisibility())); m_undoViewEmptyLabel = i18n("%1: created", m_project->name()); setCaption(m_project->name()); return true; } void MainWin::openProject() { KConfigGroup conf(KSharedConfig::openConfig(), "MainWin"); const QString& dir = conf.readEntry("LastOpenDir", ""); const QString& path = QFileDialog::getOpenFileName(this,i18n("Open Project"), dir, i18n("LabPlot Projects (%1);;Origin Projects (%2)", Project::supportedExtensions(), OriginProjectParser::supportedExtensions()) ); if (path.isEmpty())// "Cancel" was clicked return; this->openProject(path); //save new "last open directory" int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { const QString& newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastOpenDir", newDir); } } void MainWin::openProject(const QString& filename) { if (filename == m_currentFileName) { KMessageBox::information(this, i18n("The project file %1 is already opened.", filename), i18n("Open Project")); return; } if (!newProject()) return; WAIT_CURSOR; QElapsedTimer timer; timer.start(); bool rc = false; if (Project::isLabPlotProject(filename)) rc = m_project->load(filename); else if (OriginProjectParser::isOriginProject(filename)) { OriginProjectParser parser; parser.setProjectFileName(filename); parser.importTo(m_project, QStringList()); //TODO: add return code rc = true; } if (!rc) { closeProject(); RESET_CURSOR; return; } m_currentFileName = filename; m_project->setFileName(filename); m_project->undoStack()->clear(); m_undoViewEmptyLabel = i18n("%1: opened", m_project->name()); m_recentProjectsAction->addUrl( QUrl(filename) ); setCaption(m_project->name()); updateGUIOnProjectChanges(); updateGUI(); //there are most probably worksheets or spreadsheets in the open project -> update the GUI m_saveAction->setEnabled(false); statusBar()->showMessage( i18n("Project successfully opened (in %1 seconds).", (float)timer.elapsed()/1000) ); if (m_autoSaveActive) m_autoSaveTimer.start(); RESET_CURSOR; } void MainWin::openRecentProject(const QUrl& url) { if (url.isLocalFile()) // fix for Windows this->openProject(url.toLocalFile()); else this->openProject(url.path()); } /*! Closes the current project, if available. Return \c true, if the project was closed. */ bool MainWin::closeProject() { if (m_project == 0) return true; //nothing to close if (warnModified()) return false; delete m_aspectTreeModel; m_aspectTreeModel = 0; delete m_project; m_project = 0; m_currentFileName = ""; //update the UI if we're just closing a project //and not closing(quitting) the application if (!m_closing) { m_projectExplorerDock->hide(); m_propertiesDock->hide(); m_currentAspect = 0; m_currentFolder = 0; updateGUIOnProjectChanges(); if (m_autoSaveActive) m_autoSaveTimer.stop(); } return true; } bool MainWin::saveProject() { const QString& fileName = m_project->fileName(); if (fileName.isEmpty()) return saveProjectAs(); else return save(fileName); } bool MainWin::saveProjectAs() { KConfigGroup conf(KSharedConfig::openConfig(), "MainWin"); const QString& dir = conf.readEntry("LastOpenDir", ""); QString path = QFileDialog::getSaveFileName(this, i18n("Save Project As"), dir, i18n("LabPlot Projects (*.lml *.lml.gz *.lml.bz2 *.lml.xz *.LML *.LML.GZ *.LML.BZ2 *.LML.XZ)")); if (path.isEmpty())// "Cancel" was clicked return false; if (path.contains(QLatin1String(".lml"), Qt::CaseInsensitive) == false) path.append(QLatin1String(".lml")); //save new "last open directory" int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { const QString& newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastOpenDir", newDir); } return save(path); } /*! * auxillary function that does the actual saving of the project */ bool MainWin::save(const QString& fileName) { WAIT_CURSOR; // use file ending to find out how to compress file QIODevice* file; // if ending is .lml, do gzip compression anyway if (fileName.endsWith(QLatin1String(".lml"))) file = new KCompressionDevice(fileName, KCompressionDevice::GZip); else file = new KFilterDev(fileName); if (file == 0) file = new QFile(fileName); bool ok; if (file->open(QIODevice::WriteOnly)) { m_project->setFileName(fileName); QXmlStreamWriter writer(file); m_project->save(&writer); m_project->undoStack()->clear(); m_project->setChanged(false); file->close(); setCaption(m_project->name()); statusBar()->showMessage(i18n("Project saved")); m_saveAction->setEnabled(false); m_recentProjectsAction->addUrl( QUrl(fileName) ); ok = true; //if the project dock is visible, refresh the shown content //(version and modification time might have been changed) if (stackedWidget->currentWidget() == projectDock) projectDock->setProject(m_project); //we have a file name now // -> auto save can be activated now if not happened yet if (m_autoSaveActive && !m_autoSaveTimer.isActive()) m_autoSaveTimer.start(); } else { KMessageBox::error(this, i18n("Sorry. Could not open file for writing.")); ok = false; } delete file; RESET_CURSOR; return ok; } /*! * automatically saves the project in the specified time interval. */ void MainWin::autoSaveProject() { //don't auto save when there are no changes or the file name //was not provided yet (the project was never explicitly saved yet). if ( !m_project->hasChanged() || m_project->fileName().isEmpty()) return; this->saveProject(); } /*! prints the current sheet (worksheet, spreadsheet or matrix) */ void MainWin::print() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = dynamic_cast(win)->part(); statusBar()->showMessage(i18n("Preparing printing of %1", part->name())); if (part->printView()) statusBar()->showMessage(i18n("%1 printed", part->name())); else statusBar()->showMessage(""); } void MainWin::printPreview() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = dynamic_cast(win)->part(); statusBar()->showMessage(i18n("Preparing printing of %1", part->name())); if (part->printPreview()) statusBar()->showMessage(i18n("%1 printed", part->name())); else statusBar()->showMessage(""); } /**************************************************************************************/ /*! adds a new Folder to the project. */ void MainWin::newFolder() { Folder* folder = new Folder(i18n("Folder")); this->addAspectToProject(folder); } /*! adds a new Workbook to the project. */ void MainWin::newWorkbook() { Workbook* workbook = new Workbook(0, i18n("Workbook")); this->addAspectToProject(workbook); } /*! adds a new Datapicker to the project. */ void MainWin::newDatapicker() { Datapicker* datapicker = new Datapicker(0, i18n("Datapicker")); this->addAspectToProject(datapicker); } /*! adds a new Spreadsheet to the project. */ void MainWin::newSpreadsheet() { Spreadsheet* spreadsheet = new Spreadsheet(0, i18n("Spreadsheet")); //if the current active window is a workbook and no folder/project is selected in the project explorer, //add the new spreadsheet to the workbook Workbook* workbook = activeWorkbook(); if (workbook) { QModelIndex index = m_projectExplorer->currentIndex(); AbstractAspect* aspect = static_cast(index.internalPointer()); if (!aspect->inherits("Folder")) { workbook->addChild(spreadsheet); return; } } this->addAspectToProject(spreadsheet); } /*! adds a new Matrix to the project. */ void MainWin::newMatrix() { Matrix* matrix = new Matrix(0, i18n("Matrix")); //if the current active window is a workbook and no folder/project is selected in the project explorer, //add the new matrix to the workbook Workbook* workbook = activeWorkbook(); if (workbook) { QModelIndex index = m_projectExplorer->currentIndex(); AbstractAspect* aspect = static_cast(index.internalPointer()); if (!aspect->inherits("Folder")) { workbook->addChild(matrix); return; } } this->addAspectToProject(matrix); } /*! adds a new Worksheet to the project. */ void MainWin::newWorksheet() { Worksheet* worksheet = new Worksheet(0, i18n("Worksheet")); this->addAspectToProject(worksheet); } /*! adds a new Note to the project. */ void MainWin::newNotes() { Note* notes = new Note(i18n("Note")); this->addAspectToProject(notes); } /*! returns a pointer to a Workbook-object, if the currently active Mdi-Subwindow is \a WorkbookView. Otherwise returns \a 0. */ Workbook* MainWin::activeWorkbook() const { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return 0; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); return dynamic_cast(part); } /*! returns a pointer to a Datapicker-object, if the currently active Mdi-Subwindow is \a DatapickerView. Otherwise returns \a 0. */ Datapicker* MainWin::activeDatapicker() const { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return 0; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); return dynamic_cast(part); } /*! returns a pointer to a \c Spreadsheet object, if the currently active Mdi-Subwindow or if the currently selected tab in a \c WorkbookView is a \c SpreadsheetView Otherwise returns \c 0. */ Spreadsheet* MainWin::activeSpreadsheet() const { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return 0; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); Spreadsheet* spreadsheet = 0; const Workbook* workbook = dynamic_cast(part); if (workbook) { spreadsheet = workbook->currentSpreadsheet(); if (!spreadsheet) { //potentially, the spreadsheet was not selected in workbook yet since the selection in project explorer //arrives in workbook's slot later than in this function //->check whether we have a spreadsheet or one of its columns currently selected in the project explorer spreadsheet = dynamic_cast(m_currentAspect); if (!spreadsheet) { if (m_currentAspect->parentAspect()) spreadsheet = dynamic_cast(m_currentAspect->parentAspect()); } } } else spreadsheet = dynamic_cast(part); return spreadsheet; } /*! returns a pointer to a \c Matrix object, if the currently active Mdi-Subwindow or if the currently selected tab in a \c WorkbookView is a \c MatrixView Otherwise returns \c 0. */ Matrix* MainWin::activeMatrix() const { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return 0; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); Matrix* matrix = 0; Workbook* workbook = dynamic_cast(part); if (workbook) { matrix = workbook->currentMatrix(); if (!matrix) { //potentially, the matrix was not selected in workbook yet since the selection in project explorer //arrives in workbook's slot later than in this function //->check whether we have a matrix currently selected in the project explorer matrix = dynamic_cast(m_currentAspect); } } else matrix = dynamic_cast(part); return matrix; } /*! returns a pointer to a Worksheet-object, if the currently active Mdi-Subwindow is \a WorksheetView Otherwise returns \a 0. */ Worksheet* MainWin::activeWorksheet() const { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return 0; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); return dynamic_cast(part); } #ifdef HAVE_CANTOR_LIBS /* adds a new Cantor Spreadsheet to the project. */ void MainWin::newCantorWorksheet(QAction* action) { CantorWorksheet* cantorworksheet = new CantorWorksheet(0, action->data().toString()); this->addAspectToProject(cantorworksheet); } /********************************************************************************/ /*! returns a pointer to a CantorWorksheet-object, if the currently active Mdi-Subwindow is \a CantorWorksheetView Otherwise returns \a 0. */ CantorWorksheet* MainWin::activeCantorWorksheet() const { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return 0; AbstractPart* part = dynamic_cast(win)->part(); Q_ASSERT(part); return dynamic_cast(part); } #endif /*! called if there were changes in the project. Adds "changed" to the window caption and activates the save-Action. */ void MainWin::projectChanged() { setCaption(i18n("%1 [Changed]", m_project->name())); m_saveAction->setEnabled(true); m_undoAction->setEnabled(true); return; } void MainWin::handleCurrentSubWindowChanged(QMdiSubWindow* win) { if (!win) return; PartMdiView *view = qobject_cast(win); if (!view) { updateGUI(); return; } if (view == m_currentSubWindow) { //do nothing, if the current sub-window gets selected again. //This event happens, when labplot loses the focus (modal window is opened or the user switches to another application) //and gets it back (modal window is closed or the user switches back to labplot). return; } else m_currentSubWindow = view; updateGUI(); if (!m_suppressCurrentSubWindowChangedEvent) m_projectExplorer->setCurrentAspect(view->part()); } void MainWin::handleAspectAdded(const AbstractAspect* aspect) { const AbstractPart* part = dynamic_cast(aspect); if (part) { //TODO: export, print and print preview should be handled in the views and not in MainWin. connect(part, SIGNAL(exportRequested()), this, SLOT(exportDialog())); connect(part, SIGNAL(printRequested()), this, SLOT(print())); connect(part, SIGNAL(printPreviewRequested()), this, SLOT(printPreview())); connect(part, SIGNAL(showRequested()), this, SLOT(handleShowSubWindowRequested())); } } void MainWin::handleAspectRemoved(const AbstractAspect* parent,const AbstractAspect* before,const AbstractAspect* aspect) { Q_UNUSED(before); Q_UNUSED(aspect); m_projectExplorer->setCurrentAspect(parent); } void MainWin::handleAspectAboutToBeRemoved(const AbstractAspect *aspect) { const AbstractPart *part = qobject_cast(aspect); if (!part) return; const Workbook* workbook = dynamic_cast(aspect->parentAspect()); const Datapicker* datapicker = dynamic_cast(aspect->parentAspect()); if (!datapicker) datapicker = dynamic_cast(aspect->parentAspect()->parentAspect()); if (!workbook && !datapicker) { PartMdiView* win = part->mdiSubWindow(); if (win) m_mdiArea->removeSubWindow(win); } } /*! called when the current aspect in the tree of the project explorer was changed. Selects the new aspect. */ void MainWin::handleCurrentAspectChanged(AbstractAspect *aspect) { if (!aspect) aspect = m_project; // should never happen, just in case m_suppressCurrentSubWindowChangedEvent = true; if (aspect->folder() != m_currentFolder) { m_currentFolder = aspect->folder(); updateMdiWindowVisibility(); } m_currentAspect = aspect; //activate the corresponding MDI sub window for the current aspect activateSubWindowForAspect(aspect); m_suppressCurrentSubWindowChangedEvent = false; updateGUI(); } void MainWin::activateSubWindowForAspect(const AbstractAspect* aspect) const { const AbstractPart* part = dynamic_cast(aspect); if (part) { //for LiveDataSource we currently don't show any view /*if (dynamic_cast(part)) return;*/ PartMdiView* win; //for aspects being children of a Workbook, we show workbook's window, otherwise the window of the selected part const Workbook* workbook = dynamic_cast(aspect->parentAspect()); const Datapicker* datapicker = dynamic_cast(aspect->parentAspect()); if (!datapicker) datapicker = dynamic_cast(aspect->parentAspect()->parentAspect()); if (workbook) win = workbook->mdiSubWindow(); else if (datapicker) win = datapicker->mdiSubWindow(); else win = part->mdiSubWindow(); if (m_mdiArea->subWindowList().indexOf(win) == -1) { if (dynamic_cast(part)) m_mdiArea->addSubWindow(win, Qt::Tool); else m_mdiArea->addSubWindow(win); win->show(); } m_mdiArea->setActiveSubWindow(win); } else { //activate the mdiView of the parent, if a child was selected const AbstractAspect* parent = aspect->parentAspect(); if (parent) { activateSubWindowForAspect(parent); //if the parent's parent is a Workbook (a column of a spreadsheet in workbook was selected), //we need to select the corresponding tab in WorkbookView too if (parent->parentAspect()) { Workbook* workbook = dynamic_cast(parent->parentAspect()); Datapicker* datapicker = dynamic_cast(parent->parentAspect()); if (!datapicker) datapicker = dynamic_cast(parent->parentAspect()->parentAspect()); if (workbook) workbook->childSelected(parent); else if (datapicker) datapicker->childSelected(parent); } } } return; } void MainWin::setMdiWindowVisibility(QAction * action) { m_project->setMdiWindowVisibility((Project::MdiWindowVisibility)(action->data().toInt())); } /*! shows the sub window of a worksheet, matrix or a spreadsheet. Used if the window was closed before and the user asks to show the window again via the context menu in the project explorer. */ void MainWin::handleShowSubWindowRequested() { activateSubWindowForAspect(m_currentAspect); } /*! this is called on a right click on the root folder in the project explorer */ void MainWin::createContextMenu(QMenu* menu) const { menu->addMenu(m_newMenu); //The tabbed view collides with the visibility policy for the subwindows. //Hide the menus for the visibility policy if the tabbed view is used. if (m_mdiArea->viewMode() != QMdiArea::TabbedView) menu->addMenu(m_visibilityMenu); } /*! this is called on a right click on a non-root folder in the project explorer */ void MainWin::createFolderContextMenu(const Folder* folder, QMenu* menu) const { Q_UNUSED(folder); //Folder provides it's own context menu. Add a separator before adding additional actions. menu->addSeparator(); this->createContextMenu(menu); } void MainWin::undo() { WAIT_CURSOR; m_project->undoStack()->undo(); if (m_project->undoStack()->index()==0) { setCaption(m_project->name()); m_saveAction->setEnabled(false); m_undoAction->setEnabled(false); m_project->setChanged(false); } m_redoAction->setEnabled(true); RESET_CURSOR; } void MainWin::redo() { WAIT_CURSOR; m_project->undoStack()->redo(); projectChanged(); if (m_project->undoStack()->index() == m_project->undoStack()->count()) m_redoAction->setEnabled(false); RESET_CURSOR; } /*! Shows/hides mdi sub-windows depending on the current visibility policy. */ void MainWin::updateMdiWindowVisibility() const { QList windows = m_mdiArea->subWindowList(); PartMdiView* part_view; switch (m_project->mdiWindowVisibility()) { case Project::allMdiWindows: for (auto* window : windows) window->show(); break; case Project::folderOnly: for (auto* window : windows) { part_view = qobject_cast(window); Q_ASSERT(part_view); if (part_view->part()->folder() == m_currentFolder) part_view->show(); else part_view->hide(); } break; case Project::folderAndSubfolders: for (auto* window : windows) { part_view = qobject_cast(window); if (part_view->part()->isDescendantOf(m_currentFolder)) part_view->show(); else part_view->hide(); } break; } } void MainWin::toggleDockWidget(QAction* action) const { if (action->objectName() == "toggle_project_explorer_dock") { if (m_projectExplorerDock->isVisible()) m_projectExplorerDock->hide(); else m_projectExplorerDock->show(); } else if (action->objectName() == "toggle_properties_explorer_dock") { if (m_propertiesDock->isVisible()) m_propertiesDock->hide(); else m_propertiesDock->show(); } } void MainWin::projectExplorerDockVisibilityChanged(bool visible) { m_toggleProjectExplorerDockAction->setChecked(visible); } void MainWin::propertiesDockVisibilityChanged(bool visible) { m_togglePropertiesDockAction->setChecked(visible); } void MainWin::toggleFullScreen() { if (this->windowState() == Qt::WindowFullScreen) this->setWindowState(m_lastWindowState); else { m_lastWindowState = this->windowState(); this->showFullScreen(); } } void MainWin::closeEvent(QCloseEvent* event) { m_closing = true; if (!this->closeProject()) { m_closing = false; event->ignore(); } } void MainWin::dragEnterEvent(QDragEnterEvent* event) { event->accept(); } void MainWin::dropEvent(QDropEvent* event) { if (event->mimeData() && !event->mimeData()->urls().isEmpty()) { QUrl url = event->mimeData()->urls().at(0); const QString& f = url.toLocalFile(); if (Project::isLabPlotProject(f) || OriginProjectParser::isOriginProject(f)) openProject(f); else { if (!m_project) newProject(); importFileDialog(f); } event->accept(); } else event->ignore(); } void MainWin::handleSettingsChanges() { const KConfigGroup group = KSharedConfig::openConfig()->group( "Settings_General" ); QMdiArea::ViewMode viewMode = QMdiArea::ViewMode(group.readEntry("ViewMode", 0)); if (m_mdiArea->viewMode() != viewMode) { m_mdiArea->setViewMode(viewMode); if (viewMode == QMdiArea::SubWindowView) this->updateMdiWindowVisibility(); } if (m_mdiArea->viewMode() == QMdiArea::TabbedView) { m_tileWindows->setVisible(false); m_cascadeWindows->setVisible(false); QTabWidget::TabPosition tabPosition = QTabWidget::TabPosition(group.readEntry("TabPosition", 0)); if (m_mdiArea->tabPosition() != tabPosition) m_mdiArea->setTabPosition(tabPosition); } else { m_tileWindows->setVisible(true); m_cascadeWindows->setVisible(true); } //autosave bool autoSave = group.readEntry("AutoSave", 0); if (m_autoSaveActive != autoSave) { m_autoSaveActive = autoSave; if (autoSave) m_autoSaveTimer.start(); else m_autoSaveTimer.stop(); } int interval = group.readEntry("AutoSaveInterval", 1); interval *= 60*1000; if (interval != m_autoSaveTimer.interval()) m_autoSaveTimer.setInterval(interval); } /***************************************************************************************/ /************************************** dialogs ***************************************/ /***************************************************************************************/ /*! shows the dialog with the Undo-history. */ void MainWin::historyDialog() { if (!m_project->undoStack()) return; HistoryDialog* dialog = new HistoryDialog(this, m_project->undoStack(), m_undoViewEmptyLabel); int index = m_project->undoStack()->index(); if (dialog->exec() != QDialog::Accepted) { if (m_project->undoStack()->count() != 0) m_project->undoStack()->setIndex(index); } //disable undo/redo-actions if the history was cleared //(in both cases, when accepted or rejected in the dialog) if (m_project->undoStack()->count() == 0) { m_undoAction->setEnabled(false); m_redoAction->setEnabled(false); } } /*! Opens the dialog to import data to the selected workbook, spreadsheet or matrix */ void MainWin::importFileDialog(const QString& fileName) { DEBUG("MainWin::importFileDialog()"); ImportFileDialog* dlg = new ImportFileDialog(this, false, fileName); // select existing container if (m_currentAspect->inherits("Spreadsheet") || m_currentAspect->inherits("Matrix") || m_currentAspect->inherits("Workbook")) dlg->setCurrentIndex( m_projectExplorer->currentIndex()); else if (m_currentAspect->inherits("Column")) { if (m_currentAspect->parentAspect()->inherits("Spreadsheet")) dlg->setCurrentIndex(m_aspectTreeModel->modelIndexOfAspect(m_currentAspect->parentAspect())); } if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importFileDialog() DONE"); } void MainWin::importSqlDialog() { DEBUG("MainWin::importSqlDialog()"); ImportSQLDatabaseDialog* dlg = new ImportSQLDatabaseDialog(this); // select existing container if (m_currentAspect->inherits("Spreadsheet") || m_currentAspect->inherits("Matrix") || m_currentAspect->inherits("Workbook")) dlg->setCurrentIndex( m_projectExplorer->currentIndex()); else if (m_currentAspect->inherits("Column")) { if (m_currentAspect->parentAspect()->inherits("Spreadsheet")) dlg->setCurrentIndex( m_aspectTreeModel->modelIndexOfAspect(m_currentAspect->parentAspect())); } if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importSqlDialog() DONE"); } void MainWin::importProjectDialog() { DEBUG("MainWin::importProjectDialog()"); ImportProjectDialog::ProjectType type; if (QObject::sender() == m_importOpjAction) type = ImportProjectDialog::ProjectOrigin; else type = ImportProjectDialog::ProjectLabPlot; ImportProjectDialog* dlg = new ImportProjectDialog(this, type); // set current folder dlg->setCurrentFolder(m_currentFolder); if (dlg->exec() == QDialog::Accepted) { dlg->importTo(statusBar()); m_project->setChanged(true); } delete dlg; DEBUG("MainWin::importProjectDialog() DONE"); } /*! opens the dialog for the export of the currently active worksheet, spreadsheet or matrix. */ void MainWin::exportDialog() { QMdiSubWindow* win = m_mdiArea->currentSubWindow(); if (!win) return; AbstractPart* part = dynamic_cast(win)->part(); if (part->exportView()) statusBar()->showMessage(i18n("%1 exported", part->name())); } void MainWin::editFitsFileDialog() { FITSHeaderEditDialog* editDialog = new FITSHeaderEditDialog(this); if (editDialog->exec() == QDialog::Accepted) { if (editDialog->saved()) statusBar()->showMessage(i18n("FITS files saved")); } } /*! adds a new file data source to the current project. */ void MainWin::newLiveDataSourceActionTriggered() { ImportFileDialog* dlg = new ImportFileDialog(this, true); if (dlg->exec() == QDialog::Accepted) { if(static_cast(dlg->sourceType()) == LiveDataSource::MQTT) { #ifdef HAVE_MQTT MQTTClient* mqttClient = new MQTTClient(i18n("MQTT Client%1", 1)); dlg->importToMQTT(mqttClient); mqttClient->setName(mqttClient->clientHostName()); QVector existingClients = m_project->children(AbstractAspect::Recursive); + //doesn't make sense to have more MQTTClients connected to the same broker bool found = false; for(int i = 0; i < existingClients.size(); ++i) { if(existingClients[i]->clientHostName() == mqttClient->clientHostName()) { found = true; break; } } if(!found) this->addAspectToProject(mqttClient); else { delete mqttClient; QMessageBox::warning(this, "Warning", "There already is a MQTTClient with this host name!"); } #endif } else { LiveDataSource* dataSource = new LiveDataSource(0, i18n("Live data source%1", 1), false); dlg->importToLiveDataSource(dataSource, statusBar()); this->addAspectToProject(dataSource); } } delete dlg; } void MainWin::addAspectToProject(AbstractAspect* aspect) { const QModelIndex& index = m_projectExplorer->currentIndex(); if (index.isValid()) { AbstractAspect* parent = static_cast(index.internalPointer()); #ifdef HAVE_MQTT - + //doesn't make sense to add a new MQTTClient to an existing MQTTClient or to any of its successors QString className = parent->metaObject()->className(); MQTTClient* clientAncestor = parent->ancestor(); if(className == "MQTTClient") parent = parent->parentAspect(); else if (clientAncestor != nullptr) parent = clientAncestor->parentAspect(); #endif parent->folder()->addChild(aspect); } else m_project->addChild(aspect); } void MainWin::settingsDialog() { SettingsDialog* dlg = new SettingsDialog(this); connect (dlg, SIGNAL(settingsChanged()), this, SLOT(handleSettingsChanges())); dlg->exec(); } diff --git a/src/kdefrontend/datasources/ImportFileDialog.cpp b/src/kdefrontend/datasources/ImportFileDialog.cpp index be65578ed..f706a7f6f 100644 --- a/src/kdefrontend/datasources/ImportFileDialog.cpp +++ b/src/kdefrontend/datasources/ImportFileDialog.cpp @@ -1,516 +1,519 @@ /*************************************************************************** File : ImportDialog.cc Project : LabPlot Description : import file data dialog -------------------------------------------------------------------- Copyright : (C) 2008-2018 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2008-2015 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportFileDialog.h" #include "ImportFileWidget.h" #include "backend/core/AspectTreeModel.h" #include "backend/datasources/LiveDataSource.h" #include "backend/datasources/filters/AbstractFileFilter.h" #include "backend/datasources/filters/HDF5Filter.h" #include "backend/datasources/filters/NetCDFFilter.h" #include "backend/datasources/filters/ROOTFilter.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/matrix/Matrix.h" #include "backend/core/Workbook.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/MainWin.h" #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include /*! \class ImportFileDialog \brief Dialog for importing data from a file. Embeds \c ImportFileWidget and provides the standard buttons. \ingroup kdefrontend */ ImportFileDialog::ImportFileDialog(MainWin* parent, bool liveDataSource, const QString& fileName) : ImportDialog(parent), m_importFileWidget(new ImportFileWidget(this, fileName)), m_showOptions(false) { vLayout->addWidget(m_importFileWidget); //dialog buttons QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Reset |QDialogButtonBox::Cancel); okButton = buttonBox->button(QDialogButtonBox::Ok); m_optionsButton = buttonBox->button(QDialogButtonBox::Reset); //we highjack the default "Reset" button and use if for showing/hiding the options okButton->setEnabled(false); //ok is only available if a valid container was selected vLayout->addWidget(buttonBox); //hide the data-source related widgets if (!liveDataSource) { setModel(); //TODO: disable for file data sources m_importFileWidget->hideDataSource(); } else m_importFileWidget->initializeAndFillPortsAndBaudRates(); //Signals/Slots #ifdef HAVE_MQTT - connect(m_importFileWidget, &ImportFileWidget::subscriptionMade, this, &ImportFileDialog::checkOkButton); + connect(m_importFileWidget, &ImportFileWidget::subscriptionsChanged, this, &ImportFileDialog::checkOkButton); connect(m_importFileWidget, &ImportFileWidget::checkFileType, this, &ImportFileDialog::checkOkButton); #endif connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); if (!liveDataSource) { setWindowTitle(i18nc("@title:window", "Import Data to Spreadsheet or Matrix")); m_importFileWidget->hideDataSource(); } else setWindowTitle(i18nc("@title:window", "Add New Live Data Source")); setWindowIcon(QIcon::fromTheme("document-import-database")); QTimer::singleShot(0, this, &ImportFileDialog::loadSettings); } void ImportFileDialog::loadSettings() { //restore saved settings QApplication::processEvents(QEventLoop::AllEvents, 0); KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileDialog"); m_showOptions = conf.readEntry("ShowOptions", false); m_showOptions ? m_optionsButton->setText(i18n("Hide Options")) : m_optionsButton->setText(i18n("Show Options")); m_importFileWidget->showOptions(m_showOptions); m_importFileWidget->loadSettings(); //do the signal-slot connections after all settings where loaded in import file widget and check the OK button after this connect(m_importFileWidget, SIGNAL(checkedFitsTableToMatrix(bool)), this, SLOT(checkOnFitsTableToMatrix(bool))); connect(m_importFileWidget, SIGNAL(fileNameChanged()), this, SLOT(checkOkButton())); connect(m_importFileWidget, SIGNAL(sourceTypeChanged()), this, SLOT(checkOkButton())); connect(m_importFileWidget, SIGNAL(hostChanged()), this, SLOT(checkOkButton())); connect(m_importFileWidget, SIGNAL(portChanged()), this, SLOT(checkOkButton())); connect(m_importFileWidget, SIGNAL(previewRefreshed()), this, SLOT(checkOkButton())); connect(m_optionsButton, SIGNAL(clicked()), this, SLOT(toggleOptions())); checkOkButton(); KWindowConfig::restoreWindowSize(windowHandle(), conf); } ImportFileDialog::~ImportFileDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileDialog"); conf.writeEntry("ShowOptions", m_showOptions); if (cbPosition) conf.writeEntry("Position", cbPosition->currentIndex()); KWindowConfig::saveWindowSize(windowHandle(), conf); } int ImportFileDialog::sourceType() const { return static_cast(m_importFileWidget->currentSourceType()); } /*! triggers data import to the live data source \c source */ void ImportFileDialog::importToLiveDataSource(LiveDataSource* source, QStatusBar* statusBar) const { DEBUG("ImportFileDialog::importToLiveDataSource()"); m_importFileWidget->saveSettings(source); //show a progress bar in the status bar QProgressBar* progressBar = new QProgressBar(); progressBar->setRange(0, 100); connect(source->filter(), SIGNAL(completed(int)), progressBar, SLOT(setValue(int))); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); WAIT_CURSOR; QTime timer; timer.start(); DEBUG(" Inital read()"); source->read(); statusBar->showMessage( i18n("Live data source created in %1 seconds.", (float)timer.elapsed()/1000) ); RESET_CURSOR; statusBar->removeWidget(progressBar); source->ready(); } #ifdef HAVE_MQTT +/*! + triggers data import to the MQTTClient \c client +*/ void ImportFileDialog::importToMQTT(MQTTClient* client) const{ m_importFileWidget->saveMQTTSettings(client); client->read(); client->ready(); } #endif /*! triggers data import to the currently selected data container */ void ImportFileDialog::importTo(QStatusBar* statusBar) const { DEBUG("ImportFileDialog::importTo()"); QDEBUG(" cbAddTo->currentModelIndex() =" << cbAddTo->currentModelIndex()); AbstractAspect* aspect = static_cast(cbAddTo->currentModelIndex().internalPointer()); if (!aspect) { DEBUG("ERROR in importTo(): No aspect available"); DEBUG(" cbAddTo->currentModelIndex().isValid() = " << cbAddTo->currentModelIndex().isValid()); DEBUG(" cbAddTo->currentModelIndex() row/column = " << cbAddTo->currentModelIndex().row() << ' ' << cbAddTo->currentModelIndex().column()); return; } if (m_importFileWidget->isFileEmpty()) { KMessageBox::information(0, i18n("No data to import."), i18n("No Data")); return; } QString fileName = m_importFileWidget->fileName(); AbstractFileFilter* filter = m_importFileWidget->currentFileFilter(); AbstractFileFilter::ImportMode mode = AbstractFileFilter::ImportMode(cbPosition->currentIndex()); //show a progress bar in the status bar QProgressBar* progressBar = new QProgressBar(); progressBar->setRange(0, 100); connect(filter, SIGNAL(completed(int)), progressBar, SLOT(setValue(int))); statusBar->clearMessage(); statusBar->addWidget(progressBar, 1); WAIT_CURSOR; QApplication::processEvents(QEventLoop::AllEvents, 100); QTime timer; timer.start(); if (aspect->inherits("Matrix")) { DEBUG(" to Matrix"); Matrix* matrix = qobject_cast(aspect); filter->readDataFromFile(fileName, matrix, mode); } else if (aspect->inherits("Spreadsheet")) { DEBUG(" to Spreadsheet"); Spreadsheet* spreadsheet = qobject_cast(aspect); DEBUG(" Calling readDataFromFile()"); filter->readDataFromFile(fileName, spreadsheet, mode); } else if (aspect->inherits("Workbook")) { DEBUG(" to Workbook"); Workbook* workbook = qobject_cast(aspect); QVector sheets = workbook->children(); AbstractFileFilter::FileType fileType = m_importFileWidget->currentFileType(); // multiple data sets/variables for HDF5, NetCDF and ROOT if (fileType == AbstractFileFilter::HDF5 || fileType == AbstractFileFilter::NETCDF || fileType == AbstractFileFilter::ROOT) { QStringList names; switch (fileType) { case AbstractFileFilter::HDF5: names = m_importFileWidget->selectedHDF5Names(); break; case AbstractFileFilter::NETCDF: names = m_importFileWidget->selectedNetCDFNames(); break; case AbstractFileFilter::ROOT: names = m_importFileWidget->selectedROOTNames(); break; case AbstractFileFilter::Ascii: case AbstractFileFilter::Binary: case AbstractFileFilter::Image: case AbstractFileFilter::FITS: case AbstractFileFilter::Json: case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: break; // never reached, omit warning } int nrNames = names.size(), offset = sheets.size(); int start=0; if (mode == AbstractFileFilter::Replace) start=offset; // add additional sheets for (int i = start; i < nrNames; ++i) { Spreadsheet *spreadsheet = new Spreadsheet(0, i18n("Spreadsheet")); if (mode == AbstractFileFilter::Prepend) workbook->insertChildBefore(spreadsheet,sheets[0]); else workbook->addChild(spreadsheet); } if (mode != AbstractFileFilter::Append) offset = 0; // import to sheets sheets = workbook->children(); for (int i = 0; i < nrNames; ++i) { switch (fileType) { case AbstractFileFilter::HDF5: ((HDF5Filter*) filter)->setCurrentDataSetName(names[i]); break; case AbstractFileFilter::NETCDF: ((NetCDFFilter*) filter)->setCurrentVarName(names[i]); break; case AbstractFileFilter::ROOT: ((ROOTFilter*) filter)->setCurrentHistogram(names[i]); break; case AbstractFileFilter::Ascii: case AbstractFileFilter::Binary: case AbstractFileFilter::Image: case AbstractFileFilter::FITS: case AbstractFileFilter::Json: case AbstractFileFilter::NgspiceRawAscii: case AbstractFileFilter::NgspiceRawBinary: break; // never reached, omit warning } if (sheets[i+offset]->inherits("Matrix")) filter->readDataFromFile(fileName, qobject_cast(sheets[i+offset])); else if (sheets[i+offset]->inherits("Spreadsheet")) filter->readDataFromFile(fileName, qobject_cast(sheets[i+offset])); } } else { // single import file types // use active spreadsheet/matrix if present, else new spreadsheet Spreadsheet* spreadsheet = workbook->currentSpreadsheet(); Matrix* matrix = workbook->currentMatrix(); if (spreadsheet) filter->readDataFromFile(fileName, spreadsheet, mode); else if (matrix) filter->readDataFromFile(fileName, matrix, mode); else { spreadsheet = new Spreadsheet(0, i18n("Spreadsheet")); workbook->addChild(spreadsheet); filter->readDataFromFile(fileName, spreadsheet, mode); } } } statusBar->showMessage( i18n("File %1 imported in %2 seconds.", fileName, (float)timer.elapsed()/1000) ); RESET_CURSOR; statusBar->removeWidget(progressBar); delete filter; } void ImportFileDialog::toggleOptions() { m_importFileWidget->showOptions(!m_showOptions); m_showOptions = !m_showOptions; m_showOptions ? m_optionsButton->setText(i18n("Hide Options")) : m_optionsButton->setText(i18n("Show Options")); //resize the dialog layout()->activate(); resize( QSize(this->width(), 0).expandedTo(minimumSize()) ); } void ImportFileDialog::checkOnFitsTableToMatrix(const bool enable) { if (cbAddTo) { QDEBUG("cbAddTo->currentModelIndex() = " << cbAddTo->currentModelIndex()); AbstractAspect* aspect = static_cast(cbAddTo->currentModelIndex().internalPointer()); if (!aspect) { DEBUG("ERROR: no aspect available."); return; } if(aspect->inherits("Matrix")) { okButton->setEnabled(enable); if (enable) okButton->setToolTip(i18n("Close the dialog and import the data.")); else okButton->setToolTip(i18n("Cannot import into a matrix since the data contains non-numerical data.")); } } } void ImportFileDialog::checkOkButton() { DEBUG("ImportFileDialog::checkOkButton()"); if (cbAddTo) { //only check for the target container when no file data source is being added QDEBUG(" cbAddTo->currentModelIndex() = " << cbAddTo->currentModelIndex()); AbstractAspect* aspect = static_cast(cbAddTo->currentModelIndex().internalPointer()); if (!aspect) { okButton->setEnabled(false); okButton->setToolTip(i18n("Select a data container where the data has to be imported into.")); lPosition->setEnabled(false); cbPosition->setEnabled(false); return; } else { lPosition->setEnabled(true); cbPosition->setEnabled(true); //when doing ASCII import to a matrix, hide the options for using the file header (first line) //to name the columns since the column names are fixed in a matrix const Matrix* matrix = dynamic_cast(aspect); m_importFileWidget->showAsciiHeaderOptions(matrix == NULL); } } QString fileName = m_importFileWidget->fileName(); #ifndef HAVE_WINDOWS if (!fileName.isEmpty() && fileName.at(0) != QDir::separator()) fileName = QDir::homePath() + QDir::separator() + fileName; #endif DEBUG("Data Source Type: " << ENUM_TO_STRING(LiveDataSource, SourceType, m_importFileWidget->currentSourceType())); switch (m_importFileWidget->currentSourceType()) { case LiveDataSource::SourceType::FileOrPipe: { DEBUG("fileName = " << fileName.toUtf8().constData()); const bool enable = QFile::exists(fileName); okButton->setEnabled(enable); if (enable) okButton->setToolTip(i18n("Close the dialog and import the data.")); else okButton->setToolTip(i18n("Provide an existing file.")); break; } case LiveDataSource::SourceType::LocalSocket: { const bool enable = QFile::exists(fileName); if (enable) { QLocalSocket lsocket{this}; DEBUG("CONNECT"); lsocket.connectToServer(fileName, QLocalSocket::ReadOnly); if (lsocket.waitForConnected()) { // this is required for server that send data as soon as connected lsocket.waitForReadyRead(); DEBUG("DISCONNECT"); lsocket.disconnectFromServer(); // read-only socket is disconnected immediately (no waitForDisconnected()) okButton->setEnabled(true); okButton->setToolTip(i18n("Close the dialog and import the data.")); } else { DEBUG("failed connect to local socket - " << lsocket.errorString().toStdString()); okButton->setEnabled(false); okButton->setToolTip(i18n("Could not connect to the provided local socket.")); } } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Selected local socket does not exist.")); } break; } case LiveDataSource::SourceType::NetworkTcpSocket: { const bool enable = !m_importFileWidget->host().isEmpty() && !m_importFileWidget->port().isEmpty(); if (enable) { QTcpSocket socket(this); socket.connectToHost(m_importFileWidget->host(), m_importFileWidget->port().toUShort(), QTcpSocket::ReadOnly); if (socket.waitForConnected()) { okButton->setEnabled(true); okButton->setToolTip(i18n("Close the dialog and import the data.")); socket.disconnectFromHost(); } else { DEBUG("failed to connect to TCP socket - " << socket.errorString().toStdString()); okButton->setEnabled(false); okButton->setToolTip(i18n("Could not connect to the provided TCP socket.")); } } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Either the host name or the port number is missing.")); } break; } case LiveDataSource::SourceType::NetworkUdpSocket: { const bool enable = !m_importFileWidget->host().isEmpty() && !m_importFileWidget->port().isEmpty(); if (enable) { QUdpSocket socket(this); socket.bind(QHostAddress(m_importFileWidget->host()), m_importFileWidget->port().toUShort()); socket.connectToHost(m_importFileWidget->host(), 0, QUdpSocket::ReadOnly); if (socket.waitForConnected()) { okButton->setEnabled(true); okButton->setToolTip(i18n("Close the dialog and import the data.")); socket.disconnectFromHost(); // read-only socket is disconnected immediately (no waitForDisconnected()) } else { DEBUG("failed to connect to UDP socket - " << socket.errorString().toStdString()); okButton->setEnabled(false); okButton->setToolTip(i18n("Could not connect to the provided UDP socket.")); } } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Either the host name or the port number is missing.")); } break; } case LiveDataSource::SourceType::SerialPort: { const QString sPort = m_importFileWidget->serialPort(); const int baudRate = m_importFileWidget->baudRate(); if (!sPort.isEmpty()) { QSerialPort serialPort{this}; DEBUG(" Port name: " << sPort.toStdString()); DEBUG(" Settings: " << baudRate << ',' << serialPort.dataBits() << ',' << serialPort.parity() << ',' << serialPort.stopBits()); serialPort.setPortName(sPort); serialPort.setBaudRate(baudRate); //TODO: Test /*const bool serialPortOpened = serialPort.open(QIODevice::ReadOnly); okButton->setEnabled(serialPortOpened); if (serialPortOpened) { okButton->setToolTip(i18n("Close the dialog and import the data.")); serialPort.close(); } else { DEBUG("Could not connect to the provided serial port"); okButton->setToolTip(i18n("Could not connect to the provided serial port.")); }*/ } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Serial port number is missing.")); } } case LiveDataSource::SourceType::MQTT: { #ifdef HAVE_MQTT const bool enable = m_importFileWidget->isMqttValid(); if (enable) { okButton->setEnabled(true); okButton->setToolTip(i18n("Close the dialog and import the data.")); } else { okButton->setEnabled(false); okButton->setToolTip(i18n("Either there is no connection, or no subscriptions were made, or the file filter isn't Ascii.")); } #endif break; } } } QString ImportFileDialog::selectedObject() const { return m_importFileWidget->selectedObject(); } diff --git a/src/kdefrontend/datasources/ImportFileWidget.cpp b/src/kdefrontend/datasources/ImportFileWidget.cpp index 405b04689..89958d2b2 100644 --- a/src/kdefrontend/datasources/ImportFileWidget.cpp +++ b/src/kdefrontend/datasources/ImportFileWidget.cpp @@ -1,2565 +1,2762 @@ /*************************************************************************** File : ImportFileWidget.cpp Project : LabPlot Description : import file data widget -------------------------------------------------------------------- Copyright : (C) 2009-2018 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 Fabian Kristof (fkristofszabolcs@gmail.com) +Copyright : (C) 2018 Kovacs Ferencz (kferike98@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "ImportFileWidget.h" #include "FileInfoDialog.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/BinaryFilter.h" #include "backend/datasources/filters/HDF5Filter.h" #include "backend/datasources/filters/NetCDFFilter.h" #include "backend/datasources/filters/ImageFilter.h" #include "backend/datasources/filters/FITSFilter.h" #include "backend/datasources/filters/JsonFilter.h" #include "backend/datasources/filters/QJsonModel.h" #include "backend/datasources/filters/NgspiceRawAsciiFilter.h" #include "backend/datasources/filters/NgspiceRawBinaryFilter.h" #include "backend/datasources/filters/ROOTFilter.h" #include "AsciiOptionsWidget.h" #include "BinaryOptionsWidget.h" #include "HDF5OptionsWidget.h" #include "ImageOptionsWidget.h" #include "NetCDFOptionsWidget.h" #include "FITSOptionsWidget.h" #include "JsonOptionsWidget.h" #include "ROOTOptionsWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HAVE_MQTT #include "backend/core/Project.h" #include #include #include #include #include #endif /*! \class ImportFileWidget \brief Widget for importing data from a file. \ingroup kdefrontend */ ImportFileWidget::ImportFileWidget(QWidget* parent, const QString& fileName) : QWidget(parent), m_fileName(fileName), m_fileEmpty(false), - m_liveDataSource(true), -#ifdef HAVE_MQTT + m_liveDataSource(true), + #ifdef HAVE_MQTT m_mqttReadyForPreview (false), m_searching(false), -#endif + m_searchTimer(new QTimer(this)), + m_connectTimeoutTimer(new QTimer(this)), + m_client(new QMqttClient(this)), + #endif m_suppressRefresh(false) { ui.setupUi(this); #ifdef HAVE_MQTT - m_searchTimer = new QTimer(this); m_searchTimer->setInterval(10000); - m_timeoutTimer = new QTimer(this); - m_timeoutTimer->setInterval(5000); + m_connectTimeoutTimer->setInterval(5000); #endif QCompleter* completer = new QCompleter(this); completer->setModel(new QDirModel); ui.leFileName->setCompleter(completer); ui.cbFileType->addItems(AbstractFileFilter::fileTypes()); QStringList filterItems; filterItems << i18n("Automatic") << i18n("Custom"); ui.cbFilter->addItems(filterItems); // file type specific option widgets QWidget* asciiw = new QWidget(); m_asciiOptionsWidget = std::unique_ptr(new AsciiOptionsWidget(asciiw)); ui.swOptions->insertWidget(AbstractFileFilter::Ascii, asciiw); QWidget* binaryw = new QWidget(); m_binaryOptionsWidget = std::unique_ptr(new BinaryOptionsWidget(binaryw)); ui.swOptions->insertWidget(AbstractFileFilter::Binary, binaryw); QWidget* imagew = new QWidget(); m_imageOptionsWidget = std::unique_ptr(new ImageOptionsWidget(imagew)); ui.swOptions->insertWidget(AbstractFileFilter::Image, imagew); QWidget* hdf5w = new QWidget(); m_hdf5OptionsWidget = std::unique_ptr(new HDF5OptionsWidget(hdf5w, this)); ui.swOptions->insertWidget(AbstractFileFilter::HDF5, hdf5w); QWidget* netcdfw = new QWidget(); m_netcdfOptionsWidget = std::unique_ptr(new NetCDFOptionsWidget(netcdfw, this)); ui.swOptions->insertWidget(AbstractFileFilter::NETCDF, netcdfw); QWidget* fitsw = new QWidget(); m_fitsOptionsWidget = std::unique_ptr(new FITSOptionsWidget(fitsw, this)); ui.swOptions->insertWidget(AbstractFileFilter::FITS, fitsw); QWidget* jsonw = new QWidget(); m_jsonOptionsWidget = std::unique_ptr(new JsonOptionsWidget(jsonw, this)); ui.swOptions->insertWidget(AbstractFileFilter::Json, jsonw); QWidget* rootw = new QWidget(); m_rootOptionsWidget = std::unique_ptr(new ROOTOptionsWidget(rootw, this)); ui.swOptions->insertWidget(AbstractFileFilter::ROOT, rootw); ui.tvJson->header()->setSectionResizeMode(QHeaderView::ResizeToContents); ui.tvJson->setAlternatingRowColors(true); ui.tvJson->setModel(m_jsonOptionsWidget->model()); showJsonModel(false); // the table widget for preview m_twPreview = new QTableWidget(ui.tePreview); m_twPreview->verticalHeader()->hide(); m_twPreview->setEditTriggers(QTableWidget::NoEditTriggers); QHBoxLayout* layout = new QHBoxLayout; layout->addWidget(m_twPreview); ui.tePreview->setLayout(layout); m_twPreview->hide(); // default filter ui.swOptions->setCurrentIndex(AbstractFileFilter::Ascii); #if !defined(HAVE_HDF5) || !defined(HAVE_NETCDF) || !defined(HAVE_FITS) || !defined(HAVE_ZIP) const QStandardItemModel* model = qobject_cast(ui.cbFileType->model()); #endif #ifndef HAVE_HDF5 // disable HDF5 item QStandardItem* item = model->item(AbstractFileFilter::HDF5); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_NETCDF // disable NETCDF item QStandardItem* item2 = model->item(AbstractFileFilter::NETCDF); item2->setFlags(item2->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_FITS // disable FITS item QStandardItem* item3 = model->item(AbstractFileFilter::FITS); item3->setFlags(item3->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_ZIP // disable ROOT item QStandardItem* item4 = model->item(AbstractFileFilter::ROOT); item4->setFlags(item4->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif #ifndef HAVE_MQTT // disable MQTT item QStandardItem* item5 = model->item(LiveDataSource::MQTT); item5->setFlags(item5->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); #endif ui.cbReadingType->addItem(i18n("Whole file"), LiveDataSource::WholeFile); ui.lePort->setValidator( new QIntValidator(ui.lePort) ); ui.gbOptions->hide(); ui.gbUpdateOptions->hide(); ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); ui.bFileInfo->setIcon( QIcon::fromTheme("help-about") ); ui.bManageFilters->setIcon( QIcon::fromTheme("configure") ); ui.bSaveFilter->setIcon( QIcon::fromTheme("document-save") ); ui.bRefreshPreview->setIcon( QIcon::fromTheme("view-refresh") ); -#ifdef HAVE_MQTT - m_client = new QMqttClient(this); -#endif connect( ui.leFileName, SIGNAL(textChanged(QString)), SLOT(fileNameChanged(QString)) ); connect( ui.bOpen, SIGNAL(clicked()), this, SLOT (selectFile()) ); connect( ui.bFileInfo, SIGNAL(clicked()), this, SLOT (fileInfoDialog()) ); connect( ui.bSaveFilter, SIGNAL(clicked()), this, SLOT (saveFilter()) ); connect( ui.bManageFilters, SIGNAL(clicked()), this, SLOT (manageFilters()) ); connect( ui.cbFileType, SIGNAL(currentIndexChanged(int)), SLOT(fileTypeChanged(int)) ); connect( ui.cbUpdateType, SIGNAL(currentIndexChanged(int)), this, SLOT(updateTypeChanged(int))); connect( ui.cbReadingType, SIGNAL(currentIndexChanged(int)), this, SLOT(readingTypeChanged(int))); connect( ui.cbFilter, SIGNAL(activated(int)), SLOT(filterChanged(int)) ); connect( ui.bRefreshPreview, SIGNAL(clicked()), SLOT(refreshPreview()) ); + #ifdef HAVE_MQTT connect(ui.chbID, SIGNAL(stateChanged(int)), this, SLOT(idChecked(int))); connect(ui.chbAuthentication, SIGNAL(stateChanged(int)), this, SLOT(authenticationChecked(int))); connect(ui.bConnect, SIGNAL(clicked()), this, SLOT(mqttConnection()) ); connect(m_client, SIGNAL(connected()), this, SLOT(onMqttConnect()) ); connect(ui.bSubscribe, &QPushButton::clicked, this, &ImportFileWidget::mqttSubscribe); connect(ui.bUnsubscribe, &QPushButton::clicked, this,&ImportFileWidget::mqttUnsubscribe); connect(m_client, SIGNAL(messageReceived(QByteArray, QMqttTopicName)), this, SLOT(mqttMessageReceived(QByteArray, QMqttTopicName)) ); connect(this, &ImportFileWidget::newTopic, this, &ImportFileWidget::setCompleter); connect(m_searchTimer, &QTimer::timeout, this, &ImportFileWidget::topicTimeout); connect(m_client, &QMqttClient::disconnected, this, &ImportFileWidget::onMqttDisconnect); - connect(m_timeoutTimer, &QTimer::timeout, this, &ImportFileWidget::mqttTimeout); - + connect(m_connectTimeoutTimer, &QTimer::timeout, this, &ImportFileWidget::mqttConnectTimeout); connect(ui.chbWill, &QCheckBox::stateChanged, this, &ImportFileWidget::useWillMessage); connect(ui.cbWillMessageType, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::willMessageTypeChanged); - connect(ui.cbWillUpdate, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::willUpdateChanged); + connect(ui.cbWillUpdate, static_cast(&QComboBox::currentIndexChanged), this, &ImportFileWidget::willUpdateTypeChanged); connect(this, &ImportFileWidget::newTopicForWill, this, &ImportFileWidget::updateWillTopics); connect(m_client, &QMqttClient::errorChanged, this, &ImportFileWidget::mqttErrorChanged); connect(ui.cbFileType, static_cast(&QComboBox::currentIndexChanged), [this]() {emit checkFileType();}); - connect(ui.leTopics, &QLineEdit::textChanged, this, &ImportFileWidget::searchTreeItem); - + connect(ui.leTopics, &QLineEdit::textChanged, this, &ImportFileWidget::scrollToTreeItem); connect(ui.chbAuthentication, &QCheckBox::stateChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.chbID, &QCheckBox::stateChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.leHost, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.lePort, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.lePassword, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.leUsername, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); connect(ui.leID, &QLineEdit::textChanged, this, &ImportFileWidget::checkConnectEnable); ui.bSubscribe->setIcon(ui.bSubscribe->style()->standardIcon(QStyle::SP_ArrowRight)); ui.bUnsubscribe->setIcon(ui.bUnsubscribe->style()->standardIcon(QStyle::SP_BrowserStop)); #endif connect(ui.leHost, SIGNAL(textChanged(QString)), this, SIGNAL(hostChanged())); connect(ui.lePort, SIGNAL(textChanged(QString)), this, SIGNAL(portChanged())); connect( ui.cbSourceType, SIGNAL(currentIndexChanged(int)), this, SLOT(sourceTypeChanged(int))); connect( ui.tvJson, SIGNAL(clicked(const QModelIndex&)), this, SLOT(refreshPreview())); //TODO: implement save/load of user-defined settings later and activate these buttons again ui.bSaveFilter->hide(); ui.bManageFilters->hide(); //defer the loading of settings a bit in order to show the dialog prior to blocking the GUI in refreshPreview() QTimer::singleShot( 100, this, SLOT(loadSettings()) ); hideMQTT(); } void ImportFileWidget::loadSettings() { m_suppressRefresh = true; //load last used settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); //settings for data type specific widgets m_asciiOptionsWidget->loadSettings(); m_binaryOptionsWidget->loadSettings(); m_imageOptionsWidget->loadSettings(); m_jsonOptionsWidget->loadSettings(); //read the source type first since settings in fileNameChanged() depend on this ui.cbSourceType->setCurrentIndex(conf.readEntry("SourceType").toInt()); //general settings ui.cbFileType->setCurrentIndex(conf.readEntry("Type", 0)); ui.cbFilter->setCurrentIndex(conf.readEntry("Filter", 0)); filterChanged(ui.cbFilter->currentIndex()); // needed if filter is not changed if (m_fileName.isEmpty()) ui.leFileName->setText(conf.readEntry("LastImportedFile", "")); else ui.leFileName->setText(m_fileName); //live data related settings ui.cbBaudRate->setCurrentIndex(conf.readEntry("BaudRate").toInt()); ui.cbReadingType->setCurrentIndex(conf.readEntry("ReadingType").toInt()); ui.cbSerialPort->setCurrentIndex(conf.readEntry("SerialPort").toInt()); ui.cbUpdateType->setCurrentIndex(conf.readEntry("UpdateType").toInt()); ui.leHost->setText(conf.readEntry("Host","")); ui.sbKeepNValues->setValue(conf.readEntry("KeepNValues").toInt()); ui.lePort->setText(conf.readEntry("Port","")); ui.sbSampleSize->setValue(conf.readEntry("SampleSize").toInt()); ui.sbUpdateInterval->setValue(conf.readEntry("UpdateInterval").toInt()); + #ifdef HAVE_MQTT + //MQTT related settings ui.chbID->setChecked(conf.readEntry("mqttUseId").toInt()); ui.chbAuthentication->setChecked(conf.readEntry("mqttUseAuthentication").toInt()); ui.chbRetain->setChecked(conf.readEntry("mqttUseRetain").toInt()); ui.leUsername->setText(conf.readEntry("mqttUsername","")); ui.lePassword->setText(conf.readEntry("mqttPassword","")); ui.leID->setText(conf.readEntry("mqttId","")); ui.chbWillRetain->setChecked(conf.readEntry("mqttWillRetain").toInt()); ui.cbWillUpdate->setCurrentIndex(conf.readEntry("mqttWillUpdateType").toInt()); ui.cbWillQoS->setCurrentIndex(conf.readEntry("mqttWillQoS").toInt()); ui.leWillOwnMessage->setText(conf.readEntry("mqttWillOwnMessage","")); ui.leWillUpdateInterval->setText(conf.readEntry("mqttWillUpdateInterval","")); QString willStatistics = conf.readEntry("mqttWillStatistics",""); QStringList statisticsList = willStatistics.split('|', QString::SplitBehavior::SkipEmptyParts); for(auto value : statisticsList) { QListWidgetItem* item = ui.lwWillStatistics->item(value.toInt()); item->setCheckState(Qt::Checked); } ui.cbWillMessageType->setCurrentIndex(conf.readEntry("mqttWillMessageType").toInt()); ui.chbWill->setChecked(conf.readEntry("mqttWillUse").toInt()); + //chbWill is unchecked by deafult, so if false is loaded it doesn't emit state changed signal, we have to force it if(!ui.chbWill->isChecked()) { ui.chbWill->setChecked(true); ui.chbWill->setChecked(false); } #endif + m_suppressRefresh = false; refreshPreview(); } ImportFileWidget::~ImportFileWidget() { // save current settings QString confName; if (m_liveDataSource) confName = QLatin1String("LiveDataImport"); else confName = QLatin1String("FileImport"); KConfigGroup conf(KSharedConfig::openConfig(), confName); // general settings conf.writeEntry("Type", ui.cbFileType->currentIndex()); conf.writeEntry("Filter", ui.cbFilter->currentIndex()); conf.writeEntry("LastImportedFile", ui.leFileName->text()); //live data related settings conf.writeEntry("SourceType", ui.cbSourceType->currentIndex()); conf.writeEntry("UpdateType", ui.cbUpdateType->currentIndex()); conf.writeEntry("ReadingType", ui.cbReadingType->currentIndex()); conf.writeEntry("SampleSize", ui.sbSampleSize->value()); conf.writeEntry("KeepNValues", ui.sbKeepNValues->value()); conf.writeEntry("BaudRate", ui.cbBaudRate->currentIndex()); conf.writeEntry("SerialPort", ui.cbSerialPort->currentIndex()); conf.writeEntry("Host", ui.leHost->text()); conf.writeEntry("Port", ui.lePort->text()); conf.writeEntry("UpdateInterval", ui.sbUpdateInterval->value()); + #ifdef HAVE_MQTT + //MQTT related settings conf.writeEntry("mqttUsername", ui.leUsername->text()); conf.writeEntry("mqttPassword", ui.lePassword->text()); conf.writeEntry("mqttId", ui.leID->text()); conf.writeEntry("mqttWillMessageType", ui.cbWillMessageType->currentIndex()); conf.writeEntry("mqttWillUpdateType", ui.cbWillUpdate->currentIndex()); conf.writeEntry("mqttWillQoS", ui.cbWillQoS->currentIndex()); conf.writeEntry("mqttWillOwnMessage", ui.leWillOwnMessage->text()); conf.writeEntry("mqttWillUpdateInterval", ui.leWillUpdateInterval->text()); QString willStatistics; for(int i = 0; i < ui.lwWillStatistics->count(); ++i) { QListWidgetItem* item = ui.lwWillStatistics->item(i); if (item->checkState() == Qt::Checked) willStatistics += QString::number(i)+"|"; } conf.writeEntry("mqttWillStatistics", willStatistics); conf.writeEntry("mqttWillRetain", static_cast(ui.chbWillRetain->isChecked())); conf.writeEntry("mqttWillUse", static_cast(ui.chbWill->isChecked())); conf.writeEntry("mqttUseId", static_cast(ui.chbID->isChecked())); conf.writeEntry("mqttUseAuthentication", static_cast(ui.chbAuthentication->isChecked())); conf.writeEntry("mqttUseRetain", static_cast(ui.chbRetain->isChecked())); #endif // data type specific settings m_asciiOptionsWidget->saveSettings(); m_binaryOptionsWidget->saveSettings(); m_imageOptionsWidget->saveSettings(); } void ImportFileWidget::hideDataSource() { m_liveDataSource = false; ui.gbUpdateOptions->hide(); ui.chbLinkFile->hide(); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.lSourceType->hide(); ui.cbSourceType->hide(); ui.cbUpdateType->hide(); ui.lUpdateType->hide(); ui.sbUpdateInterval->hide(); ui.lUpdateInterval->hide(); #ifdef HAVE_MQTT hideMQTT(); #endif } void ImportFileWidget::showAsciiHeaderOptions(bool b) { m_asciiOptionsWidget->showAsciiHeaderOptions(b); } void ImportFileWidget::showJsonModel(bool b) { ui.tvJson->setVisible(b); ui.lField->setVisible(b); } void ImportFileWidget::showOptions(bool b) { ui.gbOptions->setVisible(b); if (m_liveDataSource) ui.gbUpdateOptions->setVisible(b); resize(layout()->minimumSize()); } QString ImportFileWidget::fileName() const { return ui.leFileName->text(); } QString ImportFileWidget::selectedObject() const { const QString& path = ui.leFileName->text(); //determine the file name only QString name = path.right(path.length() - path.lastIndexOf(QDir::separator()) - 1); //strip away the extension if available if (name.indexOf('.') != -1) name = name.left(name.lastIndexOf('.')); //for multi-dimensinal formats like HDF, netCDF and FITS add the currently selected object const auto format = currentFileType(); if (format == AbstractFileFilter::HDF5) { const QStringList& hdf5Names = m_hdf5OptionsWidget->selectedHDF5Names(); if (hdf5Names.size()) name += hdf5Names.first(); //the names of the selected HDF5 objects already have '/' } else if (format == AbstractFileFilter::NETCDF) { const QStringList& names = m_netcdfOptionsWidget->selectedNetCDFNames(); if (names.size()) name += QLatin1Char('/') + names.first(); } else if (format == AbstractFileFilter::FITS) { const QString& extensionName = m_fitsOptionsWidget->currentExtensionName(); if (!extensionName.isEmpty()) name += QLatin1Char('/') + extensionName; } else if (format == AbstractFileFilter::ROOT) { const QStringList& names = m_rootOptionsWidget->selectedROOTNames(); if (names.size()) name += QLatin1Char('/') + names.first(); } return name; } /*! * returns \c true if the number of lines to be imported from the currently selected file is zero ("file is empty"), * returns \c false otherwise. */ bool ImportFileWidget::isFileEmpty() const { return m_fileEmpty; } QString ImportFileWidget::host() const { return ui.leHost->text(); } QString ImportFileWidget::port() const { return ui.lePort->text(); } QString ImportFileWidget::serialPort() const { return ui.cbSerialPort->currentText(); } int ImportFileWidget::baudRate() const { return ui.cbBaudRate->currentText().toInt(); } /*! saves the settings to the data source \c source. */ void ImportFileWidget::saveSettings(LiveDataSource* source) const { AbstractFileFilter::FileType fileType = static_cast(ui.cbFileType->currentIndex()); LiveDataSource::UpdateType updateType = static_cast(ui.cbUpdateType->currentIndex()); LiveDataSource::SourceType sourceType = static_cast(ui.cbSourceType->currentIndex()); LiveDataSource::ReadingType readingType = static_cast(ui.cbReadingType->currentIndex()); source->setComment( ui.leFileName->text() ); source->setFileType(fileType); source->setFilter(this->currentFileFilter()); source->setSourceType(sourceType); source->setReadingType(readingType); if (updateType == LiveDataSource::UpdateType::TimeInterval) source->setUpdateInterval(ui.sbUpdateInterval->value()); else source->setFileWatched(true); source->setKeepNValues(ui.sbKeepNValues->value()); source->setUpdateType(updateType); if (readingType != LiveDataSource::ReadingType::TillEnd) source->setSampleSize(ui.sbSampleSize->value()); switch (sourceType) { case LiveDataSource::SourceType::FileOrPipe: source->setFileName( ui.leFileName->text() ); source->setFileLinked( ui.chbLinkFile->isChecked() ); break; case LiveDataSource::SourceType::LocalSocket: source->setLocalSocketName(ui.leFileName->text()); break; case LiveDataSource::SourceType::NetworkTcpSocket: case LiveDataSource::SourceType::NetworkUdpSocket: source->setHost(ui.leHost->text()); source->setPort((quint16)ui.lePort->text().toInt()); break; case LiveDataSource::SourceType::SerialPort: source->setBaudRate(ui.cbBaudRate->currentText().toInt()); source->setSerialPort(ui.cbSerialPort->currentText()); break; default: break; } } #ifdef HAVE_MQTT +/*! + saves the settings to the MQTTClient \c client. +*/ void ImportFileWidget::saveMQTTSettings(MQTTClient* client) const { - qDebug()<<"Saving to MQTT Client"; MQTTClient::UpdateType updateType = static_cast(ui.cbUpdateType->currentIndex()); MQTTClient::ReadingType readingType = static_cast(ui.cbReadingType->currentIndex()); client->setComment( ui.leFileName->text() ); client->setFilter(this->currentFileFilter()); client->setReadingType(readingType); if (updateType == MQTTClient::UpdateType::TimeInterval) client->setUpdateInterval(ui.sbUpdateInterval->value()); client->setKeepNvalues(ui.sbKeepNValues->value()); client->setUpdateType(updateType); if (readingType != MQTTClient::ReadingType::TillEnd) client->setSampleRate(ui.sbSampleSize->value()); - qDebug()<<"Saving mqtt"; client->setMqttClientHostPort(m_client->hostname(), m_client->port()); client->setMQTTUseAuthentication(ui.chbAuthentication->isChecked()); if(ui.chbAuthentication->isChecked()) client->setMqttClientAuthentication(m_client->username(), m_client->password()); client->setMQTTUseID(ui.chbID->isChecked()); if(ui.chbID->isChecked()) client->setMqttClientId(m_client->clientId()); for(int i=0; iaddMqttSubscriptions(m_mqttSubscriptions[i]->topic(), m_mqttSubscriptions[i]->qos()); } + client->setMqttRetain(ui.chbRetain->isChecked()); client->setWillMessageType(static_cast(ui.cbWillMessageType->currentIndex()) ); client->setWillOwnMessage(ui.leWillOwnMessage->text()); client->setWillQoS(ui.cbWillQoS->currentIndex() ); client->setWillRetain(ui.chbWillRetain->isChecked()); client->setWillTimeInterval(ui.leWillUpdateInterval->text().toInt()); client->setWillTopic(ui.cbWillTopic->currentText()); client->setWillUpdateType(static_cast(ui.cbWillUpdate->currentIndex()) ); client->setMqttWillUse(ui.chbWill->isChecked()); + for(int i = 0; i < ui.lwWillStatistics->count(); ++i) { QListWidgetItem* item = ui.lwWillStatistics->item(i); if (item->checkState() == Qt::Checked) client->addWillStatistics(static_cast (i)); } } #endif /*! returns the currently used file type. */ AbstractFileFilter::FileType ImportFileWidget::currentFileType() const { return static_cast(ui.cbFileType->currentIndex()); } LiveDataSource::SourceType ImportFileWidget::currentSourceType() const { return static_cast(ui.cbSourceType->currentIndex()); } /*! returns the currently used filter. */ AbstractFileFilter* ImportFileWidget::currentFileFilter() const { DEBUG("ImportFileWidget::currentFileFilter()"); AbstractFileFilter::FileType fileType = static_cast(ui.cbFileType->currentIndex()); switch (fileType) { case AbstractFileFilter::Ascii: { - DEBUG(" ASCII"); -//TODO std::unique_ptr filter(new AsciiFilter()); - AsciiFilter* filter = new AsciiFilter(); - - if (ui.cbFilter->currentIndex() == 0) //"automatic" - filter->setAutoModeEnabled(true); - else if (ui.cbFilter->currentIndex() == 1) { //"custom" - filter->setAutoModeEnabled(false); - m_asciiOptionsWidget->applyFilterSettings(filter); - } else - filter->loadFilterSettings( ui.cbFilter->currentText() ); - - //save the data portion to import - filter->setStartRow( ui.sbStartRow->value()); - filter->setEndRow( ui.sbEndRow->value() ); - filter->setStartColumn( ui.sbStartColumn->value()); - filter->setEndColumn( ui.sbEndColumn->value()); - - return filter; - } + DEBUG(" ASCII"); + //TODO std::unique_ptr filter(new AsciiFilter()); + AsciiFilter* filter = new AsciiFilter(); + + if (ui.cbFilter->currentIndex() == 0) //"automatic" + filter->setAutoModeEnabled(true); + else if (ui.cbFilter->currentIndex() == 1) { //"custom" + filter->setAutoModeEnabled(false); + m_asciiOptionsWidget->applyFilterSettings(filter); + } else + filter->loadFilterSettings( ui.cbFilter->currentText() ); + + //save the data portion to import + filter->setStartRow( ui.sbStartRow->value()); + filter->setEndRow( ui.sbEndRow->value() ); + filter->setStartColumn( ui.sbStartColumn->value()); + filter->setEndColumn( ui.sbEndColumn->value()); + + return filter; + } case AbstractFileFilter::Binary: { - BinaryFilter* filter = new BinaryFilter(); - if ( ui.cbFilter->currentIndex() == 0 ) //"automatic" - filter->setAutoModeEnabled(true); - else if ( ui.cbFilter->currentIndex() == 1 ) { //"custom" - filter->setAutoModeEnabled(false); - m_binaryOptionsWidget->applyFilterSettings(filter); - } else { - //TODO: load filter settings -// filter->setFilterName( ui.cbFilter->currentText() ); - } + BinaryFilter* filter = new BinaryFilter(); + if ( ui.cbFilter->currentIndex() == 0 ) //"automatic" + filter->setAutoModeEnabled(true); + else if ( ui.cbFilter->currentIndex() == 1 ) { //"custom" + filter->setAutoModeEnabled(false); + m_binaryOptionsWidget->applyFilterSettings(filter); + } else { + //TODO: load filter settings + // filter->setFilterName( ui.cbFilter->currentText() ); + } - filter->setStartRow( ui.sbStartRow->value() ); - filter->setEndRow( ui.sbEndRow->value() ); + filter->setStartRow( ui.sbStartRow->value() ); + filter->setEndRow( ui.sbEndRow->value() ); - return filter; - } + return filter; + } case AbstractFileFilter::Image: { - ImageFilter* filter = new ImageFilter(); + ImageFilter* filter = new ImageFilter(); - filter->setImportFormat(m_imageOptionsWidget->currentFormat()); - filter->setStartRow( ui.sbStartRow->value() ); - filter->setEndRow( ui.sbEndRow->value() ); - filter->setStartColumn( ui.sbStartColumn->value() ); - filter->setEndColumn( ui.sbEndColumn->value() ); + filter->setImportFormat(m_imageOptionsWidget->currentFormat()); + filter->setStartRow( ui.sbStartRow->value() ); + filter->setEndRow( ui.sbEndRow->value() ); + filter->setStartColumn( ui.sbStartColumn->value() ); + filter->setEndColumn( ui.sbEndColumn->value() ); - return filter; - } + return filter; + } case AbstractFileFilter::HDF5: { - HDF5Filter* filter = new HDF5Filter(); - QStringList names = selectedHDF5Names(); - if (!names.isEmpty()) - filter->setCurrentDataSetName(names[0]); - filter->setStartRow( ui.sbStartRow->value() ); - filter->setEndRow( ui.sbEndRow->value() ); - filter->setStartColumn( ui.sbStartColumn->value() ); - filter->setEndColumn( ui.sbEndColumn->value() ); - - return filter; - } + HDF5Filter* filter = new HDF5Filter(); + QStringList names = selectedHDF5Names(); + if (!names.isEmpty()) + filter->setCurrentDataSetName(names[0]); + filter->setStartRow( ui.sbStartRow->value() ); + filter->setEndRow( ui.sbEndRow->value() ); + filter->setStartColumn( ui.sbStartColumn->value() ); + filter->setEndColumn( ui.sbEndColumn->value() ); + + return filter; + } case AbstractFileFilter::NETCDF: { - NetCDFFilter* filter = new NetCDFFilter(); + NetCDFFilter* filter = new NetCDFFilter(); - if (!selectedNetCDFNames().isEmpty()) - filter->setCurrentVarName(selectedNetCDFNames()[0]); - filter->setStartRow( ui.sbStartRow->value() ); - filter->setEndRow( ui.sbEndRow->value() ); - filter->setStartColumn( ui.sbStartColumn->value() ); - filter->setEndColumn( ui.sbEndColumn->value() ); + if (!selectedNetCDFNames().isEmpty()) + filter->setCurrentVarName(selectedNetCDFNames()[0]); + filter->setStartRow( ui.sbStartRow->value() ); + filter->setEndRow( ui.sbEndRow->value() ); + filter->setStartColumn( ui.sbStartColumn->value() ); + filter->setEndColumn( ui.sbEndColumn->value() ); - return filter; - } + return filter; + } case AbstractFileFilter::FITS: { - FITSFilter* filter = new FITSFilter(); - filter->setStartRow( ui.sbStartRow->value()); - filter->setEndRow( ui.sbEndRow->value() ); - filter->setStartColumn( ui.sbStartColumn->value()); - filter->setEndColumn( ui.sbEndColumn->value()); - return filter; - } + FITSFilter* filter = new FITSFilter(); + filter->setStartRow( ui.sbStartRow->value()); + filter->setEndRow( ui.sbEndRow->value() ); + filter->setStartColumn( ui.sbStartColumn->value()); + filter->setEndColumn( ui.sbEndColumn->value()); + return filter; + } case AbstractFileFilter::Json: { - JsonFilter* filter = new JsonFilter(); - m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); - - filter->setStartRow( ui.sbStartRow->value() ); - filter->setEndRow( ui.sbEndRow->value() ); - filter->setStartColumn( ui.sbStartColumn->value()); - filter->setEndColumn( ui.sbEndColumn->value()); - return filter; - } + JsonFilter* filter = new JsonFilter(); + m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); + + filter->setStartRow( ui.sbStartRow->value() ); + filter->setEndRow( ui.sbEndRow->value() ); + filter->setStartColumn( ui.sbStartColumn->value()); + filter->setEndColumn( ui.sbEndColumn->value()); + return filter; + } case AbstractFileFilter::ROOT: { - ROOTFilter* filter = new ROOTFilter(); - QStringList names = selectedROOTNames(); - if (!names.isEmpty()) - filter->setCurrentHistogram(names.first()); + ROOTFilter* filter = new ROOTFilter(); + QStringList names = selectedROOTNames(); + if (!names.isEmpty()) + filter->setCurrentHistogram(names.first()); - filter->setStartBin( m_rootOptionsWidget->startBin() ); - filter->setEndBin( m_rootOptionsWidget->endBin() ); - filter->setColumns( m_rootOptionsWidget->columns() ); + filter->setStartBin( m_rootOptionsWidget->startBin() ); + filter->setEndBin( m_rootOptionsWidget->endBin() ); + filter->setColumns( m_rootOptionsWidget->columns() ); - return filter; - } + return filter; + } case AbstractFileFilter::NgspiceRawAscii: { NgspiceRawAsciiFilter* filter = new NgspiceRawAsciiFilter(); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); return filter; } case AbstractFileFilter::NgspiceRawBinary: { NgspiceRawBinaryFilter* filter = new NgspiceRawBinaryFilter(); filter->setStartRow( ui.sbStartRow->value() ); filter->setEndRow( ui.sbEndRow->value() ); return filter; } } return 0; } /*! opens a file dialog and lets the user select the file data source. */ void ImportFileWidget::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "ImportFileWidget"); QString dir = conf.readEntry("LastDir", ""); QString path = QFileDialog::getOpenFileName(this, i18n("Select the File Data Source"), dir); if (path.isEmpty()) //cancel was clicked in the file-dialog return; int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastDir", newDir); } ui.leFileName->setText(path); //TODO: decide whether the selection of several files should be possible -// QStringList filelist = QFileDialog::getOpenFileNames(this,i18n("Select one or more files to open")); -// if (! filelist.isEmpty() ) -// ui.leFileName->setText(filelist.join(";")); + // QStringList filelist = QFileDialog::getOpenFileNames(this,i18n("Select one or more files to open")); + // if (! filelist.isEmpty() ) + // ui.leFileName->setText(filelist.join(";")); } -/************** SLOTS **************************************************************/ - /*! - called on file name changes. - Determines the file format (ASCII, binary etc.), if the file exists, - and activates the corresponding options. + hides the MQTT related items of the widget */ -void ImportFileWidget::fileNameChanged(const QString& name) { - QString fileName = name; -#ifndef HAVE_WINDOWS - // make relative path - if ( !fileName.isEmpty() && fileName.at(0) != QDir::separator()) - fileName = QDir::homePath() + QDir::separator() + fileName; -#endif - - bool fileExists = QFile::exists(fileName); - if (fileExists) - ui.leFileName->setStyleSheet(""); - else - ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); - - ui.gbOptions->setEnabled(fileExists); - ui.bManageFilters->setEnabled(fileExists); - ui.cbFilter->setEnabled(fileExists); - ui.cbFileType->setEnabled(fileExists); - ui.bFileInfo->setEnabled(fileExists); - ui.gbUpdateOptions->setEnabled(fileExists); - if (!fileExists) { - //file doesn't exist -> delete the content preview that is still potentially - //available from the previously selected file - ui.tePreview->clear(); - m_twPreview->clear(); - m_hdf5OptionsWidget->clear(); - m_netcdfOptionsWidget->clear(); - m_fitsOptionsWidget->clear(); - m_jsonOptionsWidget->clearModel(); - m_rootOptionsWidget->clear(); - - emit fileNameChanged(); - return; - } - - if (currentSourceType() == LiveDataSource::FileOrPipe) { - const AbstractFileFilter::FileType fileType = AbstractFileFilter::fileType(fileName); - switch(fileType) { - case AbstractFileFilter::Ascii: - ui.cbFileType->setCurrentIndex(AbstractFileFilter::Ascii); - break; - case AbstractFileFilter::Binary: - ui.cbFileType->setCurrentIndex(AbstractFileFilter::Binary); - break; - case AbstractFileFilter::Image: - ui.cbFileType->setCurrentIndex(AbstractFileFilter::Image); - break; - case AbstractFileFilter::HDF5: - ui.cbFileType->setCurrentIndex(AbstractFileFilter::HDF5); - m_hdf5OptionsWidget->updateContent((HDF5Filter*)this->currentFileFilter(), fileName); - break; - case AbstractFileFilter::NETCDF: - ui.cbFileType->setCurrentIndex(AbstractFileFilter::NETCDF); - m_netcdfOptionsWidget->updateContent((NetCDFFilter*)this->currentFileFilter(), fileName); - break; - case AbstractFileFilter::FITS: -#ifdef HAVE_FITS - ui.cbFileType->setCurrentIndex(AbstractFileFilter::FITS); - m_fitsOptionsWidget->updateContent((FITSFilter*)this->currentFileFilter(), fileName); -#endif - break; - case AbstractFileFilter::Json: - ui.cbFileType->setCurrentIndex(AbstractFileFilter::Json); - m_jsonOptionsWidget->loadDocument(fileName); - break; - case AbstractFileFilter::ROOT: - ui.cbFileType->setCurrentIndex(AbstractFileFilter::ROOT); - m_rootOptionsWidget->updateContent((ROOTFilter*)this->currentFileFilter(), fileName); - break; - case AbstractFileFilter::NgspiceRawAscii: - ui.cbFileType->setCurrentIndex(AbstractFileFilter::NgspiceRawAscii); - break; - case AbstractFileFilter::NgspiceRawBinary: - ui.cbFileType->setCurrentIndex(AbstractFileFilter::NgspiceRawBinary); - break; - } - } - - refreshPreview(); - emit fileNameChanged(); +void ImportFileWidget::hideMQTT() { + ui.leID->hide(); + ui.lMqttID->hide(); + ui.lePassword->hide(); + ui.lPassword->hide(); + ui.leUsername->hide(); + ui.lUsername->hide(); + ui.cbQos->hide(); + ui.lQos->hide(); + ui.twTopics->hide(); + ui.lTopicSearch->hide(); + ui.leTopics->hide(); + ui.twSubscriptions->hide(); + ui.chbAuthentication->hide(); + ui.chbID->hide(); + ui.bSubscribe->hide(); + ui.bUnsubscribe->hide(); + ui.bConnect->hide(); + ui.gbMqttWill->hide(); + ui.chbWill->hide(); + ui.chbWillRetain->hide(); + ui.cbWillQoS->hide(); + ui.cbWillMessageType->hide(); + ui.cbWillTopic->hide(); + ui.cbWillUpdate->hide(); + ui.leWillOwnMessage->hide(); + ui.leWillUpdateInterval->setValidator(new QIntValidator(2, 1000000) ); + ui.leWillUpdateInterval->hide(); + ui.lWillMessageType->hide(); + ui.lWillOwnMessage->hide(); + ui.lWillQos->hide(); + ui.lWillTopic->hide(); + ui.lWillUpdate->hide(); + ui.lWillUpdateInterval->hide(); + ui.lwWillStatistics->hide(); + ui.lWillStatistics->hide(); } +#ifdef HAVE_MQTT /*! - saves the current filter settings -*/ -void ImportFileWidget::saveFilter() { - bool ok; - QString text = QInputDialog::getText(this, i18n("Save Filter Settings as"), - i18n("Filter name:"), QLineEdit::Normal, i18n("new filter"), &ok); - if (ok && !text.isEmpty()) { - //TODO - //AsciiFilter::saveFilter() - } + * returns \c true if there is a valid connection to an MQTT broker and the user has subscribed to at least 1 topic, + * returns \c false otherwise. + */ +bool ImportFileWidget::isMqttValid(){ + bool connected = (m_client->state() == QMqttClient::ClientState::Connected); + bool subscribed = (ui.twSubscriptions->topLevelItemCount() > 0); + bool fileTypeOk = false; + if(this->currentFileType() == AbstractFileFilter::FileType::Ascii) + fileTypeOk = true; + return connected && subscribed && fileTypeOk; } /*! - opens a dialog for managing all available predefined filters. -*/ -void ImportFileWidget::manageFilters() { - //TODO + *\brief Checks if a topic contains another one + * + * \param superior the name of a topic + * \param inferior the name of a topic + * \return true if superior is equal to or contains(if superior contains wildcards) inferior, + * false otherwise + */ +bool ImportFileWidget::checkTopicContains(const QString& superior, const QString& inferior) { + if (superior == inferior) + return true; + else { + if(superior.contains("/")) { + QStringList superiorList = superior.split('/', QString::SkipEmptyParts); + QStringList inferiorList = inferior.split('/', QString::SkipEmptyParts); + + //a longer topic can't contain a shorter one + if(superiorList.size() > inferiorList.size()) + return false; + + bool ok = true; + for(int i = 0; i < superiorList.size(); ++i) { + if(superiorList.at(i) != inferiorList.at(i)) { + if((superiorList.at(i) != "+") && + !(superiorList.at(i) == "#" && i == superiorList.size() - 1)) { + qDebug() <setCurrentIndex(fileType); + *\brief Returns the "+" wildcard containing topic name, which includes the given topic names + * + * \param first the name of a topic + * \param second the name of a topic + * \return The name of the common topic, if it exists, otherwise "" + */ +QString ImportFileWidget::checkCommonLevel(const QString& first, const QString& second) { + qDebug()<show(); - ui.cbFilter->show(); + if(!firstList.isEmpty()) { + //the two topics have to be the same size and can't be identic + if(firstList.size() == secondtList.size() && (first != second)) { - //different file types show different number of tabs in ui.tabWidget. - //when switching from the previous file type we re-set the tab widget to its original state - //and remove/add the required tabs further below - for (int i = 0; icount(); ++i) - ui.tabWidget->count(); + //the index where they differ + int differIndex = -1; + for(int i = 0; i < firstList.size(); ++i) { + if(firstList.at(i) != secondtList.at(i)) { + differIndex = i; + break; + } + } - ui.tabWidget->addTab(ui.tabDataFormat, i18n("Data format")); - ui.tabWidget->addTab(ui.tabDataPreview, i18n("Preview")); - ui.tabWidget->addTab(ui.tabDataPortion, i18n("Data portion to read")); - - ui.lPreviewLines->show(); - ui.sbPreviewLines->show(); - ui.lStartColumn->show(); - ui.sbStartColumn->show(); - ui.lEndColumn->show(); - ui.sbEndColumn->show(); + //they can differ at only one level + bool differ = false; + if(differIndex > 0) { + for(int j = differIndex + 1; j < firstList.size(); ++j) { + if(firstList.at(j) != secondtList.at(j)) { + differ = true; + break; + } + } + } + else + differ = true; - showJsonModel(false); + if(!differ) + { + for(int i = 0; i < firstList.size(); ++i) { + if(i != differIndex) { + commonTopic.append(firstList.at(i)); + } else { + //we put "+" wildcard at the level where they differ + commonTopic.append("+"); + } - switch (fileType) { - case AbstractFileFilter::Ascii: - break; - case AbstractFileFilter::Binary: - ui.lStartColumn->hide(); - ui.sbStartColumn->hide(); - ui.lEndColumn->hide(); - ui.sbEndColumn->hide(); - break; - case AbstractFileFilter::ROOT: - ui.tabWidget->removeTab(1); - // falls through - case AbstractFileFilter::HDF5: - case AbstractFileFilter::NETCDF: - case AbstractFileFilter::FITS: - ui.lFilter->hide(); - ui.cbFilter->hide(); - // hide global preview tab. we have our own - ui.tabWidget->setTabText(0, i18n("Data format && preview")); - ui.tabWidget->removeTab(1); - ui.tabWidget->setCurrentIndex(0); - break; - case AbstractFileFilter::Image: - ui.lPreviewLines->hide(); - ui.sbPreviewLines->hide(); - ui.lFilter->hide(); - ui.cbFilter->hide(); - break; - case AbstractFileFilter::NgspiceRawAscii: - case AbstractFileFilter::NgspiceRawBinary: - ui.lStartColumn->hide(); - ui.sbStartColumn->hide(); - ui.lEndColumn->hide(); - ui.sbEndColumn->hide(); - ui.tabWidget->removeTab(0); - ui.tabWidget->setCurrentIndex(0); - break; - case AbstractFileFilter::Json: - ui.lFilter->hide(); - ui.cbFilter->hide(); - showJsonModel(true); - break; - default: - DEBUG("unknown file type"); + if(i != firstList.size() - 1) + commonTopic.append("/"); + } + } + } } - m_hdf5OptionsWidget->clear(); - m_netcdfOptionsWidget->clear(); - m_rootOptionsWidget->clear(); - - int lastUsedFilterIndex = ui.cbFilter->currentIndex(); - ui.cbFilter->clear(); - ui.cbFilter->addItem( i18n("Automatic") ); - ui.cbFilter->addItem( i18n("Custom") ); - - //TODO: populate the combobox with the available pre-defined filter settings for the selected type - ui.cbFilter->setCurrentIndex(lastUsedFilterIndex); - filterChanged(lastUsedFilterIndex); - - refreshPreview(); + qDebug() << "Common topic: "<selectedHDF5Names(); -} - -const QStringList ImportFileWidget::selectedNetCDFNames() const { - return m_netcdfOptionsWidget->selectedNetCDFNames(); -} + if(!firstList.isEmpty()) { + //the two topics have to be the same size and can't be identic + if(firstList.size() == secondtList.size() && (first != second)) { -const QStringList ImportFileWidget::selectedFITSExtensions() const { - return m_fitsOptionsWidget->selectedFITSExtensions(); -} + //the index where they differ + for(int i = 0; i < firstList.size(); ++i) { + if(firstList.at(i) != secondtList.at(i)) { + differIndex = i; + break; + } + } -const QStringList ImportFileWidget::selectedROOTNames() const { - return m_rootOptionsWidget->selectedROOTNames(); -} + //they can differ at only one level + bool differ = false; + if(differIndex > 0) { + for(int j = differIndex + 1; j < firstList.size(); ++j) { + if(firstList.at(j) != secondtList.at(j)) { + differ = true; + break; + } + } + } + else + differ = true; -/*! - shows the dialog with the information about the file(s) to be imported. -*/ -void ImportFileWidget::fileInfoDialog() { - QStringList files = ui.leFileName->text().split(';'); - FileInfoDialog* dlg = new FileInfoDialog(this); - dlg->setFiles(files); - dlg->exec(); -} + if(!differ) + { + for(int i = 0; i < firstList.size(); ++i) { + if(i != differIndex) + commonTopic.append(firstList.at(i)); + else + commonTopic.append("+"); -/*! - enables the options if the filter "custom" was chosen. Disables the options otherwise. -*/ -void ImportFileWidget::filterChanged(int index) { - // ignore filter for these formats - if (ui.cbFileType->currentIndex() == AbstractFileFilter::HDF5 || - ui.cbFileType->currentIndex() == AbstractFileFilter::NETCDF || - ui.cbFileType->currentIndex() == AbstractFileFilter::Image || - ui.cbFileType->currentIndex() == AbstractFileFilter::FITS || - ui.cbFileType->currentIndex() == AbstractFileFilter::Json || - ui.cbFileType->currentIndex() == AbstractFileFilter::ROOT) { - ui.swOptions->setEnabled(true); - return; + if(i != firstList.size() - 1) + commonTopic.append("/"); + } + } + } } - if (index == 0) { // "automatic" - ui.swOptions->setEnabled(false); - ui.bSaveFilter->setEnabled(false); - } else if (index == 1) { //custom - ui.swOptions->setEnabled(true); - ui.bSaveFilter->setEnabled(true); - } else { - // predefined filter settings were selected. - //load and show them in the GUI. - //TODO - } + qDebug() << "Common topic: "<unsubscribe(filter); - WAIT_CURSOR; + qDebug()<<"unsubscribe occured"; - QString fileName = ui.leFileName->text(); -#ifndef HAVE_WINDOWS - if (!fileName.isEmpty() && fileName.at(0) != QDir::separator()) - fileName = QDir::homePath() + QDir::separator() + fileName; -#endif - DEBUG("refreshPreview(): file name = " << fileName.toStdString()); + for(int i = 0; i< m_mqttSubscriptions.count(); ++i) + if(m_mqttSubscriptions[i]->topic().filter() == topicName) { + m_mqttSubscriptions.remove(i); + break; + } - QVector importedStrings; - AbstractFileFilter::FileType fileType = (AbstractFileFilter::FileType)ui.cbFileType->currentIndex(); + m_mqttReadyForPreview = false; - // generic table widget - if (fileType == AbstractFileFilter::Ascii || fileType == AbstractFileFilter::Binary - || fileType == AbstractFileFilter::Json || fileType == AbstractFileFilter::NgspiceRawAscii - || fileType == AbstractFileFilter::NgspiceRawBinary) - m_twPreview->show(); - else - m_twPreview->hide(); + QMapIterator i(m_messageArrived); + while(i.hasNext()) { + i.next(); + if(checkTopicContains(topicName, i.key().name())) { + m_messageArrived.remove(i.key()); + } + } - int lines = ui.sbPreviewLines->value(); + QMapIterator j(m_lastMessage); + while(j.hasNext()) { + j.next(); + if(checkTopicContains(topicName, j.key().name())) { + m_lastMessage.remove(j.key()); + } + } - bool ok = true; - QTableWidget* tmpTableWidget{nullptr}; - QStringList vectorNameList; - QVector columnModes; - DEBUG("Data File Type: " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType)); - switch (fileType) { - case AbstractFileFilter::Ascii: { - ui.tePreview->clear(); + for(int row = 0; rowtopLevelItemCount(); row++) { + if(ui.twSubscriptions->topLevelItem(row)->text(0) == topicName) { + ui.twSubscriptions->topLevelItem(row)->takeChildren(); + ui.twSubscriptions->takeTopLevelItem(row); + } + } - AsciiFilter* filter = static_cast(this->currentFileFilter()); + for(int i = 0; i < m_subscribedTopicNames.size(); ++i) { + if(checkTopicContains(topicName, m_subscribedTopicNames[i])) { + m_subscribedTopicNames.remove(i); + i--; + } + } - DEBUG("Data Source Type: " << ENUM_TO_STRING(LiveDataSource, SourceType, currentSourceType())); - switch (currentSourceType()) { - case LiveDataSource::SourceType::FileOrPipe: { - importedStrings = filter->preview(fileName, lines); - break; - } - case LiveDataSource::SourceType::LocalSocket: { - QLocalSocket lsocket{this}; - DEBUG("Local socket: CONNECT PREVIEW"); - lsocket.connectToServer(fileName, QLocalSocket::ReadOnly); - if (lsocket.waitForConnected()) { - DEBUG("connected to local socket " << fileName.toStdString()); - if (lsocket.waitForReadyRead()) - importedStrings = filter->preview(lsocket); - DEBUG("Local socket: DISCONNECT PREVIEW"); - lsocket.disconnectFromServer(); - // read-only socket is disconnected immediately (no waitForDisconnected()) - } else { - DEBUG("failed connect to local socket " << fileName.toStdString() << " - " << lsocket.errorString().toStdString()); - } + for(int item = 0; item < ui.cbWillTopic->count(); ++item) { + if(checkTopicContains(topicName, ui.cbWillTopic->itemText(item))) { + ui.cbWillTopic->removeItem(item); + item--; + } + } - break; - } - case LiveDataSource::SourceType::NetworkTcpSocket: { - QTcpSocket tcpSocket{this}; - tcpSocket.connectToHost(host(), port().toInt(), QTcpSocket::ReadOnly); - if (tcpSocket.waitForConnected()) { - DEBUG("connected to TCP socket"); - if ( tcpSocket.waitForReadyRead() ) - importedStrings = filter->preview(tcpSocket); - - tcpSocket.disconnectFromHost(); - } else { - DEBUG("failed to connect to TCP socket " << " - " << tcpSocket.errorString().toStdString()); - } + //signals that there was a change among the subscribed topics + emit subscriptionsChanged(); + refreshPreview(); + } +} - break; - } - case LiveDataSource::SourceType::NetworkUdpSocket: { - QUdpSocket udpSocket{this}; - DEBUG("UDP Socket: CONNECT PREVIEW, state = " << udpSocket.state()); - udpSocket.bind(QHostAddress(host()), port().toInt()); - udpSocket.connectToHost(host(), 0, QUdpSocket::ReadOnly); - if (udpSocket.waitForConnected()) { - DEBUG(" connected to UDP socket " << host().toStdString() << ':' << port().toInt()); - if (!udpSocket.waitForReadyRead(2000) ) - DEBUG(" ERROR: not ready for read after 2 sec"); - if (udpSocket.hasPendingDatagrams()) { - DEBUG(" has pending data"); - } else { - DEBUG(" has no pending data"); - } - importedStrings = filter->preview(udpSocket); - - DEBUG("UDP Socket: DISCONNECT PREVIEW, state = " << udpSocket.state()); - udpSocket.disconnectFromHost(); - } else { - DEBUG("failed to connect to UDP socket " << " - " << udpSocket.errorString().toStdString()); - } - - break; - } - case LiveDataSource::SourceType::SerialPort: { - QSerialPort sPort{this}; - DEBUG(" Port name: " << serialPort().toStdString()); - DEBUG(" Settings: " << baudRate() << ',' << sPort.dataBits() << ',' << sPort.parity() - << ',' << sPort.stopBits()); - sPort.setPortName(serialPort()); - sPort.setBaudRate(baudRate()); - - if (sPort.open(QIODevice::ReadOnly)) { - if (sPort.waitForReadyRead(2000)) - importedStrings = filter->preview(sPort); - else { - DEBUG(" ERROR: not ready for read after 2 sec"); - } - - sPort.close(); - } else { - DEBUG(" ERROR: failed to open serial port. error: " << sPort.error()); - } - break; +/*! + *\brief Adds to a # wildcard containing topic, every topic present in twTopics that the former topic contains + * + * \param topic pointer to the TreeWidgetItem which was selected before subscribing + * \param subscription pointer to the TreeWidgetItem which represents the new subscirption, + * we add all of the children to this item + */ +void ImportFileWidget::addSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription) { + //if the topic doesn't have any children we don't do anything + if(topic->childCount() > 0) { + for(int i = 0; i < topic->childCount(); ++i) { + QTreeWidgetItem* temp = topic->child(i); + QString name; + //if it has children, then we add it as a # wildcrad containing topic + if(topic->child(i)->childCount() > 0) { + name.append(temp->text(0) + "/#"); + while(temp->parent() != nullptr) { + temp = temp->parent(); + name.prepend(temp->text(0) + "/"); } - case LiveDataSource::SourceType::MQTT: { -#ifdef HAVE_MQTT - qDebug()<<"preview mqtt, is it ready:"<vectorNames().clear(); - QMapIterator i(m_lastMessage); - while(i.hasNext()) { - i.next(); - qDebug()<<"calling ascii mqtt preview"<< importedStrings << " "<mqttPreview(importedStrings, QString(i.value().payload().data()), i.key().name() ); - if(importedStrings.isEmpty()) - break; - } - QMapIterator j(m_messageArrived); - while(j.hasNext()) { - j.next(); - qDebug()<<"Set false after preview: "<text(0)); + while(temp->parent() != nullptr) { + temp = temp->parent(); + name.prepend(temp->text(0) + "/"); + } } - - tmpTableWidget = m_twPreview; - vectorNameList = filter->vectorNames(); - columnModes = filter->columnModes(); - break; - } - case AbstractFileFilter::Binary: { - ui.tePreview->clear(); - BinaryFilter *filter = (BinaryFilter *)this->currentFileFilter(); - importedStrings = filter->preview(fileName, lines); - tmpTableWidget = m_twPreview; - break; - } - case AbstractFileFilter::Image: { - ui.tePreview->clear(); - - QImage image(fileName); - QTextCursor cursor = ui.tePreview->textCursor(); - cursor.insertImage(image); - RESET_CURSOR; - return; - } - case AbstractFileFilter::HDF5: { - HDF5Filter *filter = (HDF5Filter *)this->currentFileFilter(); - lines = m_hdf5OptionsWidget->lines(); - importedStrings = filter->readCurrentDataSet(fileName, NULL, ok, AbstractFileFilter::Replace, lines); - tmpTableWidget = m_hdf5OptionsWidget->previewWidget(); - break; - } - case AbstractFileFilter::NETCDF: { - NetCDFFilter *filter = (NetCDFFilter *)this->currentFileFilter(); - lines = m_netcdfOptionsWidget->lines(); - importedStrings = filter->readCurrentVar(fileName, NULL, AbstractFileFilter::Replace, lines); - tmpTableWidget = m_netcdfOptionsWidget->previewWidget(); - break; + QStringList nameList; + nameList.append(name); + QTreeWidgetItem* childItem = new QTreeWidgetItem(nameList); + subscription->addChild(childItem); + //we use the function recursively on the given item + addSubscriptionChildren(topic->child(i), childItem); } - case AbstractFileFilter::FITS: { - FITSFilter* filter = (FITSFilter*)this->currentFileFilter(); - lines = m_fitsOptionsWidget->lines(); - - // update file name (may be any file type) - m_fitsOptionsWidget->updateContent(filter, fileName); - QString extensionName = m_fitsOptionsWidget->extensionName(&ok); - if (!extensionName.isEmpty()) { - DEBUG(" extension name = " << extensionName.toStdString()); - fileName = extensionName; - } - DEBUG(" file name = " << fileName.toStdString()); - - bool readFitsTableToMatrix; - importedStrings = filter->readChdu(fileName, &readFitsTableToMatrix, lines); - emit checkedFitsTableToMatrix(readFitsTableToMatrix); + } +} - tmpTableWidget = m_fitsOptionsWidget->previewWidget(); - break; - } - case AbstractFileFilter::Json: { - ui.tePreview->clear(); - m_jsonOptionsWidget->loadDocument(fileName); - JsonFilter *filter = (JsonFilter*)this->currentFileFilter(); - m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); - importedStrings = filter->preview(fileName); - tmpTableWidget = m_twPreview; - columnModes = filter->columnModes(); - break; - } - case AbstractFileFilter::ROOT: { - ROOTFilter *filter = (ROOTFilter *)this->currentFileFilter(); - lines = m_rootOptionsWidget->lines(); - m_rootOptionsWidget->setNBins(filter->binsInCurrentHistogram(fileName)); - importedStrings = filter->previewCurrentHistogram( - fileName, - m_rootOptionsWidget->startBin(), - qMin(m_rootOptionsWidget->startBin() + m_rootOptionsWidget->lines() - 1, - m_rootOptionsWidget->endBin()) - ); - tmpTableWidget = m_rootOptionsWidget->previewWidget(); - // the last vector element contains the column names - vectorNameList = importedStrings.last(); - importedStrings.removeLast(); - columnModes = QVector(vectorNameList.size(), AbstractColumn::Numeric); - break; - } - case AbstractFileFilter::NgspiceRawAscii: { - ui.tePreview->clear(); - NgspiceRawAsciiFilter* filter = (NgspiceRawAsciiFilter*)this->currentFileFilter(); - importedStrings = filter->preview(fileName, lines); - tmpTableWidget = m_twPreview; - vectorNameList = filter->vectorNames(); - columnModes = filter->columnModes(); - break; - } - case AbstractFileFilter::NgspiceRawBinary: { - ui.tePreview->clear(); - NgspiceRawBinaryFilter* filter = (NgspiceRawBinaryFilter*)this->currentFileFilter(); - importedStrings = filter->preview(fileName, lines); - tmpTableWidget = m_twPreview; - vectorNameList = filter->vectorNames(); - columnModes = filter->columnModes(); - break; +/*! + *\brief Fills the children vector, with the root item's (twSubscriptions) leaf children (meaning no wildcard containing topics) + * + * \param children vector of TreeWidgetItem pointers + * \param root pointer to a TreeWidgetItem of twSubscriptions + */ +void ImportFileWidget::findSubscriptionLeafChildren(QVector& children, QTreeWidgetItem* root) { + if(root->childCount() == 0) { + children.push_back(root); + } else { + for(int i = 0; i < root->childCount(); ++i) { + findSubscriptionLeafChildren(children, root->child(i)); } } +} - // fill the table widget - tmpTableWidget->setRowCount(0); - tmpTableWidget->setColumnCount(0); - if( !importedStrings.isEmpty() ) { - //QDEBUG("importedStrings =" << importedStrings); - if (!ok) { - // show imported strings as error message - tmpTableWidget->setRowCount(1); - tmpTableWidget->setColumnCount(1); - QTableWidgetItem* item = new QTableWidgetItem(); - item->setText(importedStrings[0][0]); - tmpTableWidget->setItem(0, 0, item); +/*! + *\brief Returns the amount of topics that the "+" wildcard will replace in the level position + * + * \param levelIdx the level currently being investigated + * \param level the level where the new + wildcard will be placed + * \param commonList the topic name split into levels + * \param currentItem pointer to a TreeWidgetItem which represents the parent of the level + * represented by levelIdx + * \return returns the childCount, or -1 if some topics already represented by + wildcard have different + * amount of children + */ +int ImportFileWidget::checkCommonChildCount(int levelIdx, int level, QStringList& commonList, QTreeWidgetItem* currentItem) { + //we recursively check the number of children, until we get to level-1 + if(levelIdx < level - 1) { + if(commonList[levelIdx] != "+") { + for(int j = 0; j < currentItem->childCount(); ++j) { + if(currentItem->child(j)->text(0) == commonList[levelIdx]) { + //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item, recursively + return checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); + } + } } else { - //TODO: maxrows not used - const int rows = qMax(importedStrings.size(), 1); - const int maxColumns = 300; - tmpTableWidget->setRowCount(rows); - - for (int i = 0; i < rows; ++i) { -// QDEBUG("imported string " << importedStrings[i]); - - int cols = importedStrings[i].size() > maxColumns ? maxColumns : importedStrings[i].size(); // new - if (cols > tmpTableWidget->columnCount()) - tmpTableWidget->setColumnCount(cols); + int childCount = -1; + bool ok = true; - for (int j = 0; j < cols; ++j) { - QTableWidgetItem* item = new QTableWidgetItem(importedStrings[i][j]); - tmpTableWidget->setItem(i, j, item); + //otherwise we check if every + wildcard represented topic has the same number of children, recursively + for(int j = 0; j < currentItem->childCount(); ++j) { + int temp = checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); + if((j > 0) && (temp != childCount)) { + ok = false; + break; } + childCount = temp; } - // set header if columnMode available - for (int i = 0; i < qMin(tmpTableWidget->columnCount(), columnModes.size()); ++i) { - QString columnName = QString::number(i+1); - if (i < vectorNameList.size()) - columnName = vectorNameList[i]; - auto* item = new QTableWidgetItem(columnName + QLatin1String(" {") + ENUM_TO_STRING(AbstractColumn, ColumnMode, columnModes[i]) + QLatin1String("}")); - item->setTextAlignment(Qt::AlignLeft); - item->setIcon(AbstractColumn::iconForMode(columnModes[i])); + //if yes we return this number, otherwise -1 + if(ok) + return childCount; + else + return -1; + } + } else if (levelIdx == level - 1) { + if(commonList[levelIdx] != "+") { + for(int j = 0; j < currentItem->childCount(); ++j) { + if(currentItem->child(j)->text(0) == commonList[levelIdx]) { + //if the level isn't represented by + wildcard we simply return the amount of children of the corresponding item + return currentItem->child(j)->childCount(); + } + } + } else { + int childCount = -1; + bool ok = true; - tmpTableWidget->setHorizontalHeaderItem(i, item); + //otherwise we check if every + wildcard represented topic has the same number of children + for(int j = 0; j < currentItem->childCount(); ++j) { + if((j > 0) && (currentItem->child(j)->childCount() != childCount)) { + ok = false; + break; + } + childCount = currentItem->child(j)->childCount(); } + + //if yes we return this number, otherwise -1 + if(ok) + return childCount; + else + return -1; } - tmpTableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); - m_fileEmpty = false; - } else { - m_fileEmpty = true; + } else if (level == 1 && levelIdx == 1) { + return currentItem->childCount(); } - emit previewRefreshed(); - - RESET_CURSOR; + return -1; } -void ImportFileWidget::updateTypeChanged(int idx) { - LiveDataSource::UpdateType type = static_cast(idx); +/*! + *\brief We search in twSubscriptions for topics that can be represented using + wildcards, then merge them. + * We do this until there are no topics to merge + */ +void ImportFileWidget::manageCommonLevelSubscriptions() { + bool foundEqual = false; - switch (type) { - case LiveDataSource::UpdateType::TimeInterval: - ui.lUpdateInterval->show(); - ui.sbUpdateInterval->show(); - break; - case LiveDataSource::UpdateType::NewData: - ui.lUpdateInterval->hide(); - ui.sbUpdateInterval->hide(); - } -} + do{ + foundEqual = false; + QMap> equalTopicsMap; + QVector equalTopics; -void ImportFileWidget::readingTypeChanged(int idx) { - LiveDataSource::ReadingType type = static_cast(idx); + //compare the subscriptions present in the TreeWidget + for(int i = 0; i < ui.twSubscriptions->topLevelItemCount() - 1; ++i) { + for(int j = i + 1; j < ui.twSubscriptions->topLevelItemCount(); ++j) { + qDebug()<topLevelItem(i)->text(0)<<" "<topLevelItem(j)->text(0); + QString commonTopic = checkCommonLevel(ui.twSubscriptions->topLevelItem(i)->text(0), ui.twSubscriptions->topLevelItem(j)->text(0)); - if (type == LiveDataSource::ReadingType::TillEnd || type == LiveDataSource::ReadingType::WholeFile) { - ui.lSampleSize->hide(); - ui.sbSampleSize->hide(); - } else { - ui.lSampleSize->show(); - ui.sbSampleSize->show(); - } + //if there is a common topic for the 2 compared topics, we add them to the map (using the common topic as key) + if(!commonTopic.isEmpty()) { + if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(i)->text(0))) { + equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(i)->text(0)); + } - if (type == LiveDataSource::ReadingType::WholeFile) { - ui.lKeepLastValues->hide(); - ui.sbKeepNValues->hide(); - } else { - ui.lKeepLastValues->show(); - ui.sbKeepNValues->show(); - } -} + if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(j)->text(0))) { + qDebug()<topLevelItem(i)->text(0); + equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(j)->text(0)); + } + } + } + } -void ImportFileWidget::sourceTypeChanged(int idx) { - LiveDataSource::SourceType type = static_cast(idx); + if(!equalTopicsMap.isEmpty()) { + qDebug()<<"Equal topics not empty"; - switch (type) { - case LiveDataSource::SourceType::FileOrPipe: - ui.lFileName->show(); - ui.leFileName->show(); - ui.bFileInfo->show(); - ui.bOpen->show(); - ui.chbLinkFile->show(); - - ui.cbBaudRate->hide(); - ui.lBaudRate->hide(); - ui.lHost->hide(); - ui.leHost->hide(); - ui.lPort->hide(); - ui.lePort->hide(); - ui.cbSerialPort->hide(); - ui.lSerialPort->hide(); - - fileNameChanged(ui.leFileName->text()); - break; - case LiveDataSource::SourceType::LocalSocket: - ui.lFileName->show(); - ui.leFileName->show(); - ui.bOpen->show(); - - ui.bFileInfo->hide(); - ui.cbBaudRate->hide(); - ui.lBaudRate->hide(); - ui.lHost->hide(); - ui.leHost->hide(); - ui.lPort->hide(); - ui.lePort->hide(); - ui.cbSerialPort->hide(); - ui.lSerialPort->hide(); - ui.chbLinkFile->hide(); - - ui.gbOptions->setEnabled(true); - ui.bManageFilters->setEnabled(true); - ui.cbFilter->setEnabled(true); - ui.cbFileType->setEnabled(true); - break; - case LiveDataSource::SourceType::NetworkTcpSocket: - case LiveDataSource::SourceType::NetworkUdpSocket: - ui.lHost->show(); - ui.leHost->show(); - ui.lePort->show(); - ui.lPort->show(); - - ui.lBaudRate->hide(); - ui.cbBaudRate->hide(); - ui.lSerialPort->hide(); - ui.cbSerialPort->hide(); - - ui.lFileName->hide(); - ui.leFileName->hide(); - ui.bFileInfo->hide(); - ui.bOpen->hide(); - ui.chbLinkFile->hide(); - - ui.gbOptions->setEnabled(true); - ui.bManageFilters->setEnabled(true); - ui.cbFilter->setEnabled(true); - ui.cbFileType->setEnabled(true); - break; - case LiveDataSource::SourceType::SerialPort: - ui.lBaudRate->show(); - ui.cbBaudRate->show(); - ui.lSerialPort->show(); - ui.cbSerialPort->show(); - - ui.lHost->hide(); - ui.leHost->hide(); - ui.lePort->hide(); - ui.lPort->hide(); - ui.lFileName->hide(); - ui.leFileName->hide(); - ui.bFileInfo->hide(); - ui.bOpen->hide(); - ui.chbLinkFile->hide(); - ui.cbFileType->setEnabled(true); - - ui.gbOptions->setEnabled(true); - ui.bManageFilters->setEnabled(true); - ui.cbFilter->setEnabled(true); - break; + QVector commonTopics; + QMapIterator> topics(equalTopicsMap); - case LiveDataSource::SourceType::MQTT: -#ifdef HAVE_MQTT + //check for every map entry, if the found topics can be merged or not + while(topics.hasNext()) { + topics.next(); - ui.lBaudRate->hide(); - ui.cbBaudRate->hide(); - ui.lSerialPort->hide(); - ui.cbSerialPort->hide(); + int level = commonLevelIndex(topics.value().last(), topics.value().first()); + QStringList commonList = topics.value().first().split('/', QString::SkipEmptyParts); + QTreeWidgetItem* currentItem; + //search the corresponding item to the common topics first level(root) + for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { + if(ui.twTopics->topLevelItem(i)->text(0) == commonList.first()) { + currentItem = ui.twTopics->topLevelItem(i); + break; + } + } - ui.lHost->show(); - ui.leHost->show(); - ui.lePort->show(); - ui.lPort->show(); - ui.lFileName->hide(); + //calculate the number of topics the new + wildcard could replace + int childCount = checkCommonChildCount(1, level, commonList, currentItem); + if(childCount > 0) { + //if the number of topics found and the calculated number of topics is equal, the topics can be merged + if(topics.value().size() == childCount) { + foundEqual = true; + commonTopics.push_back(topics.key()); + } + } + } + + if(foundEqual) { + //if there are more common topics, the topics of which can be merged, we choose the one which has the lowest level new "+" wildcard + int lowestLevel = INT_MAX; + int topicIdx = -1; + for(int i = 0; i < commonTopics.size(); ++i) { + int level = commonLevelIndex(equalTopicsMap[commonTopics[i]].first(), commonTopics[i]); + if(level < lowestLevel) { + topicIdx = i; + lowestLevel = level; + } + } + + equalTopics.append(equalTopicsMap[commonTopics[topicIdx]]); + + qDebug()<<"Adding common topic"; + //Add the common topic ("merging") + QString commonTopic; + commonTopic = checkCommonLevel(equalTopics.first(), equalTopics.last()); + QStringList nameList; + nameList.append(commonTopic); + QTreeWidgetItem* newTopic = new QTreeWidgetItem(nameList); + ui.twSubscriptions->addTopLevelItem(newTopic); + QMqttTopicFilter filter {commonTopic}; + QMqttSubscription *temp_subscription = m_client->subscribe(filter, static_cast (ui.cbQos->currentText().toUInt()) ); + + if(temp_subscription) { + m_mqttSubscriptions.push_back(temp_subscription); + connect(temp_subscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); + emit subscriptionsChanged(); + } + + //remove the "merged" topics + qDebug()<<"unsubscribe from equal topics"; + for(int i = 0; i < equalTopics.size(); ++i) { + for(int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j){ + if(ui.twSubscriptions->topLevelItem(j)->text(0) == equalTopics[i]) { + newTopic->addChild(ui.twSubscriptions->takeTopLevelItem(j)); + unsubscribeFromTopic(equalTopics[i]); + break; + } + } + } + + //remove any subscription that the new subscription contains + for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + if(checkTopicContains(commonTopic, ui.twSubscriptions->topLevelItem(i)->text(0)) && + commonTopic != ui.twSubscriptions->topLevelItem(i)->text(0) ) { + unsubscribeFromTopic(ui.twSubscriptions->topLevelItem(i)->text(0)); + i--; + } + } + } + } + } while(foundEqual); +} +#endif + +/************** SLOTS **************************************************************/ + +/*! + called on file name changes. + Determines the file format (ASCII, binary etc.), if the file exists, + and activates the corresponding options. +*/ +void ImportFileWidget::fileNameChanged(const QString& name) { + QString fileName = name; +#ifndef HAVE_WINDOWS + // make relative path + if ( !fileName.isEmpty() && fileName.at(0) != QDir::separator()) + fileName = QDir::homePath() + QDir::separator() + fileName; +#endif + + bool fileExists = QFile::exists(fileName); + if (fileExists) + ui.leFileName->setStyleSheet(""); + else + ui.leFileName->setStyleSheet("QLineEdit{background:red;}"); + + ui.gbOptions->setEnabled(fileExists); + ui.bManageFilters->setEnabled(fileExists); + ui.cbFilter->setEnabled(fileExists); + ui.cbFileType->setEnabled(fileExists); + ui.bFileInfo->setEnabled(fileExists); + ui.gbUpdateOptions->setEnabled(fileExists); + if (!fileExists) { + //file doesn't exist -> delete the content preview that is still potentially + //available from the previously selected file + ui.tePreview->clear(); + m_twPreview->clear(); + m_hdf5OptionsWidget->clear(); + m_netcdfOptionsWidget->clear(); + m_fitsOptionsWidget->clear(); + m_jsonOptionsWidget->clearModel(); + m_rootOptionsWidget->clear(); + + emit fileNameChanged(); + return; + } + + if (currentSourceType() == LiveDataSource::FileOrPipe) { + const AbstractFileFilter::FileType fileType = AbstractFileFilter::fileType(fileName); + switch(fileType) { + case AbstractFileFilter::Ascii: + ui.cbFileType->setCurrentIndex(AbstractFileFilter::Ascii); + break; + case AbstractFileFilter::Binary: + ui.cbFileType->setCurrentIndex(AbstractFileFilter::Binary); + break; + case AbstractFileFilter::Image: + ui.cbFileType->setCurrentIndex(AbstractFileFilter::Image); + break; + case AbstractFileFilter::HDF5: + ui.cbFileType->setCurrentIndex(AbstractFileFilter::HDF5); + m_hdf5OptionsWidget->updateContent((HDF5Filter*)this->currentFileFilter(), fileName); + break; + case AbstractFileFilter::NETCDF: + ui.cbFileType->setCurrentIndex(AbstractFileFilter::NETCDF); + m_netcdfOptionsWidget->updateContent((NetCDFFilter*)this->currentFileFilter(), fileName); + break; + case AbstractFileFilter::FITS: +#ifdef HAVE_FITS + ui.cbFileType->setCurrentIndex(AbstractFileFilter::FITS); + m_fitsOptionsWidget->updateContent((FITSFilter*)this->currentFileFilter(), fileName); +#endif + break; + case AbstractFileFilter::Json: + ui.cbFileType->setCurrentIndex(AbstractFileFilter::Json); + m_jsonOptionsWidget->loadDocument(fileName); + break; + case AbstractFileFilter::ROOT: + ui.cbFileType->setCurrentIndex(AbstractFileFilter::ROOT); + m_rootOptionsWidget->updateContent((ROOTFilter*)this->currentFileFilter(), fileName); + break; + case AbstractFileFilter::NgspiceRawAscii: + ui.cbFileType->setCurrentIndex(AbstractFileFilter::NgspiceRawAscii); + break; + case AbstractFileFilter::NgspiceRawBinary: + ui.cbFileType->setCurrentIndex(AbstractFileFilter::NgspiceRawBinary); + break; + } + } + + refreshPreview(); + emit fileNameChanged(); +} + +/*! + saves the current filter settings +*/ +void ImportFileWidget::saveFilter() { + bool ok; + QString text = QInputDialog::getText(this, i18n("Save Filter Settings as"), + i18n("Filter name:"), QLineEdit::Normal, i18n("new filter"), &ok); + if (ok && !text.isEmpty()) { + //TODO + //AsciiFilter::saveFilter() + } +} + +/*! + opens a dialog for managing all available predefined filters. +*/ +void ImportFileWidget::manageFilters() { + //TODO +} + +/*! + Depending on the selected file type, activates the corresponding options in the data portion tab + and populates the combobox with the available pre-defined fllter settings for the selected type. +*/ +void ImportFileWidget::fileTypeChanged(int fileType) { + ui.swOptions->setCurrentIndex(fileType); + + //default + ui.lFilter->show(); + ui.cbFilter->show(); + + //different file types show different number of tabs in ui.tabWidget. + //when switching from the previous file type we re-set the tab widget to its original state + //and remove/add the required tabs further below + for (int i = 0; icount(); ++i) + ui.tabWidget->count(); + + ui.tabWidget->addTab(ui.tabDataFormat, i18n("Data format")); + ui.tabWidget->addTab(ui.tabDataPreview, i18n("Preview")); + ui.tabWidget->addTab(ui.tabDataPortion, i18n("Data portion to read")); + + ui.lPreviewLines->show(); + ui.sbPreviewLines->show(); + ui.lStartColumn->show(); + ui.sbStartColumn->show(); + ui.lEndColumn->show(); + ui.sbEndColumn->show(); + + showJsonModel(false); + + switch (fileType) { + case AbstractFileFilter::Ascii: + break; + case AbstractFileFilter::Binary: + ui.lStartColumn->hide(); + ui.sbStartColumn->hide(); + ui.lEndColumn->hide(); + ui.sbEndColumn->hide(); + break; + case AbstractFileFilter::ROOT: + ui.tabWidget->removeTab(1); + // falls through + case AbstractFileFilter::HDF5: + case AbstractFileFilter::NETCDF: + case AbstractFileFilter::FITS: + ui.lFilter->hide(); + ui.cbFilter->hide(); + // hide global preview tab. we have our own + ui.tabWidget->setTabText(0, i18n("Data format && preview")); + ui.tabWidget->removeTab(1); + ui.tabWidget->setCurrentIndex(0); + break; + case AbstractFileFilter::Image: + ui.lPreviewLines->hide(); + ui.sbPreviewLines->hide(); + ui.lFilter->hide(); + ui.cbFilter->hide(); + break; + case AbstractFileFilter::NgspiceRawAscii: + case AbstractFileFilter::NgspiceRawBinary: + ui.lStartColumn->hide(); + ui.sbStartColumn->hide(); + ui.lEndColumn->hide(); + ui.sbEndColumn->hide(); + ui.tabWidget->removeTab(0); + ui.tabWidget->setCurrentIndex(0); + break; + case AbstractFileFilter::Json: + ui.lFilter->hide(); + ui.cbFilter->hide(); + showJsonModel(true); + break; + default: + DEBUG("unknown file type"); + } + + m_hdf5OptionsWidget->clear(); + m_netcdfOptionsWidget->clear(); + m_rootOptionsWidget->clear(); + + int lastUsedFilterIndex = ui.cbFilter->currentIndex(); + ui.cbFilter->clear(); + ui.cbFilter->addItem( i18n("Automatic") ); + ui.cbFilter->addItem( i18n("Custom") ); + + //TODO: populate the combobox with the available pre-defined filter settings for the selected type + ui.cbFilter->setCurrentIndex(lastUsedFilterIndex); + filterChanged(lastUsedFilterIndex); + + refreshPreview(); +} + + +const QStringList ImportFileWidget::selectedHDF5Names() const { + return m_hdf5OptionsWidget->selectedHDF5Names(); +} + +const QStringList ImportFileWidget::selectedNetCDFNames() const { + return m_netcdfOptionsWidget->selectedNetCDFNames(); +} + +const QStringList ImportFileWidget::selectedFITSExtensions() const { + return m_fitsOptionsWidget->selectedFITSExtensions(); +} + +const QStringList ImportFileWidget::selectedROOTNames() const { + return m_rootOptionsWidget->selectedROOTNames(); +} + +/*! + shows the dialog with the information about the file(s) to be imported. +*/ +void ImportFileWidget::fileInfoDialog() { + QStringList files = ui.leFileName->text().split(';'); + FileInfoDialog* dlg = new FileInfoDialog(this); + dlg->setFiles(files); + dlg->exec(); +} + +/*! + enables the options if the filter "custom" was chosen. Disables the options otherwise. +*/ +void ImportFileWidget::filterChanged(int index) { + // ignore filter for these formats + if (ui.cbFileType->currentIndex() == AbstractFileFilter::HDF5 || + ui.cbFileType->currentIndex() == AbstractFileFilter::NETCDF || + ui.cbFileType->currentIndex() == AbstractFileFilter::Image || + ui.cbFileType->currentIndex() == AbstractFileFilter::FITS || + ui.cbFileType->currentIndex() == AbstractFileFilter::Json || + ui.cbFileType->currentIndex() == AbstractFileFilter::ROOT) { + ui.swOptions->setEnabled(true); + return; + } + + if (index == 0) { // "automatic" + ui.swOptions->setEnabled(false); + ui.bSaveFilter->setEnabled(false); + } else if (index == 1) { //custom + ui.swOptions->setEnabled(true); + ui.bSaveFilter->setEnabled(true); + } else { + // predefined filter settings were selected. + //load and show them in the GUI. + //TODO + } +} + +void ImportFileWidget::refreshPreview() { + if (m_suppressRefresh) + return; + + WAIT_CURSOR; + + QString fileName = ui.leFileName->text(); +#ifndef HAVE_WINDOWS + if (!fileName.isEmpty() && fileName.at(0) != QDir::separator()) + fileName = QDir::homePath() + QDir::separator() + fileName; +#endif + DEBUG("refreshPreview(): file name = " << fileName.toStdString()); + + QVector importedStrings; + AbstractFileFilter::FileType fileType = (AbstractFileFilter::FileType)ui.cbFileType->currentIndex(); + + // generic table widget + if (fileType == AbstractFileFilter::Ascii || fileType == AbstractFileFilter::Binary + || fileType == AbstractFileFilter::Json || fileType == AbstractFileFilter::NgspiceRawAscii + || fileType == AbstractFileFilter::NgspiceRawBinary) + m_twPreview->show(); + else + m_twPreview->hide(); + + int lines = ui.sbPreviewLines->value(); + + bool ok = true; + QTableWidget* tmpTableWidget{nullptr}; + QStringList vectorNameList; + QVector columnModes; + DEBUG("Data File Type: " << ENUM_TO_STRING(AbstractFileFilter, FileType, fileType)); + switch (fileType) { + case AbstractFileFilter::Ascii: { + ui.tePreview->clear(); + + AsciiFilter* filter = static_cast(this->currentFileFilter()); + + DEBUG("Data Source Type: " << ENUM_TO_STRING(LiveDataSource, SourceType, currentSourceType())); + switch (currentSourceType()) { + case LiveDataSource::SourceType::FileOrPipe: { + importedStrings = filter->preview(fileName, lines); + break; + } + case LiveDataSource::SourceType::LocalSocket: { + QLocalSocket lsocket{this}; + DEBUG("Local socket: CONNECT PREVIEW"); + lsocket.connectToServer(fileName, QLocalSocket::ReadOnly); + if (lsocket.waitForConnected()) { + DEBUG("connected to local socket " << fileName.toStdString()); + if (lsocket.waitForReadyRead()) + importedStrings = filter->preview(lsocket); + DEBUG("Local socket: DISCONNECT PREVIEW"); + lsocket.disconnectFromServer(); + // read-only socket is disconnected immediately (no waitForDisconnected()) + } else { + DEBUG("failed connect to local socket " << fileName.toStdString() << " - " << lsocket.errorString().toStdString()); + } + + break; + } + case LiveDataSource::SourceType::NetworkTcpSocket: { + QTcpSocket tcpSocket{this}; + tcpSocket.connectToHost(host(), port().toInt(), QTcpSocket::ReadOnly); + if (tcpSocket.waitForConnected()) { + DEBUG("connected to TCP socket"); + if ( tcpSocket.waitForReadyRead() ) + importedStrings = filter->preview(tcpSocket); + + tcpSocket.disconnectFromHost(); + } else { + DEBUG("failed to connect to TCP socket " << " - " << tcpSocket.errorString().toStdString()); + } + + break; + } + case LiveDataSource::SourceType::NetworkUdpSocket: { + QUdpSocket udpSocket{this}; + DEBUG("UDP Socket: CONNECT PREVIEW, state = " << udpSocket.state()); + udpSocket.bind(QHostAddress(host()), port().toInt()); + udpSocket.connectToHost(host(), 0, QUdpSocket::ReadOnly); + if (udpSocket.waitForConnected()) { + DEBUG(" connected to UDP socket " << host().toStdString() << ':' << port().toInt()); + if (!udpSocket.waitForReadyRead(2000) ) + DEBUG(" ERROR: not ready for read after 2 sec"); + if (udpSocket.hasPendingDatagrams()) { + DEBUG(" has pending data"); + } else { + DEBUG(" has no pending data"); + } + importedStrings = filter->preview(udpSocket); + + DEBUG("UDP Socket: DISCONNECT PREVIEW, state = " << udpSocket.state()); + udpSocket.disconnectFromHost(); + } else { + DEBUG("failed to connect to UDP socket " << " - " << udpSocket.errorString().toStdString()); + } + + break; + } + case LiveDataSource::SourceType::SerialPort: { + QSerialPort sPort{this}; + DEBUG(" Port name: " << serialPort().toStdString()); + DEBUG(" Settings: " << baudRate() << ',' << sPort.dataBits() << ',' << sPort.parity() + << ',' << sPort.stopBits()); + sPort.setPortName(serialPort()); + sPort.setBaudRate(baudRate()); + + if (sPort.open(QIODevice::ReadOnly)) { + if (sPort.waitForReadyRead(2000)) + importedStrings = filter->preview(sPort); + else { + DEBUG(" ERROR: not ready for read after 2 sec"); + } + + sPort.close(); + } else { + DEBUG(" ERROR: failed to open serial port. error: " << sPort.error()); + } + break; + } + case LiveDataSource::SourceType::MQTT: { +#ifdef HAVE_MQTT + qDebug()<<"preview mqtt, is it ready:"<vectorNames().clear(); + QMapIterator i(m_lastMessage); + while(i.hasNext()) { + i.next(); + qDebug()<<"calling ascii mqtt preview"<< importedStrings << " "<mqttPreview(importedStrings, QString(i.value().payload().data()), i.key().name() ); + if(importedStrings.isEmpty()) + break; + } + + QMapIterator j(m_messageArrived); + while(j.hasNext()) { + j.next(); + qDebug()<<"Set false after preview: "<vectorNames(); + columnModes = filter->columnModes(); + break; + } + case AbstractFileFilter::Binary: { + ui.tePreview->clear(); + BinaryFilter *filter = (BinaryFilter *)this->currentFileFilter(); + importedStrings = filter->preview(fileName, lines); + tmpTableWidget = m_twPreview; + break; + } + case AbstractFileFilter::Image: { + ui.tePreview->clear(); + + QImage image(fileName); + QTextCursor cursor = ui.tePreview->textCursor(); + cursor.insertImage(image); + RESET_CURSOR; + return; + } + case AbstractFileFilter::HDF5: { + HDF5Filter *filter = (HDF5Filter *)this->currentFileFilter(); + lines = m_hdf5OptionsWidget->lines(); + importedStrings = filter->readCurrentDataSet(fileName, NULL, ok, AbstractFileFilter::Replace, lines); + tmpTableWidget = m_hdf5OptionsWidget->previewWidget(); + break; + } + case AbstractFileFilter::NETCDF: { + NetCDFFilter *filter = (NetCDFFilter *)this->currentFileFilter(); + lines = m_netcdfOptionsWidget->lines(); + importedStrings = filter->readCurrentVar(fileName, NULL, AbstractFileFilter::Replace, lines); + tmpTableWidget = m_netcdfOptionsWidget->previewWidget(); + break; + } + case AbstractFileFilter::FITS: { + FITSFilter* filter = (FITSFilter*)this->currentFileFilter(); + lines = m_fitsOptionsWidget->lines(); + + // update file name (may be any file type) + m_fitsOptionsWidget->updateContent(filter, fileName); + QString extensionName = m_fitsOptionsWidget->extensionName(&ok); + if (!extensionName.isEmpty()) { + DEBUG(" extension name = " << extensionName.toStdString()); + fileName = extensionName; + } + DEBUG(" file name = " << fileName.toStdString()); + + bool readFitsTableToMatrix; + importedStrings = filter->readChdu(fileName, &readFitsTableToMatrix, lines); + emit checkedFitsTableToMatrix(readFitsTableToMatrix); + + tmpTableWidget = m_fitsOptionsWidget->previewWidget(); + break; + } + case AbstractFileFilter::Json: { + ui.tePreview->clear(); + m_jsonOptionsWidget->loadDocument(fileName); + JsonFilter *filter = (JsonFilter*)this->currentFileFilter(); + m_jsonOptionsWidget->applyFilterSettings(filter, ui.tvJson->currentIndex()); + importedStrings = filter->preview(fileName); + tmpTableWidget = m_twPreview; + columnModes = filter->columnModes(); + break; + } + case AbstractFileFilter::ROOT: { + ROOTFilter *filter = (ROOTFilter *)this->currentFileFilter(); + lines = m_rootOptionsWidget->lines(); + m_rootOptionsWidget->setNBins(filter->binsInCurrentHistogram(fileName)); + importedStrings = filter->previewCurrentHistogram( + fileName, + m_rootOptionsWidget->startBin(), + qMin(m_rootOptionsWidget->startBin() + m_rootOptionsWidget->lines() - 1, + m_rootOptionsWidget->endBin()) + ); + tmpTableWidget = m_rootOptionsWidget->previewWidget(); + // the last vector element contains the column names + vectorNameList = importedStrings.last(); + importedStrings.removeLast(); + columnModes = QVector(vectorNameList.size(), AbstractColumn::Numeric); + break; + } + case AbstractFileFilter::NgspiceRawAscii: { + ui.tePreview->clear(); + NgspiceRawAsciiFilter* filter = (NgspiceRawAsciiFilter*)this->currentFileFilter(); + importedStrings = filter->preview(fileName, lines); + tmpTableWidget = m_twPreview; + vectorNameList = filter->vectorNames(); + columnModes = filter->columnModes(); + break; + } + case AbstractFileFilter::NgspiceRawBinary: { + ui.tePreview->clear(); + NgspiceRawBinaryFilter* filter = (NgspiceRawBinaryFilter*)this->currentFileFilter(); + importedStrings = filter->preview(fileName, lines); + tmpTableWidget = m_twPreview; + vectorNameList = filter->vectorNames(); + columnModes = filter->columnModes(); + break; + } + } + + // fill the table widget + tmpTableWidget->setRowCount(0); + tmpTableWidget->setColumnCount(0); + if( !importedStrings.isEmpty() ) { + //QDEBUG("importedStrings =" << importedStrings); + if (!ok) { + // show imported strings as error message + tmpTableWidget->setRowCount(1); + tmpTableWidget->setColumnCount(1); + QTableWidgetItem* item = new QTableWidgetItem(); + item->setText(importedStrings[0][0]); + tmpTableWidget->setItem(0, 0, item); + } else { + //TODO: maxrows not used + const int rows = qMax(importedStrings.size(), 1); + const int maxColumns = 300; + tmpTableWidget->setRowCount(rows); + + for (int i = 0; i < rows; ++i) { + // QDEBUG("imported string " << importedStrings[i]); + + int cols = importedStrings[i].size() > maxColumns ? maxColumns : importedStrings[i].size(); // new + if (cols > tmpTableWidget->columnCount()) + tmpTableWidget->setColumnCount(cols); + + for (int j = 0; j < cols; ++j) { + QTableWidgetItem* item = new QTableWidgetItem(importedStrings[i][j]); + tmpTableWidget->setItem(i, j, item); + } + } + + // set header if columnMode available + for (int i = 0; i < qMin(tmpTableWidget->columnCount(), columnModes.size()); ++i) { + QString columnName = QString::number(i+1); + if (i < vectorNameList.size()) + columnName = vectorNameList[i]; + auto* item = new QTableWidgetItem(columnName + QLatin1String(" {") + ENUM_TO_STRING(AbstractColumn, ColumnMode, columnModes[i]) + QLatin1String("}")); + item->setTextAlignment(Qt::AlignLeft); + item->setIcon(AbstractColumn::iconForMode(columnModes[i])); + + tmpTableWidget->setHorizontalHeaderItem(i, item); + } + } + + tmpTableWidget->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); + m_fileEmpty = false; + } else { + m_fileEmpty = true; + } + + emit previewRefreshed(); + + RESET_CURSOR; +} + +void ImportFileWidget::updateTypeChanged(int idx) { + LiveDataSource::UpdateType type = static_cast(idx); + + switch (type) { + case LiveDataSource::UpdateType::TimeInterval: + ui.lUpdateInterval->show(); + ui.sbUpdateInterval->show(); + break; + case LiveDataSource::UpdateType::NewData: + ui.lUpdateInterval->hide(); + ui.sbUpdateInterval->hide(); + } +} + +void ImportFileWidget::readingTypeChanged(int idx) { + LiveDataSource::ReadingType type = static_cast(idx); + + if (type == LiveDataSource::ReadingType::TillEnd || type == LiveDataSource::ReadingType::WholeFile) { + ui.lSampleSize->hide(); + ui.sbSampleSize->hide(); + } else { + ui.lSampleSize->show(); + ui.sbSampleSize->show(); + } + + if (type == LiveDataSource::ReadingType::WholeFile) { + ui.lKeepLastValues->hide(); + ui.sbKeepNValues->hide(); + } else { + ui.lKeepLastValues->show(); + ui.sbKeepNValues->show(); + } +} + +void ImportFileWidget::sourceTypeChanged(int idx) { + LiveDataSource::SourceType type = static_cast(idx); + + switch (type) { + case LiveDataSource::SourceType::FileOrPipe: + ui.lFileName->show(); + ui.leFileName->show(); + ui.bFileInfo->show(); + ui.bOpen->show(); + ui.chbLinkFile->show(); + + ui.cbBaudRate->hide(); + ui.lBaudRate->hide(); + ui.lHost->hide(); + ui.leHost->hide(); + ui.lPort->hide(); + ui.lePort->hide(); + ui.cbSerialPort->hide(); + ui.lSerialPort->hide(); + + fileNameChanged(ui.leFileName->text()); + break; + case LiveDataSource::SourceType::LocalSocket: + ui.lFileName->show(); + ui.leFileName->show(); + ui.bOpen->show(); + + ui.bFileInfo->hide(); + ui.cbBaudRate->hide(); + ui.lBaudRate->hide(); + ui.lHost->hide(); + ui.leHost->hide(); + ui.lPort->hide(); + ui.lePort->hide(); + ui.cbSerialPort->hide(); + ui.lSerialPort->hide(); + ui.chbLinkFile->hide(); + + ui.gbOptions->setEnabled(true); + ui.bManageFilters->setEnabled(true); + ui.cbFilter->setEnabled(true); + ui.cbFileType->setEnabled(true); + break; + case LiveDataSource::SourceType::NetworkTcpSocket: + case LiveDataSource::SourceType::NetworkUdpSocket: + ui.lHost->show(); + ui.leHost->show(); + ui.lePort->show(); + ui.lPort->show(); + + ui.lBaudRate->hide(); + ui.cbBaudRate->hide(); + ui.lSerialPort->hide(); + ui.cbSerialPort->hide(); + + ui.lFileName->hide(); + ui.leFileName->hide(); + ui.bFileInfo->hide(); + ui.bOpen->hide(); + ui.chbLinkFile->hide(); + + ui.gbOptions->setEnabled(true); + ui.bManageFilters->setEnabled(true); + ui.cbFilter->setEnabled(true); + ui.cbFileType->setEnabled(true); + break; + case LiveDataSource::SourceType::SerialPort: + ui.lBaudRate->show(); + ui.cbBaudRate->show(); + ui.lSerialPort->show(); + ui.cbSerialPort->show(); + + ui.lHost->hide(); + ui.leHost->hide(); + ui.lePort->hide(); + ui.lPort->hide(); + ui.lFileName->hide(); + ui.leFileName->hide(); + ui.bFileInfo->hide(); + ui.bOpen->hide(); + ui.chbLinkFile->hide(); + ui.cbFileType->setEnabled(true); + + ui.gbOptions->setEnabled(true); + ui.bManageFilters->setEnabled(true); + ui.cbFilter->setEnabled(true); + break; + + case LiveDataSource::SourceType::MQTT: +#ifdef HAVE_MQTT + + ui.lBaudRate->hide(); + ui.cbBaudRate->hide(); + ui.lSerialPort->hide(); + ui.cbSerialPort->hide(); + + ui.lHost->show(); + ui.leHost->show(); + ui.lePort->show(); + ui.lPort->show(); + ui.lFileName->hide(); ui.leFileName->hide(); ui.bFileInfo->hide(); ui.bOpen->hide(); ui.chbLinkFile->hide(); ui.cbFileType->setEnabled(true); ui.leID->hide(); ui.lMqttID->hide(); ui.lePassword->hide(); ui.lPassword->hide(); ui.leUsername->hide(); ui.lUsername->hide(); ui.cbQos->show(); ui.lQos->show(); ui.twTopics->show(); - //ui.lTopicTree->show(); ui.lTopicSearch->show(); ui.leTopics->show(); ui.twSubscriptions->show(); - //ui.lSubscriptions->show(); ui.chbAuthentication->show(); ui.chbID->show(); ui.bSubscribe->show(); ui.bUnsubscribe->show(); ui.bConnect->show(); ui.gbOptions->setEnabled(true); ui.bManageFilters->setEnabled(true); ui.cbFilter->setEnabled(true); ui.gbMqttWill->show(); ui.chbWill->show(); ui.chbWillRetain->hide(); ui.cbWillQoS->hide(); ui.cbWillMessageType->hide(); ui.cbWillTopic->hide(); ui.cbWillUpdate->hide(); ui.leWillOwnMessage->hide(); ui.leWillUpdateInterval->setValidator(new QIntValidator(2, 1000000) ); ui.leWillUpdateInterval->hide(); ui.lWillMessageType->hide(); ui.lWillOwnMessage->hide(); ui.lWillQos->hide(); ui.lWillTopic->hide(); ui.lWillUpdate->hide(); ui.lWillUpdateInterval->hide(); ui.lwWillStatistics->hide(); ui.lWillStatistics->hide(); ui.gbManageSubscriptions->setEnabled(false); checkConnectEnable(); if(ui.chbWill->isChecked()) { ui.chbWillRetain->show(); ui.cbWillQoS->show(); ui.cbWillMessageType->show(); ui.cbWillTopic->show(); ui.cbWillUpdate->show(); ui.lWillMessageType->show(); ui.lWillQos->show(); ui.lWillTopic->show(); ui.lWillUpdate->show(); if (ui.cbWillMessageType->currentIndex() == static_cast(MQTTClient::WillMessageType::OwnMessage) ) { ui.leWillOwnMessage->show(); ui.lWillOwnMessage->show(); } else if(ui.cbWillMessageType->currentIndex() == static_cast(MQTTClient::WillMessageType::Statistics) ){ qDebug()<<"source type changed show statistics"; ui.lWillStatistics->show(); ui.lwWillStatistics->show(); } if(ui.cbWillUpdate->currentIndex() == 0) { ui.leWillUpdateInterval->show(); ui.lWillUpdateInterval->show(); } else if (ui.cbWillUpdate->currentIndex() == 1) { ui.leWillUpdateInterval->hide(); ui.lWillUpdateInterval->hide(); } } #endif break; } // "whole file" item const QStandardItemModel* model = qobject_cast(ui.cbReadingType->model()); QStandardItem* item = model->item(LiveDataSource::ReadingType::WholeFile); if (type == LiveDataSource::SourceType::FileOrPipe) item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); else item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); //"update options" groupbox can be deactived for "file and pipe" if the file is invalid. //Activate the groupbox when switching from "file and pipe" to a different source type. if (type != LiveDataSource::SourceType::FileOrPipe) ui.gbUpdateOptions->setEnabled(true); emit sourceTypeChanged(); refreshPreview(); } void ImportFileWidget::initializeAndFillPortsAndBaudRates() { for (int i = 2; i < ui.swOptions->count(); ++i) ui.swOptions->removeWidget(ui.swOptions->widget(i)); const int size = ui.cbFileType->count(); for (int i = 2; i < size; ++i) ui.cbFileType->removeItem(2); ui.cbBaudRate->hide(); ui.lBaudRate->hide(); ui.lHost->hide(); ui.leHost->hide(); ui.lPort->hide(); ui.lePort->hide(); ui.cbSerialPort->hide(); ui.lSerialPort->hide(); ui.cbBaudRate->addItems(LiveDataSource::supportedBaudRates()); ui.cbSerialPort->addItems(LiveDataSource::availablePorts()); ui.tabWidget->removeTab(2); } #ifdef HAVE_MQTT -void ImportFileWidget::idChecked(int state) -{ - if (state == 2) - { + +/*! + *\brief called when ID checkbox's state is changed, if checked a lineEdit is shown so the user can set the ID + * \param state the state of the checbox + */ +void ImportFileWidget::idChecked(int state) { + if (state == 2) { ui.leID->show(); ui.lMqttID->show(); - } - else if (state == 0) - { + } else if (state == 0) { ui.leID->hide(); ui.lMqttID->hide(); } } -void ImportFileWidget::authenticationChecked(int state) -{ - if(state == 2) - { +/*! + *\brief called when authentication checkbox's state is changed, + * if checked two lineEdits are shown so the user can set the username and password + * + * \param state the state of the checbox + */ +void ImportFileWidget::authenticationChecked(int state) { + if(state == 2) { ui.leUsername->show(); ui.lePassword->show(); ui.lPassword->show(); ui.lUsername->show(); - } - else if (state == 0) - { + } else if (state == 0) { ui.leUsername->hide(); ui.lePassword->hide(); ui.lUsername->hide(); ui.lPassword->hide(); } } -void ImportFileWidget::mqttConnection() -{ +/*! + *\brief called when the connect/disconnect button is pressed + * makes the connection to the given MQTT broker, with the given options + * or disconnects from the broker + */ +void ImportFileWidget::mqttConnection() { if(m_client->state() == QMqttClient::ClientState::Disconnected) { + //Check whether the set options are valid and a connection can be made, or not const bool hostSet = !ui.leHost->text().isEmpty(); const bool portSet = !ui.lePort->text().isEmpty(); const bool idUsed = ui.chbID->isChecked(); const bool idSet = !ui.leID->text().isEmpty(); const bool idValid = !(idUsed && !idSet); const bool authenticationUsed = ui.chbAuthentication->isChecked(); const bool usernameSet = !ui.leUsername->text().isEmpty(); const bool passwordSet = !ui.lePassword->text().isEmpty(); const bool authenticationValid = ! (authenticationUsed && ( !usernameSet || !passwordSet) ); const bool valid =hostSet && portSet && idValid && authenticationValid; if (valid) { m_client->setHostname(ui.leHost->text().simplified()); m_client->setPort(ui.lePort->text().toUInt()); + if(ui.chbID->isChecked()) m_client->setClientId(ui.leID->text().simplified()); + if(ui.chbAuthentication->isChecked()) { m_client->setUsername(ui.leUsername->text().simplified()); m_client->setPassword(ui.lePassword->text().simplified()); } + qDebug()<< ui.leHost->text() << " " << m_client->hostname() << " " << m_client->port(); qDebug()<<"Trying to connect"; m_client->connectToHost(); - m_timeoutTimer->start(); + m_connectTimeoutTimer->start(); } } else if (m_client->state() == QMqttClient::ClientState::Connected) { qDebug()<<"Disconnecting from mqtt broker" ; m_client->disconnectFromHost(); } } +/*! + *\brief called when the client connects to the broker succesfully, it subscribes to every topic (# wildcard) + * in order to later list every available topic + */ void ImportFileWidget::onMqttConnect() { if(m_client->error() == QMqttClient::NoError) { - m_timeoutTimer->stop(); + m_connectTimeoutTimer->stop(); ui.gbManageSubscriptions->setEnabled(true); ui.bConnect->setText("Disconnect"); ui.leHost->setEnabled(false); ui.lePort->setEnabled(false); ui.lePassword->setEnabled(false); ui.leUsername->setEnabled(false); ui.leID->setEnabled(false); - ui.cbSourceType->setEnabled(false); - ui.chbAuthentication->setEnabled(false); - ui.chbID->setEnabled(false); - ui.chbRetain->setEnabled(false); - QMessageBox::information(this, "Connection successful", "Connection established"); - QMqttTopicFilter globalFilter{"#"}; - m_mainSubscription = m_client->subscribe(globalFilter, 1); - if(!m_mainSubscription) - QMessageBox::information(this, "Couldn't subscribe", "Something went wrong"); - } -} - -void ImportFileWidget::mqttSubscribe() { - QString name; - QTreeWidgetItem *item = ui.twTopics->currentItem(); - if(item != nullptr) { - QTreeWidgetItem *tempItem = item; - name.prepend(item->text(0)); - if(item->childCount() != 0) - name.append("/#"); - - while(tempItem->parent() != nullptr) { - tempItem = tempItem->parent(); - name.prepend(tempItem->text(0) + "/"); - } - - QList topLevelList = ui.twSubscriptions->findItems(name, Qt::MatchExactly); - - if(topLevelList.isEmpty() || topLevelList.first()->parent() != nullptr) { - - qDebug() << name; - bool foundSuperior = false; - - for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { - qDebug()<topLevelItemCount(); - if(checkTopicContains(name, ui.twSubscriptions->topLevelItem(i)->text(0)) - && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { - qDebug()<<"1"<topLevelItem(i)->text(0); - unsubscribeFromTopic(ui.twSubscriptions->topLevelItem(i)->text(0)); - qDebug()<<"After Delete"; - i--; - continue; - } - qDebug()<<"checked inferior"; - - if(checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), name) - && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { - foundSuperior = true; - qDebug()<<"2"<topLevelItem(i)->text(0); - break; - } - qDebug()<<"checked superior"; - } - - if(!foundSuperior) { - qDebug()<<"Adding new topic"; - QStringList toplevelName; - toplevelName.push_back(name); - QTreeWidgetItem* newTopLevelItem = new QTreeWidgetItem(toplevelName); - ui.twSubscriptions->addTopLevelItem(newTopLevelItem); - - QMqttTopicFilter filter {name}; - QMqttSubscription *temp_subscription = m_client->subscribe(filter, static_cast (ui.cbQos->currentText().toUInt()) ); - - if(temp_subscription) { - m_mqttSubscriptions.push_back(temp_subscription); - connect(temp_subscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); - emit subscriptionMade(); - } - - if(name.endsWith("#")) { - addSubscriptionChildren(item, newTopLevelItem); - } - } - - if(name.endsWith("#") && !foundSuperior) { - QStringList nameList = name.split('/', QString::SkipEmptyParts); - QString root = nameList.first(); - QVector children; - for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { - if(ui.twSubscriptions->topLevelItem(i)->text(0).startsWith(root) - && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { - children.clear(); - findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i)); - for(int j = 0; j < children.size(); ++j) { - if(checkTopicContains(name, children[j]->text(0))) { - ui.twSubscriptions->setCurrentItem(children[j]); - mqttUnsubscribe(); - } - } - } - } - } - - if(!foundSuperior) - manageCommonLevelSubscriptions(); - - if(foundSuperior) { - QMessageBox::warning(this, "Warning", "You already subscribed to a topic containing this one"); - } - } - else - QMessageBox::warning(this, "Warning", "You already subscribed to this topic"); - } - else - QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); -} - -void ImportFileWidget::mqttUnsubscribe() { - QTreeWidgetItem* unsubscribeItem = ui.twSubscriptions->currentItem(); - - if(unsubscribeItem != nullptr) { - if(unsubscribeItem->parent() == nullptr) - unsubscribeFromTopic(unsubscribeItem->text(0)); - else{ - while(unsubscribeItem->parent() != nullptr) { - for(int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { - if(unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { - QMqttTopicFilter filter {unsubscribeItem->parent()->child(i)->text(0)}; - QMqttSubscription *temp_subscription = m_client->subscribe(filter, static_cast (ui.cbQos->currentText().toUInt()) ); - - ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); - - if(temp_subscription) { - m_mqttSubscriptions.push_back(temp_subscription); - connect(temp_subscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); - emit subscriptionMade(); - } - i--; - } - } - unsubscribeItem = unsubscribeItem->parent(); - } - unsubscribeFromTopic(unsubscribeItem->text(0)); - - manageCommonLevelSubscriptions(); - } - } - else - QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); -} - -void ImportFileWidget::mqttMessageReceived(const QByteArray &message , const QMqttTopicName &topic) { - if(!m_addedTopics.contains(topic.name())) { - m_addedTopics.push_back(topic.name()); - QStringList name; - QChar sep = '/'; - QString rootName; - if(topic.name().contains(sep)) { - QStringList list = topic.name().split(sep, QString::SkipEmptyParts); - - rootName = list.at(0); - name.append(list.at(0)); - QTreeWidgetItem* currentItem; - int topItemIdx = -1; - for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { - if(ui.twTopics->topLevelItem(i)->text(0) == list.at(0)) { - topItemIdx = i; - break; - } - } - if( topItemIdx < 0) { - currentItem = new QTreeWidgetItem(name); - ui.twTopics->addTopLevelItem(currentItem); - for(int i = 1; i < list.size(); ++i) { - name.clear(); - name.append(list.at(i)); - currentItem->addChild(new QTreeWidgetItem(name)); - currentItem = currentItem->child(0); - } - } else { - currentItem = ui.twTopics->topLevelItem(topItemIdx); - int listIdx = 1; - for(; listIdx < list.size(); ++listIdx) { - QTreeWidgetItem* childItem = nullptr; - bool found = false; - for(int j = 0; j < currentItem->childCount(); ++j) { - childItem = currentItem->child(j); - if(childItem->text(0) == list.at(listIdx)) { - found = true; - currentItem = childItem; - break; - } - } - if(!found) - break; - } - - for(; listIdx < list.size(); ++listIdx) { - name.clear(); - name.append(list.at(listIdx)); - currentItem->addChild(new QTreeWidgetItem(name)); - currentItem = currentItem->child(currentItem->childCount() - 1); - } - } - } - else { - rootName = topic.name(); - name.append(topic.name()); - ui.twTopics->addTopLevelItem(new QTreeWidgetItem(name)); - } - emit newTopic(rootName); - } -} - -void ImportFileWidget::setCompleter(const QString& topic) { - if(!m_searching) { - if(!m_topicList.contains(topic)) { - m_topicList.append(topic); - m_completer = new QCompleter(m_topicList, this); - m_completer->setCompletionMode(QCompleter::PopupCompletion); - m_completer->setCaseSensitivity(Qt::CaseSensitive); - ui.leTopics->setCompleter(m_completer); - } - } -} - -void ImportFileWidget::topicTimeout() { - qDebug()<<"lejart ido"; - m_searching = false; - m_searchTimer->stop(); -} - -bool ImportFileWidget::isMqttValid(){ - bool connected = (m_client->state() == QMqttClient::ClientState::Connected); - bool subscribed = (ui.twSubscriptions->topLevelItemCount() > 0); - bool fileTypeOk = false; - if(this->currentFileType() == AbstractFileFilter::FileType::Ascii) - fileTypeOk = true; - return connected && subscribed && fileTypeOk; -} - -void ImportFileWidget::mqttSubscriptionMessageReceived(const QMqttMessage &msg) { - qDebug()<<"message received from: "< i(m_messageArrived); - while(i.hasNext()) { - i.next(); - if(i.value() == false ) { - qDebug()<<"Found false: "<setEnabled(false); + ui.chbAuthentication->setEnabled(false); + ui.chbID->setEnabled(false); + ui.chbRetain->setEnabled(false); + QMessageBox::information(this, "Connection successful", "Connection established"); - if (check == true) { - m_mqttReadyForPreview = true; - refreshPreview(); + //subscribing to every topic (# wildcard) in order to later list every available topic + QMqttTopicFilter globalFilter{"#"}; + m_mainSubscription = m_client->subscribe(globalFilter, 1); + if(!m_mainSubscription) + QMessageBox::information(this, "Couldn't subscribe", "Something went wrong"); } } +/*! + *\brief called when the client disconnects from the broker succesfully + * removes every information about the former connection + */ void ImportFileWidget::onMqttDisconnect() { ui.gbManageSubscriptions->setEnabled(false); ui.bConnect->setText("Connect"); ui.leHost->setEnabled(true); ui.leHost->clear(); ui.lePort->setEnabled(true); ui.lePort->clear(); ui.lePassword->setEnabled(true); ui.lePassword->clear(); ui.leUsername->setEnabled(true); ui.leUsername->clear(); ui.leID->setEnabled(true); ui.leID->clear(); ui.twSubscriptions->clear(); ui.twTopics->clear(); ui.chbRetain->setEnabled(true); ui.cbSourceType->setEnabled(true); ui.chbAuthentication->setEnabled(true); ui.chbID->setEnabled(true); m_mqttReadyForPreview = false; m_mqttSubscriptions.clear(); m_completer = new QCompleter; m_topicList.clear(); m_searching = false; m_searchTimer->stop(); m_messageArrived.clear(); m_lastMessage.clear(); } -void ImportFileWidget::useWillMessage(int state) { - if(state == Qt::Checked) { - ui.chbWillRetain->show(); - ui.cbWillQoS->show(); - ui.cbWillMessageType->show(); - ui.cbWillTopic->show(); - ui.cbWillUpdate->show(); - ui.lWillMessageType->show(); - ui.lWillQos->show(); - ui.lWillTopic->show(); - ui.lWillUpdate->show(); - - if (ui.cbWillMessageType->currentIndex() == static_cast(MQTTClient::WillMessageType::OwnMessage) ) { - ui.leWillOwnMessage->show(); - ui.lWillOwnMessage->show(); - } - else if(ui.cbWillMessageType->currentIndex() == static_cast(MQTTClient::WillMessageType::Statistics) ){ - qDebug()<<"will use checked show statistics"; - ui.lWillStatistics->show(); - ui.lwWillStatistics->show(); - } - +/*! + *\brief called when the subscribe button is pressed + * subscribes to the topic represented by the current item of twTopics + */ +void ImportFileWidget::mqttSubscribe() { + QString name; + QTreeWidgetItem *item = ui.twTopics->currentItem(); + if(item != nullptr) { + QTreeWidgetItem *tempItem = item; - if(ui.cbWillUpdate->currentIndex() == 0) { - ui.leWillUpdateInterval->show(); - ui.lWillUpdateInterval->show(); + //produce the topic name that the current item represents + name.prepend(item->text(0)); + if(item->childCount() != 0) + name.append("/#"); + while(tempItem->parent() != nullptr) { + tempItem = tempItem->parent(); + name.prepend(tempItem->text(0) + "/"); } - } - else if (state == Qt::Unchecked) { - qDebug()<<"will use unchecked"; - ui.chbWillRetain->hide(); - ui.cbWillQoS->hide(); - ui.cbWillMessageType->hide(); - ui.cbWillTopic->hide(); - ui.cbWillUpdate->hide(); - ui.leWillOwnMessage->hide(); - ui.leWillUpdateInterval->hide(); - ui.lWillMessageType->hide(); - ui.lWillOwnMessage->hide(); - ui.lWillQos->hide(); - ui.lWillTopic->hide(); - ui.lWillUpdate->hide(); - ui.lWillUpdateInterval->hide(); - ui.lWillStatistics->hide(); - ui.lwWillStatistics->hide(); - } -} + QList topLevelList = ui.twSubscriptions->findItems(name, Qt::MatchExactly); -void ImportFileWidget::willMessageTypeChanged(int type) { - if(static_cast (type) == MQTTClient::WillMessageType::OwnMessage) { - ui.leWillOwnMessage->show(); - ui.lWillOwnMessage->show(); - ui.lWillStatistics->hide(); - ui.lwWillStatistics->hide(); - } - else if(static_cast (type) == MQTTClient::WillMessageType::LastMessage) { - ui.leWillOwnMessage->hide(); - ui.lWillOwnMessage->hide(); - ui.lWillStatistics->hide(); - ui.lwWillStatistics->hide(); - } - else if(static_cast (type) == MQTTClient::WillMessageType::Statistics) { - qDebug()<<"will message type changed show statistics"; - ui.lWillStatistics->show(); - ui.lwWillStatistics->show(); - ui.leWillOwnMessage->hide(); - ui.lWillOwnMessage->hide(); - } -} + //check if the subscription already exists + if(topLevelList.isEmpty() || topLevelList.first()->parent() != nullptr) { + qDebug() << name; + bool foundSuperior = false; -void ImportFileWidget::updateWillTopics() { - ui.cbWillTopic->clear(); - for(int i = 0; i < m_subscribedTopicNames.size(); ++i) { - ui.cbWillTopic->addItem(m_subscribedTopicNames[i]); - } -} + for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + qDebug()<topLevelItemCount(); -void ImportFileWidget::willUpdateChanged(int updateType) { - if(static_cast(updateType) == MQTTClient::WillUpdateType::TimePeriod) { - ui.leWillUpdateInterval->show(); - ui.lWillUpdateInterval->show(); - } - else if (static_cast(updateType) == MQTTClient::WillUpdateType::OnClick) { - ui.leWillUpdateInterval->hide(); - ui.lWillUpdateInterval->hide(); - } -} -#endif + //if the new subscirptions contains an already existing one, we remove the inferior one + if(checkTopicContains(name, ui.twSubscriptions->topLevelItem(i)->text(0)) + && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { + qDebug()<<"1"<topLevelItem(i)->text(0); + unsubscribeFromTopic(ui.twSubscriptions->topLevelItem(i)->text(0)); + i--; + continue; + } + qDebug()<<"checked inferior"; -void ImportFileWidget::hideMQTT() { - ui.leID->hide(); - ui.lMqttID->hide(); - ui.lePassword->hide(); - ui.lPassword->hide(); - ui.leUsername->hide(); - ui.lUsername->hide(); - ui.cbQos->hide(); - ui.lQos->hide(); - ui.twTopics->hide(); - //ui.lTopicTree->hide(); - ui.lTopicSearch->hide(); - ui.leTopics->hide(); - ui.twSubscriptions->hide(); - //ui.lSubscriptions->hide(); - ui.chbAuthentication->hide(); - ui.chbID->hide(); - ui.bSubscribe->hide(); - ui.bUnsubscribe->hide(); - ui.bConnect->hide(); + //if there is a subscription containing the new one we set foundSuperior true + if(checkTopicContains(ui.twSubscriptions->topLevelItem(i)->text(0), name) + && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { + foundSuperior = true; + qDebug()<<"2"<topLevelItem(i)->text(0); + break; + } + qDebug()<<"checked superior"; + } - ui.gbMqttWill->hide(); - ui.chbWill->hide(); - ui.chbWillRetain->hide(); - ui.cbWillQoS->hide(); - ui.cbWillMessageType->hide(); - ui.cbWillTopic->hide(); - ui.cbWillUpdate->hide(); - ui.leWillOwnMessage->hide(); - ui.leWillUpdateInterval->setValidator(new QIntValidator(2, 1000000) ); - ui.leWillUpdateInterval->hide(); - ui.lWillMessageType->hide(); - ui.lWillOwnMessage->hide(); - ui.lWillQos->hide(); - ui.lWillTopic->hide(); - ui.lWillUpdate->hide(); - ui.lWillUpdateInterval->hide(); - ui.lwWillStatistics->hide(); - ui.lWillStatistics->hide(); -} + //if there wasn't a superior subscription we can subscribe to the new topic + if(!foundSuperior) { + qDebug()<<"Adding new topic"; + QStringList toplevelName; + toplevelName.push_back(name); + QTreeWidgetItem* newTopLevelItem = new QTreeWidgetItem(toplevelName); + ui.twSubscriptions->addTopLevelItem(newTopLevelItem); -#ifdef HAVE_MQTT -void ImportFileWidget::mqttErrorChanged(QMqttClient::ClientError clientError) { - switch (clientError) { - case QMqttClient::BadUsernameOrPassword: - QMessageBox::warning(this, "Couldn't connect", "Bad username or password"); - break; - case QMqttClient::IdRejected: - QMessageBox::warning(this, "Couldn't connect", "The client ID wasn't accepted"); - break; - case QMqttClient::ServerUnavailable: - QMessageBox::warning(this, "Server unavailable", "The network connection has been established, but the service is unavailable on the broker side."); - break; - case QMqttClient::NotAuthorized: - QMessageBox::warning(this, "Couldn't connect", "The client is not authorized to connect."); - break; - case QMqttClient::UnknownError: - QMessageBox::warning(this, "Unknown MQTT error", "An unknown error occurred."); - break; - default: - break; - } -} + QMqttTopicFilter filter {name}; + QMqttSubscription *temp_subscription = m_client->subscribe(filter, static_cast (ui.cbQos->currentText().toUInt()) ); -bool ImportFileWidget::checkTopicContains(const QString& superior, const QString& inferior) { - if (superior == inferior) - return true; - else { - if(superior.contains("/")) { - QStringList superiorList = superior.split('/', QString::SkipEmptyParts); - QStringList inferiorList = inferior.split('/', QString::SkipEmptyParts); + if(temp_subscription) { + m_mqttSubscriptions.push_back(temp_subscription); + connect(temp_subscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); + emit subscriptionsChanged(); + } - if(superiorList.size() > inferiorList.size()) - return false; + if(name.endsWith("#")) { + //adding every topic that the subscription contains + addSubscriptionChildren(item, newTopLevelItem); - bool ok = true; - for(int i = 0; i < superiorList.size(); ++i) { - if(superiorList.at(i) != inferiorList.at(i)) { - if((superiorList.at(i) != "+") && - !(superiorList.at(i) == "#" && i == superiorList.size() - 1)) { - qDebug() < children; + for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { + if(ui.twSubscriptions->topLevelItem(i)->text(0).startsWith(root) + && name != ui.twSubscriptions->topLevelItem(i)->text(0)) { + children.clear(); + //get the "leaf" children of the inspected subscription + findSubscriptionLeafChildren(children, ui.twSubscriptions->topLevelItem(i)); + for(int j = 0; j < children.size(); ++j) { + if(checkTopicContains(name, children[j]->text(0))) { + //if the new subscription contains a topic, we unsubscribe from it + ui.twSubscriptions->setCurrentItem(children[j]); + mqttUnsubscribe(); + } + } + } } } + + manageCommonLevelSubscriptions(); + } else { + QMessageBox::warning(this, "Warning", "You already subscribed to a topic containing this one"); } - return ok; } - - return false; + else + QMessageBox::warning(this, "Warning", "You already subscribed to this topic"); } + else + QMessageBox::warning(this, "Warning", "You didn't select any item from the Tree Widget"); } -QString ImportFileWidget::checkCommonLevel(const QString& first, const QString& second) { - qDebug()<currentItem(); - if(!firstList.isEmpty()) { - if(firstList.size() == secondtList.size() && (first != second)) { - int matchIndex = -1; - for(int i = 0; i < firstList.size(); ++i) { - if(firstList.at(i) != secondtList.at(i)) { - matchIndex = i; - break; - } - } - bool differ = false; - if(matchIndex > 0) { - for(int j = matchIndex +1; j < firstList.size(); ++j) { - if(firstList.at(j) != secondtList.at(j)) { - differ = true; - break; - } - } - } - else - differ = true; + if(unsubscribeItem != nullptr) { + //if it is a top level item, meaning a topic that we really subscribed to(not one that belongs to a subscription) + //we can simply unsubscribe from it + if(unsubscribeItem->parent() == nullptr) + unsubscribeFromTopic(unsubscribeItem->text(0)); + else{ + //otherwise we remove the selected item, but subscribe to every other topic, that was contained by + //the selected item's parent subscription(top level item of twSubscripitons) + while(unsubscribeItem->parent() != nullptr) { + for(int i = 0; i < unsubscribeItem->parent()->childCount(); ++i) { + if(unsubscribeItem->text(0) != unsubscribeItem->parent()->child(i)->text(0)) { + QMqttTopicFilter filter {unsubscribeItem->parent()->child(i)->text(0)}; + QMqttSubscription *temp_subscription = m_client->subscribe(filter, static_cast (ui.cbQos->currentText().toUInt()) ); - if(!differ) - { - for(int i = 0; i < firstList.size(); ++i) { - if(i != matchIndex) - commonTopic.append(firstList.at(i)); - else - commonTopic.append("+"); + ui.twSubscriptions->addTopLevelItem(unsubscribeItem->parent()->takeChild(i)); - if(i != firstList.size() - 1) - commonTopic.append("/"); + if(temp_subscription) { + m_mqttSubscriptions.push_back(temp_subscription); + connect(temp_subscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); + emit subscriptionsChanged(); + } + i--; + } } + unsubscribeItem = unsubscribeItem->parent(); } + unsubscribeFromTopic(unsubscribeItem->text(0)); + + //check if any common topics were subscribed, if possible merge them + manageCommonLevelSubscriptions(); } } - qDebug() << "Common topic: "<topLevelItemCount(); ++i) { + if(ui.twTopics->topLevelItem(i)->text(0) == list.at(0)) { + topItemIdx = i; break; } } - bool differ = false; - if(matchIndex > 0) { - for(int j = matchIndex +1; j < firstList.size(); ++j) { - if(firstList.at(j) != secondtList.at(j)) { - differ = true; + //if not we simply add every level of the topic to the tree + if( topItemIdx < 0) { + currentItem = new QTreeWidgetItem(name); + ui.twTopics->addTopLevelItem(currentItem); + for(int i = 1; i < list.size(); ++i) { + name.clear(); + name.append(list.at(i)); + currentItem->addChild(new QTreeWidgetItem(name)); + currentItem = currentItem->child(0); + } + } + //otherwise we search for the first level that isn't part of the tree, + //then add every level of the topic to the tree from that certain level + else { + currentItem = ui.twTopics->topLevelItem(topItemIdx); + int listIdx = 1; + for(; listIdx < list.size(); ++listIdx) { + QTreeWidgetItem* childItem = nullptr; + bool found = false; + for(int j = 0; j < currentItem->childCount(); ++j) { + childItem = currentItem->child(j); + if(childItem->text(0) == list.at(listIdx)) { + found = true; + currentItem = childItem; + break; + } + } + if(!found) { + //this is the level that isn't present in the tree break; } } - } - else - differ = true; - - if(!differ) - { - for(int i = 0; i < firstList.size(); ++i) { - if(i != matchIndex) - commonTopic.append(firstList.at(i)); - else - commonTopic.append("+"); - if(i != firstList.size() - 1) - commonTopic.append("/"); + //add every level to the tree starting with the first level that isn't part of the tree + for(; listIdx < list.size(); ++listIdx) { + name.clear(); + name.append(list.at(listIdx)); + currentItem->addChild(new QTreeWidgetItem(name)); + currentItem = currentItem->child(currentItem->childCount() - 1); } } } + else { + rootName = topic.name(); + name.append(topic.name()); + ui.twTopics->addTopLevelItem(new QTreeWidgetItem(name)); + } + //signals that a newTopic was added, in order to fill the completer of leTopics + emit newTopic(rootName); } - - qDebug() << "Common topic: "<start(); - - qDebug()<topLevelItemCount(); ++i) - if(ui.twTopics->topLevelItem(i)->text(0) == rootName) { - topItemIdx = i; - break; +/*! + *\brief called when a new topic is added to the tree(twTopics) + * appends the topic's root to the topicList if it isn't in the list already + * then sets the completer for leTopics + */ +void ImportFileWidget::setCompleter(const QString& topic) { + if(!m_searching) { + if(!m_topicList.contains(topic)) { + m_topicList.append(topic); + m_completer = new QCompleter(m_topicList, this); + m_completer->setCompletionMode(QCompleter::PopupCompletion); + m_completer->setCaseSensitivity(Qt::CaseSensitive); + ui.leTopics->setCompleter(m_completer); } - - if(topItemIdx >= 0) { - qDebug() << "Scroll"; - ui.twTopics->scrollToItem(ui.twTopics->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); } } -void ImportFileWidget::unsubscribeFromTopic(const QString& topicName) { - if(!topicName.isEmpty()) { - QMqttTopicFilter filter{topicName}; - m_client->unsubscribe(filter); +/*! + *\brief called when too much time passed since trying to connect to the broker + */ +void ImportFileWidget::topicTimeout() { + qDebug()<<"lejart ido"; + m_searching = false; + m_searchTimer->stop(); +} - qDebug()<<"unsubscribe occured"; +/*! + *\brief called when the client receives a message from a subscribed topic (that isn't the "#" wildcard) + */ +void ImportFileWidget::mqttSubscriptionMessageReceived(const QMqttMessage &msg) { + qDebug()<<"message received from: "<topic().filter() == topicName) { - qDebug()<<"1 subscription found at "< i(m_messageArrived); - while(i.hasNext()) { - i.next(); - if(checkTopicContains(topicName, i.key().name())) { - m_messageArrived.remove(i.key()); - qDebug()<<"2 subscription found at "< j(m_lastMessage); - while(j.hasNext()) { - j.next(); - if(checkTopicContains(topicName, j.key().name())) { - m_lastMessage.remove(j.key()); - qDebug()<<"3 subscription found at "< i(m_messageArrived); + while(i.hasNext()) { + i.next(); + if(i.value() == false ) { + qDebug()<<"Found false: "<topLevelItemCount(); row++) { - if(ui.twSubscriptions->topLevelItem(row)->text(0) == topicName) { - qDebug()<<"4 subscription found at "<topLevelItem(row)->text(0) <<"and removed"; - ui.twSubscriptions->topLevelItem(row)->takeChildren(); - ui.twSubscriptions->takeTopLevelItem(row); - } - } + //if there is a message from every subscribed topic, we refresh the preview + if (check == true) { + m_mqttReadyForPreview = true; + refreshPreview(); + } +} - for(int i = 0; i < m_subscribedTopicNames.size(); ++i) { - if(checkTopicContains(topicName, m_subscribedTopicNames[i])) { - m_subscribedTopicNames.remove(i); - i--; - } +/*! + *\brief called when use will message checkbox's state is changed, + * if state is checked it shows the options regarding the will message + * + * \param state the state of the checbox + */ +void ImportFileWidget::useWillMessage(int state) { + if(state == Qt::Checked) { + ui.chbWillRetain->show(); + ui.cbWillQoS->show(); + ui.cbWillMessageType->show(); + ui.cbWillTopic->show(); + ui.cbWillUpdate->show(); + ui.lWillMessageType->show(); + ui.lWillQos->show(); + ui.lWillTopic->show(); + ui.lWillUpdate->show(); + + if (ui.cbWillMessageType->currentIndex() == static_cast(MQTTClient::WillMessageType::OwnMessage) ) { + ui.leWillOwnMessage->show(); + ui.lWillOwnMessage->show(); + } else if(ui.cbWillMessageType->currentIndex() == static_cast(MQTTClient::WillMessageType::Statistics) ) { + ui.lWillStatistics->show(); + ui.lwWillStatistics->show(); } - for(int item = 0; item < ui.cbWillTopic->count(); ++item) { - if(checkTopicContains(topicName, ui.cbWillTopic->itemText(item))) { - ui.cbWillTopic->removeItem(item); - item--; - } + if(ui.cbWillUpdate->currentIndex() == 0) { + ui.leWillUpdateInterval->show(); + ui.lWillUpdateInterval->show(); } + } else if (state == Qt::Unchecked) { + qDebug()<<"will use unchecked"; + ui.chbWillRetain->hide(); + ui.cbWillQoS->hide(); + ui.cbWillMessageType->hide(); + ui.cbWillTopic->hide(); + ui.cbWillUpdate->hide(); + ui.leWillOwnMessage->hide(); + ui.leWillUpdateInterval->hide(); + ui.lWillMessageType->hide(); + ui.lWillOwnMessage->hide(); + ui.lWillQos->hide(); + ui.lWillTopic->hide(); + ui.lWillUpdate->hide(); + ui.lWillUpdateInterval->hide(); + ui.lWillStatistics->hide(); + ui.lwWillStatistics->hide(); + } +} + + +/*! + *\brief called when the selected will message type is changed, + * shows the options for the selected message type, hides the irrelevant ones + * + * \param type the selected will message type + */ +void ImportFileWidget::willMessageTypeChanged(int type) { + if(static_cast (type) == MQTTClient::WillMessageType::OwnMessage) { + ui.leWillOwnMessage->show(); + ui.lWillOwnMessage->show(); + ui.lWillStatistics->hide(); + ui.lwWillStatistics->hide(); + } else if(static_cast (type) == MQTTClient::WillMessageType::LastMessage) { + ui.leWillOwnMessage->hide(); + ui.lWillOwnMessage->hide(); + ui.lWillStatistics->hide(); + ui.lwWillStatistics->hide(); + } else if(static_cast (type) == MQTTClient::WillMessageType::Statistics) { + qDebug()<<"will message type changed show statistics"; + ui.lWillStatistics->show(); + ui.lwWillStatistics->show(); + ui.leWillOwnMessage->hide(); + ui.lWillOwnMessage->hide(); + } +} - emit subscriptionMade(); - refreshPreview(); +/*! + *\brief called when newTopicForWill signal is emitted, + * updates the topics that can be selected as the will message's topic + */ +void ImportFileWidget::updateWillTopics() { + QString current = ui.cbWillTopic->currentText(); + ui.cbWillTopic->clear(); + for(int i = 0; i < m_subscribedTopicNames.size(); ++i) { + ui.cbWillTopic->addItem(m_subscribedTopicNames[i]); } + ui.cbWillTopic->setCurrentText(current); } -void ImportFileWidget::addSubscriptionChildren(QTreeWidgetItem * topic, QTreeWidgetItem * subscription) { - if(topic->childCount() > 0) { - for(int i = 0; i < topic->childCount(); ++i) { - QTreeWidgetItem* temp = topic->child(i); - QString name; - if(topic->child(i)->childCount() > 0) { - name.append(temp->text(0) + "/#"); - while(temp->parent() != nullptr) { - temp = temp->parent(); - name.prepend(temp->text(0) + "/"); - } +/*! + *\brief called when the selected update type for the will message is changed, + * shows the options for the selected update type, hides the irrelevant ones + * + * \param type the selected will update type + */ +void ImportFileWidget::willUpdateTypeChanged(int updateType) { + if(static_cast(updateType) == MQTTClient::WillUpdateType::TimePeriod) { + ui.leWillUpdateInterval->show(); + ui.lWillUpdateInterval->show(); + } + else if (static_cast(updateType) == MQTTClient::WillUpdateType::OnClick) { + ui.leWillUpdateInterval->hide(); + ui.lWillUpdateInterval->hide(); + } +} - } else { - name.append(temp->text(0)); - while(temp->parent() != nullptr) { - temp = temp->parent(); - name.prepend(temp->text(0) + "/"); - } - } - QStringList nameList; - nameList.append(name); - QTreeWidgetItem* childItem = new QTreeWidgetItem(nameList); - subscription->addChild(childItem); - addSubscriptionChildren(topic->child(i), childItem); - } +/*! + *\brief called when the clientError of the MQTT client changes + * + * \param clientError the current error of the client + */ +void ImportFileWidget::mqttErrorChanged(QMqttClient::ClientError clientError) { + switch (clientError) { + case QMqttClient::BadUsernameOrPassword: + QMessageBox::warning(this, "Couldn't connect", "Bad username or password"); + break; + case QMqttClient::IdRejected: + QMessageBox::warning(this, "Couldn't connect", "The client ID wasn't accepted"); + break; + case QMqttClient::ServerUnavailable: + QMessageBox::warning(this, "Server unavailable", "The network connection has been established, but the service is unavailable on the broker side."); + break; + case QMqttClient::NotAuthorized: + QMessageBox::warning(this, "Couldn't connect", "The client is not authorized to connect."); + break; + case QMqttClient::UnknownError: + QMessageBox::warning(this, "Unknown MQTT error", "An unknown error occurred."); + break; + default: + break; } } -void ImportFileWidget::mqttTimeout() { - m_client->disconnectFromHost(); - m_timeoutTimer->stop(); - QMessageBox::warning(this, "Warning", "Couldn't connect to the given broker"); +/*! + *\brief called when leTopics' text is changed + * if the rootName can be found in twTopics, then we scroll it to the top of the tree widget + * + * \param rootName the current text of leTopics + */ +void ImportFileWidget::scrollToTreeItem(const QString& rootName) { + m_searching = true; + m_searchTimer->start(); + + int topItemIdx = -1; + for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) + if(ui.twTopics->topLevelItem(i)->text(0) == rootName) { + topItemIdx = i; + break; + } + + if(topItemIdx >= 0) { + ui.twTopics->scrollToItem(ui.twTopics->topLevelItem(topItemIdx), QAbstractItemView::ScrollHint::PositionAtTop); + } } +/*! + *\brief called when any option of the client (host, port, id, etc.) is changed before connecting to the broker, + * checks if every option needed for the connection is set, if it is, then enables the connect button + */ void ImportFileWidget::checkConnectEnable() { bool authenticationUsed = ui.chbAuthentication->isChecked(); bool idUsed = ui.chbID->isChecked(); bool authenticationFilled = !ui.leUsername->text().isEmpty() && !ui.lePassword->text().isEmpty(); bool idFilled = !ui.leID->text().isEmpty(); bool authenticationOK = !authenticationUsed || (authenticationUsed && authenticationFilled); bool idOK = !idUsed || (idUsed && idFilled); bool hostOK = !ui.leHost->text().isEmpty(); bool portOK = !ui.lePort->text().isEmpty(); bool enable = authenticationOK && idOK && hostOK && portOK; ui.bConnect->setEnabled(enable); } -void ImportFileWidget::findSubscriptionLeafChildren(QVector& children, QTreeWidgetItem* root) { - if(root->childCount() == 0) { - children.push_back(root); - } else { - for(int i = 0; i < root->childCount(); ++i) { - findSubscriptionLeafChildren(children, root->child(i)); - } - } -} - -int ImportFileWidget::checkCommonChildCount(int levelIdx, int level, QStringList& commonList, QTreeWidgetItem* currentItem) { - qDebug()<<"LevelIdx: "<text(0); - if(levelIdx < level - 1) { - if(commonList[levelIdx] != "+") { - for(int j = 0; j < currentItem->childCount(); ++j) { - if(currentItem->child(j)->text(0) == commonList[levelIdx]) { - qDebug()<<"level index: "<child(j)->text(0)<<" "<child(j)); - } - } - } else { - int childCount = -1; - bool ok = true; - for(int j = 0; j < currentItem->childCount(); ++j) { - int temp = checkCommonChildCount(levelIdx + 1, level, commonList, currentItem->child(j)); - - if((j > 0) && (temp != childCount)) { - ok = false; - break; - } - childCount = temp; - } - - if(ok) - return childCount; - else - return -1; - } - } else if (levelIdx == level - 1) { - if(commonList[levelIdx] != "+") { - for(int j = 0; j < currentItem->childCount(); ++j) { - if(currentItem->child(j)->text(0) == commonList[levelIdx]) { - qDebug()<<"level index: "<child(j)->text(0)<<" "<child(j)->childCount(); - } - } - } else { - int childCount = -1; - bool ok = true; - for(int j = 0; j < currentItem->childCount(); ++j) { - if((j > 0) && (currentItem->child(j)->childCount() != childCount)) { - ok = false; - break; - } - childCount = currentItem->child(j)->childCount(); - } - - if(ok) - return childCount; - else - return -1; - } - } else if (level == 1 && levelIdx == 1) - return currentItem->childCount(); - - return -1; -} - -void ImportFileWidget::manageCommonLevelSubscriptions() { - bool foundEqual = false; - - do{ - foundEqual = false; - QMap> equalTopicsMap; - QVector equalTopics; - - qDebug()<<"Search for common topic after unsubscribe"; - for(int i = 0; i < ui.twSubscriptions->topLevelItemCount() - 1; ++i) { - for(int j = i + 1; j < ui.twSubscriptions->topLevelItemCount(); ++j) { - qDebug()<topLevelItem(i)->text(0)<<" "<topLevelItem(j)->text(0); - QString commonTopic = checkCommonLevel(ui.twSubscriptions->topLevelItem(i)->text(0), ui.twSubscriptions->topLevelItem(j)->text(0)); - if(!commonTopic.isEmpty()) { - if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(i)->text(0))) { - qDebug()<topLevelItem(i)->text(0); - equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(i)->text(0)); - } - - if(!equalTopicsMap[commonTopic].contains(ui.twSubscriptions->topLevelItem(j)->text(0))) { - qDebug()<topLevelItem(i)->text(0); - equalTopicsMap[commonTopic].push_back(ui.twSubscriptions->topLevelItem(j)->text(0)); - } - } - } - } - - if(!equalTopicsMap.isEmpty()) { - qDebug()<<"Equal topics not empty"; - - QVector commonTopics; - QMapIterator> topics(equalTopicsMap); - while(topics.hasNext()) { - topics.next(); - qDebug()<<"Checking: " << topics.key(); - - int level = commonLevelIndex(topics.value().last(), topics.value().first()); - QStringList commonList = topics.value().first().split('/', QString::SkipEmptyParts); - QTreeWidgetItem* currentItem; - for(int i = 0; i < ui.twTopics->topLevelItemCount(); ++i) { - if(ui.twTopics->topLevelItem(i)->text(0) == commonList.first()) { - currentItem = ui.twTopics->topLevelItem(i); - break; - } - } - - qDebug()<<"level "< 0) { - if(topics.value().size() == childCount) { - foundEqual = true; - commonTopics.push_back(topics.key()); - qDebug()<addTopLevelItem(newTopic); - QMqttTopicFilter filter {commonTopic}; - QMqttSubscription *temp_subscription = m_client->subscribe(filter, static_cast (ui.cbQos->currentText().toUInt()) ); - - if(temp_subscription) { - m_mqttSubscriptions.push_back(temp_subscription); - connect(temp_subscription, &QMqttSubscription::messageReceived, this, &ImportFileWidget::mqttSubscriptionMessageReceived); - emit subscriptionMade(); - } - - qDebug()<<"unsubscribe from equal topics"; - for(int i = 0; i < equalTopics.size(); ++i) { - for(int j = 0; j < ui.twSubscriptions->topLevelItemCount(); ++j){ - if(ui.twSubscriptions->topLevelItem(j)->text(0) == equalTopics[i]) { - newTopic->addChild(ui.twSubscriptions->takeTopLevelItem(j)); - unsubscribeFromTopic(equalTopics[i]); - break; - } - } - } - - for(int i = 0; i < ui.twSubscriptions->topLevelItemCount(); ++i) { - if(checkTopicContains(commonTopic, ui.twSubscriptions->topLevelItem(i)->text(0)) && - commonTopic != ui.twSubscriptions->topLevelItem(i)->text(0) ) { - unsubscribeFromTopic(ui.twSubscriptions->topLevelItem(i)->text(0)); - i--; - } - } - } - } - } while(foundEqual); +/*! + *\brief called when m_connectTimeoutTimer ticks, + * meaning that the client couldn't connect to the broker in 5 seconds + * disconnects the client, stops the timer, and warns the user + */ +void ImportFileWidget::mqttConnectTimeout() { + m_client->disconnectFromHost(); + m_connectTimeoutTimer->stop(); + QMessageBox::warning(this, "Warning", "Couldn't connect to the given broker"); } #endif diff --git a/src/kdefrontend/datasources/ImportFileWidget.h b/src/kdefrontend/datasources/ImportFileWidget.h index c700cc56c..3414a307a 100644 --- a/src/kdefrontend/datasources/ImportFileWidget.h +++ b/src/kdefrontend/datasources/ImportFileWidget.h @@ -1,202 +1,202 @@ /*************************************************************************** File : ImportFileWidget.h Project : LabPlot Description : import file data widget -------------------------------------------------------------------- Copyright : (C) 2009-2017 by Stefan Gerlach (stefan.gerlach@uni-konstanz.de) Copyright : (C) 2009-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 Fabian Kristof (fkristofszabolcs@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef IMPORTFILEWIDGET_H #define IMPORTFILEWIDGET_H #include "ui_importfilewidget.h" #include "backend/datasources/LiveDataSource.h" #include #ifdef HAVE_MQTT #include "backend/datasources/MQTTClient.h" #include #include #include #include #endif #include #include class AbstractFileFilter; class AsciiOptionsWidget; class BinaryOptionsWidget; class HDF5OptionsWidget; class ImageOptionsWidget; class NetCDFOptionsWidget; class FITSOptionsWidget; class JsonOptionsWidget; class ROOTOptionsWidget; class QTableWidget; class QCompleter; class QTimer; class QTreeWidgetItem; class ImportFileWidget : public QWidget { Q_OBJECT public: explicit ImportFileWidget(QWidget*, const QString& fileName = QString()); ~ImportFileWidget(); void showOptions(bool); void saveSettings(LiveDataSource*) const; AbstractFileFilter::FileType currentFileType() const; LiveDataSource::SourceType currentSourceType() const; AbstractFileFilter* currentFileFilter() const; QString fileName() const; QString selectedObject() const; bool isFileEmpty() const; const QStringList selectedHDF5Names() const; const QStringList selectedNetCDFNames() const; const QStringList selectedFITSExtensions() const; const QStringList selectedROOTNames() const; void hideDataSource(); void showAsciiHeaderOptions(bool); void showJsonModel(bool); QString host() const; QString port() const; QString serialPort() const; int baudRate() const; void initializeAndFillPortsAndBaudRates(); private: Ui::ImportFileWidget ui; void hideMQTT(); std::unique_ptr m_asciiOptionsWidget; std::unique_ptr m_binaryOptionsWidget; std::unique_ptr m_hdf5OptionsWidget; std::unique_ptr m_imageOptionsWidget; std::unique_ptr m_netcdfOptionsWidget; std::unique_ptr m_fitsOptionsWidget; std::unique_ptr m_jsonOptionsWidget; std::unique_ptr m_rootOptionsWidget; QTableWidget* m_twPreview; const QString& m_fileName; bool m_fileEmpty; bool m_liveDataSource; bool m_suppressRefresh; public slots: void loadSettings(); private slots: void fileNameChanged(const QString&); void fileTypeChanged(int); void updateTypeChanged(int); void sourceTypeChanged(int); void readingTypeChanged(int); void saveFilter(); void manageFilters(); void filterChanged(int); void selectFile(); void fileInfoDialog(); void refreshPreview(); signals: void fileNameChanged(); void sourceTypeChanged(); void hostChanged(); void portChanged(); void previewRefreshed(); void checkedFitsTableToMatrix(const bool enable); friend class HDF5OptionsWidget; // to access refreshPreview() friend class NetCDFOptionsWidget; // to access refreshPreview() and others friend class FITSOptionsWidget; friend class JsonOptionsWidget; friend class ROOTOptionsWidget; // to access refreshPreview() and others #ifdef HAVE_MQTT private: bool checkTopicContains(const QString&, const QString&) ; QString checkCommonLevel(const QString&, const QString&); int commonLevelIndex(const QString& first, const QString& second); void unsubscribeFromTopic(const QString&); void addSubscriptionChildren(QTreeWidgetItem*, QTreeWidgetItem*); void findSubscriptionLeafChildren(QVector&, QTreeWidgetItem*); int checkCommonChildCount(int levelIdx, int level, QStringList& namelist, QTreeWidgetItem* currentItem); void manageCommonLevelSubscriptions(); QMqttClient *m_client; QMqttSubscription *m_mainSubscription; QMqttTopicFilter *m_filter; QVector m_mqttSubscriptions; QCompleter* m_completer; QStringList m_topicList; bool m_searching; QTimer *m_searchTimer; - QTimer *m_timeoutTimer; + QTimer *m_connectTimeoutTimer; QMap m_messageArrived; QMap m_lastMessage; bool m_mqttReadyForPreview; QVector m_subscribedTopicNames; QVector m_addedTopics; public: void saveMQTTSettings(MQTTClient*) const; bool isMqttValid(); signals: void newTopic(QString); - void subscriptionMade(); + void subscriptionsChanged(); void checkFileType(); void newTopicForWill(); private slots: void idChecked(int); void authenticationChecked(int); void mqttConnection(); void onMqttConnect(); void mqttSubscribe(); void mqttUnsubscribe(); void mqttMessageReceived(const QByteArray&, const QMqttTopicName&); void setCompleter(const QString&); void topicTimeout(); void mqttSubscriptionMessageReceived(const QMqttMessage& ); void onMqttDisconnect(); void useWillMessage(int); void willMessageTypeChanged(int); void updateWillTopics(); - void willUpdateChanged(int); + void willUpdateTypeChanged(int); void mqttErrorChanged(QMqttClient::ClientError); - void searchTreeItem(const QString&); - void mqttTimeout(); + void scrollToTreeItem(const QString&); + void mqttConnectTimeout(); void checkConnectEnable(); #endif }; #endif