"Fossies" - the Fresh Open Source Software Archive

Member "labplot-2.8.2/src/kdefrontend/datasources/ImportProjectDialog.cpp" (24 Feb 2021, 17522 Bytes) of package /linux/privat/labplot-2.8.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "ImportProjectDialog.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.8.1_vs_2.8.2.

    1 /***************************************************************************
    2     File                 : ImportProjectDialog.cpp
    3     Project              : LabPlot
    4     Description          : import project dialog
    5     --------------------------------------------------------------------
    6     Copyright            : (C) 2017-2021 Alexander Semke (alexander.semke@web.de)
    7 
    8  ***************************************************************************/
    9 
   10 /***************************************************************************
   11  *                                                                         *
   12  *  This program is free software; you can redistribute it and/or modify   *
   13  *  it under the terms of the GNU General Public License as published by   *
   14  *  the Free Software Foundation; either version 2 of the License, or      *
   15  *  (at your option) any later version.                                    *
   16  *                                                                         *
   17  *  This program is distributed in the hope that it will be useful,        *
   18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of         *
   19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the          *
   20  *  GNU General Public License for more details.                           *
   21  *                                                                         *
   22  *   You should have received a copy of the GNU General Public License     *
   23  *   along with this program; if not, write to the Free Software           *
   24  *   Foundation, Inc., 51 Franklin Street, Fifth Floor,                    *
   25  *   Boston, MA  02110-1301  USA                                           *
   26  *                                                                         *
   27  ***************************************************************************/
   28 
   29 #include "ImportProjectDialog.h"
   30 #include "backend/core/AspectTreeModel.h"
   31 #include "backend/core/Project.h"
   32 #include "backend/datasources/projects/LabPlotProjectParser.h"
   33 #ifdef HAVE_LIBORIGIN
   34 #include "backend/datasources/projects/OriginProjectParser.h"
   35 #endif
   36 #include "kdefrontend/GuiTools.h"
   37 #include "kdefrontend/MainWin.h"
   38 #include "commonfrontend/widgets/TreeViewComboBox.h"
   39 
   40 #include <QDialogButtonBox>
   41 #include <QDir>
   42 #include <QElapsedTimer>
   43 #include <QFileDialog>
   44 #include <QInputDialog>
   45 #include <QProgressBar>
   46 #include <QStatusBar>
   47 #include <QWindow>
   48 
   49 #include <KLocalizedString>
   50 #include <KMessageBox>
   51 #include <KSharedConfig>
   52 #include <KUrlComboBox>
   53 #include <KWindowConfig>
   54 
   55 /*!
   56     \class ImportProjectDialog
   57     \brief Dialog for importing project files.
   58 
   59     \ingroup kdefrontend
   60  */
   61 ImportProjectDialog::ImportProjectDialog(MainWin* parent, ProjectType type) : QDialog(parent),
   62     m_mainWin(parent),
   63     m_projectType(type),
   64     m_aspectTreeModel(new AspectTreeModel(parent->project())) {
   65 
   66     auto* vLayout = new QVBoxLayout(this);
   67 
   68     //main widget
   69     QWidget* mainWidget = new QWidget(this);
   70     ui.setupUi(mainWidget);
   71     ui.chbUnusedObjects->hide();
   72 
   73     m_cbFileName = new KUrlComboBox(KUrlComboBox::Mode::Files, this);
   74     m_cbFileName->setMaxItems(7);
   75     auto* l = dynamic_cast<QHBoxLayout*>(ui.gbProject->layout());
   76     if (l)
   77         l->insertWidget(1, m_cbFileName);
   78 
   79     vLayout->addWidget(mainWidget);
   80 
   81     ui.tvPreview->setAnimated(true);
   82     ui.tvPreview->setAlternatingRowColors(true);
   83     ui.tvPreview->setSelectionBehavior(QAbstractItemView::SelectRows);
   84     ui.tvPreview->setSelectionMode(QAbstractItemView::ExtendedSelection);
   85     ui.tvPreview->setUniformRowHeights(true);
   86 
   87     ui.bOpen->setIcon( QIcon::fromTheme("document-open") );
   88 
   89     m_cbAddTo = new TreeViewComboBox(ui.gbImportTo);
   90     m_cbAddTo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
   91     ui.gbImportTo->layout()->addWidget(m_cbAddTo);
   92 
   93     QList<AspectType> list{AspectType::Folder};
   94     m_cbAddTo->setTopLevelClasses(list);
   95     m_aspectTreeModel->setSelectableAspects(list);
   96     m_cbAddTo->setModel(m_aspectTreeModel);
   97 
   98     m_bNewFolder = new QPushButton(ui.gbImportTo);
   99     m_bNewFolder->setIcon(QIcon::fromTheme("list-add"));
  100     m_bNewFolder->setToolTip(i18n("Add new folder"));
  101     ui.gbImportTo->layout()->addWidget(m_bNewFolder);
  102 
  103     //dialog buttons
  104     m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
  105     vLayout->addWidget(m_buttonBox);
  106 
  107     //ok-button is only enabled if some project objects were selected (s.a. ImportProjectDialog::selectionChanged())
  108     m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
  109 
  110     //Signals/Slots
  111     connect(m_cbFileName, &KUrlComboBox::urlActivated,
  112             this, [=](const QUrl &url){fileNameChanged(url.path());});
  113     connect(ui.bOpen, &QPushButton::clicked, this, &ImportProjectDialog::selectFile);
  114     connect(m_bNewFolder, &QPushButton::clicked, this, &ImportProjectDialog::newFolder);
  115     connect(ui.chbUnusedObjects, &QCheckBox::stateChanged, this, &ImportProjectDialog::refreshPreview);
  116     connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
  117     connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
  118 
  119     QString title;
  120     switch (m_projectType) {
  121     case ProjectType::LabPlot:
  122         m_projectParser = new LabPlotProjectParser();
  123         title = i18nc("@title:window", "Import LabPlot Project");
  124         break;
  125     case ProjectType::Origin:
  126 #ifdef HAVE_LIBORIGIN
  127         m_projectParser = new OriginProjectParser();
  128         title = i18nc("@title:window", "Import Origin Project");
  129 #endif
  130         break;
  131     }
  132 
  133     //dialog title and icon
  134     setWindowTitle(title);
  135     setWindowIcon(QIcon::fromTheme("document-import"));
  136 
  137     //"What's this?" texts
  138     QString info = i18n("Specify the file where the project content has to be imported from.");
  139     m_cbFileName->setWhatsThis(info);
  140 
  141     info = i18n("Select one or several objects to be imported into the current project.\n"
  142                 "Note, all children of the selected objects as well as all the dependent objects will be automatically selected.\n"
  143                 "To import the whole project, select the top-level project node."
  144                );
  145     ui.tvPreview->setWhatsThis(info);
  146 
  147     info = i18n("Specify the target folder in the current project where the selected objects have to be imported into.");
  148     m_cbAddTo->setWhatsThis(info);
  149 
  150     //restore saved settings if available
  151     create(); // ensure there's a window created
  152     KConfigGroup conf(KSharedConfig::openConfig(), "ImportProjectDialog");
  153     if (conf.exists()) {
  154         KWindowConfig::restoreWindowSize(windowHandle(), conf);
  155         resize(windowHandle()->size()); // workaround for QTBUG-40584
  156     } else
  157         resize(QSize(300, 0).expandedTo(minimumSize()));
  158 
  159     QString file;
  160     QString files;
  161     switch (m_projectType) {
  162     case ProjectType::LabPlot:
  163         file = QLatin1String("LastImportedLabPlotProject");
  164         files = QLatin1String("LastImportedLabPlotProjects");
  165         break;
  166     case ProjectType::Origin:
  167         file = QLatin1String("LastImportedOriginProject");
  168         files = QLatin1String("LastImportedOriginProjects");
  169         break;
  170     }
  171 
  172     QApplication::processEvents(QEventLoop::AllEvents, 100);
  173     m_cbFileName->setUrl(conf.readEntry(file, ""));
  174     QStringList urls = m_cbFileName->urls();
  175     urls.append(conf.readXdgListEntry(files));
  176     m_cbFileName->setUrls(urls);
  177     fileNameChanged(m_cbFileName->currentText());
  178 }
  179 
  180 ImportProjectDialog::~ImportProjectDialog() {
  181     //save current settings
  182     KConfigGroup conf(KSharedConfig::openConfig(), "ImportProjectDialog");
  183     KWindowConfig::saveWindowSize(windowHandle(), conf);
  184 
  185     QString file;
  186     QString files;
  187     switch (m_projectType) {
  188     case ProjectType::LabPlot:
  189         file = QLatin1String("LastImportedLabPlotProject");
  190         files = QLatin1String("LastImportedLabPlotProjects");
  191         break;
  192     case ProjectType::Origin:
  193         file = QLatin1String("LastImportedOriginProject");
  194         files = QLatin1String("LastImportedOriginProjects");
  195         break;
  196     }
  197 
  198     conf.writeEntry(file, m_cbFileName->currentText());
  199     conf.writeXdgListEntry(files, m_cbFileName->urls());
  200 }
  201 
  202 void ImportProjectDialog::setCurrentFolder(const Folder* folder) {
  203     m_cbAddTo->setCurrentModelIndex(m_aspectTreeModel->modelIndexOfAspect(folder));
  204 }
  205 
  206 void ImportProjectDialog::importTo(QStatusBar* statusBar) const {
  207     DEBUG("ImportProjectDialog::importTo()");
  208 
  209     //determine the selected objects, convert the model indexes to string pathes
  210     const QModelIndexList& indexes = ui.tvPreview->selectionModel()->selectedIndexes();
  211     QStringList selectedPathes;
  212     for (int i = 0; i < indexes.size()/4; ++i) {
  213         QModelIndex index = indexes.at(i*4);
  214         const auto* aspect = static_cast<const AbstractAspect*>(index.internalPointer());
  215 
  216         //path of the current aspect and the pathes of all aspects it depends on
  217         selectedPathes << aspect->path();
  218         QDEBUG(" aspect path: " << aspect->path());
  219         for (const auto* depAspect : aspect->dependsOn())
  220             selectedPathes << depAspect->path();
  221     }
  222     selectedPathes.removeDuplicates();
  223 
  224     Folder* targetFolder = static_cast<Folder*>(m_cbAddTo->currentModelIndex().internalPointer());
  225 
  226     //check whether the selected pathes already exist in the target folder and warn the user
  227     const QString& targetFolderPath = targetFolder->path();
  228     const Project* targetProject = targetFolder->project();
  229     QStringList targetAllPathes;
  230     for (const auto* aspect : targetProject->children<AbstractAspect>(AbstractAspect::ChildIndexFlag::Recursive)) {
  231         if (aspect && !dynamic_cast<const Folder*>(aspect))
  232             targetAllPathes << aspect->path();
  233     }
  234 
  235     QStringList existingPathes;
  236     for (const auto& path : selectedPathes) {
  237         const QString& newPath = targetFolderPath + path.right(path.length() - path.indexOf('/'));
  238         if (targetAllPathes.indexOf(newPath) != -1)
  239             existingPathes << path;
  240     }
  241 
  242     QDEBUG("project objects to be imported: " << selectedPathes);
  243     QDEBUG("all already available project objects: " << targetAllPathes);
  244     QDEBUG("already available project objects to be overwritten: " << existingPathes);
  245 
  246     if (!existingPathes.isEmpty()) {
  247         QString msg = i18np("The object listed below already exists in target folder and will be overwritten:",
  248                             "The objects listed below already exist in target folder and will be overwritten:",
  249                             existingPathes.size());
  250         msg += '\n';
  251         for (const auto& path : existingPathes)
  252             msg += '\n' + path.right(path.length() - path.indexOf('/') - 1); //strip away the name of the root folder "Project"
  253         msg += "\n\n" + i18n("Do you want to proceed?");
  254 
  255         const int rc = KMessageBox::warningYesNo(nullptr, msg, i18n("Override existing objects?"));
  256         if (rc == KMessageBox::No)
  257             return;
  258     }
  259 
  260     //show a progress bar in the status bar
  261     auto* progressBar = new QProgressBar();
  262     progressBar->setMinimum(0);
  263     progressBar->setMaximum(100);
  264 
  265     statusBar->clearMessage();
  266     statusBar->addWidget(progressBar, 1);
  267 
  268     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
  269     QApplication::processEvents(QEventLoop::AllEvents, 100);
  270 
  271     //import the selected project objects into the specified folder
  272     QElapsedTimer timer;
  273     timer.start();
  274     connect(m_projectParser, &ProjectParser::completed, progressBar, &QProgressBar::setValue);
  275 
  276 #ifdef HAVE_LIBORIGIN
  277     if (m_projectType == ProjectType::Origin && ui.chbUnusedObjects->isVisible() && ui.chbUnusedObjects->isChecked())
  278         reinterpret_cast<OriginProjectParser*>(m_projectParser)->setImportUnusedObjects(true);
  279 #endif
  280 
  281     m_projectParser->importTo(targetFolder, selectedPathes);
  282     statusBar->showMessage( i18n("Project data imported in %1 seconds.", (float)timer.elapsed()/1000) );
  283 
  284     QApplication::restoreOverrideCursor();
  285     statusBar->removeWidget(progressBar);
  286 }
  287 
  288 /*!
  289  * show the content of the project in the tree view
  290  */
  291 void ImportProjectDialog::refreshPreview() {
  292     const QString& project = m_cbFileName->currentText();
  293     m_projectParser->setProjectFileName(project);
  294 
  295 #ifdef HAVE_LIBORIGIN
  296     if (m_projectType == ProjectType::Origin) {
  297         auto* originParser = reinterpret_cast<OriginProjectParser*>(m_projectParser);
  298         if (originParser->hasUnusedObjects())
  299             ui.chbUnusedObjects->show();
  300         else
  301             ui.chbUnusedObjects->hide();
  302 
  303         originParser->setImportUnusedObjects(ui.chbUnusedObjects->isVisible() && ui.chbUnusedObjects->isChecked());
  304     }
  305 #endif
  306 
  307     ui.tvPreview->setModel(m_projectParser->model());
  308 
  309     connect(ui.tvPreview->selectionModel(), &QItemSelectionModel::selectionChanged,
  310         this, &ImportProjectDialog::selectionChanged);
  311 
  312     //show top-level containers only
  313     if (ui.tvPreview->model()) {
  314         QModelIndex root = ui.tvPreview->model()->index(0,0);
  315         showTopLevelOnly(root);
  316     }
  317 
  318     //select the first top-level node and
  319     //expand the tree to show all available top-level objects and adjust the header sizes
  320     ui.tvPreview->setCurrentIndex(ui.tvPreview->model()->index(0,0));
  321     ui.tvPreview->expandAll();
  322     ui.tvPreview->header()->resizeSections(QHeaderView::ResizeToContents);
  323 }
  324 
  325 /*!
  326     Hides the non-toplevel items of the model used in the tree view.
  327 */
  328 void ImportProjectDialog::showTopLevelOnly(const QModelIndex& index) {
  329     int rows = index.model()->rowCount(index);
  330     for (int i = 0; i < rows; ++i) {
  331         QModelIndex child = index.model()->index(i, 0, index);
  332         showTopLevelOnly(child);
  333         const auto* aspect = static_cast<const AbstractAspect*>(child.internalPointer());
  334         ui.tvPreview->setRowHidden(i, index, !isTopLevel(aspect));
  335     }
  336 }
  337 
  338 /*!
  339     checks whether \c aspect is one of the allowed top level types
  340 */
  341 bool ImportProjectDialog::isTopLevel(const AbstractAspect* aspect) const {
  342     foreach (AspectType type, m_projectParser->topLevelClasses()) {
  343         if (aspect->inherits(type))
  344             return true;
  345     }
  346     return false;
  347 }
  348 
  349 //##############################################################################
  350 //#################################  SLOTS  ####################################
  351 //##############################################################################
  352 void ImportProjectDialog::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) {
  353     Q_UNUSED(deselected);
  354 
  355     //determine the dependent objects and select/deselect them too
  356     const QModelIndexList& indexes = selected.indexes();
  357     if (indexes.isEmpty())
  358         return;
  359 
  360     //for the just selected aspect, determine all the objects it depends on and select them, too
  361     //TODO: we need a better "selection", maybe with tri-state check boxes in the tree view
  362     const auto* aspect = static_cast<const AbstractAspect*>(indexes.at(0).internalPointer());
  363     const QVector<AbstractAspect*> aspects = aspect->dependsOn();
  364 
  365     const auto* model = reinterpret_cast<AspectTreeModel*>(ui.tvPreview->model());
  366     for (const auto* aspect : aspects) {
  367         QModelIndex index = model->modelIndexOfAspect(aspect, 0);
  368         ui.tvPreview->selectionModel()->select(index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
  369     }
  370 
  371     //Ok-button is only enabled if some project objects were selected
  372     bool enable = (selected.indexes().size() != 0);
  373     m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable);
  374     if (enable)
  375         m_buttonBox->button(QDialogButtonBox::Ok)->setToolTip(i18n("Close the dialog and import the selected objects."));
  376     else
  377         m_buttonBox->button(QDialogButtonBox::Ok)->setToolTip(i18n("Select object(s) to be imported."));
  378 }
  379 
  380 /*!
  381     opens a file dialog and lets the user select the project file.
  382 */
  383 void ImportProjectDialog::selectFile() {
  384     KConfigGroup conf(KSharedConfig::openConfig(), "ImportProjectDialog");
  385 
  386     QString title;
  387     QString lastDir;
  388     QString supportedFormats;
  389     QString lastDirConfEntryName;
  390     switch (m_projectType) {
  391     case ProjectType::LabPlot:
  392         title = i18nc("@title:window", "Open LabPlot Project");
  393         lastDirConfEntryName = QLatin1String("LastImportLabPlotProjectDir");
  394         supportedFormats = i18n("LabPlot Projects (%1)", Project::supportedExtensions());
  395         break;
  396     case ProjectType::Origin:
  397 #ifdef HAVE_LIBORIGIN
  398         title = i18nc("@title:window", "Open Origin Project");
  399         lastDirConfEntryName = QLatin1String("LastImportOriginProjecttDir");
  400         supportedFormats = i18n("Origin Projects (%1)", OriginProjectParser::supportedExtensions());
  401 #endif
  402         break;
  403     }
  404 
  405     lastDir = conf.readEntry(lastDirConfEntryName, "");
  406     QString path = QFileDialog::getOpenFileName(this, title, lastDir, supportedFormats);
  407     if (path.isEmpty())
  408         return; //cancel was clicked in the file-dialog
  409 
  410     int pos = path.lastIndexOf(QLatin1String("/"));
  411     if (pos != -1) {
  412         QString newDir = path.left(pos);
  413         if (newDir != lastDir)
  414             conf.writeEntry(lastDirConfEntryName, newDir);
  415     }
  416 
  417     QStringList urls = m_cbFileName->urls();
  418     urls.insert(0, QUrl::fromLocalFile(path).url());
  419     m_cbFileName->setUrls(urls);
  420     m_cbFileName->setCurrentText(urls.first());
  421     fileNameChanged(path); // why do I have to call this function separately
  422 
  423     refreshPreview();
  424 }
  425 
  426 void ImportProjectDialog::fileNameChanged(const QString& name) {
  427     QString fileName{name};
  428 
  429     // make relative path
  430 #ifdef HAVE_WINDOWS
  431     if ( !fileName.isEmpty() && fileName.at(1) != QLatin1String(":"))
  432 #else
  433     if ( !fileName.isEmpty() && fileName.at(0) != QLatin1String("/"))
  434 #endif
  435         fileName = QDir::homePath() + QLatin1String("/") + fileName;
  436 
  437     bool fileExists = QFile::exists(fileName);
  438     if (!fileExists) {
  439         //file doesn't exist -> delete the content preview that is still potentially
  440         //available from the previously selected file
  441         ui.tvPreview->setModel(nullptr);
  442         m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
  443         return;
  444     }
  445 
  446     refreshPreview();
  447 }
  448 
  449 void ImportProjectDialog::newFolder() {
  450     const QString& path = m_cbFileName->currentText();
  451     QString name = path.right( path.length() - path.lastIndexOf(QLatin1String("/"))-1 );
  452 
  453     bool ok;
  454     auto* dlg = new QInputDialog(this);
  455     name = dlg->getText(this, i18n("Add new folder"), i18n("Folder name:"), QLineEdit::Normal, name, &ok);
  456     if (ok) {
  457         auto* folder = new Folder(name);
  458         m_mainWin->addAspectToProject(folder);
  459         m_cbAddTo->setCurrentModelIndex(m_mainWin->model()->modelIndexOfAspect(folder));
  460     }
  461 
  462     delete dlg;
  463 }