File indexing completed on 2024-04-28 16:26:30

0001 /***********************************************************************************
0002   Copyright (C) 2011-2012 by Holger Danielsson (holger.danielsson@versanet.de)
0003             (C) 2017-2019 by Michel Ludwig (michel.ludwig@kdemail.net)
0004  ***********************************************************************************/
0005 
0006 /***************************************************************************
0007  *                                                                         *
0008  *   This program is free software; you can redistribute it and/or modify  *
0009  *   it under the terms of the GNU General Public License as published by  *
0010  *   the Free Software Foundation; either version 2 of the License, or     *
0011  *   (at your option) any later version.                                   *
0012  *                                                                         *
0013  ***************************************************************************/
0014 
0015 #include "usermenu/usermenu.h"
0016 
0017 #include <QFile>
0018 #include <QRegExp>
0019 
0020 #include <QTemporaryFile>
0021 #include <KXMLGUIFactory>
0022 #include <QMenuBar>
0023 #include <QAction>
0024 #include <KMessageBox>
0025 #include <QFileDialog>
0026 
0027 #include "kileactions.h"
0028 #include "editorextension.h"
0029 #include "kileviewmanager.h"
0030 
0031 #include "kileconfig.h"
0032 #include "kiledebug.h"
0033 #include "utilities.h"
0034 
0035 
0036 namespace KileMenu {
0037 
0038 // The UserMenu uses six values/data structures:
0039 //
0040 //  - getUserMenu(): the menu with its entries/actions itself (QMenu *)
0041 //    (actions for menu items are named 'useraction-n', where n is a
0042 //     number starting at 0. It is also used as index for the m_menudata list.)
0043 //
0044 //  - m_menudata: a list, containing all info for menu item (QList<UserMenuData>)
0045 //
0046 //  - m_actioncollection: KActionCollection of KileMainWindow (KActionCollection *)
0047 //
0048 //  - m_actionlist: a list with all actions of the menu (QList<QAction *>)
0049 //
0050 //  - m_actionlistContextMenu: a list with all actions of the context menu for selected text (QList<QAction *>)
0051 //
0052 //  - a menu is defined in an xml file, which is placed in KileUtilities::locate(QStandardPaths::AppDataLocation, "usermenu", QStandardPaths::LocateDirectory)
0053 
0054 UserMenu::UserMenu(KileInfo *ki, QObject *receiver)
0055     : m_ki(ki), m_receiver(receiver), m_proc(Q_NULLPTR)
0056 {
0057     KXmlGuiWindow *mainwindow = m_ki->mainWindow();
0058     m_actioncollection = mainwindow->actionCollection();
0059 
0060     // add actions and menu entries
0061     m_wizardAction1 = new QAction(this);
0062     m_wizardAction1->setSeparator(true);
0063     m_wizardAction2 = createAction("wizard_usermenu");
0064 
0065     m_latexAction1 = new QAction(this);
0066     m_latexAction1->setSeparator(true);
0067     m_latexAction2 = createAction("wizard_usermenu2");
0068 
0069     m_latexMenuEntry = new QMenu(i18n("User Menu"));
0070     m_latexMenuEntry->setObjectName("usermenu-submenu");
0071 
0072     addSpecialActionsToMenus();
0073 
0074     // look for an existing menufile:
0075     // if filename matches 'basename.ext' then the file is placed in 'KILE-LOCAL-DIR/usermenu' directory
0076     m_currentXmlFile = KileConfig::userMenuFile();
0077     if ( !m_currentXmlFile.isEmpty() ) {
0078         if ( !m_currentXmlFile.contains("/") ) {
0079             m_currentXmlFile = KileUtilities::locate(QStandardPaths::AppDataLocation, "usermenu", QStandardPaths::LocateDirectory) + m_currentXmlFile;
0080         }
0081 
0082         if ( QFile(m_currentXmlFile).exists() ) {
0083             KILE_DEBUG_MAIN << "install menufile: " << m_currentXmlFile;
0084             installXml(m_currentXmlFile);
0085         }
0086         else {
0087             m_currentXmlFile.clear();
0088         }
0089     }
0090 
0091     updateUsermenuPosition();
0092 }
0093 
0094 UserMenu::~UserMenu()
0095 {
0096     delete m_proc;
0097 }
0098 
0099 bool UserMenu::isEmpty()
0100 {
0101     return (getMenuItem()->actions().size() == 0);
0102 }
0103 /////////////////////// install usermenu//////////////////////////////
0104 
0105 QAction *UserMenu::createAction(const QString &name)
0106 {
0107     QAction *action = m_actioncollection->addAction(name, m_receiver, SLOT(quickUserMenuDialog()));
0108     action->setText(i18n("Edit User Menu"));
0109     action->setIcon(QIcon::fromTheme("wizard_usermenu"));
0110     return action;
0111 }
0112 
0113 void UserMenu::addSpecialActionsToMenus()
0114 {
0115     KXmlGuiWindow *mainwindow = m_ki->mainWindow();
0116 
0117     // update wizard menu
0118     QMenu *wizard_menu = dynamic_cast<QMenu*>(mainwindow->guiFactory()->container("wizard", mainwindow));
0119     wizard_menu->addAction(m_wizardAction1);
0120     wizard_menu->addAction(m_wizardAction2);
0121 
0122     // update latex menu
0123     QMenu *latex_menu  = dynamic_cast<QMenu*>(mainwindow->guiFactory()->container("menu_latex", mainwindow));
0124     latex_menu->addAction(m_latexAction1);
0125     latex_menu->addAction(m_latexAction2);
0126     latex_menu->addMenu(m_latexMenuEntry);
0127 }
0128 
0129 void UserMenu::updateUsermenuPosition()
0130 {
0131     // and set the new one
0132     const bool show = !isEmpty() && m_ki->viewManager()->currentTextView();
0133     if(getUserMenuLocation() == StandAloneLocation) {
0134         setStandAloneMenuVisible(true, show);
0135     }
0136     else {
0137         setStandAloneMenuVisible(false, show);
0138     }
0139 }
0140 
0141 void UserMenu::setStandAloneMenuVisible(bool state, bool show)
0142 {
0143     m_wizardAction1->setVisible(state);
0144     m_wizardAction2->setVisible(state);
0145 
0146     m_latexAction1->setVisible(!state);
0147     m_latexAction2->setVisible(!state);
0148 
0149     m_latexMenuEntry->menuAction()->setVisible(!state && show);
0150 
0151     KXmlGuiWindow *mainwindow = m_ki->mainWindow();
0152     QMenu *standAloneMenu = dynamic_cast<QMenu*>(mainwindow->guiFactory()->container("menu_usermenu", mainwindow));
0153     if(standAloneMenu) {
0154         standAloneMenu->menuAction()->setVisible(state && show);
0155     }
0156 }
0157 
0158 ///////////////////////////// clear all data //////////////////////////////
0159 
0160 // clear all lists and data for an existing usermenu
0161 void UserMenu::clear()
0162 {
0163     // clear usermenu and menudata
0164     if(getMenuItem()) {
0165         getMenuItem()->clear();
0166     }
0167     m_menudata.clear();
0168 
0169     // remove all actions from actioncollection
0170     for(QAction *action : m_actionlist) {
0171         m_actioncollection->removeAction(action);
0172     }
0173 
0174     // clear actionlists
0175     m_actionlist.clear();
0176     m_actionlistContextMenu.clear();
0177 }
0178 
0179 ///////////////////////////// update GUI //////////////////////////////
0180 
0181 // repopulate the user menu and show it at the desired location
0182 void UserMenu::updateGUI()
0183 {
0184     KILE_DEBUG_MAIN << "updating usermenu ...";
0185 
0186     addSpecialActionsToMenus(); // adding actions twice has no effect
0187 
0188     // like installXmlFile(), but without updating KileConfig::userMenuFile
0189     // first clear old usermenu, menudata, actions and actionlists
0190     clear();
0191 
0192     // then install
0193     if(!m_currentXmlFile.isEmpty() && installXml(m_currentXmlFile)) {
0194         // add changed context menu to all existing views
0195         KileView::Manager* viewManager = m_ki->viewManager();
0196         int views = viewManager->textViewCount();
0197         for ( int i=0; i<views; ++i ) {
0198             viewManager->installContextMenu( viewManager->textView(i) );
0199         }
0200     }
0201 
0202     updateUsermenuPosition();
0203 }
0204 
0205 ///////////////////////////// update key bindings //////////////////////////////
0206 
0207 // shortcut dialog was called, so key bindings may have been changed
0208 void UserMenu::updateKeyBindings()
0209 {
0210     if ( m_currentXmlFile.isEmpty() && !QFile(m_currentXmlFile).exists() ) {
0211         return;
0212     }
0213 
0214     // new key bindings are found in kileui.rc (ActionProperties)
0215     // remove them, as they will be written into usermenu xml file
0216     removeActionProperties();
0217 
0218     // update xml file of current usermenu
0219     updateXmlFile(m_currentXmlFile);
0220 }
0221 
0222 QMenu* UserMenu::getMenuItem()
0223 {
0224 
0225     if(getUserMenuLocation() == StandAloneLocation) {
0226         KParts::MainWindow *mainWindow = m_ki->mainWindow();
0227         return dynamic_cast<QMenu*>(mainWindow->guiFactory()->container("menu_usermenu", mainWindow));
0228     }
0229     else {
0230         return m_latexMenuEntry;
0231     }
0232 }
0233 
0234 void UserMenu::removeActionProperties()
0235 {
0236     QString xmlfile = "kileui.rc";
0237     QString xml(KXMLGUIFactory::readConfigFile(xmlfile));
0238     if ( xml.isEmpty() ) {
0239         KILE_DEBUG_MAIN << "STOP: xmlfile not found: " << xmlfile;
0240         return;
0241     }
0242 
0243     QDomDocument doc;
0244     doc.setContent( xml );
0245 
0246     // process XML data in section 'ActionProperties'
0247     QDomElement actionPropElement = KXMLGUIFactory::actionPropertiesElement( doc );
0248     if ( actionPropElement.isNull() ) {
0249         KILE_DEBUG_MAIN << "QDomElement actionPropertiesElement not found ";
0250         return;
0251     }
0252 
0253     // search for all actions of the user-defined UserMenu
0254     KILE_DEBUG_MAIN << "QDomElement actionPropertiesElement found ";
0255     bool changed = false;
0256     QRegExp re("useraction-(\\d+)$");
0257     QDomElement e = actionPropElement.firstChildElement();
0258     while(!e.isNull()) {
0259         QString tag = e.tagName();
0260         if(tag != "Action") {
0261             continue;
0262         }
0263 
0264         QString shortcut = e.attribute("shortcut");
0265         QString name = e.attribute("name");
0266 
0267         QDomElement removeElement;
0268         if ( re.indexIn(name) == 0) {
0269             int index = re.cap(1).toInt();
0270             KILE_DEBUG_MAIN << "action property was changed: old=" << m_menudata[index].shortcut << " new=" << name << " actionIndex=" << index;
0271             removeElement = e;
0272             changed = true;
0273         }
0274 
0275         e = e.nextSiblingElement();
0276 
0277         // finally delete element
0278         if ( !removeElement.isNull() ) {
0279             KILE_DEBUG_MAIN << "remove ActionProperty: shortcut=" << shortcut << " name=" << name;
0280             actionPropElement.removeChild(removeElement);
0281         }
0282     }
0283 
0284     // Write back to XML file
0285     if ( changed ) {
0286         KXMLGUIFactory::saveConfigFile(doc,xmlfile);
0287     }
0288 }
0289 
0290 ///////////////////////////// update action properties (shortcuts) //////////////////////////////
0291 
0292 // Calling m_mainWindow->guiFactory()->refreshActionProperties() in kile.cpp removes all
0293 // user-defined action shortcuts and icons. Here they will be refreshed again.
0294 void UserMenu::refreshActionProperties()
0295 {
0296     KILE_DEBUG_MAIN << "refresh action properties";
0297 
0298     QRegExp re("useraction-(\\d+)$");
0299     foreach ( QAction *action, m_actionlist ) {
0300         if ( re.indexIn(action->objectName()) == 0 ) {
0301             int actionIndex = re.cap(1).toInt();
0302             if ( !m_menudata[actionIndex].icon.isEmpty() ) {
0303                 action->setIcon( QIcon::fromTheme(m_menudata[actionIndex].icon) );
0304             }
0305             if ( !m_menudata[actionIndex].shortcut.isEmpty() ) {
0306                 action->setShortcut( QKeySequence(m_menudata[actionIndex].shortcut,QKeySequence::NativeText) );
0307             }
0308         }
0309     }
0310 }
0311 
0312 // Before calling usermenu dialog, all user-defined action shortcuts must be removed,
0313 // or the dialog will give a lot of warnings. All shortcuts (even if changed) in the usermenu
0314 // will be refreshed again, when the dialog is finished
0315 void UserMenu::removeShortcuts()
0316 {
0317     foreach ( QAction *action, m_actionlist ) {
0318         action->setShortcut( QKeySequence() );
0319     }
0320 }
0321 
0322 ///////////////////////////// install/remove xml //////////////////////////////
0323 
0324 // call from the menu: no xml file given
0325 void UserMenu::installXmlMenufile()
0326 {
0327     KILE_DEBUG_MAIN << "install xml file with QFileDialog::getOpenFileName";
0328 
0329     QString directory = selectUserMenuDir();
0330     QString filter = i18n("User Menu Files (*.xml)");
0331 
0332     QString filename = QFileDialog::getOpenFileName(m_ki->mainWindow(), i18n("Select Menu File"), directory, filter);
0333     if(filename.isEmpty()) {
0334         return;
0335     }
0336 
0337     if( !QFile::exists(filename) ) {
0338         KMessageBox::error(m_ki->mainWindow(), i18n("File '%1' does not exist.", filename));
0339     }
0340     else {
0341         installXmlFile(filename);
0342     }
0343 }
0344 
0345 // SIGNAL from usermenu dialog: install new usermenu (xml file given)
0346 //
0347 // use 'basename.ext' if the file is placed in 'KILE-LOCAL-DIR/usermenu' directory and full filepath else
0348 void UserMenu::installXmlFile(const QString &filename)
0349 {
0350     KILE_DEBUG_MAIN << "install xml file" << filename;
0351 
0352     // clear old usermenu, menudata, actions and actionlists
0353     clear();
0354 
0355     if ( installXml(filename) ) {
0356         // update current xml filename (with path)
0357         m_currentXmlFile = filename;
0358 
0359         // save xml file in config (with or without path)
0360         QString xmlfile = filename;
0361         QString dir = KileUtilities::locate(QStandardPaths::AppDataLocation, "usermenu", QStandardPaths::LocateDirectory);
0362         if ( filename.startsWith(dir) ) {
0363             QString basename = filename.right( filename.length()-dir.length() );
0364             if ( !basename.isEmpty() && !basename.contains("/") )  {
0365                 xmlfile = basename;
0366             }
0367         }
0368         KileConfig::setUserMenuFile(xmlfile);
0369         emit (updateStatus());
0370 
0371         // add changed context menu to all existing views
0372         KileView::Manager* viewManager = m_ki->viewManager();
0373         int views = viewManager->textViewCount();
0374         for ( int i=0; i<views; ++i ) {
0375             viewManager->installContextMenu( viewManager->textView(i) );
0376         }
0377     }
0378 }
0379 
0380 void UserMenu::removeXmlFile()
0381 {
0382     KILE_DEBUG_MAIN << "remove xml file";
0383 
0384     clear();
0385     m_currentXmlFile.clear();
0386 
0387     KileConfig::setUserMenuFile(m_currentXmlFile);
0388     emit (updateStatus());
0389 }
0390 
0391 ///////////////////////////// install usermenu from XML //////////////////////////////
0392 
0393 // pre: usermenu is already cleared
0394 bool UserMenu::installXml(const QString &filename)
0395 {
0396     KILE_DEBUG_MAIN << "install: start";
0397 
0398     QMenu *userMenu = getMenuItem();
0399 
0400     if(!userMenu) {
0401         KILE_DEBUG_MAIN << "Hmmmm: found no usermenu";
0402         return false;
0403     }
0404 
0405     // read content of xml file
0406     QDomDocument doc("UserMenu");
0407     QFile file(filename);
0408     if ( !file.open(QFile::ReadOnly | QFile::Text) ) {
0409         // TODO KMessageBox
0410         KILE_DEBUG_MAIN << "STOP: can't open xml file " << filename;
0411         return false;
0412     }
0413 
0414     if( !doc.setContent( &file ) ) {
0415         file.close();
0416         return false;
0417     }
0418     file.close();
0419 
0420     KILE_DEBUG_MAIN << "parse xml ...";
0421     m_actionsContextMenu = 0;
0422 
0423     // parse toplevelitems
0424     int actionnumber = 0;
0425     QDomElement root = doc.documentElement();
0426     QDomElement e = root.firstChildElement();
0427     while ( !e.isNull()) {
0428         QString tag = e.tagName();
0429 
0430         if ( tag=="submenu" || tag=="separator") {
0431             if ( tag == "submenu" ) {
0432                 installXmlSubmenu(e, userMenu, actionnumber);
0433             }
0434             else { /* tag=="separator" */
0435                 userMenu->addSeparator();
0436             }
0437 
0438             // try to get some structure into to the context menu
0439             if ( m_actionsContextMenu > 0 ) {
0440                 m_actionlistContextMenu.append(Q_NULLPTR);
0441                 m_actionsContextMenu = 0;
0442             }
0443         }
0444         else { /* if ( tag == "menu" ) */
0445             installXmlMenuentry(e, userMenu, actionnumber);
0446         }
0447 
0448         e = e.nextSiblingElement();
0449     }
0450     KILE_DEBUG_MAIN << "install: finished ";
0451 
0452     return true;
0453 }
0454 
0455 // install a submenu item
0456 void UserMenu::installXmlSubmenu(const QDomElement &element, QMenu *parentmenu, int &actionnumber)
0457 {
0458     QMenu *submenu = parentmenu->addMenu(QString());
0459 
0460     if ( element.hasChildNodes() ) {
0461         QDomElement e = element.firstChildElement();
0462         while ( !e.isNull()) {
0463 
0464             QString tag = e.tagName();
0465             if ( tag == "title" ) {
0466                 QString title = e.text();
0467                 submenu->setTitle(title);
0468             }
0469             else if ( tag == "submenu" ) {
0470                 installXmlSubmenu(e,submenu,actionnumber);
0471             }
0472             else if ( tag == "separator" ) {
0473                 submenu->addSeparator();
0474             }
0475             else { /* if ( tag == "menu" ) */
0476                 installXmlMenuentry(e,submenu,actionnumber);
0477             }
0478 
0479             e = e.nextSiblingElement();
0480         }
0481     }
0482 }
0483 
0484 // install a standard menu item
0485 void UserMenu::installXmlMenuentry(const QDomElement &element, QMenu *parentmenu, int &actionnumber)
0486 {
0487     UserMenuData menudata;
0488 
0489     menudata.menutype  = UserMenuData::xmlMenuType( element.attribute("type") );
0490 
0491     // read values
0492     if ( element.hasChildNodes() ) {
0493         QDomElement e = element.firstChildElement();
0494         while ( !e.isNull()) {
0495             QString tag = e.tagName();
0496             QString text = e.text();
0497 
0498             int index = UserMenuData::xmlMenuTag(tag);
0499             switch (index) {
0500             case  UserMenuData::XML_TITLE:
0501                 menudata.menutitle = text;
0502                 break;
0503             case  UserMenuData::XML_PLAINTEXT:
0504                 menudata.text = UserMenuData::decodeLineFeed(text);
0505                 break;
0506             case  UserMenuData::XML_FILENAME:
0507                 menudata.filename = text;
0508                 break;
0509             case  UserMenuData::XML_PARAMETER:
0510                 menudata.parameter = text;
0511                 break;
0512             case  UserMenuData::XML_ICON:
0513                 menudata.icon = text;
0514                 break;
0515             case  UserMenuData::XML_SHORTCUT:
0516                 menudata.shortcut = text;
0517                 break;
0518             case  UserMenuData::XML_NEEDSSELECTION:
0519                 menudata.needsSelection   = str2bool(text);
0520                 break;
0521             case  UserMenuData::XML_USECONTEXTMENU:
0522                 menudata.useContextMenu   = str2bool(text);
0523                 break;
0524             case  UserMenuData::XML_REPLACESELECTION:
0525                 menudata.replaceSelection = str2bool(text);
0526                 break;
0527             case  UserMenuData::XML_SELECTINSERTION:
0528                 menudata.selectInsertion  = str2bool(text);
0529                 break;
0530             case  UserMenuData::XML_INSERTOUTPUT:
0531                 menudata.insertOutput     = str2bool(text);
0532                 break;
0533             }
0534 
0535             e = e.nextSiblingElement();
0536         }
0537     }
0538 
0539     // add menu item, if its title is not empty
0540     if ( !menudata.menutitle.isEmpty() ) {
0541 
0542         QAction *action = m_actioncollection->addAction(QString("useraction-%1").arg(actionnumber), this, SLOT(slotUserMenuAction()) );
0543         if ( action ) {
0544             action->setText(menudata.menutitle);
0545 
0546             if ( !menudata.icon.isEmpty() ) {
0547                 action->setIcon( QIcon::fromTheme(menudata.icon) );
0548             }
0549 
0550             if ( !menudata.shortcut.isEmpty() ) {
0551                 action->setShortcut( QKeySequence(menudata.shortcut,QKeySequence::PortableText) );
0552             }
0553 
0554             parentmenu->addAction(action);
0555             m_menudata.append(menudata);
0556             m_actionlist.append(action);
0557             if ( menudata.useContextMenu )  {
0558                 m_actionlistContextMenu.append(action);
0559                 m_actionsContextMenu++;
0560             }
0561 
0562             actionnumber++;
0563         }
0564     }
0565 }
0566 
0567 ///////////////////////////// update XML file //////////////////////////////
0568 
0569 // key bindings dialog was called, so the usermenu xml file must be updated, if one of these actions was changed
0570 // pre: xml file exists
0571 void UserMenu::updateXmlFile(const QString &filename)
0572 {
0573     KILE_DEBUG_MAIN << "update xml file: " << filename;
0574 
0575     // read content of xml file
0576     QDomDocument doc("UserMenu");
0577     QFile file(filename);
0578     file.open(QFile::ReadOnly | QFile::Text);
0579     doc.setContent(&file);
0580     file.close();
0581 
0582     KILE_DEBUG_MAIN << "parse xml ...";
0583 
0584     // parse toplevelitems
0585     bool changed = false;
0586     int actionnumber = 0;
0587     QDomElement root = doc.documentElement();
0588     QDomElement e = root.firstChildElement();
0589     while ( !e.isNull()) {
0590         QString tag = e.tagName();
0591         if ( tag == "submenu" ) {
0592             changed = changed || updateXmlSubmenu(doc,e,actionnumber);
0593         }
0594         else if ( tag == "menu" ) {
0595             changed = changed || updateXmlMenuentry(doc,e,actionnumber);
0596         }
0597         e = e.nextSiblingElement();
0598     }
0599     KILE_DEBUG_MAIN << "update finished ";
0600 
0601     if ( changed ) {
0602         KILE_DEBUG_MAIN << "found changes, so write updated xml file ";
0603         QFile outfile(filename);
0604         outfile.open(QFile::WriteOnly | QFile::Text);
0605         QTextStream stream(&outfile);
0606         doc.save(stream,3);
0607         outfile.close();
0608     }
0609 }
0610 
0611 // install a submenu item
0612 bool UserMenu::updateXmlSubmenu(QDomDocument &doc, QDomElement &element, int &actionnumber)
0613 {
0614     bool changed = false;
0615 
0616     if ( element.hasChildNodes() ) {
0617         QDomElement e = element.firstChildElement();
0618         while ( !e.isNull()) {
0619             QString tag = e.tagName();
0620             if ( tag == "submenu" ) {
0621                 changed = changed || updateXmlSubmenu(doc,e,actionnumber);
0622             }
0623             else if ( tag == "menu" )  {
0624                 changed = changed || updateXmlMenuentry(doc,e,actionnumber);
0625             }
0626 
0627             e = e.nextSiblingElement();
0628         }
0629     }
0630     return changed;
0631 }
0632 
0633 // install a standard menu item
0634 bool UserMenu::updateXmlMenuentry(QDomDocument &doc, QDomElement &element, int &actionnumber)
0635 {
0636     bool changed = false;
0637 
0638     // read values
0639     if ( element.hasChildNodes() ) {
0640         QDomElement oldElement;
0641         QDomElement e = element.firstChildElement();
0642         while ( !e.isNull()) {
0643             QString tag = e.tagName();
0644 
0645             if ( UserMenuData::xmlMenuTag(tag) == UserMenuData::XML_SHORTCUT) {
0646                 oldElement = e;
0647                 //oldText = e.text();    value not needed, is also in m_menudata[]
0648             }
0649 
0650             e = e.nextSiblingElement();
0651         }
0652 
0653         // keybindings dialog has already updated all actions
0654         QString currentShortcut = m_actionlist[actionnumber]->shortcut().toString(QKeySequence::PortableText);
0655         if ( currentShortcut != m_menudata[actionnumber].shortcut ) {
0656             // an existing shortcut always needs a new QDomElement
0657             if ( !currentShortcut.isEmpty() ) {
0658                 // create element with new shortcut
0659                 QDomElement newElement = doc.createElement( UserMenuData::xmlMenuTagName(UserMenuData::XML_SHORTCUT) );
0660                 QDomText newText = doc.createTextNode(currentShortcut);
0661                 newElement.appendChild(newText);
0662 
0663                 // replace existing node with new node
0664                 if ( !oldElement.isNull() ) {
0665                     element.replaceChild(newElement,oldElement);
0666                 }
0667                 // or insert a new node
0668                 else {
0669                     element.appendChild(newElement);
0670                 }
0671             }
0672             // or delete an existing QDomElement
0673             else {
0674                 element.removeChild(oldElement);
0675             }
0676             changed = true;
0677         }
0678     }
0679 
0680     actionnumber++;
0681     return changed;
0682 }
0683 
0684 ////////////////////////////// load dir for xml files (static) //////////////////////////////
0685 
0686 // - start with search for xml files in the local directory
0687 // - if no files are present, but in global directory, start with global
0688 // - if not a single file was found: back to local directory
0689 
0690 QString UserMenu::selectUserMenuDir()
0691 {
0692     QStringList dirs = KileUtilities::locateAll(QStandardPaths::AppDataLocation, "usermenu", QStandardPaths::LocateDirectory);
0693     if ( dirs.size() < 2 ) {
0694         return dirs.at(0);
0695     }
0696 
0697     QStringList namefilter = QStringList() << "*.xml";
0698     QString localDirName = dirs.at(0);
0699     QDir localDir = QDir(localDirName);
0700     QStringList localList = localDir.entryList (namefilter,QDir::Files | QDir::Readable);
0701     if ( localList.size() > 0 ) {
0702         return localDirName;
0703     }
0704 
0705     QDir globalDir = QDir(dirs.at(1));
0706     QStringList globalList = globalDir.entryList (namefilter,QDir::Files | QDir::Readable);
0707     return ( globalList.size() > 0 ) ? dirs.at(1) : localDirName;
0708 }
0709 
0710 ////////////////////////////// execUserMenuAction //////////////////////////////
0711 
0712 // an action was called from the usermenu:
0713 //  - identify action
0714 //  - find textview
0715 //  - execute action
0716 void UserMenu::slotUserMenuAction()
0717 {
0718     KILE_DEBUG_MAIN << "want to start an action from usermenu ...";
0719 
0720     QAction *action = dynamic_cast<QAction *>(sender());
0721     if ( !action ) {
0722         return;
0723     }
0724 
0725     QString actionName = action->objectName();
0726     KILE_DEBUG_MAIN << "action name: " << actionName << "classname=" << action->metaObject()->className();
0727 
0728     QRegExp re("useraction-(\\d+)$");
0729     if ( re.indexIn(actionName) != 0) {
0730         KILE_DEBUG_MAIN << "STOP: found wrong action name: " << actionName;
0731         return;
0732     }
0733 
0734     bool ok;
0735     int actionIndex = re.cap(1).toInt(&ok);
0736     if ( actionIndex < 0 || actionIndex >= m_menudata.size() ) {
0737         KILE_DEBUG_MAIN << "STOP: invalid action (range error): " << actionIndex << "  list size: " << m_menudata.size();
0738         return;
0739     }
0740 
0741     // check view and action requirements
0742     KTextEditor::View *view = m_ki->viewManager()->currentTextView();
0743 
0744     if ( !view ) {
0745         return;
0746     }
0747 
0748     if ( !view->selection() && m_menudata[actionIndex].needsSelection ) {
0749         return;
0750     }
0751 
0752     UserMenuData::MenuType type = m_menudata[actionIndex].menutype;
0753 
0754     if ( type == UserMenuData::Text ) {
0755         execActionText(view,m_menudata[actionIndex]);
0756     }
0757     else if ( type == UserMenuData::FileContent ) {
0758         execActionFileContent(view,m_menudata[actionIndex]);
0759     }
0760     else if ( type == UserMenuData::Program ) {
0761         execActionProgramOutput(view,m_menudata[actionIndex]);
0762     }
0763     else {
0764         KILE_DEBUG_MAIN << "STOP: unknown action type: " << type;
0765     }
0766 }
0767 
0768 ////////////////////////////// execActionText //////////////////////////////
0769 
0770 // execute an action: insert text
0771 void UserMenu::execActionText(KTextEditor::View *view, const UserMenuData &menudata)
0772 {
0773     KILE_DEBUG_MAIN << "want to insert text ... ";
0774     insertText(view, menudata.text, menudata.replaceSelection, menudata.selectInsertion);
0775 }
0776 
0777 ////////////////////////////// execActionFileContent //////////////////////////////
0778 
0779 // execute an action: insert file contents
0780 void UserMenu::execActionFileContent(KTextEditor::View *view, const UserMenuData &menudata)
0781 {
0782     KILE_DEBUG_MAIN << "want to insert contents of a file: " << menudata.filename;
0783 
0784     QFile file(menudata.filename);
0785     if ( !file.open(QFile::ReadOnly | QFile::Text) ) {
0786         KILE_DEBUG_MAIN << "STOP: could not open file " << menudata.filename;
0787         return;
0788     }
0789 
0790     QTextStream stream( &file );
0791     QString text = stream.readAll();
0792     file.close();
0793 
0794     if ( !text.isEmpty() ) {
0795         insertText(view, text, menudata.replaceSelection, menudata.selectInsertion);
0796     }
0797 }
0798 
0799 ////////////////////////////// execActionFileContent //////////////////////////////
0800 
0801 // execute an action: run a program
0802 void UserMenu::execActionProgramOutput(KTextEditor::View *view, const UserMenuData &menudata)
0803 {
0804     KILE_DEBUG_MAIN << "want to start a program ... ";
0805 
0806     // delete old process
0807     if (m_proc) {
0808         delete m_proc;
0809         m_proc = Q_NULLPTR;
0810     }
0811 
0812     // build commandline
0813     QString cmdline = menudata.filename + ' ' + menudata.parameter;
0814     bool useTemporaryFile = cmdline.contains("%M");
0815 
0816     bool needsSelection = menudata.needsSelection;
0817     bool hasSelection = view->selection();
0818 
0819     // check parameter
0820     if ( needsSelection && !hasSelection ) {
0821         KILE_DEBUG_MAIN << "STOP: this program needs selected text";
0822         return;
0823     }
0824 
0825     // do we need a temporary file for the selected text?
0826     if ( hasSelection && useTemporaryFile ) {
0827         KILE_DEBUG_MAIN << "selection and 'placeholder' %M found --> create temporary file";
0828 
0829         // create temporary file
0830         QTemporaryFile tempfile;
0831 //code was      tempfile.setSuffix(".txt");
0832 //Add to constructor and adapt if necessay: QDir::tempPath() + QLatin1String("/myapp_XXXXXX") + QLatin1String(".txt")
0833         tempfile.setAutoRemove(false);
0834 
0835         if ( !tempfile.open() ) {
0836             KILE_DEBUG_MAIN << "STOP: could not create tempfile for selected text" ;
0837             return;
0838         }
0839 
0840         // get filename
0841         QString selfile = tempfile.fileName();
0842 
0843         // write selection
0844         QTextStream stream( &tempfile );
0845         stream << view->selectionText() << "\n";
0846         tempfile.close();
0847 
0848         // update comamndline with temporary filename of selection
0849         cmdline.replace("%M",selfile);
0850     }
0851 
0852     // replace %F with the complete base name of the file without the path.
0853     // The complete base name consists of all characters in the file up to
0854     // (but not including) the last '.' character.
0855     if (  cmdline.contains("%S") ) {
0856         QFileInfo fi(view->document()->url().toLocalFile());
0857         QString basename = fi.completeBaseName();
0858         cmdline.replace("%S",basename);
0859     }
0860 
0861     m_proc = new KProcess(this);
0862     m_proc->setShellCommand(cmdline);
0863     m_proc->setOutputChannelMode(KProcess::MergedChannels);
0864     m_proc->setReadChannel(QProcess::StandardOutput);
0865 
0866     connect(m_proc, SIGNAL(readyReadStandardOutput()), this, SLOT(slotProcessOutput()));
0867     connect(m_proc, SIGNAL(readyReadStandardError()),  this, SLOT(slotProcessOutput()));
0868     connect(m_proc, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(slotProcessExited(int,QProcess::ExitStatus)));
0869 
0870     KILE_DEBUG_MAIN << "... start proc: " << cmdline;
0871     // init and/or save important data
0872     m_procOutput.clear();
0873     m_procView = view;
0874     m_procMenudata = &menudata;
0875 
0876     m_proc->start();
0877 }
0878 
0879 void UserMenu::slotProcessOutput()
0880 {
0881     m_procOutput += m_proc->readAll();
0882 }
0883 
0884 void UserMenu::slotProcessExited(int /* exitCode */, QProcess::ExitStatus exitStatus)
0885 {
0886     KILE_DEBUG_MAIN << "... finish proc ";
0887     KILE_DEBUG_MAIN << "output:  " << m_procOutput;
0888 
0889     if ( exitStatus == QProcess::NormalExit && m_procMenudata->insertOutput && !m_procOutput.isEmpty() ) {
0890         insertText(m_procView, m_procOutput, m_procMenudata->replaceSelection, m_procMenudata->selectInsertion);
0891     }
0892 }
0893 
0894 ////////////////////////////// auxiliary //////////////////////////////
0895 
0896 // action is finished, now insert some text
0897 void UserMenu::insertText(KTextEditor::View *view, const QString &text, bool replaceSelection, bool selectInsertion)
0898 {
0899     KILE_DEBUG_MAIN << "insert text from action: " << text;
0900     // metachars: %R - references (like \ref{%R}, \pageref{%R} ...)
0901     //            %T - citations  (like \cite{%T} ...)
0902     QString metachar,label;
0903     int actiontype =0;
0904 
0905     if(text.contains("%R")) {
0906         metachar = "%R";
0907         label = i18n("Label");
0908         actiontype = KileAction::FromLabelList;
0909     }
0910     else if(text.contains("%T")) {
0911         metachar = "%T";
0912         label = i18n("Reference");
0913         actiontype = KileAction::FromBibItemList;
0914     }
0915     if(!metachar.isEmpty()) {
0916         QStringList list = text.split(metachar);
0917 
0918         KileAction::InputTag tag(m_ki, i18n("Input Dialog"), QString(), QKeySequence(), m_receiver, SLOT(insertTag(KileAction::TagData)), m_actioncollection,"tag_temporary_action", m_ki->mainWindow(), actiontype, list.at(0)+metachar, list.at(1), list.at(0).length(), 0, QString(), label);
0919 
0920         tag.activate(QAction::Trigger);
0921         return;
0922     }
0923 
0924     // metachars: %B - bullet
0925     //            %M - selected text
0926     //            %C - place cursor
0927     //            %E - indent in environment
0928     QString ins = text;
0929     bool bullet = ins.contains("%B");
0930 
0931     // deselect and/or remove current selection
0932     if(view->selection()) {
0933         if(ins.contains("%M")) {
0934             ins.replace("%M", view->selectionText());
0935         }
0936         if(replaceSelection) {
0937             view->removeSelectionText();
0938         }
0939         else {
0940             view->removeSelection();
0941         }
0942     }
0943     else {
0944         ins.replace("%M", QString());
0945     }
0946     KILE_DEBUG_MAIN << " ---> " << ins;
0947 
0948     // insert new text
0949     KTextEditor::Cursor cursor1 = view->cursorPosition();
0950     emit( sendText(ins) );
0951 
0952     // select inserted text
0953     if(selectInsertion) {
0954         KTextEditor::Cursor cursor2 = view->cursorPosition();
0955         view->setSelection(KTextEditor::Range(cursor1, cursor2));
0956     }
0957 
0958     // text with bullet metachar %B
0959     if(bullet) {
0960         view->setCursorPosition(cursor1);
0961         m_ki->editorExtension()->gotoBullet(false, view);
0962     }
0963 }
0964 
0965 ////////////////////////////// auxiliary //////////////////////////////
0966 
0967 bool UserMenu::str2bool(const QString &value)
0968 {
0969     return ( value == "true" );
0970 }
0971 
0972 
0973 }
0974