File indexing completed on 2022-12-06 18:50:11

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003     SPDX-FileCopyrightText: 2009 Alexander Rieder <alexanderrieder@gmail.com>
0004     SPDX-FileCopyrightText: 2018-2022 Alexander Semke <alexander.semke@web.de>
0005 */
0006 #include "cantor.h"
0007 #include "lib/session.h"
0008 
0009 #include <cassert>
0010 
0011 #include <KActionCollection>
0012 #include <KConfigDialog>
0013 #include <KConfigGroup>
0014 #include <KConfig>
0015 #include <KMessageBox>
0016 #include <KShortcutsDialog>
0017 #include <KStandardAction>
0018 #include <KNSWidgets/Action>
0019 #include <KParts/ReadWritePart>
0020 #include <KRecentFilesAction>
0021 #include <KPluginFactory>
0022 
0023 #include <QApplication>
0024 #include <QCloseEvent>
0025 #include <QDebug>
0026 #include <QDir>
0027 #include <QDockWidget>
0028 #include <QFileDialog>
0029 #include <QStatusBar>
0030 #include <QGraphicsView>
0031 #include <QPushButton>
0032 #include <QRegularExpression>
0033 
0034 
0035 #include "lib/backend.h"
0036 #include "lib/worksheetaccess.h"
0037 
0038 #include "backendchoosedialog.h"
0039 #include "settings.h"
0040 #include "ui_settings.h"
0041 #include "ui_formating.h"
0042 #include "backendchoosedialog.h"
0043 #include <QMetaObject>
0044 
0045 CantorShell::CantorShell() : KParts::MainWindow(), m_tabWidget(new QTabWidget(this))
0046 {
0047     // set the shell's ui resource file
0048     setXMLFile(QLatin1String("cantor_shell.rc"));
0049 
0050     // then, setup our actions
0051     setupActions();
0052 
0053     createGUI(nullptr);
0054 
0055     m_tabWidget->setTabsClosable(true);
0056     m_tabWidget->setMovable(true);
0057     m_tabWidget->setDocumentMode(true);
0058     setCentralWidget(m_tabWidget);
0059 
0060     connect(m_tabWidget, &QTabWidget::currentChanged, this, &CantorShell::activateWorksheet);
0061     connect(m_tabWidget, &QTabWidget::tabCloseRequested, this, &CantorShell::closeTab);
0062 
0063     // apply the saved mainwindow settings, if any, and ask the mainwindow
0064     // to automatically save settings if changed: window size, toolbar
0065     // position, icon size, etc.
0066     setAutoSaveSettings();
0067 
0068     setDockOptions(QMainWindow::AnimatedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::VerticalTabs);
0069 
0070     initPanels();
0071 
0072     updateNewSubmenu();
0073 }
0074 
0075 CantorShell::~CantorShell()
0076 {
0077     if (m_recentProjectsAction)
0078         m_recentProjectsAction->saveEntries(KSharedConfig::openConfig()->group(QLatin1String("Recent Files")));
0079 
0080     if (!m_newBackendActions.isEmpty())
0081     {
0082         unplugActionList(QLatin1String("new_worksheet_with_backend_list"));
0083         qDeleteAll(m_newBackendActions);
0084         m_newBackendActions.clear();
0085     }
0086 
0087     unplugActionList(QLatin1String("view_show_panel_list"));
0088     qDeleteAll(m_panels);
0089     m_panels.clear();
0090 }
0091 
0092 void CantorShell::load(const QUrl& url)
0093 {
0094     // If the url already opened, then don't open the url in another tab, but
0095     // just activate the already existed tab
0096     for (int i = 0; i < m_parts.size(); i++)
0097     {
0098         auto* part = m_parts[i];
0099         if (part && part->url() == url)
0100         {
0101             if (m_tabWidget->currentIndex() != i)
0102                 activateWorksheet(i);
0103             else
0104                 KMessageBox::information(this, i18n("The file %1 is already opened.", QFileInfo(url.toLocalFile()).fileName()), i18n("Open file"));
0105             return;
0106         }
0107     }
0108 
0109     if (!m_part || !m_part->url().isEmpty() || m_part->isModified())
0110     {
0111         addWorksheet(QString());
0112         m_tabWidget->setCurrentIndex(m_parts.size() - 1);
0113     }
0114 
0115     if (m_part->openUrl(url)) {
0116         if (m_recentProjectsAction)
0117             m_recentProjectsAction->addUrl(url);
0118 
0119         updateWindowTitle(m_part->url().fileName());
0120     } else
0121         closeTab(m_tabWidget->currentIndex());
0122 }
0123 
0124 void CantorShell::setupActions()
0125 {
0126     QAction* openNew = KStandardAction::openNew(this, SLOT(fileNew()), actionCollection());
0127     openNew->setPriority(QAction::LowPriority);
0128     QAction* open = KStandardAction::open(this, SLOT(fileOpen()), actionCollection());
0129     open->setPriority(QAction::LowPriority);
0130     m_recentProjectsAction = KStandardAction::openRecent(this, &CantorShell::load, actionCollection());
0131     m_recentProjectsAction->setPriority(QAction::LowPriority);
0132     m_recentProjectsAction->loadEntries(KSharedConfig::openConfig()->group(QLatin1String("Recent Files")));
0133 
0134     KStandardAction::close(this, SLOT(closeTab()),  actionCollection());
0135 
0136     KStandardAction::quit(qApp, SLOT(closeAllWindows()), actionCollection());
0137 
0138     createStandardStatusBarAction();
0139     //setStandardToolBarMenuEnabled(true);
0140 
0141     KStandardAction::keyBindings(this, SLOT(optionsConfigureKeys()), actionCollection());
0142     KStandardAction::configureToolbars(this, SLOT(configureToolbars()), actionCollection());
0143 
0144     KStandardAction::preferences(this, SLOT(showSettings()), actionCollection());
0145 
0146     KNSWidgets::Action* downloadExamples = new KNSWidgets::Action(i18n("Download Examples"), QStringLiteral("cantor.knsrc"), actionCollection());
0147     actionCollection()->addAction(QLatin1String("file_example_download"), downloadExamples);
0148 
0149     QAction* openExample = new QAction(i18n("&Open Example"), actionCollection());
0150     openExample->setIcon(QIcon::fromTheme(QLatin1String("document-open")));
0151     actionCollection()->addAction(QLatin1String("file_example_open"), openExample);
0152     connect(openExample, &QAction::triggered, this, &CantorShell::openExample);
0153 
0154     QAction* toPreviousTab = new QAction(i18n("Go to previous worksheet"), actionCollection());
0155     actionCollection()->addAction(QLatin1String("go_to_previous_tab"), toPreviousTab);
0156     actionCollection()->setDefaultShortcut(toPreviousTab, Qt::CTRL+Qt::Key_PageDown);
0157     connect(toPreviousTab, &QAction::triggered, toPreviousTab, [this](){
0158         const int index = m_tabWidget->currentIndex()-1;
0159         if (index >= 0)
0160             m_tabWidget->setCurrentIndex(index);
0161         else
0162             m_tabWidget->setCurrentIndex(m_tabWidget->count()-1);
0163     });
0164     addAction(toPreviousTab);
0165 
0166     QAction* toNextTab = new QAction(i18n("Go to next worksheet"), actionCollection());
0167     actionCollection()->addAction(QLatin1String("go_to_next_tab"), toNextTab);
0168     actionCollection()->setDefaultShortcut(toNextTab, Qt::CTRL+Qt::Key_PageUp);
0169     connect(toNextTab, &QAction::triggered, toNextTab, [this](){
0170         const int index = m_tabWidget->currentIndex()+1;
0171         if (index < m_tabWidget->count())
0172             m_tabWidget->setCurrentIndex(index);
0173         else
0174             m_tabWidget->setCurrentIndex(0);
0175     });
0176     addAction(toNextTab);
0177 }
0178 
0179 void CantorShell::saveProperties(KConfigGroup & /*config*/)
0180 {
0181     // the 'config' object points to the session managed
0182     // config file.  anything you write here will be available
0183     // later when this app is restored
0184 }
0185 
0186 void CantorShell::readProperties(const KConfigGroup & /*config*/)
0187 {
0188     // the 'config' object points to the session managed
0189     // config file.  this function is automatically called whenever
0190     // the app is being restored.  read in here whatever you wrote
0191     // in 'saveProperties'
0192 }
0193 
0194 /*!
0195  * called when one of the "new backend" action or the "New" action are called
0196  * adds a new worksheet with the backend assossiated with the called action
0197  * or opens the "Choose Backend" dialog, respectively.
0198  */
0199 void CantorShell::fileNew()
0200 {
0201     QAction* a = static_cast<QAction*>(sender());
0202     const QString& backendName = a->data().toString();
0203     if (!backendName.isEmpty())
0204     {
0205         addWorksheet(backendName);
0206         return;
0207     }
0208 
0209     //"New" action was called -> open the "Choose Backend" dialog.
0210     addWorksheet();
0211 }
0212 
0213 void CantorShell::optionsConfigureKeys()
0214 {
0215     KShortcutsDialog dlg( KShortcutsEditor::AllActions, KShortcutsEditor::LetterShortcutsDisallowed, this );
0216     dlg.addCollection( actionCollection(), i18n("Cantor") );
0217     if (m_part)
0218         dlg.addCollection( m_part->actionCollection(), i18n("Cantor") );
0219     dlg.configure( true );
0220 }
0221 
0222 void CantorShell::fileOpen()
0223 {
0224     // this slot is called whenever the File->Open menu is selected,
0225     // the Open shortcut is pressed (usually CTRL+O) or the Open toolbar
0226     // button is clicked
0227     static const QString& filter = i18n("All supported files (*.cws *ipynb);;Cantor Worksheet (*.cws);;Jupyter Notebook (*.ipynb)");
0228     const QUrl& url = QFileDialog::getOpenFileUrl(this, i18n("Open file"), QUrl(), filter, &m_previousFilter);
0229 
0230     if (url.isEmpty() == false)
0231     {
0232         // About this function, the style guide
0233         // says that it should open a new window if the document is _not_
0234         // in its initial state.  This is what we do here..
0235         /*if ( m_part->url().isEmpty() && ! m_part->isModified() )
0236         {
0237             // we open the file in this window...
0238             load( url );
0239         }
0240         else
0241         {
0242             // we open the file in a new window...
0243             CantorShell* newWin = new CantorShell;
0244             newWin->load( url );
0245             newWin->show();
0246             }*/
0247         load(url);
0248     }
0249 }
0250 
0251 void CantorShell::addWorksheet()
0252 {
0253     bool hasBackend = false;
0254     for (auto* b : Cantor::Backend::availableBackends())
0255     {
0256         if(b->isEnabled())
0257         {
0258             hasBackend = true;
0259             break;
0260         }
0261     }
0262 
0263     if(hasBackend) //There is no point in asking for the backend, if no one is available
0264     {
0265         QString backend = Settings::self()->defaultBackend();
0266         if (backend.isEmpty())
0267         {
0268             QPointer<BackendChooseDialog> dlg=new BackendChooseDialog(this);
0269             if(dlg->exec())
0270             {
0271                 backend = dlg->backendName();
0272                 addWorksheet(backend);
0273             }
0274 
0275             delete dlg;
0276         }
0277         else
0278         {
0279             addWorksheet(backend);
0280         }
0281 
0282     }else
0283     {
0284         QTextBrowser* browser = new QTextBrowser(this);
0285         QString backendList = QLatin1String("<ul>");
0286         int backendListSize = 0;
0287         for (auto* backend : Cantor::Backend::availableBackends())
0288         {
0289             if(!backend->requirementsFullfilled()) //It's disabled because of missing dependencies, not because of some other reason(like eg. nullbackend)
0290             {
0291                 backendList += QString::fromLatin1("<li>%1: <a href=\"%2\">%2</a></li>").arg(backend->name(), backend->url());
0292                 ++backendListSize;
0293             }
0294         }
0295         browser->setHtml(i18np("<h1>No Backend Found</h1>\n"             \
0296                                "<div>You could try:\n"                   \
0297                                "  <ul>"                                  \
0298                                "    <li>Changing the settings in the config dialog;</li>" \
0299                                "    <li>Installing packages for the following program:</li>" \
0300                                "     %2 "                                \
0301                                "  </ul> "                                 \
0302                                "</div> "
0303                               , "<h1>No Backend Found</h1>\n"             \
0304                                "<div>You could try:\n"                   \
0305                                "  <ul>"                                  \
0306                                "    <li>Changing the settings in the config dialog;</li>" \
0307                                "    <li>Installing packages for one of the following programs:</li>" \
0308                                "     %2 "                                \
0309                                "  </ul> "                                 \
0310                                "</div> "
0311                               , backendListSize, backendList
0312                               ));
0313 
0314         browser->setObjectName(QLatin1String("ErrorMessage"));
0315         m_tabWidget->addTab(browser, i18n("Error"));
0316     }
0317 }
0318 
0319 void CantorShell::addWorksheet(const QString& backendName)
0320 {
0321     static int sessionCount=1;
0322 
0323     // this routine will find and load our Part.  it finds the Part by
0324     // name which is a bad idea usually.. but it's alright in this
0325     // case since our Part is made for this Shell
0326 
0327     const auto partResult = KPluginFactory::instantiatePlugin<KParts::ReadWritePart>(KPluginMetaData(QStringLiteral("kf5/parts/cantorpart")), m_tabWidget, {backendName});
0328 
0329     if (partResult)
0330     {
0331         Cantor::Backend* backend = nullptr;
0332         if (!backendName.isEmpty())
0333         {
0334             backend = Cantor::Backend::getBackend(backendName);
0335             if (!backend)
0336             {
0337                 KMessageBox::error(this, i18n("Backend %1 is not installed", backendName), i18n("Cantor"));
0338                 return;
0339             }
0340             else
0341             {
0342                 if (!backend->isEnabled())
0343                 {
0344                     KMessageBox::error(this, i18n("%1 backend installed, but inactive. Please check installation and Cantor settings", backendName), i18n("Cantor"));
0345                     return;
0346                 }
0347             }
0348         }
0349 
0350         // now that the Part is loaded, we cast it to a Part to get our hands on it
0351         auto part = partResult.plugin;
0352 
0353         connect(part, SIGNAL(setCaption(QString,QIcon)), this, SLOT(setTabCaption(QString,QIcon)));
0354         connect(part, SIGNAL(worksheetSave(QUrl)), this, SLOT(onWorksheetSave(QUrl)));
0355         connect(part, SIGNAL(showHelp(QString)), this, SIGNAL(showHelp(QString)));
0356         connect(part, SIGNAL(hierarchyChanged(QStringList, QStringList, QList<int>)), this, SIGNAL(hierarchyChanged(QStringList, QStringList, QList<int>)));
0357         connect(part, SIGNAL(hierarhyEntryNameChange(QString, QString, int)), this, SIGNAL(hierarhyEntryNameChange(QString, QString, int)));
0358         connect(this, SIGNAL(requestScrollToHierarchyEntry(QString)), part, SIGNAL(requestScrollToHierarchyEntry(QString)));
0359         connect(this, SIGNAL(settingsChanges()), part, SIGNAL(settingsChanges()));
0360         connect(part, SIGNAL(requestDocumentation(QString)), this, SIGNAL(requestDocumentation(QString)));
0361 
0362         m_parts.append(part);
0363         if (backend) {// If backend empty (loading worksheet from file), then we connect to signal and wait
0364             m_parts2Backends[part] = backend->id();
0365 
0366             //show the default help string in the help panel
0367             emit showHelp(backend->defaultHelp());
0368         } else
0369         {
0370             m_parts2Backends[part] = QString();
0371             connect(part, SIGNAL(setBackendName(QString)), this, SLOT(updateBackendForPart(QString)));
0372         }
0373         int tab = m_tabWidget->addTab(part->widget(), i18n("Session %1", sessionCount++));
0374         m_tabWidget->setCurrentIndex(tab);
0375         // Setting focus on worksheet view, because Qt clear focus of added widget inside addTab
0376         // This fix https://bugs.kde.org/show_bug.cgi?id=395976
0377         part->widget()->findChild<QGraphicsView*>()->setFocus();
0378 
0379         // Force run updateCaption for getting proper backend icon
0380         QMetaObject::invokeMethod(part, "updateCaption");
0381     }
0382     else
0383     {
0384         // if we couldn't find our Part, we exit since the Shell by
0385         // itself can't do anything useful
0386         KMessageBox::error(this, i18n("Failed to find the Cantor Part with error %1", partResult.errorString));
0387         qApp->quit();
0388         // we return here, cause qApp->quit() only means "exit the
0389         // next time we enter the event loop...
0390         return;
0391     }
0392 
0393 }
0394 
0395 void CantorShell::activateWorksheet(int index)
0396 {
0397     // Save part panels states before change worksheet
0398     if (m_part)
0399     {
0400         QStringList visiblePanelNames;
0401         for (auto* doc : m_panels)
0402         {
0403             if (doc->widget() && doc->widget()->isVisible())
0404                 visiblePanelNames << doc->objectName();
0405         }
0406         m_pluginsVisibility[m_part] = visiblePanelNames;
0407 
0408         auto* wa = m_part->findChild<Cantor::WorksheetAccessInterface*>(Cantor::WorksheetAccessInterface::Name);
0409         assert(wa);
0410         Cantor::PanelPluginHandler::PanelStates states;
0411         auto plugins = m_panelHandler.plugins(wa->session());
0412         for(auto* plugin : plugins)
0413         {
0414             states.insert(plugin->name(), plugin->saveState());
0415         }
0416         m_pluginsStates[m_part] = states;
0417     }
0418 
0419     if (index != -1)
0420     {
0421         m_part = findPart(m_tabWidget->widget(index));
0422         if(m_part)
0423         {
0424             createGUI(m_part);
0425 
0426             //update the status bar
0427             auto* wa = m_part->findChild<Cantor::WorksheetAccessInterface*>(Cantor::WorksheetAccessInterface::Name);
0428             if (wa->session())
0429             {
0430                 auto status = wa->session()->status();
0431                 switch (status) {
0432                     case Cantor::Session::Running:
0433                         statusBar()->showMessage(i18n("Calculating..."));
0434                         break;
0435                     case Cantor::Session::Done:
0436                         statusBar()->showMessage(i18n("Ready"));
0437                         break;
0438                     case Cantor::Session::Disable:
0439                         statusBar()->showMessage(QString());
0440                         break;
0441                 }
0442             }
0443 
0444             updateWindowTitle(m_part->url().fileName(), m_part->isModified());
0445 
0446             updatePanel();
0447         }
0448         else
0449             qDebug()<<"selected part doesn't exist";
0450 
0451         m_tabWidget->setCurrentIndex(index);
0452     }
0453 }
0454 
0455 void CantorShell::setTabCaption(const QString& caption, const QIcon& icon)
0456 {
0457     auto* part = dynamic_cast<KParts::ReadWritePart*>(sender());
0458     if (part)
0459     {
0460         const int index = m_tabWidget->indexOf(part->widget());
0461         if (!caption.isEmpty())
0462         {
0463             if (part->isModified())
0464                 m_tabWidget->setTabText(index, caption + QLatin1String(" *"));
0465             else
0466                 m_tabWidget->setTabText(index, caption);
0467 
0468             if (part == m_part)
0469                 updateWindowTitle(m_part->url().fileName(), m_part->isModified());
0470         }
0471         m_tabWidget->setTabIcon(index, icon);
0472     }
0473 }
0474 
0475 void CantorShell::updateWindowTitle(const QString& fileName, bool modified)
0476 {
0477     QFileInfo info(fileName);
0478     QString title = info.baseName();
0479     if (modified)
0480         title += QLatin1String("    [") + i18n("Changed") + QLatin1Char(']');
0481 
0482     setWindowTitle(title);
0483 }
0484 
0485 void CantorShell::closeTab(int index)
0486 {
0487     if (index != -1)
0488     {
0489         QWidget* widget = m_tabWidget->widget(index);
0490         if (widget)
0491         {
0492             auto* part = findPart(widget);
0493             if (part && !reallyCloseThisPart(part))
0494                 return;
0495         }
0496     }
0497     else
0498     {
0499         if (!reallyClose(false))
0500             return;
0501     }
0502 
0503     QWidget* widget = nullptr;
0504     if (index >= 0)
0505     {
0506         widget = m_tabWidget->widget(index);
0507     }
0508     else if (m_part)
0509     {
0510         widget = m_part->widget();
0511     }
0512 
0513     if (!widget)
0514     {
0515         qWarning() << "Could not find widget by tab index" << index;
0516         return;
0517     }
0518 
0519 
0520     m_tabWidget->removeTab(index);
0521 
0522     bool isCurrectPartClosed = m_part ? widget == m_part->widget() : false;
0523     if(widget->objectName() == QLatin1String("ErrorMessage"))
0524     {
0525         widget->deleteLater();
0526     }else
0527     {
0528         auto* part = findPart(widget);
0529         if(part)
0530         {
0531             saveDockPanelsState(part);
0532 
0533             m_parts.removeAll(part);
0534             m_pluginsVisibility.remove(part);
0535             m_parts2Backends.remove(part);
0536             m_pluginsStates.remove(part);
0537 
0538             if (m_part == part)
0539                 m_part = nullptr; //the current worksheet/part is being closed, set to null
0540 
0541             delete part;
0542         }
0543     }
0544 
0545     if (m_tabWidget->count() == 0)
0546         setCaption(QString());
0547 
0548     if (isCurrectPartClosed || m_part == nullptr)
0549         updatePanel();
0550 }
0551 
0552 bool CantorShell::reallyClose(bool checkAllParts) {
0553     if(checkAllParts && m_parts.count() > 1) {
0554         bool modified = false;
0555         for (auto* part : m_parts)
0556         {
0557             if(part->isModified()) {
0558                 modified = true;
0559                 break;
0560             }
0561         }
0562         if(!modified) return true;
0563         int want_save = KMessageBox::warningYesNo( this,
0564             i18n("Multiple unsaved Worksheets are opened. Do you want to close them?"),
0565             i18n("Close Cantor"));
0566         switch (want_save) {
0567             case KMessageBox::Yes:
0568                 return true;
0569             case KMessageBox::No:
0570                 return false;
0571         }
0572     }
0573 
0574     return reallyCloseThisPart(m_part);
0575 }
0576 
0577 bool CantorShell::reallyCloseThisPart(KParts::ReadWritePart* part)
0578 {
0579      if (part && part->isModified() ) {
0580         int want_save = KMessageBox::warningYesNoCancel( this,
0581             i18n("The current project has been modified. Do you want to save it?"),
0582             i18n("Save Project"));
0583         switch (want_save) {
0584             case KMessageBox::Yes:
0585                 part->save();
0586                 if(part->waitSaveComplete()) {
0587                     return true;
0588                 } else {
0589                     part->setModified(true);
0590                     return false;
0591                 }
0592             case KMessageBox::Cancel:
0593                 return false;
0594             case KMessageBox::No:
0595                 return true;
0596         }
0597     }
0598     return true;
0599 }
0600 
0601 
0602 void CantorShell::closeEvent(QCloseEvent* event) {
0603     if (!reallyClose()) {
0604         event->ignore();
0605     } else {
0606         for (auto* part : m_parts)
0607             saveDockPanelsState(part);
0608 
0609         KParts::MainWindow::closeEvent(event);
0610     }
0611 }
0612 
0613 void CantorShell::showSettings()
0614 {
0615     KConfigDialog *dialog = new KConfigDialog(this,  QLatin1String("settings"), Settings::self());
0616 
0617     QWidget *generalSettings = new QWidget;
0618     Ui::SettingsBase base;
0619     base.setupUi(generalSettings);
0620 
0621     QWidget *formattingSettings = new QWidget;
0622     Ui::SettingsFormatting formatting;
0623     formatting.setupUi(formattingSettings);
0624 
0625     base.kcfg_DefaultBackend->addItems(Cantor::Backend::listAvailableBackends());
0626 
0627     dialog->addPage(generalSettings, i18n("General"), QLatin1String("preferences-other"));
0628     dialog->addPage(formattingSettings, i18n("Formatting"), QLatin1String("preferences-other"));
0629     for (auto* backend : Cantor::Backend::availableBackends())
0630     {
0631         if (backend->config()) //It has something to configure, so add it to the dialog
0632             dialog->addPage(backend->settingsWidget(dialog), backend->config(), backend->name(),  backend->icon());
0633     }
0634 
0635     dialog->show();
0636     connect(dialog, &KConfigDialog::settingsChanged, this, &CantorShell::settingsChanges);
0637 }
0638 
0639 void CantorShell::openExample()
0640 {
0641     QString dir = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + QLatin1String("/examples");
0642     if (dir.isEmpty())
0643         return;
0644 
0645     QDir().mkpath(dir);
0646 
0647     QStringList files = QDir(dir).entryList(QDir::Files);
0648     QPointer<QDialog> dlg = new QDialog(this);
0649     QListWidget* list = new QListWidget(dlg);
0650     for (const QString& file : files)
0651     {
0652         QString name = file;
0653         name.remove(QRegularExpression(QStringLiteral("-.*\\.hotstuff-access$")));
0654         list->addItem(name);
0655     }
0656 
0657     QVBoxLayout *mainLayout = new QVBoxLayout;
0658     dlg->setLayout(mainLayout);
0659     mainLayout->addWidget(list);
0660 
0661     auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0662 
0663     mainLayout->addWidget(buttonBox);
0664 
0665     buttonBox->button(QDialogButtonBox::Ok)->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogOkButton));
0666     buttonBox->button(QDialogButtonBox::Cancel)->setIcon(QApplication::style()->standardIcon(QStyle::SP_DialogCancelButton));
0667 
0668     connect(buttonBox, &QDialogButtonBox::accepted, dlg, &QDialog::accept);
0669     connect(buttonBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject);
0670 
0671     if (dlg->exec() == QDialog::Accepted&&list->currentRow()>=0)
0672     {
0673         const QString& selectedFile = files[list->currentRow()];
0674         const QUrl& url = QUrl::fromLocalFile(QDir(dir).absoluteFilePath(selectedFile));
0675 
0676         qDebug()<<"loading file "<<url;
0677         load(url);
0678     }
0679 
0680     delete dlg;
0681 }
0682 
0683 KParts::ReadWritePart* CantorShell::findPart(QWidget* widget)
0684 {
0685     for (auto* part : m_parts)
0686     {
0687         if (part->widget() == widget)
0688             return part;
0689     }
0690     return nullptr;
0691 }
0692 
0693 void CantorShell::initPanels()
0694 {
0695     m_panelHandler.loadPlugins();
0696 
0697     for (auto* plugin : m_panelHandler.allPlugins())
0698     {
0699         if (!plugin)
0700         {
0701             qDebug()<<"invalid panel found, skipping it.";
0702             continue;
0703         }
0704 
0705         qDebug()<<"adding panel for "<<plugin->name();
0706         plugin->setParentWidget(this);
0707         plugin->connectToShell(this);
0708 
0709         QDockWidget* docker = new QDockWidget(plugin->name(), this);
0710         docker->setObjectName(plugin->name());
0711         docker->setWidget(plugin->widget());
0712         docker->setWindowIcon(QIcon::fromTheme(QStringLiteral("format-text-bold")));
0713         addDockWidget(Qt::RightDockWidgetArea, docker);
0714 
0715         docker->hide();
0716 
0717         connect(plugin, &Cantor::PanelPlugin::visibilityRequested, this, &CantorShell::pluginVisibilityRequested);
0718         connect(plugin, &Cantor::PanelPlugin::requestRunCommand, this, &CantorShell::pluginCommandRunRequested);
0719 
0720         m_panels.append(docker);
0721     }
0722 }
0723 
0724 void CantorShell::updatePanel()
0725 {
0726     unplugActionList(QLatin1String("view_show_panel_list"));
0727 
0728     QList<QAction*> panelActions;
0729 
0730     bool isNewWorksheet = !m_pluginsVisibility.contains(m_part);
0731     if (isNewWorksheet)
0732     {
0733         KConfigGroup panelStatusGroup(KSharedConfig::openConfig(), QLatin1String("PanelsStatus"));
0734         if (m_parts2Backends.contains(m_part) && panelStatusGroup.hasKey(m_parts2Backends[m_part]))
0735         {
0736             const QStringList& plugins = panelStatusGroup.readEntry(m_parts2Backends[m_part]).split(QLatin1Char('\n'));
0737             m_pluginsVisibility[m_part] = plugins;
0738             isNewWorksheet = false;
0739         }
0740     }
0741 
0742     Cantor::WorksheetAccessInterface* wa = nullptr;
0743     if (m_part)
0744         wa = m_part->findChild<Cantor::WorksheetAccessInterface*>(Cantor::WorksheetAccessInterface::Name);
0745 
0746     // Worksheet interface can be missing on m_part clossing (and m_part on this moment can be nullptr)
0747     QList<Cantor::PanelPlugin*> plugins;
0748     if (wa)
0749     {
0750         QDockWidget* last = nullptr;
0751         plugins = m_panelHandler.activePluginsForSession(wa->session(), m_pluginsStates.contains(m_part) ? m_pluginsStates[m_part] : Cantor::PanelPluginHandler::PanelStates());
0752         for (auto* plugin : plugins)
0753         {
0754             QDockWidget* foundDocker = nullptr;
0755             for (auto* docker : m_panels)
0756                 if (docker->objectName() == plugin->name())
0757                 {
0758                     foundDocker = docker;
0759                     break;
0760                 }
0761 
0762             if (!foundDocker)
0763             {
0764                 qDebug() << "something wrong: can't find panel for plugin \"" << plugin->name() << "\"";
0765                 continue;
0766             }
0767 
0768             // Set visibility for dock from saved info
0769             if (isNewWorksheet)
0770             {
0771                 if (plugin->showOnStartup())
0772                     foundDocker->show();
0773                 else
0774                     foundDocker->hide();
0775             }
0776             else
0777             {
0778                 if (m_pluginsVisibility[m_part].contains(plugin->name()))
0779                     foundDocker->show();
0780                 else
0781                     foundDocker->hide();
0782             }
0783 
0784             if(last!=nullptr)
0785                 tabifyDockWidget(last, foundDocker);
0786             last = foundDocker;
0787 
0788             //Create the action to show/hide this panel
0789             panelActions<<foundDocker->toggleViewAction();
0790 
0791         }
0792     }
0793 
0794     // Hide plugins, which don't supported on current session
0795     QList<Cantor::PanelPlugin*> allPlugins=m_panelHandler.allPlugins();
0796     for(auto* plugin : allPlugins)
0797     {
0798         if (plugins.indexOf(plugin) == -1)
0799             for (QDockWidget* docker : m_panels)
0800                 if (docker->objectName() == plugin->name())
0801                 {
0802                     docker->hide();
0803                     break;
0804                 }
0805     }
0806 
0807     plugActionList(QLatin1String("view_show_panel_list"), panelActions);
0808 
0809     updateNewSubmenu();
0810 }
0811 
0812 void CantorShell::updateNewSubmenu()
0813 {
0814     unplugActionList(QLatin1String("new_worksheet_with_backend_list"));
0815     qDeleteAll(m_newBackendActions);
0816     m_newBackendActions.clear();
0817 
0818     for (auto* backend : Cantor::Backend::availableBackends())
0819     {
0820         if (!backend->isEnabled())
0821             continue;
0822         QAction* action = new QAction(QIcon::fromTheme(backend->icon()), backend->name(), nullptr);
0823         action->setData(backend->name());
0824         connect(action, SIGNAL(triggered()), this, SLOT(fileNew()));
0825         m_newBackendActions << action;
0826     }
0827     plugActionList(QLatin1String("new_worksheet_with_backend_list"), m_newBackendActions);
0828 }
0829 
0830 Cantor::WorksheetAccessInterface* CantorShell::currentWorksheetAccessInterface()
0831 {
0832     auto* wa = m_part->findChild<Cantor::WorksheetAccessInterface*>(Cantor::WorksheetAccessInterface::Name);
0833     return wa;
0834 }
0835 
0836 void CantorShell::pluginVisibilityRequested()
0837 {
0838     auto* plugin = static_cast<Cantor::PanelPlugin*>(sender());
0839     for (auto* docker : m_panels)
0840     {
0841         if (plugin->name() == docker->objectName())
0842         {
0843             if (docker->isHidden())
0844                 docker->show();
0845             docker->raise();
0846         }
0847     }
0848 }
0849 
0850 void CantorShell::onWorksheetSave(const QUrl& url)
0851 {
0852     if (m_recentProjectsAction)
0853         m_recentProjectsAction->addUrl(url);
0854 
0855     updateWindowTitle(m_part->url().fileName());
0856 }
0857 
0858 void CantorShell::saveDockPanelsState(KParts::ReadWritePart* part)
0859 {
0860     if (m_parts2Backends.contains(part))
0861     {
0862         QStringList visiblePanelNames;
0863         if (part == m_part)
0864         {
0865             for (auto* doc : m_panels)
0866             {
0867                 if (doc->widget() && doc->widget()->isVisible())
0868                     visiblePanelNames << doc->objectName();
0869             }
0870         }
0871         else if (m_pluginsVisibility.contains(part))
0872             visiblePanelNames = m_pluginsVisibility[part];
0873 
0874         KConfigGroup panelStatusGroup(KSharedConfig::openConfig(), QLatin1String("PanelsStatus"));
0875         panelStatusGroup.writeEntry(m_parts2Backends[part], visiblePanelNames.join(QLatin1Char('\n')));
0876     }
0877 }
0878 
0879 void CantorShell::updateBackendForPart(const QString& backendName)
0880 {
0881     auto* part = dynamic_cast<KParts::ReadWritePart*>(sender());
0882     if (part && m_parts2Backends.contains(part) && m_parts2Backends[part].isEmpty())
0883     {
0884         m_parts2Backends[part] = backendName;
0885 
0886         KConfigGroup panelStatusGroup(KSharedConfig::openConfig(), QLatin1String("PanelsStatus"));
0887         if (m_part == part && panelStatusGroup.hasKey(backendName))
0888         {
0889             const QStringList& plugins = panelStatusGroup.readEntry(m_parts2Backends[m_part]).split(QLatin1Char('\n'));
0890             m_pluginsVisibility[m_part] = plugins;
0891 
0892             updatePanel();
0893         }
0894 
0895         auto* backend = Cantor::Backend::getBackend(backendName);
0896         if (backend)
0897         {
0898             //show the default help string in the help panel
0899             emit showHelp(backend->defaultHelp());
0900         }
0901     }
0902 }
0903 
0904 void CantorShell::pluginCommandRunRequested(const QString& cmd)
0905 {
0906     if (m_part)
0907         QMetaObject::invokeMethod(m_part, "runCommand", Qt::QueuedConnection, Q_ARG(QString, cmd));
0908 }