File indexing completed on 2024-04-28 11:20:53

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