Alternative script organizer for Python macro

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 section is not for asking questions about writing your own macros.
Post Reply
hanya
Volunteer
Posts: 885
Joined: Fri Nov 23, 2007 9:27 am
Location: Japan

Alternative script organizer for Python macro

Post by hanya »

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

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
User avatar
karolus
Volunteer
Posts: 1158
Joined: Sat Jul 02, 2011 9:47 am

Re: Alternative script organizer for Python macro

Post by karolus »

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 6.1 on Rasbian OS (on ARM)
Libreoffice 7.4 on Debian 12 (Bookworm) (on RaspberryPI4)
Libreoffice 7.6 flatpak on Debian 12 (Bookworm) (on RaspberryPI4)
hanya
Volunteer
Posts: 885
Joined: Fri Nov 23, 2007 9:27 am
Location: Japan

Re: Alternative script organizer for Python macro

Post by hanya »

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
User avatar
Tommy
Posts: 251
Joined: Sun Dec 23, 2007 2:44 pm

Re: Alternative script organizer for Python macro

Post by Tommy »

@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
karolus
Volunteer
Posts: 1158
Joined: Sat Jul 02, 2011 9:47 am

Re: Alternative script organizer for Python macro

Post by karolus »

Hallo

@Hanya
I've found Error in Line ~244

replace

Code: Select all

    if url.startswith(self.DOC_PROTOCOL):
by

Code: Select all

    if url.startswith( OrganizerDialog.DOC_PROTOCOL):
thanks
Karolus
AOO4, Libreoffice 6.1 on Rasbian OS (on ARM)
Libreoffice 7.4 on Debian 12 (Bookworm) (on RaspberryPI4)
Libreoffice 7.6 flatpak on Debian 12 (Bookworm) (on RaspberryPI4)
hanya
Volunteer
Posts: 885
Joined: Fri Nov 23, 2007 9:27 am
Location: Japan

Re: Alternative script organizer for Python macro

Post by hanya »

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
hubert lambert
Posts: 145
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Post by hubert lambert »

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
User avatar
Zizi64
Volunteer
Posts: 11352
Joined: Wed May 26, 2010 7:55 am
Location: Budapest, Hungary

Re: Alternative script organizer for Python macro

Post by Zizi64 »

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 2171 times
Tibor Kovacs, Hungary; LO7.5.8 /Win7-10 x64Prof.
PortableApps/winPenPack: LO3.3.0-7.6.2;AOO4.1.14
Please, edit the initial post in the topic: add the word [Solved] at the beginning of the subject line - if your problem has been solved.
hubert lambert
Posts: 145
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Post by hubert lambert »

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: 145
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Post by hubert lambert »

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
Ratslinger
Posts: 34
Joined: Sun Mar 01, 2015 3:34 am

Re: Alternative script organizer for Python macro

Post by Ratslinger »

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 7.0.2.2
OpenOffice 4.0.1
Ubuntu Mate 20.04.1
Mint 20.3
hubert lambert
Posts: 145
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Post by hubert lambert »

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
DanPadric
Posts: 3
Joined: Sun Jun 18, 2017 10:53 pm

Re: Alternative script organizer for Python macro

Post by DanPadric »

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

Post by DanPadric »

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

"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

"%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
hubert lambert
Posts: 145
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Post by hubert lambert »

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
DanPadric
Posts: 3
Joined: Sun Jun 18, 2017 10:53 pm

Re: Alternative script organizer for Python macro

Post by DanPadric »

Hello. Thank you for your answer and helpful link.
- Dan
LibreOffice V5.2 | Windows 7
paco
Posts: 3
Joined: Wed Nov 30, 2016 12:01 pm

Re: Alternative script organizer for Python macro

Post by paco »

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.
hubert lambert
Posts: 145
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Post by hubert lambert »

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
j.kornelsn
Posts: 14
Joined: Wed Oct 06, 2010 6:26 am

Re: Alternative script organizer for Python macro

Post by j.kornelsn »

To handle pythonpath directories, I created a merge request at https://gitlab.com/jmzambon/apso (issue #1). However, gitlab.com reported that the merge request may not have completed successfully. If the changes are okay, then please pull the changes from https://gitlab.com/jkornelsen/apso. The suggested new version is 1.2.0.

Let me know if there is anything else needed for this merge request.
LO / AOO on Ubuntu / Windows
hubert lambert
Posts: 145
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Post by hubert lambert »

Hello,

Thanks for your contribution. I'll have a look on GitLab.
But Apso is a organizer for python macros, I'm no sure at all that the pythonpath folder, which is not intended to contain end user macro, should be seen from there.
AOOo 4.1.2 on Win7 | LibreOffice on various Linux systems
j.kornelsn
Posts: 14
Joined: Wed Oct 06, 2010 6:26 am

Re: Alternative script organizer for Python macro

Post by j.kornelsn »

Hello Hubert,

Before you make a decision, please go to Menu -> pythonpath example (fr: Exemple pythonpath) and then execute ppath_example.hello(). This python macro imports another module, something I believe many python macro developers will find useful. The alternative is either to store all code in one gigantic file or else duplicate code in multiple files, which becomes difficult to maintain.

OpenOffice Basic has the capability of calling code from another module, so why shouldn't APSO provide that capability as well?

There are also behind-the-scenes changes such as making the APSO menu code easier to maintain.

-Jim K
LO / AOO on Ubuntu / Windows
hubert lambert
Posts: 145
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Post by hubert lambert »

I never meant that pythonpath should not be used! I just said that, imho, it doesn't need to be visible within Apso. Importing from other modules has always been and will always be possible.
In the other hand, what would you do with embedded scripts ? It's not impossible to import from embedded pythonpath, but not straightforward, and maybe should not be encouraged in this specific case.
I'm not stubborn, but still asking to be convinced...
AOOo 4.1.2 on Win7 | LibreOffice on various Linux systems
j.kornelsn
Posts: 14
Joined: Wed Oct 06, 2010 6:26 am

Re: Alternative script organizer for Python macro

Post by j.kornelsn »

I'm not sure I fully understand. In the first paragraph, you stated that importing from other modules is possible. However, APSO cannot currently view or edit these modules, correct? So that makes it practically impossible to do from within APSO.

For example, let's say that under My Macros (either user or system location), I use APSO to create a file called writer.py for Writer macros, and a file called calc.py for Calc macros. Now, I need to create a helper function that is used by both. This is a setup that I believe many beginning macro users may want. With the currently released version of APSO, this is not possible.

With the new changes that I propose, APSO can be used to create the pythonpath folder and module so that the helper function can be imported from both writer.py and calc.py.

Regarding embedded scripts in a document, I do not remember finding a way to do this for pythonpath. You mentioned that it's not impossible. Would you care to provide a link or more information? I do not have as much experience with embedding in documents. Most of my work has been with developing extensions or with user-location macros ("My Macros").
LO / AOO on Ubuntu / Windows
hanya
Volunteer
Posts: 885
Joined: Fri Nov 23, 2007 9:27 am
Location: Japan

Re: Alternative script organizer for Python macro

Post by hanya »

j.kornelsn wrote:Regarding embedded scripts in a document, I do not remember finding a way to do this for pythonpath. You mentioned that it's not impossible. Would you care to provide a link or more information?
See code of checkForPythonPathBesideScript function in pythonscript.py file. To make built-in import function of the Python, os.access is used to check the path is accessible by the system IO. So we can not use embedded pythonpath/ in documents.
This can be solved by import hook introduced on Python 3.1. Using importlib, we can add custom importer for embedded python macros. But if we add many path to sys.path, some modules might conflict each other.
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
hubert lambert
Posts: 145
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Post by hubert lambert »

j.kornelsn wrote:I'm not sure I fully understand. In the first paragraph, you stated that importing from other modules is possible. However, APSO cannot currently view or edit these modules, correct? So that makes it practically impossible to do from within APSO.
That's correct. Again, I think imported libraries should be developped outside pythonpath and, once in there, should not be edited anymore. But maybe I'm wrong...
By the way, once imported, any change to a file in pythonpath will not be taken into acount until it is reloaded or document reopened.
j.kornelsn wrote:Regarding embedded scripts in a document, I do not remember finding a way to do this for pythonpath. You mentioned that it's not impossible. Would you care to provide a link or more information?
I gave an example here in the french forum.
The idea is to take advantage of the implicit zipimport mechanism, something like this (document needs a location):

Code: Select all

import sys
from os.path import dirname
from unohelper import fileUrlToSystemPath

if not 'test_utils' in sys.modules:
    doc = XSCRIPTCONTEXT.getDocument()
    url = fileUrlToSystemPath('{}/{}'.format(doc.URL,'Scripts/python/pythonpath'))
    sys.path.insert(0, url)
import test_utils

def doit(event=None):
    test_utils.msgbox("Importing from <pythonpath> successful!")
But this is probably a little bit hugly, just mentioned for the record.
Attachments
testpÿ.odt
(11.38 KiB) Downloaded 1994 times
AOOo 4.1.2 on Win7 | LibreOffice on various Linux systems
j.kornelsn
Posts: 14
Joined: Wed Oct 06, 2010 6:26 am

Re: Alternative script organizer for Python macro

Post by j.kornelsn »

The idea is to take advantage of the implicit zipimport mechanism.
That is an interesting example. @hanya, thank you for the additional information as well. But I agree, using such an approach for embedded pythonpath is not something that APSO should encourage.
I think imported libraries should be developed outside pythonpath and, once in there, should not be edited anymore.
Why not? As you said, all that is required is to restart OpenOffice or otherwise force the module to reload. I have developed many macros this way.

However, I think I see now why you are saying this. In my background as a developer, structured design in applications is fundamental. Python makes it easy. This is perhaps a different philosophy from how APSO was designed, which if I understand what you are saying, is intended as a simple tool to create small macros, not a complete solution.
LO / AOO on Ubuntu / Windows
User avatar
Zizi64
Volunteer
Posts: 11352
Joined: Wed May 26, 2010 7:55 am
Location: Budapest, Hungary

Re: Alternative script organizer for Python macro

Post by Zizi64 »

Error message on LO Portable 6.4.0 when I tried to install the Apso 1.2.6.2:

Code: Select all

(com.sun.star.uno.RuntimeException) { { Message = "<class 'ImportError'>: No module named 'Queue' (or 'apso_utils.console' is unknown), traceback follows\X000a  File \"E:\\Portable_Apps\\LO_P640\\LibreOfficePortable\\App\\libreoffice\\program\\pythonloader.py\", line 149, in writeRegistryInfo\X000a    mod = self.getModuleFromUrl( locationUrl )\X000a  File \"E:\\Portable_Apps\\LO_P640\\LibreOfficePortable\\App\\libreoffice\\program\\pythonloader.py\", line 104, in getModuleFromUrl\X000a    exec(codeobject, mod.__dict__)\X000a  File \"E:\\Portable_Apps\\LO_P640\\LibreOfficePortable\\Data\\settings\\user\\uno_packages\\cache\\uno_packages\\lu60525uldiz.tmp_\\apso.oxt\\python\\apso.py\", line 14, in <module>\X000a    from apso_utils import console, msgbox, getConfigurationAccess, getProductName\X000a  File \"E:\\Portable_Apps\\LO_P640\\LibreOfficePortable\\App\\libreoffice\\program\\uno.py\", line 434, in _uno_import\X000a    raise uno_import_exc\X000a  File \"E:\\Portable_Apps\\LO_P640\\LibreOfficePortable\\App\\libreoffice\\program\\uno.py\", line 356, in _uno_import\X000a    return _builtin_import(name, *optargs, **kwargs)\X000a  File \"E:\\Portable_Apps\\LO_P640\\LibreOfficePortable\\Data\\settings\\user\\uno_packages\\cache\\uno_packages\\lu60525uldiz.tmp_\\apso.oxt\\python\\pythonpath\\apso_utils.py\", line 99, in <module>\X000a    import Queue as queue\X000a  File \"E:\\Portable_Apps\\LO_P640\\LibreOfficePortable\\App\\libreoffice\\program\\uno.py\", line 356, in _uno_import\X000a    return _builtin_import(name, *optargs, **kwargs)\X000a\X000a", Context = (com.sun.star.uno.XInterface) @0 } }
Tibor Kovacs, Hungary; LO7.5.8 /Win7-10 x64Prof.
PortableApps/winPenPack: LO3.3.0-7.6.2;AOO4.1.14
Please, edit the initial post in the topic: add the word [Solved] at the beginning of the subject line - if your problem has been solved.
hubert lambert
Posts: 145
Joined: Mon Jun 13, 2016 10:50 am

Re: Alternative script organizer for Python macro

Post by hubert lambert »

Hi,

This is a bug of LibreOffice: last releases have lost some windows .pyd files while migrating to python 3.7.
This should be fixed quite soon, see issue 130404.
Regards.
AOOo 4.1.2 on Win7 | LibreOffice on various Linux systems
Post Reply