File indexing completed on 2023-09-24 03:50:59
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 }