diff --git a/rkward/rkward.cpp b/rkward/rkward.cpp index c0326fd0..bdfe9249 100644 --- a/rkward/rkward.cpp +++ b/rkward/rkward.cpp @@ -1,982 +1,983 @@ /*************************************************************************** rkward.cpp - description ------------------- begin : Tue Oct 29 20:06:08 CET 2002 copyright : (C) 2002-2019 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "rkward.h" // include files for QT #include #include #include #include #include #include #include #include #include #include #include // include files for KDE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // application specific includes #include "core/rkmodificationtracker.h" #include "plugin/rkcomponentmap.h" #include "settings/rksettings.h" #include "settings/rksettingsmoduleplugins.h" #include "settings/rksettingsmodulegeneral.h" #include "settings/rksettingsmoduleoutput.h" #include "settings/rksettingsmodulecommandeditor.h" #include "rbackend/rkrinterface.h" #include "core/robjectlist.h" #include "core/renvironmentobject.h" #include "misc/rkstandardicons.h" #include "misc/rkcommonfunctions.h" #include "misc/rkxmlguisyncer.h" #include "misc/rkdbusapi.h" #include "misc/rkdialogbuttonbox.h" #include "rkglobals.h" #include "dialogs/startupdialog.h" #include "dialogs/rkloadlibsdialog.h" #include "dialogs/rkimportdialog.h" #include "dialogs/rkrecoverdialog.h" #include "agents/rksaveagent.h" #include "agents/rkloadagent.h" #include "agents/rkquitagent.h" #include "windows/robjectbrowser.h" #include "windows/rcontrolwindow.h" #include "windows/rkhtmlwindow.h" #include "windows/rkworkplaceview.h" #include "windows/rkworkplace.h" #include "windows/rkcommandlog.h" #include "windows/rkhelpsearchwindow.h" #include "windows/rktoplevelwindowgui.h" #include "windows/rkfilebrowser.h" #include "windows/rktoolwindowlist.h" #include "windows/rkdebugconsole.h" #include "windows/rkcallstackviewer.h" #include "windows/rkdebugmessagewindow.h" #include "windows/katepluginintegration.h" #include "rkconsole.h" #include "debug.h" #include "agents/showedittextfileagent.h" // TODO: see below: needed purely for linking! #include "dialogs/rkreadlinedialog.h" // TODO: see below: needed purely for linking! #include "dialogs/rkselectlistdialog.h" // TODO: see below: needed purely for linking! #include "windows/detachedwindowcontainer.h" // TODO: see below: needed purely for linking! #include "dataeditor/rkeditordataframe.h" // TODO: see below: needed purely for linking! #include "agents/rkeditobjectagent.h" // TODO: see below: needed purely for linking! #include "agents/rkprintagent.h" // TODO: see below: needed purely for linking! // This nevers gets called. It's needed to trick ld into linking correctly. Nothing else. void bogusCalls () { ShowEditTextFileAgent::showEditFiles (0); // TODO: AAAAAAAARGGGH!!!! It won't link without this bogus line!!! RKReadLineDialog::readLine (0, QString(), QString(), 0, 0); // TODO: see above RKSelectListDialog::doSelect (0, QString(), QStringList(), QStringList(), false); // TODO: see above new RKEditorDataFrame (0, 0); DetachedWindowContainer (0, false); new RKWorkplaceView (0); new RKEditObjectAgent (QStringList (), 0); RKPrintAgent::printPostscript (QString (), false); } /** Main window **/ //static RKWardMainWindow *RKWardMainWindow::rkward_mainwin = 0; RKWardMainWindow::RKWardMainWindow () : KParts::MainWindow ((QWidget *)0, (Qt::WindowFlags) KDE_DEFAULT_WINDOWFLAGS) { RK_TRACE (APP); RK_ASSERT (rkward_mainwin == 0); gui_rebuild_locked = true; no_ask_save = true; workspace_modified = false; merge_loads = false; rkward_mainwin = this; katepluginintegration = 0; RKGlobals::rinter = 0; RKCommonFunctions::getRKWardDataDir(); // call this before any forking, in order to avoid potential race conditions during initialization of data dir RKSettings::settings_tracker = new RKSettingsTracker (this); /////////////////////////////////////////////////////////////////// // call inits to invoke all other construction parts RKStandardIcons::initIcons (); initActions(); initStatusBar(); new RKWorkplace (this); RKWorkplace::mainWorkplace ()->initActions (actionCollection ()); setCentralWidget (RKWorkplace::mainWorkplace ()); connect (RKWorkplace::mainWorkplace ()->view (), &RKWorkplaceView::captionChanged, this, static_cast(&RKWardMainWindow::setCaption)); connect (RKWorkplace::mainWorkplace (), &RKWorkplace::workspaceUrlChanged, this, &RKWardMainWindow::addWorkspaceUrl); part_manager = new KParts::PartManager (this); // When the manager says the active part changes, // the builder updates (recreates) the GUI connect (partManager (), &KParts::PartManager::activePartChanged, this, &RKWardMainWindow::partChanged); readOptions(); RKGlobals::mtracker = new RKModificationTracker (this); initToolViewsAndR (); /////////////////////////////////////////////////////////////////// // build the interface setHelpMenuEnabled (false); setXMLFile ("rkwardui.rc"); insertChildClient (toplevel_actions = new RKTopLevelWindowGUI (this)); insertChildClient (katePluginIntegration ()->mainWindow ()); createShellGUI (true); katePluginIntegration ()->loadPlugin ("katesearchplugin"); + katePluginIntegration ()->loadPlugin ("katesnippetsplugin"); RKXMLGUISyncer::self ()->watchXMLGUIClientUIrc (this); // replicate File->import and export menus into the Open/Save toolbar button menus QMenu *menu = dynamic_cast(guiFactory ()->container ("import", this)); if (menu) open_any_action->addAction (menu->menuAction ()); menu = dynamic_cast(guiFactory ()->container ("export", this)); if (menu) save_any_action->addAction (menu->menuAction ()); RKComponentMap::initialize (); // stuff which should wait until the event loop is running QTimer::singleShot (0, this, SLOT (doPostInit())); } RKWardMainWindow::~RKWardMainWindow() { RK_TRACE (APP); // these would not be strictly necessary, as we're exiting the app, anyway. delete RObjectList::getObjectList (); delete RObjectBrowser::mainBrowser (); delete RKCommandLog::getLog (); delete RKConsole::mainConsole (); delete RKHelpSearchWindow::mainHelpSearch (); delete RKGlobals::tracker (); delete RKGlobals::rInterface (); delete RControlWindow::getControl (); factory ()->removeClient (RKComponentMap::getMap ()); delete RKComponentMap::getMap (); } KatePluginIntegrationApp* RKWardMainWindow::katePluginIntegration () { RK_TRACE (APP); if (!katepluginintegration) { katepluginintegration = new KatePluginIntegrationApp (this); } return katepluginintegration; } void RKWardMainWindow::closeEvent (QCloseEvent *e) { RK_TRACE (APP); if (RKQuitAgent::quittingInProgress ()) { KParts::MainWindow::closeEvent (e); return; } e->ignore (); if (doQueryQuit ()) { emit (aboutToQuitRKWard()); new RKQuitAgent (this); } } void RKWardMainWindow::doPostInit () { RK_TRACE (APP); // Check installation first if (RKCommonFunctions::getRKWardDataDir ().isEmpty ()) { KMessageBox::error (this, i18n ("

RKWard either could not find its resource files at all, or only an old version of those files. The most likely cause is that the last installation failed to place the files in the correct place. This can lead to all sorts of problems, from single missing features to complete failure to function.

You should quit RKWard, now, and fix your installation. For help with that, see http://rkward.kde.org/compiling.

"), i18n ("Broken installation"), KMessageBox::Notify | KMessageBox::AllowLink); } QStringList open_urls = RKGlobals::startup_options.take ("initial_urls").toStringList (); bool warn_external = RKGlobals::startup_options.take ("warn_external").toBool (); QString evaluate_code = RKGlobals::startup_options.take ("evaluate").toString (); initPlugins (); gui_rebuild_locked = false; show (); KMessageBox::enableMessage ("external_link_warning"); // can only be disabled per session QUrl recover_url = RKRecoverDialog::checkRecoverCrashedWorkspace (); if (!recover_url.isEmpty ()) { open_urls.clear (); // Well, not a perfect solution. But we certainly don't want to overwrite the just recovered workspace. open_urls.append (recover_url.url ()); } for (int i = 0; i < open_urls.size (); ++i) { // make sure local urls are absolute, as we may be changing wd before loading QUrl url = QUrl::fromUserInput (open_urls[i], QDir::currentPath(), QUrl::AssumeLocalFile); RK_ASSERT (!url.isRelative ()); open_urls[i] = url.url (); } QString cd_to = RKSettingsModuleGeneral::initialWorkingDirectory (); if (!cd_to.isEmpty ()) { RKGlobals::rInterface ()->issueCommand ("setwd (" + RObject::rQuote (cd_to) + ")\n", RCommand::App); QDir::setCurrent (cd_to); } if (!open_urls.isEmpty()) { // this is also done when there are no urls specified on the command line. But in that case _after_ loading any workspace, so // the help window will be on top if (RKSettingsModuleGeneral::showHelpOnStartup ()) toplevel_actions->showRKWardHelp (); openUrlsFromCommandLineOrDBus (warn_external, open_urls); } else { StartupDialog::StartupDialogResult result = StartupDialog::getStartupAction (this, fileOpenRecentWorkspace); if (!result.open_url.isEmpty ()) { openWorkspace (result.open_url); } else { if (result.result == StartupDialog::ChoseFile) { askOpenWorkspace (QUrl()); } else if (result.result == StartupDialog::EmptyTable) { RKWorkplace::mainWorkplace ()->editNewDataFrame (i18n ("my.data")); } } if (RKSettingsModuleGeneral::workplaceSaveMode () == RKSettingsModuleGeneral::SaveWorkplaceWithSession) { RKWorkplace::mainWorkplace ()->restoreWorkplace (RKSettingsModuleGeneral::getSavedWorkplace (KSharedConfig::openConfig ().data ()).split ('\n')); } if (RKSettingsModuleGeneral::showHelpOnStartup ()) toplevel_actions->showRKWardHelp (); } setNoAskSave (false); // up to this point, no "real" save-worthy stuff can be pending in the backend. So mark this point as "clean". RCommand *command = new RCommand (QString (), RCommand::EmptyCommand | RCommand::Sync | RCommand::App); connect (command->notifier (), &RCommandNotifier::commandFinished, this, &RKWardMainWindow::setWorkspaceUnmodified); RKGlobals::rInterface ()->issueCommand (command); if (!evaluate_code.isEmpty ()) RKConsole::pipeUserCommand (evaluate_code); RKDBusAPI *dbus = new RKDBusAPI (this); connect (this, &RKWardMainWindow::aboutToQuitRKWard, dbus, &RKDBusAPI::deleteLater); // around on the bus in this case. updateCWD (); connect (RKGlobals::rInterface (), &RInterface::backendWorkdirChanged, this, &RKWardMainWindow::updateCWD); setCaption (QString ()); // our version of setCaption takes care of creating a correct caption, so we do not need to provide it here } void RKWardMainWindow::openUrlsFromCommandLineOrDBus (bool warn_external, QStringList _urls) { RK_TRACE (APP); bool any_dangerous_urls = false; QList urls; for (int i = 0; i < _urls.size (); ++i) { QUrl url = QUrl::fromUserInput (_urls[i], QString (), QUrl::AssumeLocalFile); if (url.scheme () == "rkward" && url.host () == "runplugin") { any_dangerous_urls = true; } urls.append (url); } if (warn_external && any_dangerous_urls) { RK_ASSERT (urls.size () == 1); QString message = i18n ("

You are about to start an RKWard dialog from outside of RKWard, probably by clicking on an 'rkward://'-link, somewhere. In case you have found this link on an external website, please bear in mind that R can be used to run arbitrary commands on your computer, potentially including downloading and installing malicious software. If you do not trust the source of the link you were following, you should press 'Cancel', below.

In case you click 'Continue', no R code will be run, unless and until you click 'Submit' in the dialog window, and you are encouraged to review the generated R code, before doing so.

Note: Checking 'Do not ask again' will suppress this message for the remainder of this session, only."); if (KMessageBox::warningContinueCancel (this, message, i18n ("A note on external links"), KStandardGuiItem::cont (), KStandardGuiItem::cancel (), "external_link_warning") != KMessageBox::Continue) return; } RKWardMainWindow::getMain ()->setMergeLoads (true); for (int i = 0; i < urls.size (); ++i) { RKWorkplace::mainWorkplace ()->openAnyUrl (urls[i], QString (), false); } RKWardMainWindow::getMain ()->setMergeLoads (false); } void RKWardMainWindow::initPlugins (const QStringList &automatically_added) { RK_TRACE (APP); slotSetStatusBarText(i18n("Setting up plugins...")); QStringList all_maps = RKSettingsModulePlugins::pluginMaps (); if (all_maps.isEmpty()) { KMessageBox::information (0, i18n ("Plugins are needed: you may manage these through \"Settings->Manage R package and plugins\".\n"), i18n ("No active plugin maps")); return; } factory ()->removeClient (RKComponentMap::getMap ()); RKComponentMap::getMap ()->clearAll (); QStringList completely_broken_maps; QStringList completely_broken_maps_details; QStringList somewhat_broken_maps; QStringList somewhat_broken_maps_details; for (int i = 0; i < all_maps.size (); ++i) { const QString &map = all_maps[i]; RKPluginMapParseResult result = RKComponentMap::getMap ()->addPluginMap (map); if (!result.valid_plugins) { RKSettingsModulePlugins::markPluginMapAsBroken (map); completely_broken_maps.append (map); completely_broken_maps_details.append (result.detailed_problems); } else if (!result.detailed_problems.isEmpty ()) { if (RKSettingsModulePlugins::markPluginMapAsQuirky (map)) { somewhat_broken_maps.append (map); somewhat_broken_maps_details.append (result.detailed_problems); } } else { RKSettingsModulePlugins::markPluginMapAsWorking (map); } } RKComponentMap::getMap ()->finalizeAll (); factory ()->addClient (RKComponentMap::getMap ()); if (!automatically_added.isEmpty ()) { // NOTE: When plugins are added from R, these must be fully initialized *before* showing any dialog, which is modal, i.e. has an event loop. Otherwise, subsequent calls e.g. to rk.call.plugin() could sneak in front of this. // This is the reason for handling notification about automatically_added plugins, here. KMessageBox::informationList (RKWardMainWindow::getMain (), i18n ("New RKWard plugin packs (listed below) have been found, and have been activated, automatically. To de-activate selected plugin packs, use Settings->Configure RKWard->Plugins."), automatically_added, i18n ("New plugins found"), "new_plugins_found"); } if (!completely_broken_maps.isEmpty ()) { QString maplist = "

  • " + completely_broken_maps.join ("
  • \n
  • ") + "
"; KMessageBox::detailedError (0, QString ("

%1

%2

").arg (i18n ("The following RKWard pluginmap files could not be loaded, and have been disabled. This could be because they are broken, not compatible with this version of RKWard, or not meant for direct loading (see the 'Details' for more information). They have been disabled.")).arg (maplist), completely_broken_maps_details.join ("\n"), i18n ("Failed to load some plugin maps")); } if (!somewhat_broken_maps.isEmpty ()) { QString maplist = "
  • " + somewhat_broken_maps.join ("
  • \n
  • ") + "
"; KMessageBox::detailedError (0, QString ("

%1

%2

%3

").arg (i18n ("Some errors were encountered while loading the following RKWard pluginmap files. This could be because individual plugins are broken or not compatible with this version of RKWard (see the 'Details' for more information). Other plugins were loaded, successfully, however.")).arg (maplist).arg (i18n ("Note: You will not be warned about these pluginmap files again, until you upgrade RKWard, or remove and re-add them in Settings->Configure RKWard->Plugins.")), somewhat_broken_maps_details.join ("\n"), i18n ("Failed to load some plugin maps")); } slotSetStatusReady (); } void RKWardMainWindow::startR () { RK_TRACE (APP); RK_ASSERT (!RKGlobals::rInterface ()); // make sure our general purpose files directory exists QString packages_path = RKSettingsModuleGeneral::filesPath() + "/.rkward_packages"; bool ok = QDir ().mkpath (packages_path); RK_ASSERT (ok); // Copy RKWard R source packages to general purpose files directory (if still needed). // This may look redundant at first (since the package still needs to be installed from the // backend. However, if frontend and backend are on different machines (eventually), only the // filesPath is shared between both. QStringList packages; packages << "rkward.tgz" << "rkwardtests.tgz"; for (int i = 0; i < packages.size (); ++i) { QString package = QDir (packages_path).absoluteFilePath (packages[i]); if (RKSettingsModuleGeneral::rkwardVersionChanged ()) { RK_DEBUG(APP, DL_INFO, "RKWard version changed. Discarding cached package at %s", qPrintable (package)); QFile::remove (package); } if (!QFileInfo (package).exists()) { RK_DEBUG(APP, DL_INFO, "Copying rkward R source package to %s", qPrintable (package)); RK_ASSERT(QFile::copy (RKCommonFunctions::getRKWardDataDir () + "/rpackages/" + packages[i], package)); } } RKGlobals::rinter = new RInterface (); new RObjectList (); RObjectBrowser::mainBrowser ()->unlock (); } void RKWardMainWindow::slotConfigure () { RK_TRACE (APP); RKSettings::configureSettings (RKSettings::NoPage, this); } void RKWardMainWindow::slotCancelAllCommands () { RK_TRACE (APP); RK_ASSERT (RKGlobals::rInterface ()); RKGlobals::rInterface ()->cancelAll (); } void RKWardMainWindow::configureCarbonCopy () { RK_TRACE (APP); QDialog *dialog = new QDialog (); dialog->setWindowTitle (i18n ("Carbon Copy Settings")); QVBoxLayout *layout = new QVBoxLayout (dialog); RKCarbonCopySettings *settings = new RKCarbonCopySettings (dialog); layout->addWidget (settings); RKDialogButtonBox *box = new RKDialogButtonBox (QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel, dialog); dialog->setAttribute (Qt::WA_DeleteOnClose); connect (dialog, &QDialog::accepted, settings, &RKCarbonCopySettings::applyChanges); connect (box->button (QDialogButtonBox::Apply), &QPushButton::clicked, settings, &RKCarbonCopySettings::applyChanges); layout->addWidget (box); dialog->show (); } void RKWardMainWindow::initToolViewsAndR () { RK_TRACE (APP); RObjectBrowser::object_browser = new RObjectBrowser (0, true); RObjectBrowser::mainBrowser ()->setCaption (i18n ("Workspace")); RKToolWindowList::registerToolWindow (RObjectBrowser::mainBrowser (), "workspace", RKToolWindowList::Left, Qt::AltModifier + Qt::Key_1); RKCommandLog::rkcommand_log = new RKCommandLog (0, true); RKToolWindowList::registerToolWindow (RKCommandLog::rkcommand_log, "commandlog", RKToolWindowList::Bottom, Qt::AltModifier + Qt::Key_3); startR (); RKFileBrowser::main_browser = new RKFileBrowser (0, true); RKFileBrowser::main_browser->setCaption (i18n ("Files")); RKToolWindowList::registerToolWindow (RKFileBrowser::main_browser, "filebrowser", RKToolWindowList::Left, Qt::AltModifier + Qt::Key_2); RControlWindow::control_window = new RControlWindow (0, true); RControlWindow::getControl ()->setCaption (i18n ("Pending Jobs")); RKToolWindowList::registerToolWindow (RControlWindow::getControl (), "pendingjobs", RKToolWindowList::Nowhere, Qt::AltModifier + Qt::Key_4); RKConsole *console = new RKConsole (0, true); RKConsole::setMainConsole (console); RKToolWindowList::registerToolWindow (console, "console", RKToolWindowList::Bottom, Qt::AltModifier + Qt::Key_5); RKHelpSearchWindow *help_search = new RKHelpSearchWindow (0, true); RKHelpSearchWindow::main_help_search = help_search; RKToolWindowList::registerToolWindow (help_search, "helpsearch", RKToolWindowList::Bottom, Qt::AltModifier + Qt::Key_6); RKCallstackViewer::_instance = new RKCallstackViewer (0, true); RKCallstackViewer::instance ()->setCaption (i18n ("Debugger Frames")); RKToolWindowList::registerToolWindow (RKCallstackViewer::instance (), "debugframes", RKToolWindowList::Right, Qt::AltModifier + Qt::Key_8); // HACK: Creating this _after_ the callstackviewer is important, so the debug console will end up the active window when entering a debug context RKDebugConsole::_instance = new RKDebugConsole (0, true); RKDebugConsole::instance ()->setCaption (i18n ("Debugger Console")); RKToolWindowList::registerToolWindow (RKDebugConsole::instance (), "debugconsole", RKToolWindowList::Nowhere, Qt::AltModifier + Qt::Key_7); RKDebugMessageWindow::_instance = new RKDebugMessageWindow (0, true); RKDebugMessageWindow::instance ()->setCaption (i18n ("RKWard Debug Messages")); RKToolWindowList::registerToolWindow (RKDebugMessageWindow::instance (), "rkdebugmessages", RKToolWindowList::Nowhere, 0); RKWorkplace::mainWorkplace ()->placeToolWindows (); } void RKWardMainWindow::initActions() { RK_TRACE (APP); QAction *action; // TODO: is there a way to insert actions between standard actions without having to give all standard actions custom ids? new_data_frame = actionCollection ()->addAction ("new_data_frame", this, SLOT (slotNewDataFrame())); new_data_frame->setText (i18n ("Dataset")); new_data_frame->setIcon (RKStandardIcons::getIcon (RKStandardIcons::WindowDataFrameEditor)); new_data_frame->setStatusTip (i18n ("Creates new empty dataset and opens it for editing")); new_command_editor = actionCollection ()->addAction (KStandardAction::New, "new_command_editor", this, SLOT(slotNewCommandEditor())); new_command_editor->setText (i18n ("Script File")); new_command_editor->setIcon (RKStandardIcons::getIcon (RKStandardIcons::WindowCommandEditor)); fileOpen = actionCollection ()->addAction (KStandardAction::Open, "file_openy", this, SLOT(slotOpenCommandEditor())); fileOpen->setText (i18n ("Open R Script File...")); fileOpenRecent = static_cast (actionCollection ()->addAction (KStandardAction::OpenRecent, "file_open_recenty", this, SLOT(slotOpenCommandEditor(QUrl)))); fileOpenRecent->setText (i18n ("Open Recent R Script File")); #if 0 // TODO: Fix import dialog and re-enable it: https://mail.kde.org/pipermail/rkward-devel/2015-June/004156.html #ifdef Q_OS_WIN // TODO: find the cause and fix it! http://sourceforge.net/p/rkward/bugs/54/ # ifdef __GNUC__ # warning TODO: import data dialog is disabled on windows due to bug in kdelibs # endif #else action = actionCollection ()->addAction ("import_data", this, SLOT (importData())); action->setText (i18n ("Import Data")); action->setStatusTip (i18n ("Import data from a variety of file formats")); #endif #endif fileOpenWorkspace = actionCollection ()->addAction (KStandardAction::Open, "file_openx", this, SLOT(slotFileOpenWorkspace())); fileOpenWorkspace->setText (i18n ("Open Workspace...")); actionCollection ()->setDefaultShortcut (fileOpenWorkspace, Qt::ControlModifier + Qt::ShiftModifier + Qt::Key_O); fileOpenWorkspace->setStatusTip (i18n ("Opens an existing document")); fileOpenRecentWorkspace = static_cast (actionCollection ()->addAction (KStandardAction::OpenRecent, "file_open_recentx", this, SLOT(askOpenWorkspace(QUrl)))); fileOpenRecentWorkspace->setText (i18n ("Open Recent Workspace")); fileOpenRecentWorkspace->setStatusTip (i18n ("Opens a recently used file")); fileSaveWorkspace = actionCollection ()->addAction (KStandardAction::Save, "file_savex", this, SLOT(slotFileSaveWorkspace())); fileSaveWorkspace->setText (i18n ("Save Workspace")); actionCollection ()->setDefaultShortcut (fileSaveWorkspace, Qt::ControlModifier + Qt::AltModifier + Qt::Key_S); fileSaveWorkspace->setStatusTip (i18n ("Saves the actual document")); fileSaveWorkspaceAs = actionCollection ()->addAction (KStandardAction::SaveAs, "file_save_asx", this, SLOT(slotFileSaveWorkspaceAs())); actionCollection ()->setDefaultShortcut (fileSaveWorkspaceAs, Qt::ControlModifier + Qt::AltModifier + Qt::ShiftModifier + Qt::Key_S); fileSaveWorkspaceAs->setText (i18n ("Save Workspace As")); fileSaveWorkspaceAs->setStatusTip (i18n ("Saves the actual document as...")); fileQuit = actionCollection ()->addAction (KStandardAction::Quit, "file_quitx", this, SLOT(close())); fileQuit->setStatusTip (i18n ("Quits the application")); interrupt_all_commands = actionCollection ()->addAction ("cancel_all_commands", this, SLOT (slotCancelAllCommands())); interrupt_all_commands->setText (i18n ("Interrupt all commands")); actionCollection ()->setDefaultShortcut (interrupt_all_commands, Qt::ShiftModifier + Qt::Key_Escape); interrupt_all_commands->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionInterrupt)); interrupt_all_commands->setEnabled (false); // enabled from within setRStatus() action = actionCollection ()->addAction ("carbon_copy", this, SLOT (configureCarbonCopy())); action->setText (i18n ("CC commands to output...")); // These two currently do the same thing action = actionCollection ()->addAction ("load_unload_libs", this, SLOT (slotFileLoadLibs())); action->setText (i18n ("Manage R packages and plugins...")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionConfigurePackages)); action = actionCollection ()->addAction ("configure_packages", this, SLOT (slotFileLoadLibs())); action->setText (i18n ("Manage R packages and plugins...")); action->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionConfigurePackages)); setStandardToolBarMenuEnabled (true); createStandardStatusBarAction (); close_all_editors = actionCollection ()->addAction ("close_all_editors", this, SLOT (slotCloseAllEditors())); close_all_editors->setText (i18n ("Close All Data")); close_all_editors->setStatusTip (i18n ("Closes all open data editors")); action = actionCollection ()->addAction (KStandardAction::Close, "window_close", this, SLOT (slotCloseWindow())); window_close_all = actionCollection ()->addAction ("window_close_all", this, SLOT (slotCloseAllWindows())); window_close_all->setText (i18n ("Close All")); window_detach = actionCollection ()->addAction ("window_detach", this, SLOT (slotDetachWindow())); window_detach->setIcon (RKStandardIcons::getIcon (RKStandardIcons::ActionDetachWindow)); window_detach->setText (i18n ("Detach")); configure = actionCollection ()->addAction (KStandardAction::Preferences, "options_configure", this, SLOT (slotConfigure())); edit_menu_dummy = actionCollection ()->addAction ("edit_menu_dummy", this); edit_menu_dummy->setText (i18n ("[No actions available for current view]")); edit_menu_dummy->setEnabled (false); view_menu_dummy = actionCollection ()->addAction ("view_menu_dummy", this); view_menu_dummy->setText (edit_menu_dummy->text ()); view_menu_dummy->setEnabled (false); // collections for the toolbar: open_any_action = new KActionMenu (QIcon::fromTheme("document-open-folder"), i18n ("Open..."), this); open_any_action->setDelayed (false); actionCollection ()->addAction ("open_any", open_any_action); open_any_action->addAction (fileOpenWorkspace); open_any_action->addAction (fileOpenRecentWorkspace); open_any_action->addSeparator (); open_any_action->addAction (fileOpen); open_any_action->addAction (fileOpenRecent); open_any_action->addSeparator (); //open_any_action->addAction (proxy_import); -> later KActionMenu* new_any_action = new KActionMenu (QIcon::fromTheme("document-new"), i18n ("Create..."), this); new_any_action->setDelayed (false); actionCollection ()->addAction ("new_any", new_any_action); new_any_action->addAction (new_data_frame); new_any_action->addAction (new_command_editor); save_any_action = new KActionMenu (QIcon::fromTheme("document-save"), i18n ("Save..."), this); save_any_action->setDelayed (false); actionCollection ()->addAction ("save_any", save_any_action); save_any_action->addAction (fileSaveWorkspace); save_any_action->addAction (fileSaveWorkspaceAs); save_any_action->addSeparator (); // TODO: A way to add R-script-save actions, dynamically, would be nice save_actions_plug_point = save_any_action->addSeparator (); //save_any_action->addAction (proxy_export); -> later } /* // debug code: prints out all current actions void printActionsRecursive (QAction* action, const QString &prefix) { if (action->menu ()) { foreach (QAction *a, action->menu ()->actions ()) printActionsRecursive (a, prefix + action->text () + "->"); } else { qDebug ("%s", qPrintable (prefix + action->text ())); } } */ void updateEmptyMenuIndicator (QAction* indicator, const QMenu *menu) { if (!menu) { indicator->setVisible (false); return; } // NOTE: QMenu::isEmpty () does not work, here QList actions = menu->actions (); for (int i = 0; i < actions.size (); ++i) { if (actions[i] == indicator) continue; if (actions[i]->isSeparator ()) continue; if (actions[i]->isVisible ()) { indicator->setVisible (false); return; } } indicator->setVisible (true); } void RKWardMainWindow::partChanged (KParts::Part *part) { RK_TRACE (APP); if (gui_rebuild_locked) return; createGUI (part); if (!guiFactory ()) { RK_ASSERT (false); return; } updateEmptyMenuIndicator (edit_menu_dummy, dynamic_cast(guiFactory ()->container ("edit", this))); updateEmptyMenuIndicator (view_menu_dummy, dynamic_cast(guiFactory ()->container ("view", this))); // plug save file actions into the toolbar collections RK_ASSERT (save_any_action); for (int i = 0; i < plugged_save_actions.size (); ++i) { QAction* a = plugged_save_actions[i].data (); if (a) save_any_action->removeAction (a); } plugged_save_actions.clear (); RKMDIWindow *w = RKWorkplace::mainWorkplace ()->activeWindow (RKMDIWindow::Attached); if (w && (w->isType (RKMDIWindow::CommandEditorWindow))) { QAction *a = static_cast(w)->fileSaveAction (); if (a) plugged_save_actions.append (a); a = static_cast(w)->fileSaveAsAction (); if (a) plugged_save_actions.append (a); } for (int i = 0; i < plugged_save_actions.size (); ++i) { save_any_action->insertAction (save_actions_plug_point, plugged_save_actions[i]); } /* // debug code: prints out all current actions foreach (QAction *action, menuBar ()->actions ()) printActionsRecursive (action, QString ()); */ } void RKWardMainWindow::lockGUIRebuild (bool lock) { RK_TRACE (APP); if (lock) { RK_ASSERT (!gui_rebuild_locked); gui_rebuild_locked = true; } else { gui_rebuild_locked = false; partChanged (part_manager->activePart ()); } } void RKWardMainWindow::initStatusBar () { RK_TRACE (APP); statusbar_ready = new QLabel (i18n ("Ready."), statusBar ()); statusBar ()->addWidget (statusbar_ready); statusbar_cwd = new KSqueezedTextLabel (statusBar ()); statusbar_cwd->setAlignment (Qt::AlignRight); statusbar_cwd->setToolTip (i18n ("Current working directory")); statusBar ()->addWidget (statusbar_cwd, 10); updateCWD (); QWidget *box = new QWidget (statusBar ()); QHBoxLayout *boxl = new QHBoxLayout (box); boxl->setSpacing (0); statusbar_r_status = new QLabel (" R ", box); statusbar_r_status->setFixedHeight (statusBar ()->fontMetrics ().height () + 2); boxl->addWidget (statusbar_r_status); QToolButton* dummy = new QToolButton (box); dummy->setDefaultAction (interrupt_all_commands); dummy->setFixedHeight (statusbar_r_status->height ()); dummy->setAutoRaise (true); boxl->addWidget (dummy); statusBar ()->addPermanentWidget (box, 0); setRStatus (Starting); } void RKWardMainWindow::openWorkspace (const QUrl &url) { RK_TRACE (APP); if (url.isEmpty ()) return; new RKLoadAgent (url, merge_loads); } void RKWardMainWindow::saveOptions () { RK_TRACE (APP); KSharedConfig::Ptr config = KSharedConfig::openConfig (); KConfigGroup cg = config->group ("main window options"); saveMainWindowSettings (cg); cg = config->group ("General Options"); // TODO: WORKAROUND. See corresponding line in readOptions () cg.writeEntry("Geometry", size ()); fileOpenRecentWorkspace->saveEntries (config->group ("Recent Files")); fileOpenRecent->saveEntries (config->group ("Recent Command Files")); RKSettings::saveSettings (config.data ()); config->sync (); } void RKWardMainWindow::readOptions () { RK_TRACE (APP); KSharedConfig::Ptr config = KSharedConfig::openConfig (); applyMainWindowSettings (config->group ("main window options")); // TODO: WORKAROUND: Actually applyMainWindowSettings could/should do this, but apparently this just does not work for maximized windows. Therefore we use our own version instead. // KDE4: still needed? // KF5 TODO: still needed? KConfigGroup cg = config->group ("General Options"); QSize size = cg.readEntry ("Geometry", QSize ()); if (size.isEmpty ()) { size = QApplication::desktop ()->availableGeometry ().size (); } resize (size); RKSettings::loadSettings (config.data ()); // initialize the recent file list fileOpenRecentWorkspace->loadEntries (config->group ("Recent Files")); fileOpenRecent->setMaxItems (RKSettingsModuleCommandEditor::maxNumRecentFiles ()); fileOpenRecent->loadEntries (config->group ("Recent Command Files")); } bool RKWardMainWindow::doQueryQuit () { RK_TRACE (APP); slotSetStatusBarText (i18n ("Exiting...")); saveOptions (); if (RKSettingsModuleGeneral::workplaceSaveMode () == RKSettingsModuleGeneral::SaveWorkplaceWithSession) { RKSettingsModuleGeneral::setSavedWorkplace (RKWorkplace::mainWorkplace ()->makeWorkplaceDescription ().join ("\n"), KSharedConfig::openConfig ().data ()); } // if (!RObjectList::getGlobalEnv ()->isEmpty ()) { int res; res = KMessageBox::questionYesNoCancel (this, i18n ("Quitting RKWard: Do you want to save the workspace?"), i18n ("Save Workspace?"), KStandardGuiItem::save (), KStandardGuiItem::discard (), KGuiItem (i18n ("Do Not Quit"))); if (res == KMessageBox::Yes) { new RKSaveAgent (RKWorkplace::mainWorkplace ()->workspaceURL (), false, RKSaveAgent::DoNothing); } else if (res == KMessageBox::Cancel) { slotSetStatusReady (); return false; } // } RKWorkplace::RKWorkplaceObjectList map = RKWorkplace::mainWorkplace ()->getObjectList (); for (RKWorkplace::RKWorkplaceObjectList::const_iterator it = map.constBegin (); it != map.constEnd (); ++it) { lockGUIRebuild (true); if (!(*it)->close (true)) { if (!(*it)->isType (RKMDIWindow::X11Window)) { // X11 windows have a delayed close // If a child refuses to close, we return false. slotSetStatusReady (); lockGUIRebuild (false); return false; } } gui_rebuild_locked = false; // like lockGUIRebuild (false), but does not trigger an immediate rebuild, as we are about to leave, anyway. } return true; } void RKWardMainWindow::slotNewDataFrame () { RK_TRACE (APP); bool ok; QString name = QInputDialog::getText (this, i18n ("New dataset"), i18n ("Enter name for the new dataset"), QLineEdit::Normal, "my.data", &ok); if (ok) RKWorkplace::mainWorkplace ()->editNewDataFrame (name); } void RKWardMainWindow::askOpenWorkspace (const QUrl &url) { RK_TRACE (APP); if (!no_ask_save && ((!RObjectList::getGlobalEnv ()->isEmpty () && workspace_modified) || !RKGlobals::rInterface ()->backendIsIdle ())) { int res; res = KMessageBox::questionYesNoCancel (this, i18n ("Do you want to save the current workspace?"), i18n ("Save Workspace?")); if (res == KMessageBox::Yes) { new RKSaveAgent (RKWorkplace::mainWorkplace ()->workspaceURL (), false, RKSaveAgent::Load, url); } else if (res != KMessageBox::No) { // Cancel return; } } slotCloseAllEditors (); slotSetStatusBarText(i18n("Opening workspace...")); QUrl lurl = url; if (lurl.isEmpty ()) { lurl = QFileDialog::getOpenFileUrl (this, i18n("Select workspace to open..."), RKSettingsModuleGeneral::lastUsedUrlFor ("workspaces"), i18n ("R Workspace Files [%1](%1);;All files [*](*)", RKSettingsModuleGeneral::workspaceFilenameFilter ())); } if (!lurl.isEmpty ()) { RKSettingsModuleGeneral::updateLastUsedUrl ("workspaces", lurl.adjusted (QUrl::RemoveFilename)); openWorkspace (lurl); } slotSetStatusReady (); } void RKWardMainWindow::slotFileOpenWorkspace () { RK_TRACE (APP); askOpenWorkspace (QUrl ()); } void RKWardMainWindow::slotFileLoadLibs () { RK_TRACE (APP); RKLoadLibsDialog *dial = new RKLoadLibsDialog (this, 0); dial->show (); } void RKWardMainWindow::slotFileSaveWorkspace () { RK_TRACE (APP); new RKSaveAgent (RKWorkplace::mainWorkplace ()->workspaceURL ()); } void RKWardMainWindow::slotFileSaveWorkspaceAs () { RK_TRACE (APP); new RKSaveAgent (RKWorkplace::mainWorkplace ()->workspaceURL (), true); } void RKWardMainWindow::addWorkspaceUrl (const QUrl &url) { RK_TRACE (APP); if (!url.isEmpty ()) fileOpenRecentWorkspace->addUrl (url); setCaption (QString ()); // trigger update of caption } void RKWardMainWindow::updateCWD () { RK_TRACE (APP); statusbar_cwd->setText (QDir::currentPath ()); } void RKWardMainWindow::slotSetStatusBarText (const QString &text) { RK_TRACE (APP); //KDE4: still needed? QString ntext = text.trimmed (); ntext.replace ("", QString ()); // WORKAROUND: what the ?!? is going on? The KTHMLPart seems to post such messages. if (ntext.isEmpty ()) { statusBar ()->clearMessage (); } else { statusBar ()->showMessage (ntext); } } void RKWardMainWindow::slotCloseWindow () { RK_TRACE (APP); RKWorkplace::mainWorkplace ()->closeActiveWindow (); } void RKWardMainWindow::slotCloseAllWindows () { RK_TRACE (APP); RKWorkplace::mainWorkplace ()->closeAll (); } void RKWardMainWindow::slotCloseAllEditors () { RK_TRACE (APP); RKWorkplace::mainWorkplace ()->closeAll (RKMDIWindow::DataEditorWindow); } void RKWardMainWindow::slotDetachWindow () { RK_TRACE (APP); RKWorkplace::mainWorkplace ()->detachWindow (RKWorkplace::mainWorkplace ()->activeWindow (RKMDIWindow::Attached)); } void RKWardMainWindow::setRStatus (RStatus status) { RK_TRACE (APP); QColor status_color; if (status == Busy) { status_color = QColor (255, 0, 0); statusbar_r_status->setToolTip (i18n ("The R engine is busy.")); interrupt_all_commands->setEnabled (true); } else if (status == Idle) { status_color = QColor (0, 255, 0); statusbar_r_status->setToolTip (i18n ("The R engine is idle.")); interrupt_all_commands->setEnabled (false); } else { status_color = QColor (255, 255, 0); statusbar_r_status->setToolTip (i18n ("The R engine is being initialized.")); } QPalette palette = statusbar_r_status->palette (); palette.setBrush (statusbar_r_status->backgroundRole(), QBrush (status_color)); statusbar_r_status->setAutoFillBackground (true); statusbar_r_status->setPalette (palette); } void RKWardMainWindow::importData () { RK_TRACE (APP); new RKImportDialog ("import", this); } void RKWardMainWindow::slotNewCommandEditor () { RK_TRACE (APP); slotOpenCommandEditor (QUrl ()); } void RKWardMainWindow::addScriptUrl (const QUrl &url) { RK_TRACE (APP); if (!url.isEmpty ()) fileOpenRecent->addUrl (url); } void RKWardMainWindow::slotOpenCommandEditor (const QUrl &url, const QString &encoding) { RK_TRACE (APP); RKWorkplace::mainWorkplace ()->openScriptEditor (url, encoding, url.isEmpty() || RKSettingsModuleCommandEditor::matchesScriptFileFilter (url.fileName())); } void RKWardMainWindow::slotOpenCommandEditor () { RK_TRACE (APP); KEncodingFileDialog::Result res; res = KEncodingFileDialog::getOpenUrlsAndEncoding (QString (), RKSettingsModuleGeneral::lastUsedUrlFor ("rscripts"), QString ("%1|R Script Files (%1)\n*|All Files (*)").arg (RKSettingsModuleCommandEditor::scriptFileFilter ()), this, i18n ("Open script file(s)")); for (int i = 0; i < res.URLs.size (); ++i) { if (i == 0) RKSettingsModuleGeneral::updateLastUsedUrl ("rscripts", res.URLs[i].adjusted (QUrl::RemoveFilename)); slotOpenCommandEditor (res.URLs[i], res.encoding); } }; void RKWardMainWindow::setCaption (const QString &) { RK_TRACE (APP); QString wcaption = RKWorkplace::mainWorkplace ()->workspaceURL ().fileName (); if (wcaption.isEmpty ()) wcaption = RKWorkplace::mainWorkplace ()->workspaceURL ().toDisplayString (); if (wcaption.isEmpty ()) wcaption = i18n ("[Unnamed Workspace]"); RKMDIWindow *window = RKWorkplace::mainWorkplace ()->view ()->activePage (); if (window) wcaption.append (" - " + window->fullCaption ()); KParts::MainWindow::setCaption (wcaption); } diff --git a/rkward/windows/katepluginintegration.cpp b/rkward/windows/katepluginintegration.cpp index 1b467dc9..340f908a 100644 --- a/rkward/windows/katepluginintegration.cpp +++ b/rkward/windows/katepluginintegration.cpp @@ -1,460 +1,474 @@ /*************************************************************************** katepluginintegration - description ------------------- begin : Mon Jun 12 2017 copyright : (C) 2017-2020 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "katepluginintegration.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "../rkward.h" #include "rkworkplace.h" #include "rkworkplaceview.h" #include "rkcommandeditorwindow.h" #include "../misc/rkdummypart.h" #include "../settings/rksettingsmodulecommandeditor.h" #include "../debug.h" /// BEGIN KTextEditor::Application interface KatePluginIntegrationApp::KatePluginIntegrationApp(QObject *parent) : QObject (parent) { RK_TRACE (APP); dummy_view = 0; window = new KatePluginIntegrationWindow(this); app = new KTextEditor::Application(this); KTextEditor::Editor::instance()->setApplication(app); // enumerate all available kate plugins QVector plugins = KPluginLoader::findPlugins(QStringLiteral ("ktexteditor"), [](const KPluginMetaData &md) { return md.serviceTypes().contains(QLatin1String("KTextEditor/Plugin")); }); for (int i = plugins.size() -1; i >= 0; --i) { PluginInfo info; info.plugin = 0; info.data = plugins[i]; // Note: creates a lookup-table *and* eliminates potential dupes later in the search path known_plugins.insert(idForPlugin(info.data), info); // TODO: remove me qDebug ("%s", qPrintable(info.data.fileName())); } + // NOTE: Destructor is too late for this, esp. As plugin destructors will try to unregister from the guiFactory(), and such. + connect(RKWardMainWindow::getMain(), &RKWardMainWindow::aboutToQuitRKWard, this, &KatePluginIntegrationApp::saveConfigAndUnload); } KatePluginIntegrationApp::~KatePluginIntegrationApp() { RK_TRACE (APP); } KTextEditor::View *KatePluginIntegrationApp::dummyView() { if (!dummy_view) { RK_TRACE (APP); KTextEditor::Document *doc = KTextEditor::Editor::instance()->createDocument (this); dummy_view = doc->createView(0); dummy_view->hide(); // Make sure it does not accumulate cruft. connect(doc, &KTextEditor::Document::textChanged, doc, &KTextEditor::Document::clear); } return dummy_view; } QString KatePluginIntegrationApp::idForPlugin(const KPluginMetaData &plugin) const { return QFileInfo(plugin.fileName()).baseName(); } QObject* KatePluginIntegrationApp::loadPlugin (const QString& identifier) { RK_TRACE (APP); if (!known_plugins.contains (identifier)) { RK_DEBUG (APP, DL_WARNING, "Plugin %s is not known", qPrintable (identifier)); return 0; } KPluginFactory *factory = KPluginLoader(known_plugins[identifier].data.fileName ()).factory (); if (factory) { KTextEditor::Plugin *plugin = factory->create(this, QVariantList () << identifier); if (plugin) { known_plugins[identifier].plugin = plugin; emit KTextEditor::Editor::instance()->application()->pluginCreated(identifier, plugin); - mainWindow()->createPluginView(plugin); - QObject* created = mainWindow()->pluginView(identifier); + QObject* created = mainWindow()->createPluginView(plugin); if (created) { + emit mainWindow()->main->pluginViewCreated(identifier, created); KTextEditor::SessionConfigInterface *interface = qobject_cast(created); if (interface) { // NOTE: Some plugins (noteably the Search in files plugin) will misbehave, unless readSessionConfig has been called! KConfigGroup group = KSharedConfig::openConfig()->group(QStringLiteral("KatePlugin:%1:").arg(identifier)); interface->readSessionConfig(group); } } return plugin; } } return 0; } -void KatePluginIntegrationApp::savePluginConfig() { +void KatePluginIntegrationApp::saveConfigAndUnload() { RK_TRACE (APP); - for (auto it = known_plugins.constBegin(); it != known_plugins.constEnd(); ++it) { + for (auto it = known_plugins.constBegin(); it != known_plugins.constEnd(); ++it) { KTextEditor::Plugin* plugin = it.value().plugin; if (!plugin) continue; - KTextEditor::SessionConfigInterface * interface = qobject_cast(mainWindow()->pluginView(it.key())); - if (!interface) continue; - KConfigGroup group = KSharedConfig::openConfig()->group(QStringLiteral("KatePlugin:%1:").arg(it.key())); - interface->writeSessionConfig(group); + QObject* view = mainWindow()->pluginView(it.key()); + if (view) { + KTextEditor::SessionConfigInterface* interface = qobject_cast(view); + if (interface) { + KConfigGroup group = KSharedConfig::openConfig()->group(QStringLiteral("KatePlugin:%1:").arg(it.key())); + interface->writeSessionConfig(group); + } + emit mainWindow()->main->pluginViewDeleted(it.key(), view); + delete view; + } + emit app->pluginDeleted(it.key(), plugin); + delete plugin; } + known_plugins.clear(); } QList KatePluginIntegrationApp::mainWindows() { RK_TRACE (APP); QList ret; ret.append (window->main); return ret; } KTextEditor::MainWindow *KatePluginIntegrationApp::activeMainWindow() { RK_TRACE (APP); return window->main; } RKCommandEditorWindow* findWindowForView(KTextEditor::View *view) { RK_TRACE (APP); QList w = RKWorkplace::mainWorkplace()->getObjectList(RKMDIWindow::CommandEditorWindow); for (int i = 0; i < w.size(); ++i) { KTextEditor::View *v = static_cast(w[i])->getView(); if (v && (v == view)) { return static_cast(w[i]); } } return 0; } RKCommandEditorWindow* findWindowForDocument(KTextEditor::Document *document) { RK_TRACE (APP); QList w = RKWorkplace::mainWorkplace()->getObjectList(RKMDIWindow::CommandEditorWindow); for (int i = 0; i < w.size(); ++i) { KTextEditor::View *v = static_cast(w[i])->getView(); if (v && (v->document() == document)) { return static_cast(w[i]); } } return 0; } QList KatePluginIntegrationApp::documents() { RK_TRACE (APP); QList w = RKWorkplace::mainWorkplace()->getObjectList(RKMDIWindow::CommandEditorWindow); QList ret; for (int i = 0; i < w.size (); ++i) { KTextEditor::View *v = static_cast(w[i])->getView(); if (v) ret.append(v->document()); } if (ret.isEmpty()) { // See the NOTE in KatePluginIntegrationWindow::activeView() ret.append(dummyView()->document()); } return ret; } KTextEditor::Document *KatePluginIntegrationApp::findUrl(const QUrl &url) { RK_TRACE (APP); QUrl _url = url.adjusted(QUrl::NormalizePathSegments); // Needed? QList w = RKWorkplace::mainWorkplace()->getObjectList(RKMDIWindow::CommandEditorWindow); for (int i = 0; i < w.size (); ++i) { if (_url == static_cast(w[i])->url().adjusted(QUrl::NormalizePathSegments)) { KTextEditor::View *v = static_cast(w[i])->getView(); if (v) return v->document(); } } return 0; } KTextEditor::Document *KatePluginIntegrationApp::openUrl(const QUrl &url, const QString &encoding) { RK_TRACE (APP); KTextEditor::View *v = window->openUrl(url, encoding); if (v) return v->document(); return 0; } bool KatePluginIntegrationApp::closeDocument(KTextEditor::Document *document) { RK_TRACE (APP); RKMDIWindow *w = findWindowForDocument(document); if (w) return RKWorkplace::mainWorkplace()->closeWindow(w); // NOTE: Closes only a single view of the document return false; } bool KatePluginIntegrationApp::closeDocuments(const QList &documents) { RK_TRACE (APP); bool allfound = true; QList w = RKWorkplace::mainWorkplace()->getObjectList(RKMDIWindow::CommandEditorWindow); QList toclose; for (int i = 0; i < documents.size(); ++i) { bool found = false; for (int j = 0; j < w.size(); ++j) { KTextEditor::View *v = static_cast(w[j])->getView(); if (v && v->document() == documents[i]) { toclose.append(w[i]); found = true; break; } } if (!found) allfound = false; } return RKWorkplace::mainWorkplace()->closeWindows(toclose) && allfound; } KTextEditor::Plugin *KatePluginIntegrationApp::plugin(const QString &name) { RK_TRACE (APP); if (known_plugins.contains(name)) { return known_plugins[name].plugin; } return 0; } /// END KTextEditor::Application interface /// BEGIN KTextEditor::MainWindow interface KatePluginIntegrationWindow::KatePluginIntegrationWindow (KatePluginIntegrationApp *parent) : QObject (parent), KXMLGUIClient () { RK_TRACE (APP); // This one is passed to each created plugin main = new KTextEditor::MainWindow(this); // While this one may be accessed from plugins via KTextEditor::Editor::instance()->application() app = parent; } /** This is a bit lame, but the plugin does not add itself to the parent widget's layout by itself. So we need this class * to do that. Where did the good old KVBox go? */ class KatePluginWrapperWidget : public QWidget { Q_OBJECT public: KatePluginWrapperWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *layout = new QVBoxLayout(this); setLayout(layout); setFocusPolicy (Qt::StrongFocus); } void childEvent(QChildEvent *ev) override { if ((ev->type() == QEvent::ChildAdded) && qobject_cast(ev->child())) { QWidget *widget = qobject_cast(ev->child()); setFocusProxy(widget); layout()->addWidget(widget); } QWidget::childEvent(ev); } }; class KatePluginToolWindow : public RKMDIWindow { Q_OBJECT public: KatePluginToolWindow(QWidget *parent, RKMDIWindow::Type type) : RKMDIWindow(parent, type, true) { RK_TRACE (APP); QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); wrapper = new KatePluginWrapperWidget(this); layout->addWidget (wrapper); setPart(new RKDummyPart(this, wrapper)); } ~KatePluginToolWindow() { RK_TRACE (APP); } QWidget* wrapper; }; QWidget * KatePluginIntegrationWindow::createToolView (KTextEditor::Plugin *plugin, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos, const QIcon &icon, const QString &text) { RK_TRACE (APP); // TODO: Set proper RKMDIWindow:type KatePluginToolWindow *window = new KatePluginToolWindow(RKWorkplace::mainWorkplace()->view(), RKMDIWindow::ConsoleWindow); window->setCaption(text); window->setWindowIcon(icon); RKWorkplace::mainWorkplace()->placeInToolWindowBar(window, pos); return window->wrapper; } bool KatePluginIntegrationWindow::showToolView (QWidget *widget) { RK_TRACE (APP); RKMDIWindow *w = qobject_cast(widget->parentWidget()); if (w) w->activate(); else { RK_ASSERT(w); widget->show(); } return true; } KXMLGUIFactory *KatePluginIntegrationWindow::guiFactory () { RK_TRACE (APP); // We'd rather like to add the plugin to our own RKMDIWindows, rather than // allowing it direct access to the guiFactory() return factory (); // TODO } QWidget *KatePluginIntegrationWindow::window() { RK_TRACE (APP); return RKWorkplace::mainWorkplace()->view()->window(); } QList KatePluginIntegrationWindow::views() { RK_TRACE (APP); QList w = RKWorkplace::mainWorkplace()->getObjectList(RKMDIWindow::CommandEditorWindow); QList ret; for (int i = 0; i < w.size (); ++i) { ret.append (static_cast(w[i])->getView()); } return ret; } KTextEditor::View *KatePluginIntegrationWindow::activeView() { RK_TRACE (APP); RKMDIWindow *w = RKWorkplace::mainWorkplace()->activeWindow(RKMDIWindow::AnyWindowState); if (w && w->isType (RKMDIWindow::CommandEditorWindow)) { return static_cast(w)->getView(); } - // NOTE: It looks like some plugins assume this cannot return 0. That's a bug in the plugin, but still one that could - // be quite prevalent, as in kate, that assumption holds. So, to be safe, we create a dummy window on the fly. + // NOTE: As far as RKWard is concerned, the active window will most likely be the tool window at this point, while the + // intention will be to get an active window that the tool should operate on. So get the last used window from + // history. (Another option would be to check which window is on top in the view area, but this will be difficult + // for split views. RKMDIWindow* candidate = RKWorkplace::getHistory()->previousDocumentWindow(); if (candidate && candidate->isType(RKMDIWindow::CommandEditorWindow)) return static_cast(candidate)->getView(); + // NOTE: It looks like some plugins assume this cannot return 0. That's a bug in the plugin, but still one that could + // be quite prevalent, as in kate, that assumption holds. So, to be safe, we create a dummy window on the fly. return app->dummyView(); - // TODO: This probably isn't right: As far as RKWard is concerned, the active window will most likely be the tool window - // at this point, while the intention will be to get an active window that the tool should operate on. } KTextEditor::View *KatePluginIntegrationWindow::activateView(KTextEditor::Document *document) { RK_TRACE (APP); RKCommandEditorWindow* w = findWindowForDocument(document); if (w) { w->activate(); return w->getView(); } if (app->dummy_view && document == app->dummy_view->document()) return app->dummy_view; return 0; } KTextEditor::View *KatePluginIntegrationWindow::openUrl(const QUrl &url, const QString &encoding) { RK_TRACE (APP); RKMDIWindow *w = RKWorkplace::mainWorkplace()->openScriptEditor(url, encoding, RKSettingsModuleCommandEditor::matchesScriptFileFilter(url.fileName())); if (w) return static_cast(w)->getView(); RK_ASSERT(w); // should not happen return 0; } QObject *KatePluginIntegrationWindow::pluginView(const QString &name) { RK_TRACE (APP); return plugin_views.value(app->plugin(name)); } bool KatePluginIntegrationWindow::closeSplitView(KTextEditor::View* view) { RK_TRACE (APP); // TODO: This should close the area that this view is in, not necessarily the view itself. However, if the same doc // is also present in the area to merge into, then close this view, keeping the other. return closeView(view); } bool KatePluginIntegrationWindow::closeView(KTextEditor::View* view) { RK_TRACE (APP); RKMDIWindow *w = findWindowForView(view); if (w) return RKWorkplace::mainWorkplace()->closeWindow(w); return false; } bool KatePluginIntegrationWindow::hideToolView(QWidget* widget) { RK_TRACE (APP); RKMDIWindow *w = qobject_cast(widget); if (w) w->close(false); else { RK_ASSERT(w); widget->hide(); } return true; } /* These appear to be truly optional, so let's disable them for now. void KatePluginIntegrationWindow::hideViewBar(KTextEditor::View* view) {} void KatePluginIntegrationWindow::showViewBar(KTextEditor::View* view) {} void KatePluginIntegrationWindow::deleteViewBar(KTextEditor::View* view) {} void KatePluginIntegrationWindow::addWidgetToViewBar(KTextEditor::View* view, QWidget* bar) {} QWidget *KatePluginIntegrationWindow::createViewBar(KTextEditor::View *view) {} */ bool KatePluginIntegrationWindow::moveToolView(QWidget* widget, KTextEditor::MainWindow::ToolViewPosition pos) { RK_TRACE (APP); RKMDIWindow *w = qobject_cast(widget); if (w) { RKWorkplace::mainWorkplace ()->placeInToolWindowBar (w, pos); return true; } return false; } void KatePluginIntegrationWindow::splitView(Qt::Orientation orientation) { RK_TRACE (APP); RKWorkplace::mainWorkplace()->view()->splitView(orientation); } bool KatePluginIntegrationWindow::viewsInSameSplitView(KTextEditor::View* view1, KTextEditor::View* view2) { RK_TRACE (APP); // TODO not sure what the semantics of this really are. The two views are in the same view area (not visible, simultaneously), or in two areas split side-by-side? // However, this is essentially unused in kate. return false; } -void KatePluginIntegrationWindow::createPluginView(KTextEditor::Plugin* plugin) { +QObject* KatePluginIntegrationWindow::createPluginView(KTextEditor::Plugin* plugin) { RK_TRACE (APP); QObject *view = plugin->createView(main); plugin_views.insert(plugin, view); connect(plugin, &QObject::destroyed, [&]() { plugin_views.remove(plugin); }); + return view; } // TODO: Don't forget to make sure to emit all the signals! /// END KTextEditor::MainWindow interface #include "katepluginintegration.moc" diff --git a/rkward/windows/katepluginintegration.h b/rkward/windows/katepluginintegration.h index 4fb96724..4b706b5b 100644 --- a/rkward/windows/katepluginintegration.h +++ b/rkward/windows/katepluginintegration.h @@ -1,109 +1,109 @@ /*************************************************************************** katepluginintegration - description ------------------- begin : Mon Jun 12 2017 copyright : (C) 2017-2020 by Thomas Friedrichsmeier email : thomas.friedrichsmeier@kdemail.net ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #ifndef KATEPLUGININTEGRATION_H #define KATEPLUGININTEGRATION_H #include #include #include #include #include class KatePluginIntegrationWindow; /** This class provides implementations for the KTextEditor::Application interface. * Note that there is a separate interface KatePluginIntegrationWindow / KTextEditor::MainWindow that serves * as an abstraction of main windows. Even though - for now - we will be creating (detachable) plugins only in * one "main" window, we follow this separation to be on the safe side for future extensions (and also there is * a name-clash in on of the slots (openUrl()), otherwise. */ class KatePluginIntegrationApp : public QObject { Q_OBJECT public: KatePluginIntegrationApp(QObject *parent); ~KatePluginIntegrationApp(); QObject* loadPlugin(const QString& identifier); KatePluginIntegrationWindow *mainWindow() const { return window; }; private slots: friend class KatePluginIntegrationWindow; - void savePluginConfig(); + void saveConfigAndUnload(); // These are the implementations of the KTextEditor::Application interface. // NOTE that they are not technically overrides, but get invoked via QMetaObject::invokeMethod() QList mainWindows(); KTextEditor::MainWindow *activeMainWindow(); QList documents(); KTextEditor::Document *findUrl(const QUrl &url); KTextEditor::Document *openUrl(const QUrl &url, const QString &encoding = QString()); bool closeDocument(KTextEditor::Document *document); bool closeDocuments(const QList &documents); KTextEditor::Plugin *plugin(const QString &name); private: KatePluginIntegrationWindow *window; // For now, only one main window KTextEditor::Application *app; /** Provides a hidden dummy view (created on the fly as needed), for plugins that assume there is always at least one view/document around. */ KTextEditor::View *dummyView(); KTextEditor::View *dummy_view; struct PluginInfo { KPluginMetaData data; KTextEditor::Plugin *plugin; }; QMap known_plugins; QString idForPlugin(const KPluginMetaData &plugin) const; }; class KatePluginIntegrationWindow : public QObject, public KXMLGUIClient { Q_OBJECT public: KatePluginIntegrationWindow(KatePluginIntegrationApp *parent); KTextEditor::MainWindow *mainWindow() const { return main; }; private slots: // These are the implementations of the KTextEditor::MainWindow interface. // NOTE that they are not technically overrides, but get invoked via QMetaObject::invokeMethod() QWidget *createToolView(KTextEditor::Plugin *plugin, const QString &identifier, KTextEditor::MainWindow::ToolViewPosition pos, const QIcon &icon, const QString &text); KXMLGUIFactory *guiFactory(); QWidget *window(); QList views(); KTextEditor::View *activeView(); KTextEditor::View *activateView(KTextEditor::Document *document); KTextEditor::View *openUrl(const QUrl &url, const QString &encoding = QString()); bool closeView(KTextEditor::View *view); void splitView(Qt::Orientation orientation); bool closeSplitView(KTextEditor::View *view); bool viewsInSameSplitView(KTextEditor::View *view1, KTextEditor::View *view2); bool moveToolView(QWidget *widget, KTextEditor::MainWindow::ToolViewPosition pos); bool showToolView(QWidget *widget); bool hideToolView(QWidget *widget); QObject *pluginView(const QString &name); /* Apparently, these are truely optional, so let's disable them for the time being QWidget *createViewBar(KTextEditor::View *view); void deleteViewBar(KTextEditor::View *view); void showViewBar(KTextEditor::View *view); void hideViewBar(KTextEditor::View *view); void addWidgetToViewBar(KTextEditor::View *view, QWidget *bar); */ private: friend class KatePluginIntegrationApp; KTextEditor::MainWindow *main; - void createPluginView(KTextEditor::Plugin* plugin); + QObject* createPluginView(KTextEditor::Plugin* plugin); QHash plugin_views; KatePluginIntegrationApp *app; }; #endif