Alternative script organizer for Python macro

Creating Extension - Shared Libraries
Forum rules
For sharing working examples of macros / scripts. These can be in any script language supported by OpenOffice.org [Basic, Python, Netbean] or as source code files in Java or C# even - but requires the actual source code listing. This forum is not for asking questions about writing your own macros.

Alternative script organizer for Python macro

Postby hanya » Sun Jul 22, 2012 7:27 pm

When I execute Python macro even through Script Organizer for Python entry in the main menu, all script providers are instantiated. All language engine for macro are initialized and script provider for each context for all language engine are instantiated. Because script organizer uses css.script.browse.theBrowseNodeFactory singleton to create MACROORGANIZER view and to load script nodes.
This does not happen when I execute Python macro from menu item. I wrote a toy, alternative script organizer dialog for Python.

Code: Select all   Expand viewCollapse view
import uno
import unohelper
import traceback
import sys

try:
    import pythonscript
except:
    import pythonloader
    pythonscript = None
    for url, module in pythonloader.g_loadedComponents.iteritems():
        if url.endswith("script-provider-for-python/pythonscript.py"):
            pythonscript = module
    if pythonscript is None:
        raise Exception("Failed to find pythonscript module.")

from com.sun.star.awt import XActionListener, XMouseListener, \
    XKeyListener, Rectangle, Selection
from com.sun.star.awt.tree import XTreeExpansionListener
from com.sun.star.uno import Exception as UNOException

# Default content of the new file.
TEMPLATE = """"""


class DialogBase(object):
    """ Base class for dialog. """
    def __init__(self, ctx):
        self.ctx = ctx
   
    def create(self, name):
        """ Create service instance. """
        return self.ctx.getServiceManager().createInstanceWithContext(
            name, self.ctx)


class RuntimeDialogBase(DialogBase):
    """ Runtime dialog base. """
   
    def __init__(self, ctx):
        DialogBase.__init__(self, ctx)
        self.dialog = None
   
    def _result(self):
        """ Returns result. """
        return None
   
    def _init(self):
        """ Initialize, create dialog and controls. """
   
    def execute(self):
        """ Execute to show this dialog.
        None return value should mean canceled.
        """
        self._init()
        result = None
        self.dialog.setVisible(True)
        if self.dialog.execute():
            result = self._result()
        self.dialog.dispose()
        return result
   
    def create_control(self, name, type, pos, size,
                        prop_names, prop_values, full_name=False):
        """ Create and insert control. """
        if not full_name:
            type = "com.sun.star.awt.UnoControl" + type + "Model"
        dialog_model = self.dialog.getModel()
        model = dialog_model.createInstance(type)
        if prop_names and prop_values:
            model.setPropertyValues(prop_names, prop_values)
        dialog_model.insertByName(name, model)
        ctrl = self.dialog.getControl(name)
        ctrl.setPosSize(pos[0], pos[1], size[0], size[1], 15)
        return ctrl
       
    def create_dialog(self, title, pos=None, size=None, parent=None):
        """ Create base dialog. """
        dialog = self.create("com.sun.star.awt.UnoControlDialog")
        dialog_model = self.create("com.sun.star.awt.UnoControlDialogModel")
        dialog.setModel(dialog_model)
        if isinstance(size, tuple) and len(size) == 2:
            dialog.setPosSize(0, 0, size[0], size[1], 12)
        if isinstance(pos, tuple) and len(pos) == 2:
            dialog.setPosSize(pos[0], pos[1], 0, 0, 3)
        elif parent:
            pass
        dialog.setTitle(title)
        self.dialog = dialog
   
    def create_label(self, name, command, pos, size,
                        prop_names=None, prop_values=None, action=None):
        """ Create and add new label. """
        label = self.create_control(name, "Label", pos, size,
                    prop_names, prop_values)
       
   
    def create_button(self, name, command, pos, size,
                        prop_names=None, prop_values=None, action=None):
        """ Create and add new button. """
        btn = self.create_control(name, "Button", pos, size,
                    prop_names, prop_values)
        btn.setActionCommand(command)
        if action:
            btn.addActionListener(action)
   
    def create_edit(self, name, pos, size,
                        prop_names=None, prop_values=None):
        """ Create and add new edit control. """
        edit = self.create_control(name, "Edit", pos, size,
            prop_names, prop_values)
   
   
    def create_tree(self, name, pos, size,
                        prop_names=None, prop_values=None):
        """ Create and add new tree. """
        self.create_control(name,
            "com.sun.star.awt.tree.TreeControlModel",
            pos, size, prop_names, prop_values, full_name=True)
   
    def get(self, name):
        """ Returns specified control by name. """
        return self.dialog.getControl(name)
   
    def get_text(self, name):
        """ Returns value of Text attribute specified by name. """
        return self.dialog.getControl(name).getModel().Text
   
    def set_focus(self, name):
        """ Set focus to the control specified by the name. """
        self.dialog.getControl(name).setFocus()


class NameInput(RuntimeDialogBase):
    """ Input dialog. """
    MARGIN = 3
    BUTTON_WIDTH  = 80
    BUTTON_HEIGHT = 26
    HEIGHT = MARGIN * 3 + BUTTON_HEIGHT * 2
    WIDTH = 300
    EDIT_NAME = "edit_name"
   
    def __init__(self, ctx, title, default="", parent=None):
        RuntimeDialogBase.__init__(self, ctx)
        self.title = title
        self.default = default
        self.parent = parent
   
    def _init(self):
        margin = self.MARGIN
        self.create_dialog(self.title, size=(self.WIDTH, self.HEIGHT))
        self.create_edit(self.EDIT_NAME,
            pos=(margin, margin),
            size=(self.WIDTH - margin * 2, self.BUTTON_HEIGHT),
            prop_names=("HideInactiveSelection", "Text",),
            prop_values=(True, self.default,))
        self.create_button("btn_ok", "ok",
            pos=(self.WIDTH - self.BUTTON_WIDTH * 2 - margin * 2,
                    self.BUTTON_HEIGHT + margin * 2),
            size=(self.BUTTON_WIDTH, self.BUTTON_HEIGHT),
            prop_names=("DefaultButton", "Label", "PushButtonType",),
            prop_values=(True, "OK", 1))
        self.create_button("btn_cancel", "cancel",
            pos=(self.WIDTH - self.BUTTON_WIDTH - margin,
                    self.BUTTON_HEIGHT + margin * 2),
            size=(self.BUTTON_WIDTH, self.BUTTON_HEIGHT),
            prop_names=("Label", "PushButtonType"), prop_values=("Cancel", 2))
        self.set_focus(self.EDIT_NAME)
        if self.parent:
            self.dialog.createPeer(self.parent.getToolkit(), self.parent)
        if self.default:
            self.get(self.EDIT_NAME).setSelection(Selection(0, len(self.default)))
   
    def _result(self):
        return self.get_text("edit_name")


class FileOpenDialog(DialogBase):
    """ To get file url to open. """
    def __init__(self, ctx, **kwds):
        DialogBase.__init__(self, ctx)
        self.args = kwds
   
    def execute(self):
        fp = self.create("com.sun.star.ui.dialogs.FilePicker")
        args = self.args
        if "title" in args:
            fp.setTitle(args["title"])
        if "default" in args:
            default = args["default"]
            fp.setDefaultName(self._substitute_variables(default))
        if "directory" in args:
            fp.setDisplayDirectory(args["directory"])
        if "filters" in args:
            for title, filter in args["filters"]:
                fp.appendFilter(title, filter)
        result = None
        if fp.execute():
            result = fp.getFiles()[0]
        return result
   
    def _substitute_variables(self, uri):
        return self.create("com.sun.star.util.PathSubstitution").\
            substituteVariables(uri, True)


class MessageDialog(DialogBase):
    """ Shows message in standard message box. """
    def __init__(self, ctx, parent, **kwds):
        DialogBase.__init__(self, ctx)
        self.parent = parent
        self.args = kwds
   
    def execute(self):
        args = self.args
        type = args.get("type", "messbox")
        buttons = args.get("buttons", 1)
        title = args.get("title", "")
        message = args.get("message", "")
       
        toolkit = self.parent.getToolkit()
        dialog = toolkit.createMessageBox(
            self.parent, Rectangle(), type, buttons, title, message)
        n = dialog.execute()
        dialog.dispose()
        return n


def join_url(base, name, name_encode=True):
    """ Join name to base URL. """
    if name_encode:
        _name = name
    else:
        _name = unohelper.systemPathToFileUrl(name)
    if base.endswith("/"):
        return base + _name
    return base + "/" + _name


def base_url(url):
    """ Returns directory of URL. """
    if url.startswith( OrganizerDialog.DOC_PROTOCOL):
        return "/".join(url.split("/")[:-1])
    else:
        return unohelper.absolutize(url, "../")


class ErrorMessageDialog(RuntimeDialogBase):
    """ Shows error message in custom dialog with selectable text. """
    MARGIN = 3
    BUTTON_WIDTH  = 80
    BUTTON_HEIGHT = 26
    EDIT_HEIGHT = 300
    HEIGHT = EDIT_HEIGHT + MARGIN * 5 + BUTTON_HEIGHT + MARGIN
    WIDTH = 420
    EDIT_NAME = "edit_name"
   
    ERROR_ICON = "private:standardimage/error"
   
    def __init__(self, ctx, **kwds):
        RuntimeDialogBase.__init__(self, ctx)
        self.args = kwds
   
    def _init(self):
        args = self.args
        title = args.get("title", "")
        message = args.get("message", "")
       
        margin = self.MARGIN
        self.create_dialog(title, size=(self.WIDTH, self.HEIGHT))
       
        self.create_edit("edit_message",
            pos=(margin * 4, margin * 4),
            size=(self.WIDTH - margin * 8, self.EDIT_HEIGHT),
            prop_names=("Border", "MultiLine", "PaintTransparent",
                        "ReadOnly", "VScroll"),
            prop_values=(0, True, True, True, True))
        self.get("edit_message").getModel().Text = message
        self.create_button("btn_ok", "ok",
            pos=((self.WIDTH - self.BUTTON_WIDTH)/2,
                    self.HEIGHT - self.BUTTON_HEIGHT - margin),
            size=(self.BUTTON_WIDTH, self.BUTTON_HEIGHT),
            prop_names=("DefaultButton", "Label", "PushButtonType",),
            prop_values=(True, "OK", 1))


class ErrorAsMessage(Exception):
    """ Tracked error message will be shown for user. """
    pass


class NodeManager(object):
    """ Maps between tree node and script node. """
    LOADED = 0x100000
    SCRIPT = 0x700000
    MASK   =  0xfffff
    TYPE_MASK = 0xf00000
   
    def __init__(self):
        self.nodes = [] # to avoid adapter creation for each node
   
    def _node_set(self, tree_node, node, script=False):
        """ Set script node for tree_node. """
        self.nodes.append(node)
        i = len(self.nodes) -1
        if script:
            i |= self.SCRIPT
        tree_node.DataValue = i
   
    def _node_get(self, tree_node):
        """ Get script node for tree_node. """
        try:
            i = tree_node.DataValue
            if isinstance(i, int) or isinstance(i, long):
                return self.nodes[i & self.MASK]
        except:
            pass
   
    def _node_delete(self, tree_node):
        """ Delete script node for tree_node. """
        try:
            i = tree_node.DataValue
            if isinstance(i, int):
                node = self.nodes[i & sel.MASK]
                self.nodes[i] = None
        except:
            pass
   
    def _node_is_script(self, tree_node):
        """ Check the node is script for tree_node. """
        return (tree_node.DataValue & self.TYPE_MASK) == self.SCRIPT
   
    def _node_set_loaded(self, tree_node):
        """ Set loaded flag. """
        tree_node.DataValue = tree_node.DataValue | self.LOADED
   
    def _node_is_loaded(self, tree_node):
        """ Check loaded flag. """
        return (tree_node.DataValue & self.TYPE_MASK) == self.LOADED


class OrganizerDialog(NodeManager, RuntimeDialogBase):
    """ Alternative organizer dialog for Python scripts. """
   
    TITLE = "Python Scripts"
    TREE_NAME = "tree"
    FILE_EXT = ".py"
    DOC_PROTOCOL = "vnd.sun.star.tdoc"
    SCRIPT_PROTOCOL = "vnd.sun.star.script"
    DEFAULT_NAME = "macro"
   
    DISK_ICON = "private:graphicrepository/res/harddisk_16.png"
    DOC_ICON = "private:graphicrepository/res/sx03150.png"
    DIR_ICON = "private:graphicrepository/res/fileopen.png"
    FILE_ICON = "private:graphicrepository/res/im30820.png"
    SCRIPT_ICON = "private:graphicrepository/res/im30821.png"
   
    MARGIN = 3
    BUTTON_WIDTH  = 80
    BUTTON_HEIGHT = 26
    TREE_HEIGHT = 250
    HEIGHT = TREE_HEIGHT + BUTTON_HEIGHT + MARGIN * 3
    WIDTH = 300
   
    ENABLE_EDIT = False
    ENABLE_DEBUG = False
   
    def __init__(self, ctx,
                user_provider, share_provider, document_provider,
                parent, show_icon=False):
        NodeManager.__init__(self)
        RuntimeDialogBase.__init__(self, ctx)
        self.parent = parent
        self.user_provider = user_provider
        self.share_provider = share_provider
        self.document_provider = document_provider
        self.show_icon = show_icon
        self.tree = None
        self.menu = None
   
    def execute(self, history=None):
        """ Show dialog with history representes script in URI form. """
        self._create_ui()
        self._set_history(history)
        result = None
        self.dialog.setVisible(True)
        n = self.dialog.execute()
        if n:
            result = self.tree_get_selected_node_uri()
        self.tree = None
        self.dialog.dispose()
        return result
   
    def button_pushed(self, command):
        try:
            tree_node = self.tree_get_selected_node()
            if tree_node:
                node = self._node_get(tree_node)
                getattr(self, "exec_" + command)(tree_node, node)
        except ErrorAsMessage, e:
            MessageDialog(self.ctx, self.dialog.getPeer(),
                type="errorbox", title="Error", message=str(e)).execute()
        except Exception, e:
            print(e)
            traceback.print_exc()
   
    def exec_execute(self, tree_node, node):
        """ Execute selected macro. """
        if self._node_is_script(tree_node):
            self.dialog.endDialog(1)
   
    def exec_menu(self, tree_node, node):
        """ Shows dropdown menu. """
        if not self.menu:
            self._create_menu()
        menu = self.menu
       
        if tree_node:
            if isinstance(node, pythonscript.ScriptBrowseNode):
                states = (False, False, self.ENABLE_EDIT, False,
                        False, True, self.ENABLE_DEBUG)
            elif isinstance(node, pythonscript.FileBrowseNode):
                states = [False, False, self.ENABLE_EDIT, False,
                        True, True, False]
                if node.uri.startswith(self.DOC_PROTOCOL):
                    states[3] = True
            elif isinstance(node, pythonscript.DirBrowseNode):
                states = (True, True, False, False, True, True, False)
            else:
                states = (True, True, False, False, False, False, False)
            for i, state in enumerate(states):
                menu.enableItem(i +1, state)
       
        btn = self.dialog.getControl("btn_menu")
        n = menu.execute(
                btn.getContext().getPeer(), btn.getPosSize(), 0)
        if n > 0:
            self.button_pushed(menu.getCommand(n))
   
    def exec_create_file(self, tree_node, node):
        """ Create new file under selected node. """
        if isinstance(node, pythonscript.PythonScriptProvider):
            _node = node
            node = node.dirBrowseNode
        elif not isinstance(node, pythonscript.DirBrowseNode):
            return
        name = self._input_name("New File Name")
        if not name is None and not name == "":
            uri = join_url(node.rootUrl, name)
            if not uri.endswith(self.FILE_EXT):
                uri += self.FILE_EXT
            sfa = node.provCtx.sfa
            if sfa.exists(uri):
                raise ErrorAsMessage("File already exists: \n" + name)
            is_doc = uri.startswith(self.DOC_PROTOCOL)
            try:
                if is_doc:
                    io = self.create("com.sun.star.io.Pipe")
                else:
                    io = sfa.openFileWrite(uri)
            except Exception, e:
                raise ErrorAsMessage(str(e))
            try:
                if TEMPLATE or is_doc:
                    text_out = self.create(
                                "com.sun.star.io.TextOutputStream")
                    text_out.setOutputStream(io)
                    text_out.setEncoding("UTF-8")
                    text_out.writeString(TEMPLATE)
                    if is_doc:
                        text_out.closeOutput()
                        sfa.writeFile(uri, io)
            except Exception, e:
                raise ErrorAsMessage(str(e))
            finally:
                if is_doc:
                    io.closeInput()
                else:
                    io.closeOutput()
           
            child_node = pythonscript.FileBrowseNode(
                                    node.provCtx, uri, name)
            self._create_new_tree_node(
                    tree_node, name, False, child_node)
   
    def exec_create_dir(self, tree_node, node):
        """ Create new directory under selected node. """
        if isinstance(node, pythonscript.PythonScriptProvider):
            _node = node
            node = node.dirBrowseNode
        elif not isinstance(node, pythonscript.DirBrowseNode):
            return
        name = self._input_name("New Directory Name", default="directory")
        if not name is None and not name == "":
            uri = join_url(node.rootUrl, name)
            sfa = node.provCtx.sfa
            if sfa.exists(uri):
                raise ErrorAsMessage("Directory already exists: \n" + name)
            try:
                sfa.createFolder(uri)
                child_node = pythonscript.DirBrowseNode(
                                    node.provCtx, name, uri)
                self._create_new_tree_node(
                        tree_node, name, False, child_node, True)
            except Exception, e:
                raise ErrorAsMessage(str(e))
   
    def exec_substitute(self, tree_node, node):
        """ Substitute script file in documents. """
        if not isinstance(node, pythonscript.FileBrowseNode):
            return
        if not node.uri.startswith(self.DOC_PROTOCOL):
            return
        url = FileOpenDialog(self.ctx,
            default="$(user)/Scripts/python",
            filters=(("All Files (*.*)", "*.*"),
                     ("Python Script (*.py)", "*.py"),)).execute()
        if not url is None:
            sfa = node.provCtx.sfa
            if not sfa.exists(url):
                raise ErrorAsMessage("File did not find: \n" + url)
            try:
                sfa.kill(node.uri)
                sfa.copy(url, node.uri)
            except Exception, e:
                raise ErrorAsMessage(str(e))
   
    def exec_rename(self, tree_node, node):
        """ Rename selected file. """
        if not (isinstance(node, pythonscript.FileBrowseNode) or \
                isinstance(node, pythonscript.DirBrowseNode)):
            return
        current = node.getName()
        name = self._input_name("Rename File", current)
        if not name is None or not name == "":
            if isinstance(node, pythonscript.FileBrowseNode):
                uri = node.uri
            elif isinstance(node, pythonscript.DirBrowseNode):
                uri = node.rootUrl
            new_uri = join_url(base_url(uri), name)
            if not new_uri.endswith(self.FILE_EXT):
                new_uri += self.FILE_EXT
            sfa = node.provCtx.sfa
            if not sfa.exists(uri):
                raise ErrorAsMessage("Source file did not exist: \n" + current)
            if sfa.exists(new_uri):
                raise ErrorAsMessage("File exists: \n" + name)
            try:
                sfa.move(uri, new_uri)
                tree_node.setDisplayValue(name)
                node.name = name
            except Exception, e:
                raise ErrorAsMessage(str(e))
   
    def exec_delete(self, tree_node, node):
        """ Delete selected node. """
        if not (isinstance(node, pythonscript.FileBrowseNode) or \
                isinstance(node, pythonscript.DirBrowseNode)):
            return
        name = node.getName()
        n = MessageDialog(self.ctx, self.dialog.getPeer(),
                buttons=2, title="Delete File",
                message="Do you want to delete file: \n" + name).execute()
        if n == 1:
            if isinstance(node, pythonscript.FileBrowseNode):
                uri = node.uri
            elif isinstance(node, pythonscript.DirBrowseNode):
                uri = node.rootUrl
            sfa = node.provCtx.sfa
            try:
                sfa.kill(uri)
                self._node_delete(tree_node)
                parent_node = tree_node.getParent()
                parent_node.removeChildByIndex(
                        parent_node.getIndex(tree_node))
            except Exception, e:
                raise ErrorAsMessage(str(e))
   
    def exec_edit(self, tree_node, node):
        pass
   
    def exec_debug(self, tree_node, node):
        pass
   
    class ListenerBase(unohelper.Base):
        def __init__(self, act):
            self.act = act
        def disposing(self):
            self.act = None
   
    class ActionListener(ListenerBase, XActionListener):
        def actionPerformed(self, ev):
            self.act.button_pushed(ev.ActionCommand)
   
    class KeyListener(ListenerBase, XKeyListener):
        def keyPressed(self, ev): pass
        def keyReleased(self, ev):
            if ev.KeyCode == 1280:
                self.act._key_pressed()
   
    def _key_pressed(self):
        node = self.tree_get_selected_node()
        if node and self._node_is_script(node):
            self.dialog.endDialog(1)
   
    class MouseListener(ListenerBase, XMouseListener):
        def mouseReleased(self, ev): pass
        def mouseEntered(self, ev): pass
        def mouseExited(self, ev): pass
        def mousePressed(self, ev):
            if ev.ClickCount == 2 and ev.Buttons == 1:
                self.act._mouse_pressed(ev)
   
    def _mouse_pressed(self, ev):
        if ev.ClickCount == 2 and ev.Buttons == 1:
            node = self.tree_get_selected_node()
            if node and self._node_is_script(node):
                self.dialog.endDialog(1)
   
    class TreeExpansionListener(ListenerBase, XTreeExpansionListener):
        def treeExpanding(self, ev): pass
        def treeCollapsing(self, ev): pass
        def treeExpanded(self, ev): pass
        def treeCollapsed(self, ev): pass
        def requestChildNodes(self, ev):
            try:
                node = ev.Node
                if node:
                    self.act.node_requested(node)
            except ErrorAsMessage, e:
                MessageDialog(self.act.ctx, self.act.dialog.getPeer(),
                    type="errorbox", title="Error", message=str(e)).execute()
            except Exception, e:
                print(e)
                traceback.print_exc()
   
    def node_requested(self, tree_node):
        """ Add children for the tree_node at expanding. """
        data_model = self.tree.getModel().DataModel
        node = self._node_get(tree_node)
        try:
            child_nodes = node.getChildNodes()
        except Exception, e:
            raise ErrorAsMessage(str(e))
        if node and not tree_node.getChildCount() and child_nodes:
            show_icon = self.show_icon
            child_ondemand = True
            is_script = False
            if isinstance(node, pythonscript.ScriptBrowseNode):
                return
            elif isinstance(node, pythonscript.FileBrowseNode):
                child_ondemand = False
                is_script = True
            nodes = [(child.getName(), child) for child in child_nodes]
            for name, child in sorted(nodes):
                tree_child = data_model.createNode(name, child_ondemand)
                if show_icon:
                    if isinstance(child, pythonscript.ScriptBrowseNode):
                        icon = self.SCRIPT_ICON
                    elif isinstance(child, pythonscript.FileBrowseNode):
                        icon = self.FILE_ICON
                    elif isinstance(child, pythonscript.DirBrowseNode):
                        icon = self.DIR_ICON
                    else:
                        icon = self.FILE_ICON
                    self._set_node_icon(tree_child, icon)
                tree_node.appendChild(tree_child)
                self._node_set(tree_child, child, is_script)
            self._node_set_loaded(tree_node)
   
    def _input_name(self, title="", default=DEFAULT_NAME):
        """ Let user input new name. """
        return NameInput(self.ctx, title, default, self.dialog.getPeer()).execute()
   
    def _set_history(self, history):
        """ Show and select node by history. """
        if history and history.startswith(self.SCRIPT_PROTOCOL):
            parts = history[len(self.SCRIPT_PROTOCOL)+1:].split("?", 1)
            if not len(parts) == 2 or parts[0].find("$") == -1:
                return
            params = parts[1].split("&")
            if not "language=Python" in params:
                return
            location = None
            for param in params:
                if param.startswith("location="):
                    location = param[9:]
                    break
            provider = None
            if hasattr(self, location + "_provider"):
                provider = getattr(self, location + "_provider")
            if provider:
                parent_node = self._get_tree_node(location)
                self.node_requested(parent_node)
                self._expand_node(parent_node)
               
                path, func = parts[0].split("$", 1)
                paths = path.split("|")
                if paths[-1].endswith(self.FILE_EXT):
                    paths[-1] = paths[-1][:-len(self.FILE_EXT)]
                paths.append(func)
                tree_node = None
                for path in paths:
                    for i in range(parent_node.getChildCount()):
                        tree_node = parent_node.getChildAt(i)
                        if tree_node.getDisplayValue() == \
                                    uno.fileUrlToSystemPath(path):
                            parent_node = tree_node
                            self.node_requested(tree_node)
                            self._expand_node(parent_node)
                            break
                if tree_node:
                    self.tree_select_node(tree_node)
   
    def tree_get_selected_node(self):
        """ Returns selected tree node. """
        tree_node = self.tree.getSelection()
        if not isinstance(tree_node, tuple):
            return tree_node
        return None
   
    def tree_get_selected_node_uri(self):
        """ Returns script uri if selected node is script node. """
        tree_node = self.tree_get_selected_node()
        if self._node_is_script(tree_node):
            node = self._node_get(tree_node)
            if node:
                return node.getPropertyValue("URI")
   
    def tree_select_node(self, tree_node):
        """ Select tree node. """
        self.tree.select(tree_node)
   
    def _create_new_tree_node(self, tree_parent_node,
                    name, ondemand, node, directory=False, select=True):
        """ Create new tree node under tree_parent_node. """
        if not self._node_is_loaded(tree_parent_node):
            self.node_requested(tree_parent_node)
            return
        tree_node = self.tree.getModel().DataModel.createNode(name, ondemand)
        if self.show_icon:
            if directory:
                self._set_node_icon(tree_node, self.FILE_ICON)
            else:
                self._set_node_icon(tree_node, self.FILE_ICON)
        tree_parent_node.appendChild(tree_node)
        self._node_set(tree_node, node)
        if select:
            self.tree.select(tree_node)
   
    def _create_menu(self):
        """ Create popup menu. """
        menu = self.create("com.sun.star.awt.PopupMenu")
        menu.hideDisabledEntries(True)
        self.menu = menu
        items = (
            (1, 0, 0, "Create File", "create_file"),
            (2, 1, 0, "Create Directory", "create_dir"),
            (None, 2),
            (3, 3, 0, "Edit", "edit"),
            (4, 4, 0, "Substitute", "substitute"),
            (5, 5, 0, "Rename", "rename"),
            (None, 6),
            (6, 7, 0, "Delete", "delete"),
            (None, 8),
            (7, 9, 0, "Debug", "debug")
        )
        for i in items:
            if i[0] is None or i[0] == -1:
                menu.insertSeparator(i[1])
            else:
                menu.insertItem(i[0], i[3], i[2], i[1])
                menu.setCommand(i[0], i[4])
   
    def _set_node_icon(self, node, icon):
        """ Set node icon. """
        node.setExpandedGraphicURL(icon)
        node.setCollapsedGraphicURL(icon)
   
    def _expand_node(self, tree_node):
        """ Request to expand node. """
        self.tree.expandNode(tree_node)
   
    def _get_tree_node(self, type):
        tree_node = None
        root_node = self.tree.getModel().DataModel.getRoot()
        n = -1
        if type == "user":
            if self.user_provider:
                n = 0
        elif type == "share":
            if self.share_provider:
                n = 0
                if self.user_provider:
                    n = 1
        elif type == "document":
            if self.document_provider:
                n = 0
                if self.user_provider:
                    n += 1
                if self.share_provider:
                    n += 1
        if n >= 0:
            tree_node = root_node.getChildAt(n)
        return tree_node
   
    def _create_ui(self):
        """ Create dialog ui. """
        btn_action = self.ActionListener(self)
       
        margin = self.MARGIN
        btn_size = self.BUTTON_WIDTH, self.BUTTON_HEIGHT
        btn_width, btn_height = btn_size
        btn_prop_names = ("Label", "PushButtonType")
        self.create_dialog(self.TITLE, size=(self.WIDTH, self.HEIGHT))
        self.create_tree(self.TREE_NAME,
                (margin, btn_height + margin * 2),
                (self.WIDTH - 2 * margin, self.TREE_HEIGHT),
                ("Tabstop",), (True,))
        self.create_button("btn_execute", "execute",
                (margin, margin), btn_size,
                btn_prop_names, ("Execute", 0), btn_action)
        self.create_button("btn_menu", "menu",
                (btn_width + margin * 2, margin), btn_size,
                btn_prop_names, ("Menu", 0), btn_action)
        self.create_button("btn_close", "",
                (self.WIDTH - margin - btn_width, margin), btn_size,
                btn_prop_names, ("Close", 2))
       
        tree = self.dialog.getControl(self.TREE_NAME)
        self.tree = tree
        tree_model = tree.getModel()
        data_model = self.create("com.sun.star.awt.tree.MutableTreeDataModel")
        tree_model.DataModel = data_model
       
        root = data_model.createNode("ROOT", False)
        data_model.setRoot(root)
       
        def add_node(name, provider, icon):
            node = data_model.createNode(name, True)
            if self.show_icon:
                self._set_node_icon(node, icon)
            root.appendChild(node)
            self._node_set(node, provider)
       
        if self.user_provider:
            add_node("user", self.user_provider, self.DISK_ICON)
        if self.share_provider:
            add_node("share", self.share_provider, self.DISK_ICON)
        if self.document_provider:
            add_node("Document", self.document_provider, self.DOC_ICON)
       
        tree_model.SelectionType = 1
        tree_model.RootDisplayed = False
       
        tree.addTreeExpansionListener(self.TreeExpansionListener(self))
        tree.addMouseListener(self.MouseListener(self))
        tree.addKeyListener(self.KeyListener(self))
        tree.setFocus()
       
        parent = self.parent
        ps = parent.getPosSize()
        self.dialog.setPosSize(
            (ps.Width - self.WIDTH)/2, (ps.Height - self.HEIGHT)/2, 0, 0, 3)
        self.dialog.createPeer(parent.getToolkit(), parent)


class PythonScriptOrganizer(object):
    """ Alternative script organizer for Python scripting. """
   
    History = None # URI of previous executed script
   
    def __init__(self, ctx,
            show_user=True, show_share=False, show_doc=True, show_icon=True):
        self.ctx = ctx
        self.show_icon = show_icon
        self.show_user = show_user
        self.show_share = show_share
        self.doc = None
        if show_doc:
            self.doc = self._get_active_doc_uri()
        self.user_sp = None
        self.share_sp = None
        self.document_sp = None
   
    def _get_active_doc_uri(self):
        """ Returns internal uri for the current document. """
        doc = self._get_desktop().getCurrentComponent()
        uri = self._get_document_uri(doc)
        if uri:
            return uri.rstrip("/")
   
    def _get_desktop(self):
        """ Returns desktop. """
        return self.ctx.getServiceManager().createInstanceWithContext(
            "com.sun.star.frame.Desktop", self.ctx)
   
    def _get_active_frame(self):
        """ Returns active frame. """
        return self._get_desktop().ActiveFrame
   
    def _store_history(self, result):
        """ Store history. """
        self.__class__.History = result
   
    def _get_document_uri(self, doc):
        """ Get document transient URI. """
        tddc = self.ctx.getServiceManager().createInstanceWithContext(
        "com.sun.star.frame.TransientDocumentsDocumentContentFactory", self.ctx)
        try:
            content = tddc.createDocumentContent(doc)
            id = content.getIdentifier()
            return id.getContentIdentifier()
        except:
            pass
   
    def execute(self):
        """ Show dialog. """
        PythonScriptProvider = pythonscript.PythonScriptProvider
        if self.show_user:
            self.user_sp = PythonScriptProvider(self.ctx, u"user")
        if self.show_share:
            self.share_sp = PythonScriptProvider(self.ctx, u"share")
        if self.doc:
            self.document_sp = PythonScriptProvider(self.ctx, self.doc)
        dialog = OrganizerDialog(
                    self.ctx,
                    self.user_sp, self.share_sp, self.document_sp,
                    self._get_active_frame().getContainerWindow(),
                    self.show_icon)
        result = dialog.execute(self.__class__.History)
        if result:
            self._store_history(result)
            self.run(result)
   
    def _get_provider(self, uri):
        """ Find specific provider. """
        for p in uri.split("&"):
            if p.startswith("location="):
                try:
                    return getattr(self, p[9:] + "_sp")
                except:
                    pass
   
    def run(self, uri):
        """ Run script specified by script uri. """
        try:
            sp = self._get_provider(uri)
            if sp:
                s = sp.getScript(uri)
                if s:
                    s.invoke((), (), ())
                else:
                    raise ErrorAsMessage("No script found for: \n" + uri)
            else:
                raise ErrorAsMessage("No provider found for: \n" + uri)
        except UNOException, e:
            ErrorMessageDialog(
                self.ctx, title="Error", message=str(e)).execute()
        except ErrorAsMessage, e:
            MessageDialog(self.ctx, self._get_active_frame().getContainerWindow(),
                type="errorbox", title="Error", message=str(e)).execute()


def user(*args):
    """ User's script only. """
    ctx = XSCRIPTCONTEXT.getComponentContext()
    pso = PythonScriptOrganizer(ctx, True, False, False, True)
    pso.execute()


def doc(*args):
    """ Both user's and Document's scripts are shown. """
    ctx = XSCRIPTCONTEXT.getComponentContext()
    pso = PythonScriptOrganizer(ctx, True, False, True, True)
    pso.execute()

g_exportedScripts = user, doc


Store above script in user_preference/Scripts/python/aso.py and assign "user" or "doc" function to use this in somewhere you want.

Supported functions:
- Create new script file or directory
- Substitute script file embedded in a document
- Delete script file and directory
- Rename script file and directory

Any Python macros installed by extensions are not shown now.

 Edit: Fix according to karolus's post. 
Last edited by hanya on Wed Nov 13, 2013 9:50 am, edited 1 time in total.
Please, edit this thread's initial post and add "[Solved]" to the subject line if your problem has been solved.
Apache OpenOffice 4-dev on Xubuntu 14.04
hanya
Volunteer
 
Posts: 879
Joined: Fri Nov 23, 2007 9:27 am
Location: Japan

Re: Alternative script organizer for Python macro

Postby karolus » Mon Jul 23, 2012 4:21 pm

Hallo Hanya

Failed first with Libreoffice3.5 plus installed Extension ModifiedPythonScriptProvider-0.4.3.oxt

I've fixed it with:
____________________________________________________
except:
    import pythonloader
    pythonscript = None
    for url, module in pythonloader.g_loadedComponents.iteritems():
      if url.endswith("script-provider-for-python/pythonscript.py")\
      or url.endswith("ModifiedPythonScriptProvider.py"):

        pythonscript = module
_____________________________________________________

Thank you, Hanya !

Karo
AOO4, Libreoffice - 5.1 … 5.3.2.2 on Linux Mint17
User avatar
karolus
Volunteer
 
Posts: 843
Joined: Sat Jul 02, 2011 9:47 am

Re: Alternative script organizer for Python macro

Postby hanya » Mon Jul 23, 2012 5:29 pm

Failed first with Libreoffice3.5 plus installed Extension ModifiedPythonScriptProvider-0.4.3.oxt
Thanks for mentioning about it. I forgot it. But if you use this alternative dialog, you do not need ModifiedPythonScriptProvider anymore except for editing function.

If you want to execute edit function with this, change ENABLE_EDIT field of OrganizerDialog calss to True and implement exec_edit instance method for your needs. For user and share macro, URL to real file can be get with node.uri and convert it to system path and then pass to your favorite editor. For macro embedded in a document, it can be like that watching timestamp for temporaly file created with macro content and updating embedded file with modified data. But substituting embedded file with existing file is better choice, in my oppinion.
Debug function is prepared to implement by the user also if you have a tool for that. But writing Python macro as RPC script and debugging is easier way to do that.
Please, edit this thread's initial post and add "[Solved]" to the subject line if your problem has been solved.
Apache OpenOffice 4-dev on Xubuntu 14.04
hanya
Volunteer
 
Posts: 879
Joined: Fri Nov 23, 2007 9:27 am
Location: Japan

Re: Alternative script organizer for Python macro

Postby Tommy » Sat Nov 10, 2012 2:05 am

@hanya
very nice script. thanks 4 sharing
-----
using latest X-LibreOffice release, made portable by winPenPack.com
http://www.winpenpack.com/main/download.php?view.1354
User avatar
Tommy
 
Posts: 252
Joined: Sun Dec 23, 2007 2:44 pm

Re: Alternative script organizer for Python macro

Postby karolus » Thu Nov 07, 2013 5:36 pm

Hallo

@Hanya
I've found Error in Line ~244

replace
Code: Select all   Expand viewCollapse view
    if url.startswith(self.DOC_PROTOCOL):

by
Code: Select all   Expand viewCollapse view
    if url.startswith( OrganizerDialog.DOC_PROTOCOL):


thanks
Karolus
AOO4, Libreoffice - 5.1 … 5.3.2.2 on Linux Mint17
User avatar
karolus
Volunteer
 
Posts: 843
Joined: Sat Jul 02, 2011 9:47 am

Re: Alternative script organizer for Python macro

Postby hanya » Wed Nov 13, 2013 9:51 am

Hi,
@karolus
Thanks. Fixed in the above code.
Please, edit this thread's initial post and add "[Solved]" to the subject line if your problem has been solved.
Apache OpenOffice 4-dev on Xubuntu 14.04
hanya
Volunteer
 
Posts: 879
Joined: Fri Nov 23, 2007 9:27 am
Location: Japan

Re: Alternative script organizer for Python macro

Postby hubert lambert » Wed Nov 30, 2016 1:26 pm

Hello,

I recently discover this script from Hanya, which I find just awesome !
This script, IMHO, deserves far better visibility. As a start point, I made some tweaks in the code and packaged it in a extension for easier distribution.

Changes in the code are minor:
- localisation (by now EN, DE, FR and IT) [added HU thanks to Zizi64];
- two new menu commands "copy to document" and "export" from someone else document (code reused froma Hanya's);
- quick (and dirty) implementation of "exec_edit" so that script can be opened by the system default editor associated with py files (it would be nice here to merge EditorKicker code).

The extension adds a new element "Organise python script" under menu Tools/Macros with default shortcut Alt+Shift+F11.

Note that all this is only "packaging work", core remains Hanya's code.

OXT is available on the dedicated post of the french forum, with some more details and screenshots.

This is a first draft, but quite functional. Hope this could be useful to the community...
Last edited by hubert lambert on Thu Dec 01, 2016 11:15 pm, edited 2 times in total.
AOOo 4.1.2 on Win7 | LibreOffice on various Linux systems
hubert lambert
 
Posts: 59
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Postby Zizi64 » Thu Dec 01, 2016 1:24 am

I just added the Hungarian localization to the recent .oxt
I hope it will work. I tried the modified .oxt in "hu" and "en" locale settings.

If you needed, I can upload the added/modified files of the archive separated too.

apso_v0.3_plus_hu.oxt
(15.52 KiB) Downloaded 87 times
Tibor Kovacs, Hungary; LO4.4.7, LO5.3.7 on Win7x64Prof.
PortableApps, WinPenPack: LO3.3.0-LO5.4.2 and AOO4.1.3
Please, edit the topic's initial post, and add the word "[Solved]" at the beginning of the subject line - if your problem has been solved.
User avatar
Zizi64
Volunteer
 
Posts: 6060
Joined: Wed May 26, 2010 7:55 am
Location: Budapest, Hungary

Re: Alternative script organizer for Python macro

Postby hubert lambert » Thu Dec 01, 2016 11:13 pm

Hi Zizi64,

It works all fine, I've uploaded a version 0.4 with this locale.
Thank you very much!
AOOo 4.1.2 on Win7 | LibreOffice on various Linux systems
hubert lambert
 
Posts: 59
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Postby hubert lambert » Tue Jan 31, 2017 2:52 pm

I merged Hanya's EditorKicker code and the corresponding lines of his ModifiedPythonScriptProvider.

The new release of the APSO extension has therefore some new features :
- a setting page (optional) for providing a custom text editor and some file opening parameters (i.e. EditorKicker);
- macro opens to the right line provided the above settings are given;
- error dialogs give direct access to the erroneous line and offset in case of syntax error (again provided the above settings are given);
- quick edition of embedded scripts is possible (but only in the context of APSO extension);
- improved error dialogs in case of non-ascii message;
- terminology fits the default organizer one;
- the document property AllowMacroExecution is evaluated and taken in account before execution.

Some localized strings have been rewritten. Please advise me for any error or suggestion, thanks.
AOOo 4.1.2 on Win7 | LibreOffice on various Linux systems
hubert lambert
 
Posts: 59
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Postby Ratslinger » Thu May 11, 2017 7:11 am

Hanya & hubert lambert Much thanks to both for this fine tool. Just started with Python and have had questions on organizing until now. Super easy to install and use. One less thing to think about.
LibreOffice 5.4.1.2
OpenOffice 4.0.1
Xubuntu 16.04
Mint 18.2
Ratslinger
 
Posts: 13
Joined: Sun Mar 01, 2015 3:34 am

Re: Alternative script organizer for Python macro

Postby hubert lambert » Sun Jun 18, 2017 9:56 pm

Hi,

APSO extension has now its own page on the LibreOffice extension site : https://extensions.libreoffice.org/exte ... for-python.
Next releases will henceforth be available from that address.
Last edited by hubert lambert on Mon Jun 19, 2017 10:05 am, edited 1 time in total.
AOOo 4.1.2 on Win7 | LibreOffice on various Linux systems
hubert lambert
 
Posts: 59
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Postby DanPadric » Sun Jun 18, 2017 11:15 pm

Hello Hubert.

As luck would have it, just today I was looking into running Python Macros and stumbled onto your note. I am very excited to try out your efforts on my system.

APSO extension has now is own page on the LibreOffice extension site

A suggestion if you allow me.

I have only the basics for now.. that means IDLE. I assumed to need to point the extension to the editor. Under LibreOffice > Extension Manager > :: APSO 1.0.0 :: > Options > APSO > EditorKicker > Editor > C:\Python36\Lib\idlelib\idle.bat

Very probable I did something wrong. I am hoping you add a note on how to add the editor of your choice in the extension description notes.

Regards.
- Dan Padric
LibreOffice V5.2 | Windows 7
DanPadric
 
Posts: 3
Joined: Sun Jun 18, 2017 10:53 pm

Re: Alternative script organizer for Python macro

Postby DanPadric » Wed Jun 21, 2017 12:41 am

Hello.

The following works for setting the options:
    Editor ... C:\Program Files (x86)\Notepad++\notepad++.exe
    Options ... {FILENAME}

I have no success getting IDLE to work. What does work in the command prompt are:
    1) %PY_HOME%\Lib\idlelib\idle -e {FILENAME}
    2) %PY_HOME%\pythonw.exe -m idlelib {FILENAME}
    3) %PY_HOME%\python.exe -m idlelib {FILENAME}

..where {FILENAME} is substitute for the called file and %PY_HOME% is the installed directory of python.exe (V3.6).

Creating a BAT file works when having...
Code: Select all   Expand viewCollapse view
"C:\Program Files (x86)\Notepad++\notepad++.exe" %1

where....
    Editor ... openPY.bat
    Options ... {FILENAME}
[/list]

It does not work where the BAT file contains...
Code: Select all   Expand viewCollapse view
"%PY_HOME%\pythonw.exe" -m idlelib %1


Both BAT files work from command prompt.

Is is appropriate to continue this conversation here?
- Dan
LibreOffice V5.2 | Windows 7
DanPadric
 
Posts: 3
Joined: Sun Jun 18, 2017 10:53 pm

Re: Alternative script organizer for Python macro

Postby hubert lambert » Thu Jun 22, 2017 9:27 am

Hello,

I can't figure out exactly what is the problem with idle command. But it seems to me a bad idea to use it with apso : idle is bound to the system python interpreter, which is different from the bundled version. The system interpreter does not know the uno context : executing or debugging from idle window will not work.
If you have Notepad++ installed, it's quite a good option : edit the code, save it and then execute it from LibreOffice.

If you prefer to use python from outside the office process, you'll find usefull informations here as a starting point.
AOOo 4.1.2 on Win7 | LibreOffice on various Linux systems
hubert lambert
 
Posts: 59
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Postby DanPadric » Thu Jun 22, 2017 5:32 pm

Hello. Thank you for your answer and helpful link.
- Dan
LibreOffice V5.2 | Windows 7
DanPadric
 
Posts: 3
Joined: Sun Jun 18, 2017 10:53 pm

Re: Alternative script organizer for Python macro

Postby paco » Wed Sep 13, 2017 5:20 pm

Congratulations to all contributors to this excellent and useful piece of software, *Apso*! It is unlikely that I would have finished my first *Office* macro with *Python* without it!

In these exercitations I have discovered a small problem, that I report here just in case it could be helpful. Whereas when I execute *Organize Python scripts* from the initial *Office* switchboard or from the database initial one (after having opened a database) things work normally, if I launch *Apso* after openning any *database form*, the *Apso* user interface refuses to appear. There are neither error messages nor misbehaviours, just nothing perceptible happens. One can know however that something has happened by the fact that, in spite of it, the auxiliary *apso_utils* become available to the other routines, something that does not happen before it is executed for the first time.

This odd behaviour appears as well in *OpenOffice 4.1.3* as in *LibreOffice 5.2.3* over *Windows 7*, the programs that I use.

Kind regards
Windows 7 + Apache OpenOffice 4.1.3.
paco
 
Posts: 3
Joined: Wed Nov 30, 2016 12:01 pm

Re: Alternative script organizer for Python macro

Postby hubert lambert » Tue Sep 19, 2017 7:56 pm

Hello paco,

Thank you very much for reporting this bug!
I've fixed it in a version 1.0.2 that should be available right now from the Extension Manager.
Regards,

HL
AOOo 4.1.2 on Win7 | LibreOffice on various Linux systems
hubert lambert
 
Posts: 59
Joined: Mon Jun 13, 2016 10:50 am


Return to Code Snippets

Who is online

Users browsing this forum: No registered users and 1 guest