EventGhost/EventGhost

View on GitHub
plugins/SysTrayMenu/__init__.py

Summary

Maintainability
C
1 day
Test Coverage
# -*- coding: utf-8 -*-
#
# This file is a plugin for EventGhost.
# Copyright © 2005-2020 EventGhost Project <http://www.eventghost.net/>
#
# EventGhost is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free
# Software Foundation, either version 2 of the License, or (at your option)
# any later version.
#
# EventGhost is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with EventGhost. If not, see <http://www.gnu.org/licenses/>.

import eg

eg.RegisterPlugin(
    name = "System Tray Menu",
    description = (
        "Allows you to add custom menu entries to the tray menu of EventGhost."
    ),
    author = "Bitmonster",
    version = "1.0",
    guid = "{842BFFE8-DCB9-4C72-9877-AB2EF49794C5}",
)


class Text:
    labelHeader = "Label"
    eventHeader = "Event"
    editLabel = "Label:"
    editEvent = "Event:"
    addBox = "Append:"
    addItemButton = "Menu Item"
    addSeparatorButton = "Separator"
    deleteButton = "Delete"
    unnamedLabel = "New Menu Item %s"
    unnamedEvent = "Event%s"


import wx
import wx.gizmos
import types


class MenuItemData:
    pass

class MenuTreeListCtrl(wx.gizmos.TreeListCtrl):

    def __init__(self, parent, text, menuData, selectedItem=None):
        self.highestMenuId = 0
        wx.gizmos.TreeListCtrl.__init__(
            self,
            parent,
            style = wx.TR_FULL_ROW_HIGHLIGHT
                |wx.TR_DEFAULT_STYLE
                |wx.VSCROLL
                |wx.ALWAYS_SHOW_SB
                |wx.CLIP_CHILDREN
        )
        self.SetMinSize((10, 150))
        self.AddColumn(text.labelHeader)
        self.AddColumn(text.eventHeader)
        root = self.AddRoot(text.name)
        for data in menuData:
            name, kind, eventName, menuId = data
            if menuId > self.highestMenuId:
                self.highestMenuId = menuId
            eventName = data[2]
            item = self.AppendItem(root, name)
            self.SetItemText(item, eventName, 1)
            self.SetPyData(item, data)
            if menuId == selectedItem:
                self.SelectItem(item)

        self.SetColumnWidth(0, 200)
        self.ExpandAll(root)

        self.__inSizing = False
        self.GetMainWindow().Bind(wx.EVT_SIZE, self.OnSize)


    def GetNewMenuId(self):
        newMenuId = self.highestMenuId + 1
        self.highestMenuId = newMenuId
        return newMenuId


    def OnSize(self, event):
        event.Skip()
        if not self.__inSizing:
            self.__inSizing = True
            wx.CallAfter(self.OnSize2)


    def OnSize2(self):
        w, h = self.GetMainWindow().GetClientSize()
        newWidth = w - self.GetColumnWidth(0)
        if self.GetColumnWidth(1) != newWidth:
            self.SetColumnWidth(1, newWidth)
        self.__inSizing = False


    def GetPrevious(self, item):
        previous = self.GetPrevSibling(item)
        if previous.IsOk():
            return previous
        previous = self.GetItemParent(item)
        if not self.HasChildren(previous):
            return previous
        return self.GetLastChild(previous)[0]


    def GetNext(self, item):
        if self.HasChildren(item):
            return self.GetFirstChild()
        while 1:
            next = self.GetNextSibling(item)
            if next.IsOk():
                return next
            item = self.GetItemParent(item)
            if item == self.GetRootItem():
                return None


    def CopyItem(self, item, parent, prev=None):
        text = self.GetItemText(item)
        img = self.GetItemImage(item, wx.TreeItemIcon_Normal)
        selImg = self.GetItemImage(item, wx.TreeItemIcon_Selected)
        data = self.GetPyData(item)
        if prev is None:
            id = self.InsertItemBefore(parent, 0, text, img, selImg)
        else:
            id = self.InsertItem(parent, prev, text, img, selImg)
        self.SetPyData(id, data)
        self.SetItemText(id, self.GetItemText(item, 1), 1)
        return id


    def GetSelectedId(self):
        selectedId = self.GetSelection()
        data = self.GetPyData(selectedId)
        if data is not None:
            return data[3]



class SysTrayMenu(eg.PluginBase):
    text = Text

    def __init__(self):
        self.AddAction(Enable)
        self.AddAction(Disable)


    def Compile(self, menuData=[]):
        self.menuIdToData = {}
        # convert menuData to the new format
        newData = []
        i = 0
        for data in menuData:
            if len(data) < 4:
                name, kind, eventName  = data
                i += 1
                data = (name, kind, eventName, i)
            newData.append(data)
            self.menuIdToData[data[3]] = data
        self.menuData = newData
        return newData


    def __start__(self, dummy=None):
        self.wxIdToData = {}
        self.menuIdToWxItem = {}
        self.menuIdToData = {}
        if len(self.menuData) == 0:
            return
        menu =  eg.taskBarIcon.menu
        self.menuIdToWxItem[-1] = menu.PrependSeparator()
        for data in reversed(self.menuData):
            name, kind, eventName, menuId = data
            if kind == "item":
                wxId = wx.NewId()
                wxItem = menu.Prepend(wxId, name)
                wx.EVT_MENU(menu, wxId, self.OnMenuItem)
                self.wxIdToData[wxId] = data
            elif kind == "separator":
                wxItem = menu.PrependSeparator()
            self.menuIdToWxItem[menuId] = wxItem
            self.menuIdToData[menuId] = data


    def __stop__(self):
        for item in self.menuIdToWxItem.values():
            eg.taskBarIcon.menu.RemoveItem(item)
        self.menuIdToWxItem.clear()
        self.wxIdToData.clear()


    @eg.LogIt
    def OnMenuItem(self, event):
        data = self.wxIdToData[event.GetId()]
        self.TriggerEvent(data[2])


    def Configure(self, menuData=[]):
        menuData = self.Compile(menuData)
        panel = eg.ConfigPanel(resizable=True)
        text = self.text

        tree = MenuTreeListCtrl(panel, text, menuData)
        root = tree.GetRootItem()

        @eg.LogIt
        def OnSelectionChanged(dummyEvent):
            itemType = 0
            item = tree.GetSelection()
            if item == root:
                enableMoveFlag = False
                enableEditFlag = False
            elif tree.GetPyData(item)[1] == "separator":
                enableMoveFlag = True
                enableEditFlag = False
                itemType = 2
            else:
                enableMoveFlag = True
                enableEditFlag = True
            upButton.Enable(enableMoveFlag)
            downButton.Enable(enableMoveFlag)
            deleteButton.Enable(enableMoveFlag)
            labelBox.Enable(enableEditFlag)
            eventBox.Enable(enableEditFlag)
            labelBox.SetLabel(tree.GetItemText(item, 0))
            eventBox.SetLabel(tree.GetItemText(item, 1))
            #itemTypeCtrl.SetSelection(itemType)
            #event.Skip()
        tree.Bind(wx.EVT_TREE_SEL_CHANGED, OnSelectionChanged)

        # Delete button
        deleteButton = wx.Button(panel, -1, text.deleteButton)
        deleteButton.Enable(False)
        def OnDelete(dummyEvent):
            item = tree.GetSelection()
            next = tree.GetNextSibling(item)
            if not next.IsOk():
                next = tree.GetPrevSibling(item)
                if not next.IsOk():
                    next = tree.GetItemParent(item)
            tree.SelectItem(next)
            tree.Delete(item)
            tree.EnsureVisible(next)
        deleteButton.Bind(wx.EVT_BUTTON, OnDelete)

        # Up button
        bmp = wx.ArtProvider.GetBitmap(wx.ART_GO_UP, wx.ART_OTHER, (16, 16))
        upButton = wx.BitmapButton(panel, -1, bmp)
        upButton.Enable(False)
        def OnUp(dummyEvent):
            item = tree.GetSelection()
            previous = tree.GetPrevSibling(item)
            if previous.IsOk():
                id = tree.GetPrevSibling(previous)
                if not id.IsOk():
                    id = None
                newId = tree.CopyItem(item, tree.GetItemParent(previous), id)
                tree.SelectItem(newId)
                tree.EnsureVisible(newId)
                tree.Delete(item)
        upButton.Bind(wx.EVT_BUTTON, OnUp)

        # Down button
        bmp = wx.ArtProvider.GetBitmap(wx.ART_GO_DOWN, wx.ART_OTHER, (16, 16))
        downButton = wx.BitmapButton(panel, -1, bmp)
        downButton.Enable(False)
        def OnDown(dummyEvent):
            item = tree.GetSelection()
            nextId = tree.GetNext(item)
            if nextId is not None:
                newId = tree.CopyItem(item, tree.GetItemParent(nextId), nextId)
                tree.Delete(item)
                tree.SelectItem(newId)
                tree.EnsureVisible(newId)
        downButton.Bind(wx.EVT_BUTTON, OnDown)

        # Add menu item button
        addItemButton = wx.Button(panel, -1, text.addItemButton)
        @eg.LogIt
        def OnAddItem(dummyEvent):
            numStr = str(tree.GetCount() + 1)
            item = tree.AppendItem(root, text.unnamedLabel % numStr)
            data = ("", "item", "", tree.GetNewMenuId())
            tree.SetPyData(item, data)
            tree.SetItemText(item, text.unnamedEvent % numStr, 1)
            tree.Expand(tree.GetItemParent(item))
            tree.SelectItem(item)
            tree.EnsureVisible(item)
            tree.Update()
        addItemButton.Bind(wx.EVT_BUTTON, OnAddItem)

        # Add separator button
        addSeparatorButton = wx.Button(panel, -1, text.addSeparatorButton)
        def OnAddSeparator(dummyEvent):
            item = tree.AppendItem(root, "---------")
            tree.SetPyData(item, ("", "separator", "", tree.GetNewMenuId()))
            tree.Expand(tree.GetItemParent(item))
            tree.SelectItem(item)
            tree.EnsureVisible(item)
            tree.Update()
        addSeparatorButton.Bind(wx.EVT_BUTTON, OnAddSeparator)

        # Label edit box
        labelBox = wx.TextCtrl(panel, -1)
        def OnLabelTextChange(event):
            item = tree.GetSelection()
            tree.SetItemText(item, labelBox.GetValue(), 0)
            event.Skip()
        labelBox.Bind(wx.EVT_TEXT, OnLabelTextChange)

        # Event edit box
        eventBox = wx.TextCtrl(panel, -1)
        def OnEventTextChange(event):
            item = tree.GetSelection()
            tree.SetItemText(item, eventBox.GetValue(), 1)
            event.Skip()
        eventBox.Bind(wx.EVT_TEXT, OnEventTextChange)

        # Item type control
        #choices = ["Menu item", "Check menu item", "Separator"]
        #itemTypeCtrl = wx.Choice(dialog, choices=choices)

        # construction of the dialog with sizers
        staticBox = wx.StaticBox(panel, -1, text.addBox)
        addSizer = wx.StaticBoxSizer(staticBox, wx.VERTICAL)
        addSizer.Add(addItemButton, 0, wx.EXPAND)
        addSizer.Add((5, 5))
        addSizer.Add(addSeparatorButton, 0, wx.EXPAND)

        editSizer = wx.FlexGridSizer(2, 2, 5, 5)
        editSizer.AddGrowableCol(1)
        editSizer.AddMany((
            (panel.StaticText(text.editLabel), 0, wx.ALIGN_CENTER_VERTICAL),
            (labelBox, 0, wx.EXPAND),
            (panel.StaticText(text.editEvent), 0, wx.ALIGN_CENTER_VERTICAL),
            (eventBox, 0, wx.EXPAND),
        ))

        mainSizer = eg.HBoxSizer(
            (
                eg.VBoxSizer(
                    (tree, 1, wx.EXPAND),
                    (editSizer, 0, wx.EXPAND|wx.TOP, 5),
                ), 1, wx.EXPAND
            ),
            ((5, 5)),
            (
                eg.VBoxSizer(
                    (deleteButton, 0, wx.EXPAND),
                    ((5, 5), 1, wx.EXPAND),
                    (upButton),
                    (downButton, 0, wx.TOP, 5),
                    ((5, 5), 1, wx.EXPAND),
                    (addSizer, 0, wx.EXPAND),
                ), 0, wx.EXPAND
            ),
        )
        panel.sizer.Add(mainSizer, 1, wx.EXPAND)

        nextId = tree.GetFirstChild(root)[0]
        if nextId.IsOk():
            tree.SelectItem(nextId)
        else:
            tree.SelectItem(root)

        while panel.Affirmed():
            resultList = []
            def Traverse(item):
                child, cookie = tree.GetFirstChild(item)
                while child.IsOk():
                    name, kind, eventString, menuId = tree.GetPyData(child)
                    name = tree.GetItemText(child, 0)
                    eventString = tree.GetItemText(child, 1)
                    resultList.append((name, kind, eventString, menuId))
                    if tree.HasChildren(child):
                        Traverse(child)
                    child, cookie = tree.GetNextChild(item, cookie)
            Traverse(root)
            self.Compile(resultList)
            panel.SetResult(resultList)



class Enable(eg.ActionBase):

    class text:
        name = "Enable Item"
        description = "Enables a menu item."

    def __call__(self, menuId):
        item = self.plugin.menuIdToWxItem.get(menuId, None)
        if item is not None:
            item.Enable(True)


    def GetLabel(self, menuId):
        return self.name + ": " + self.plugin.menuIdToData[menuId][0]


    def Configure(self, menuId=None):
        plugin = self.plugin
        panel = eg.ConfigPanel()
        tree = MenuTreeListCtrl(panel, plugin.text, plugin.menuData, menuId)
        panel.sizer.Add(tree, 1, wx.EXPAND)
        while panel.Affirmed():
            panel.SetResult(tree.GetSelectedId())



class Disable(Enable):

    class text:
        name = "Disable Item"
        description = "Disables a menu item."

    def __call__(self, menuId):
        item = self.plugin.menuIdToWxItem.get(menuId, None)
        if item is not None:
            item.Enable(False)