Fri, 18 Apr 2014 19:45:32 +0200
Implemented most of the functionality.
--- a/.hgignore Fri Apr 18 16:15:44 2014 +0200 +++ b/.hgignore Fri Apr 18 19:45:32 2014 +0200 @@ -15,3 +15,4 @@ glob:tmp glob:__pycache__ glob:**.DS_Store +glob:**Ui_*.py
--- a/PluginSelectionEncloser.py Fri Apr 18 16:15:44 2014 +0200 +++ b/PluginSelectionEncloser.py Fri Apr 18 19:45:32 2014 +0200 @@ -0,0 +1,313 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2014 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the Selection Encloser plug-in. +""" + +from __future__ import unicode_literals # __IGNORE_WARNING__ + +import os +import json + +from PyQt4.QtCore import QObject, QTranslator, QCoreApplication +from PyQt4.QtGui import QAction, QMenu + +from E5Gui.E5Application import e5App + +import Preferences + +# Start-Of-Header +name = "Selection Encloser Plug-in" +author = "Detlev Offenbach <detlev@die-offenbachs.de>" +autoactivate = True +deactivateable = True +version = "0.1.0" +className = "SelectionEncloserPlugin" +packageName = "SelectionEncloser" +shortDescription = "Enclose the selection with a string." +longDescription = \ + """This plug-in implements a tool to enclose the selection of""" \ + """ the current editor with a string. The enclosing string is""" \ + """ selectable via a configurable menu hierarchy.""" +needsRestart = False +pyqtApi = 2 +python2Compatible = True +# End-Of-Header + +error = "" + +selectionEncloserPluginObject = None + + +def createSelectionEncloserPage(configDlg): + """ + Module function to create the Selection Encloser configuration page. + + @param configDlg reference to the configuration dialog + @return reference to the configuration page + """ + global selectionEncloserPluginObject + from SelectionEncloser.ConfigurationPage.SelectionEncloserPage import \ + SelectionEncloserPage + page = SelectionEncloserPage(selectionEncloserPluginObject) + return page + + +def getConfigData(): + """ + Module function returning data as required by the configuration dialog. + + @return dictionary containing the relevant data + """ + if e5App().getObject("UserInterface").versionIsNewer('5.2.99', '20121012'): + return { + "selectionEncloserPage": [ + QCoreApplication.translate("SelectionEncloserPlugin", + "Selection Encloser"), + os.path.join("SelectionEncloser", "icons", + "selectionEncloser.png"), + createSelectionEncloserPage, None, None], + } + else: + return {} + + +def prepareUninstall(): + """ + Module function to prepare for an uninstallation. + """ + Preferences.Prefs.settings.remove(SelectionEncloserPlugin.PreferencesKey) + + +class SelectionEncloserPlugin(QObject): + """ + Class implementing the Selection Encloser plugin. + """ + PreferencesKey = "SelectionEncloser" + + def __init__(self, ui): + """ + Constructor + + @param ui reference to the user interface object (UI.UserInterface) + """ + QObject.__init__(self, ui) + self.__ui = ui + + # menu is a list of lists; each list consists of a string for the + # submenu title and a list of submenu entries. The title of the submenu + # entry is the enclosing string. + defaultMenu = [ + [self.tr("Quotes"), ['"', "'", '"""', "'''"]], + ] + self.__defaults = { + "MenuHierarchy": json.dumps(defaultMenu), + } + + self.__translator = None + self.__loadTranslator() + + self.__initMenu() + + self.__editors = {} + + def activate(self): + """ + Public method to activate this plugin. + + @return tuple of None and activation status (boolean) + """ + global error + error = "" # clear previous error + + global selectionEncloserPluginObject + selectionEncloserPluginObject = self + + self.__ui.showMenu.connect(self.__populateMenu) + + e5App().getObject("ViewManager").editorOpenedEd.connect( + self.__editorOpened) + e5App().getObject("ViewManager").editorClosedEd.connect( + self.__editorClosed) + + for editor in e5App().getObject("ViewManager").getOpenEditors(): + self.__editorOpened(editor) + + return None, True + + def deactivate(self): + """ + Public method to deactivate this plugin. + """ + self.__ui.showMenu.disconnect(self.__populateMenu) + + e5App().getObject("ViewManager").editorOpenedEd.disconnect( + self.__editorOpened) + e5App().getObject("ViewManager").editorClosedEd.disconnect( + self.__editorClosed) + + for editor, acts in self.__editors.items(): + menu = editor.getMenu("Tools") + if menu is not None: + for act in acts: + menu.removeAction(act) + self.__editors = {} + + def __loadTranslator(self): + """ + Private method to load the translation file. + """ + if self.__ui is not None: + loc = self.__ui.getLocale() + if loc and loc != "C": + locale_dir = os.path.join( + os.path.dirname(__file__), "SelectionEncloser", "i18n") + translation = "selectionencloser_{0}".format(loc) + translator = QTranslator(None) + loaded = translator.load(translation, locale_dir) + if loaded: + self.__translator = translator + e5App().installTranslator(self.__translator) + else: + print("Warning: translation file '{0}' could not be" + " loaded.".format(translation)) + print("Using default.") + + def getPreferences(self, key): + """ + Public method to retrieve the various settings. + + @param key the key of the value to get (string) + @return the requested setting + """ + if key in ["MenuHierarchy"]: + return json.loads( + Preferences.Prefs.settings.value( + self.PreferencesKey + "/" + key, self.__defaults[key])) + else: + return Preferences.Prefs.settings.value( + self.PreferencesKey + "/" + key, self.__defaults[key]) + + def setPreferences(self, key, value): + """ + Public method to store the various settings. + + @param key the key of the setting to be set (string) + @param value the value to be set + """ + if key in ["MenuHierarchy"]: + Preferences.Prefs.settings.setValue( + self.PreferencesKey + "/" + key, json.dumps(value)) + else: + Preferences.Prefs.settings.setValue( + self.PreferencesKey + "/" + key, value) + + def __initMenu(self): + """ + Private method to initialize the menu. + """ + self.__menu = QMenu("Enclose Selection") + self.__menu.setEnabled(False) + self.__menu.aboutToShow.connect(self.__showMenu) + + def __populateMenu(self, name, menu): + """ + Private slot to populate the tools menu with our entry. + + @param name name of the menu (string) + @param menu reference to the menu to be populated (QMenu) + """ + if name != "Tools": + return + + editor = e5App().getObject("ViewManager").activeWindow() + + if not menu.isEmpty(): + menu.addSeparator() + + act = menu.addMenu(self.__menu) + act.setEnabled(editor is not None and editor.hasSelectedText()) + + def __editorOpened(self, editor): + """ + Private slot called, when a new editor was opened. + + @param editor reference to the new editor (QScintilla.Editor) + """ + menu = editor.getMenu("Tools") + if menu is not None: + self.__editors[editor] = [] + if not menu.isEmpty(): + act = menu.addSeparator() + self.__editors[editor].append(act) + act = menu.addMenu(self.__menu) + self.__menu.setEnabled(True) + self.__editors[editor].append(act) + editor.showMenu.connect(self.__editorShowMenu) + + def __editorClosed(self, editor): + """ + Private slot called, when an editor was closed. + + @param editor reference to the editor (QScintilla.Editor) + """ + try: + del self.__editors[editor] + if not self.__editors: + self.__menu.setEnabled(False) + except KeyError: + pass + + def __editorShowMenu(self, menuName, menu, editor): + """ + Private slot called, when the the editor context menu or a submenu is + about to be shown. + + @param menuName name of the menu to be shown (string) + @param menu reference to the menu (QMenu) + @param editor reference to the editor + """ + if menuName == "Tools": + for act in self.__editors[editor]: + if not act.isSeparator(): + act.setEnabled(editor.hasSelectedText()) + + def __showMenu(self): + """ + Private slot to build the menu hierarchy. + """ + self.__menu.clear() + hierarchy = self.getPreferences("MenuHierarchy") + for menuTitle, entries in hierarchy: + submenu = QMenu(menuTitle, self.__menu) + for entry in entries: + act = submenu.addAction(entry, self.__encloseSelection) + act.setData(entry) + self.__menu.addMenu(submenu) + + def __encloseSelection(self): + """ + Private slot to enclose the selection with the selected string. + """ + act = self.sender() + if act is None or not isinstance(act, QAction): + return + + editor = e5App().getObject("ViewManager").activeWindow() + if editor is None: + return + + if not editor.hasSelectedText(): + return + + string = act.data() + if not string: + return + + newText = string + editor.selectedText() + string + editor.beginUndoAction() + editor.replaceSelectedText(newText) + editor.endUndoAction()
--- a/SelectionEncloser.e4p Fri Apr 18 16:15:44 2014 +0200 +++ b/SelectionEncloser.e4p Fri Apr 18 19:45:32 2014 +0200 @@ -12,16 +12,24 @@ <Author>Detlev Offenbach</Author> <Email>detlev@die-offenbachs.de</Email> <TranslationPattern>SelectionEncloser/i18n/selectionencloser_%language%.ts</TranslationPattern> + <Eol index="-1"/> <Sources> <Source>__init__.py</Source> <Source>PluginSelectionEncloser.py</Source> + <Source>SelectionEncloser/__init__.py</Source> + <Source>SelectionEncloser/ConfigurationPage/__init__.py</Source> + <Source>SelectionEncloser/ConfigurationPage/SelectionEncloserPage.py</Source> </Sources> - <Forms/> + <Forms> + <Form>SelectionEncloser/ConfigurationPage/SelectionEncloserPage.ui</Form> + </Forms> <Translations/> <Resources/> <Interfaces/> <Others> <Other>.hgignore</Other> + <Other>SelectionEncloser/icons/topAdd.png.png</Other> + <Other>SelectionEncloser/icons/selectionEncloser.png</Other> </Others> <MainScript>PluginSelectionEncloser.py</MainScript> <Vcs> @@ -141,5 +149,6 @@ <FiletypeAssociation pattern="*.ts" type="TRANSLATIONS"/> <FiletypeAssociation pattern="*.ui" type="FORMS"/> <FiletypeAssociation pattern="*.ui.h" type="FORMS"/> + <FiletypeAssociation pattern="Ui_*.py" type="__IGNORE__"/> </FiletypeAssociations> </Project>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SelectionEncloser/ConfigurationPage/SelectionEncloserPage.py Fri Apr 18 19:45:32 2014 +0200 @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- + +""" +Module implementing Selection Encloser configuration page. +""" + +from __future__ import unicode_literals # __IGNORE_WARNING__ + +import os + +from PyQt4.QtCore import pyqtSlot, Qt +from PyQt4.QtGui import QTreeWidgetItem, QInputDialog, QLineEdit + +from Preferences.ConfigurationPages.ConfigurationPageBase import \ + ConfigurationPageBase +from .Ui_SelectionEncloserPage import Ui_SelectionEncloserPage + +import UI.PixmapCache + + +class SelectionEncloserPage(ConfigurationPageBase, Ui_SelectionEncloserPage): + """ + Class implementing Selection Encloser configuration page. + """ + def __init__(self, plugin): + """ + Constructor + + @param plugin reference to the plugin object + """ + super(SelectionEncloserPage, self).__init__() + self.setupUi(self) + self.setObjectName("SelectionEncloserPage") + + self.addButton.setIcon(UI.PixmapCache.getIcon("plus.png")) + self.deleteButton.setIcon(UI.PixmapCache.getIcon("minus.png")) + self.upButton.setIcon(UI.PixmapCache.getIcon("1uparrow.png")) + self.downButton.setIcon(UI.PixmapCache.getIcon("1downarrow.png")) + self.addMenuButton.setIcon(UI.PixmapCache.getIcon( + os.path.join("SelectionEncloser", "icons", "topAdd.png"))) + + self.addButton.setEnabled(False) + self.deleteButton.setEnabled(False) + self.upButton.setEnabled(False) + self.downButton.setEnabled(False) + + self.__plugin = plugin + + # set initial values + hierarchy = self.__plugin.getPreferences("MenuHierarchy") + for menuTitle, entries in hierarchy: + top = QTreeWidgetItem(self.menuTree, [menuTitle]) + top.setFlags(top.flags() | Qt.ItemIsEditable) + for entry in entries: + itm = QTreeWidgetItem(top, [entry]) + itm.setFlags(itm.flags() | Qt.ItemIsEditable) + top.setExpanded(True) + + def save(self): + """ + Public slot to save the Selection Encloser configuration. + """ + hierarchy = [] + for topIndex in range(self.menuTree.topLevelItemCount()): + topItem = self.menuTree.topLevelItem(topIndex) + topEntry = [topItem.text(0), []] + for index in range(topItem.childCount()): + itm = topItem.child(index) + topEntry[1].append(itm.text(0)) + hierarchy.append(topEntry) + self.__plugin.setPreferences("MenuHierarchy", hierarchy) + + @pyqtSlot() + def on_addMenuButton_clicked(self): + """ + Private slot to add a top level menu item. + """ + menuTitle, ok = QInputDialog.getText( + self, + self.tr("Menu Title"), + self.tr("Enter menu title:"), + QLineEdit.Normal) + if ok and menuTitle: + top = QTreeWidgetItem(self.menuTree, [menuTitle]) + top.setFlags(top.flags() | Qt.ItemIsEditable) + top.setExpanded(True) + + @pyqtSlot() + def on_addButton_clicked(self): + """ + Private slot to add a menu entry. + """ + entry, ok = QInputDialog.getText( + self, + self.tr("Menu Entry"), + self.tr("Enter menu entry text:"), + QLineEdit.Normal) + if ok and entry: + itm = QTreeWidgetItem(self.menuTree.selectedItems()[0], [entry]) + itm.setFlags(itm.flags() | Qt.ItemIsEditable) + + @pyqtSlot() + def on_deleteButton_clicked(self): + """ + Private slot to delete the selected entry. + """ + itm = self.menuTree.selectedItems()[0] + parent = itm.parent() + if parent is None: + index = self.menuTree.indexOfTopLevelItem(itm) + self.menuTree.takeTopLevelItem(index) + else: + index = parent.indexOfChild(itm) + parent.takeChild(index) + del itm + + @pyqtSlot() + def on_upButton_clicked(self): + """ + Slot documentation goes here. + """ + # TODO: not implemented yet + raise NotImplementedError + + @pyqtSlot() + def on_downButton_clicked(self): + """ + Slot documentation goes here. + """ + # TODO: not implemented yet + raise NotImplementedError + + @pyqtSlot() + def on_menuTree_itemSelectionChanged(self): + """ + Private slot handling the selection of an item. + """ + if len(self.menuTree.selectedItems()) == 0: + self.addButton.setEnabled(False) + self.deleteButton.setEnabled(False) + self.upButton.setEnabled(False) + self.downButton.setEnabled(False) + else: + addEnable = True + upEnable = True + downEnable = True + itm = self.menuTree.selectedItems()[0] + parent = itm.parent() + if parent is None: + # top level item + if self.menuTree.indexOfTopLevelItem(itm) == 0: + upEnable = False + if self.menuTree.indexOfTopLevelItem(itm) == \ + self.menuTree.topLevelItemCount() - 1: + downEnable = False + else: + # sub item + if parent.indexOfChild(itm) == 0: + upEnable = False + if parent.indexOfChild(itm) == \ + parent.childCount() - 1: + downEnable = False + addEnable = False + self.addButton.setEnabled(addEnable) + self.deleteButton.setEnabled(True) + self.upButton.setEnabled(upEnable) + self.downButton.setEnabled(downEnable)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SelectionEncloser/ConfigurationPage/SelectionEncloserPage.ui Fri Apr 18 19:45:32 2014 +0200 @@ -0,0 +1,120 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>SelectionEncloserPage</class> + <widget class="QWidget" name="SelectionEncloserPage"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>482</width> + <height>363</height> + </rect> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QLabel" name="headerLabel"> + <property name="text"> + <string><b>Configure Selection Encloser</b></string> + </property> + </widget> + </item> + <item> + <widget class="Line" name="line15"> + <property name="frameShape"> + <enum>QFrame::HLine</enum> + </property> + <property name="frameShadow"> + <enum>QFrame::Sunken</enum> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Menu Hierarchy:</string> + </property> + </widget> + </item> + <item> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" rowspan="6"> + <widget class="QTreeWidget" name="menuTree"> + <property name="alternatingRowColors"> + <bool>true</bool> + </property> + <property name="headerHidden"> + <bool>true</bool> + </property> + <column> + <property name="text"> + <string notr="true">1</string> + </property> + </column> + </widget> + </item> + <item row="0" column="1"> + <widget class="QToolButton" name="addMenuButton"> + <property name="toolTip"> + <string>Press to add a new top level menu item</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QToolButton" name="addButton"> + <property name="toolTip"> + <string>Press to add a new menu item</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QToolButton" name="deleteButton"> + <property name="toolTip"> + <string>Press to delete the selected item</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QToolButton" name="upButton"> + <property name="toolTip"> + <string>Press to move the selected entry up</string> + </property> + </widget> + </item> + <item row="4" column="1"> + <widget class="QToolButton" name="downButton"> + <property name="toolTip"> + <string>Press to move the selected entry down</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + </layout> + </item> + </layout> + </widget> + <tabstops> + <tabstop>menuTree</tabstop> + <tabstop>addMenuButton</tabstop> + <tabstop>addButton</tabstop> + <tabstop>deleteButton</tabstop> + <tabstop>upButton</tabstop> + <tabstop>downButton</tabstop> + </tabstops> + <resources/> + <connections/> +</ui>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SelectionEncloser/ConfigurationPage/__init__.py Fri Apr 18 19:45:32 2014 +0200 @@ -0,0 +1,8 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2014 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package implementing the Selection Encloser plug-in configuration page. +"""
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/SelectionEncloser/__init__.py Fri Apr 18 19:45:32 2014 +0200 @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2014 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Package implementing the Selection Encloser plug-in data and configuration +dialog. +"""