EventGhost/EventGhost

View on GitHub
plugins/DesktopRemote/__init__.py

Summary

Maintainability
D
2 days
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/>.

u"""
    Name: Desktop Remote
    Author: Bitmonster
    Version: 1.0
    Description: Creates a remote-like desktop window.
    GUID: {5DFFBD61-7582-4D6F-8EA9-9CB36284C9CF}
    URL: http://www.eventghost.net/forum/viewtopic.php?t=513
"""

import eg

eg.RegisterPlugin(
    name = "Desktop Remote",
    author = "Bitmonster",
    version = "1.0.1158",
    guid = "{5DFFBD61-7582-4D6F-8EA9-9CB36284C9CF}",
    canMultiLoad = True,
    url = "http://www.eventghost.net/forum/viewtopic.php?t=513",
    description = "Creates a remote-like desktop window.",
)


import wx
import wx.lib.buttons as buttons
import os
from base64 import b64decode, b64encode
from cStringIO import StringIO
from eg.WinApi import GetCursorPos



class Text:
    class CreateNew:
        pass

    class AddButton:
        label = "Label:"
        event = "Event:"


class ButtonType:
    pass

class LineType:
    pass



class ImageButton(wx.Button):

    def __init__(self, parent, value=None, label=""):
        self.value = value
        self.view = None
        wx.Button.__init__(self, parent, label=label)
        self.Bind(wx.EVT_BUTTON, self.OnButton)


    def OnButton(self, event):
        dialog = wx.FileDialog(
            self.GetParent(),
            #message=self.mesg,
            style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST,
            wildcard="All image files|*jpg;*.png;*.bmp;*.gif|All files|*.*"
        )
        if dialog.ShowModal() == wx.ID_OK:
            filePath = dialog.GetPath()
            imageFile = open(filePath, "rb")
            stream = imageFile.read()
            imageFile.close()
            self.SetValue(b64encode(stream))
            event.Skip()


    def SetValue(self, value):
        self.value = value
        if value and self.view:
            stream = StringIO(b64decode(value))
            image = wx.ImageFromStream(stream)
            stream.close()
            boxWidth, boxHeight = self.view.GetClientSizeTuple()
            width, height = image.GetSize()
            if width > boxWidth:
                height *= 1.0 * boxWidth / width
                width = boxWidth
            if height > boxHeight:
                width *= 1.0 * boxHeight / height
                height = boxHeight
            image.Rescale(width, height)
            bmp = wx.BitmapFromImage(image)
            self.view.SetBitmap(bmp)
            self.view.SetClientSize((boxWidth, boxHeight))


    def GetValue(self):
        return self.value



class DesktopRemote(eg.PluginBase):
    text = Text

    def __init__(self):
        self.AddEvents()

        self.AddAction(CreateNew)
        self.AddAction(AddButton)
        self.AddAction(StartNewLine)
        self.AddAction(Show)
        self.AddAction(Close)
        self.data = []
        self.lastEvent = None
        self.defaults = {
            "fontInfo": u'0;-13;0;0;0;700;0;0;0;0;3;2;1;34;Arial',
            "image": None,
            "foregroundColour": (255, 255, 255),
            "backgroundColour": (78, 78, 78),
            "width": 40,
            "height": 40,
        }
        self.frame = None


    def OnButtonUp(self, event):
        event.Skip()
        self.lastEvent.SetShouldEnd()


    def __stop__(self):
        if self.frame:
            wx.CallAfter(self.frame.Destroy)
            self.frame = None



class CreateNew(eg.ActionBase):
    name = "Create New Remote"

    def __call__(
        self,
        width=40,
        height=40,
        rowGap=3,
        columnGap=3,
        borderSize=4,
        windowColour=(108, 108, 108),
        foregroundColour=(255, 255, 255),
        backgroundColour=(78, 78, 78),
        fontInfo=u'0;-13;0;0;0;700;0;0;0;0;3;2;1;34;Arial',
        caption="Remote",
        windowStyle=0,
        showInTaskbar=True,
        moveOnDrag=True,
        iconizeOnDoubleClick=True,
        *args
    ):
        plugin = self.plugin
        plugin.data = []
        plugin.defaults["width"] = width
        plugin.defaults["height"] = height
        plugin.defaults["foregroundColour"] = foregroundColour
        plugin.defaults["backgroundColour"] = backgroundColour
        plugin.defaults["fontInfo"] = fontInfo
        plugin.rowGap = rowGap
        plugin.columnGap = columnGap
        plugin.borderSize = borderSize
        plugin.windowColour = windowColour
        plugin.caption = caption
        plugin.windowStyle = windowStyle
        plugin.showInTaskbar = showInTaskbar
        plugin.moveOnDrag = moveOnDrag
        plugin.iconizeOnDoubleClick = iconizeOnDoubleClick


    def GetLabel(self, *args):
        return self.name


    @eg.LogIt
    def Configure(
        self,
        width=40,
        height=40,
        rowGap=3,
        columnGap=3,
        borderSize=4,
        windowColour=(108, 108, 108),
        foregroundColour=(255, 255, 255),
        backgroundColour=(78, 78, 78),
        fontInfo=u'0;-13;0;0;0;700;0;0;0;0;3;2;1;34;Arial',
        caption="Remote",
        windowStyle=0,
        showInTaskbar=True,
        moveOnDrag=True,
        iconizeOnDoubleClick=True,
        *args
    ):
        panel = eg.ConfigPanel()
        panel.SetSizerProperty(vgap=2)
        captionCtrl = panel.TextCtrl(caption)
        showInTaskbarCtrl = panel.CheckBox(showInTaskbar, "Show in taskbar")
        moveOnDragCtrl = panel.CheckBox(
            moveOnDrag,
            "Move window on drag click in empty area"
        )
        iconizeOnDoubleClickCtrl = panel.CheckBox(
            iconizeOnDoubleClick,
            "Minimize window on double click in empty area"
        )
        choices = [
            "Normal window",
            "Simple border",
            "No border",
        ]
        windowStyleCtrl = panel.Choice(windowStyle, choices=choices)
        widthCtrl = panel.SpinIntCtrl(width)
        heightCtrl = panel.SpinIntCtrl(height)
        rowGapCtrl = panel.SpinIntCtrl(rowGap)
        columnGapCtrl = panel.SpinIntCtrl(columnGap)
        borderSizeCtrl = panel.SpinIntCtrl(borderSize)
        windowColourCtrl = panel.ColourSelectButton(windowColour)
        foregroundColourCtrl = panel.ColourSelectButton(foregroundColour)
        backgroundColourCtrl = panel.ColourSelectButton(backgroundColour)
        fontCtrl = panel.FontSelectButton(fontInfo)

        panel.AddLine("Caption:", captionCtrl)
        panel.AddLine(showInTaskbarCtrl)
        panel.AddLine(moveOnDragCtrl)
        panel.AddLine(iconizeOnDoubleClickCtrl)
        panel.AddLine("Window style:", windowStyleCtrl)
        panel.AddLine("Default button width:", widthCtrl)
        panel.AddLine("Default button height:", heightCtrl)
        panel.AddLine("Row gap:", rowGapCtrl)
        panel.AddLine("Column gap:", columnGapCtrl)
        panel.AddLine("Outside border size:", borderSizeCtrl)
        panel.AddLine("Window colour:", windowColourCtrl)
        panel.AddLine("Button foreground colour:", foregroundColourCtrl)
        panel.AddLine("Button background colour:", backgroundColourCtrl)
        panel.AddLine("Button font:", fontCtrl)

        while panel.Affirmed():
            panel.SetResult(
                widthCtrl.GetValue(),
                heightCtrl.GetValue(),
                rowGapCtrl.GetValue(),
                columnGapCtrl.GetValue(),
                borderSizeCtrl.GetValue(),
                windowColourCtrl.GetValue(),
                foregroundColourCtrl.GetValue(),
                backgroundColourCtrl.GetValue(),
                fontCtrl.GetValue(),
                captionCtrl.GetValue(),
                windowStyleCtrl.GetValue(),
                showInTaskbarCtrl.GetValue(),
                moveOnDragCtrl.GetValue(),
                iconizeOnDoubleClickCtrl.GetValue(),
            )



class AddButton(eg.ActionBase):
    name = "Add Button"

    def __call__(self, kwargs):
        self.plugin.data.append((ButtonType, kwargs))


    def GetLabel(self, kwargs):
        return self.name + ": " + kwargs.get("label", "")


    def Configure(self, kwargs={}):

        def MakeOption(name, checkBox, ctrl):
            value = kwargs.get(name, None)
            def OnCheckBox(event):
                ctrl.Enable(checkBox.GetValue())
                event.Skip()
            checkBox.Bind(wx.EVT_CHECKBOX, OnCheckBox)
            if value is None:
                checkBox.SetValue(False)
                ctrl.SetValue(self.plugin.defaults[name])
                ctrl.Enable(False)
            else:
                checkBox.SetValue(True)
                ctrl.SetValue(value)
            panel.AddLine(checkBox, ctrl)
            def SetResult():
                if checkBox.GetValue():
                    kwargs[name] = ctrl.GetValue()
            return SetResult

        panel = eg.ConfigPanel()
        panel.SetSizerProperty(vgap=2)
        text = self.text

        labelCtrl = panel.TextCtrl(kwargs.get("label", ""))
        panel.AddLine(text.label, labelCtrl)

        eventCtrl = panel.TextCtrl(kwargs.get("event", ""))
        panel.AddLine(text.event, eventCtrl)

        invisibleCtrl = panel.CheckBox(
            kwargs.get("invisible", False),
            "Invisible"
        )
        panel.AddLine(invisibleCtrl)

        imageButton = ImageButton(panel, label="Choose Image")
        imageBox = wx.StaticBitmap(
            panel,
            size=(40, 40),
            pos=(280, 70),
            style=wx.SUNKEN_BORDER
        )
        imageButton.view = imageBox

        imageOption = MakeOption(
            "image",
            panel.CheckBox(label="Use image as label:"),
            imageButton
        )

        foregroundColour = MakeOption(
            "foregroundColour",
            panel.CheckBox(label="Override foreground colour:"),
            panel.ColourSelectButton()
        )

        backgroundColour = MakeOption(
            "backgroundColour",
            panel.CheckBox(label="Override background colour:"),
            panel.ColourSelectButton()
        )

        fontInfo = MakeOption(
            "fontInfo",
            panel.CheckBox(label="Override button font:"),
            panel.FontSelectButton()
        )

        width = MakeOption(
            "width",
            panel.CheckBox(label="Override width:"),
            panel.SpinIntCtrl()
        )

        height = MakeOption(
            "height",
            panel.CheckBox(label="Override height:"),
            panel.SpinIntCtrl()
        )

        while panel.Affirmed():
            image = kwargs.get("image", None)
            kwargs = {}
            kwargs["label"] = labelCtrl.GetValue()
            kwargs["event"] = eventCtrl.GetValue()
            fontInfo()
            foregroundColour()
            backgroundColour()
            width()
            height()
            imageOption()
            if invisibleCtrl.GetValue():
                kwargs["invisible"] = True
            panel.SetResult(kwargs)




class StartNewLine(eg.ActionBase):
    name = "Start New Line"

    def __call__(self, height=0):
        self.plugin.data.append((LineType, (height,)))


    def GetLabel(self, *args):
        return self.name


    def Configure(self, height=0):
        panel = eg.ConfigPanel()
        heightCtrl = panel.SpinIntCtrl(height)
        panel.AddLine("Height:", heightCtrl)
        while panel.Affirmed():
            panel.SetResult(heightCtrl.GetValue())



class Show(eg.ActionBase):

    def __call__(self, xPos=0, yPos=0, alwaysOnTop=False):
        wx.CallAfter(CreateRemote, self.plugin, xPos, yPos, alwaysOnTop)


    def GetLabel(self, xPos, yPos, *args):
        return "Show at %d,%d" % (xPos, yPos)


    def Configure(self, xPos=0, yPos=0, alwaysOnTop=False):
        panel = eg.ConfigPanel()
        xPosCtrl = panel.SpinIntCtrl(xPos, min=-32768, max=32767)
        yPosCtrl = panel.SpinIntCtrl(yPos, min=-32768, max=32767)
        alwaysOnTopCtrl = panel.CheckBox(
            alwaysOnTop,
            "Keep window always on top"
        )
        panel.AddLine("Screen horizontal position:", xPosCtrl)
        panel.AddLine("Screen vertical position:", yPosCtrl)
        panel.AddLine(alwaysOnTopCtrl)
        while panel.Affirmed():
            panel.SetResult(
                xPosCtrl.GetValue(),
                yPosCtrl.GetValue(),
                alwaysOnTopCtrl.GetValue(),
            )



class Close(eg.ActionBase):

    def __call__(self):
        if self.plugin.frame:
            self.plugin.frame.Destroy()
            self.plugin.frame = None



class GenButton(buttons.GenButton):

    def DrawFocusIndicator(self, *args):
        pass



class GenBitmapButton(buttons.GenBitmapButton):

    def DrawFocusIndicator(self, *args):
        pass



class RemotePanel(wx.Panel):

    def __init__(self, parent, plugin):
        self.parent = parent
        wx.Panel.__init__(self, parent)
        if plugin.moveOnDrag:
            self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        if plugin.iconizeOnDoubleClick:
            self.Bind(wx.EVT_LEFT_DCLICK, self.OnCmdIconize)
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)

        self.menu = menu = wx.Menu()
        item = wx.MenuItem(menu, wx.NewId(), "Hide")
        menu.AppendItem(item)
        menu.Bind(wx.EVT_MENU, self.OnCmdIconize, item)
        item = wx.MenuItem(menu, wx.NewId(),"Close")
        menu.AppendItem(item)
        menu.Bind(wx.EVT_MENU, self.OnCmdClose, item)


    def OnCmdClose(self, event):
        self.parent.Close()


    def OnCmdIconize(self, event):
        self.parent.Iconize()


    def OnRightDown(self, event):
        self.PopupMenu(self.menu)


    def OnLeftDown(self, event):
        x1, y1 = GetCursorPos()
        x2, y2 = self.parent.GetScreenPosition()
        self.offset = (x1 - x2, y1 - y2)

        # from now on we want all mouse motion events
        self.Bind(wx.EVT_MOTION, self.OnDrag)
        # and the left up event
        self.Bind(wx.EVT_LEFT_UP, self.OnDragEnd)

        # and call Skip in for handling focus events etc.
        event.ResumePropagation(wx.EVENT_PROPAGATE_MAX)
        event.Skip()
        # start capturing the mouse exclusively
        self.CaptureMouse()


    def OnDrag(self, event):
        x1, y1 = GetCursorPos()
        x2, y2 = self.offset
        self.parent.SetPosition((x1 - x2, y1 - y2))


    def OnDragEnd(self, event):
        # unbind the unneeded events
        self.Unbind(wx.EVT_MOTION)
        self.Unbind(wx.EVT_LEFT_UP)

        # stop processing the mouse capture
        self.ReleaseMouse()



class RemoteFrame(wx.Frame):

    def __init__(self, plugin, pos, alwaysOnTop):
        style = wx.SYSTEM_MENU|wx.MINIMIZE_BOX|wx.CLIP_CHILDREN|wx.CLOSE_BOX
        if not plugin.showInTaskbar:
            style |= wx.FRAME_NO_TASKBAR
        if plugin.windowStyle == 0:
            style |= wx.CAPTION
        elif plugin.windowStyle == 1:
            style |= wx.RAISED_BORDER
        elif plugin.windowStyle == 2:
            style |= wx.NO_BORDER|wx.FRAME_SHAPED
        if alwaysOnTop:
            style |= wx.STAY_ON_TOP
        wx.Frame.__init__(
            self,
            None,
            title=plugin.caption,
            pos=pos,
            style=style
        )
        self.SetBackgroundColour(plugin.windowColour)



def CreateRemote(plugin, xPos, yPos, alwaysOnTop):
    data = plugin.data
    borderSize = plugin.borderSize

    if plugin.frame:
        plugin.frame.Destroy()
    plugin.frame = frame = RemoteFrame(plugin, (xPos, yPos), alwaysOnTop)
    panel = RemotePanel(frame, plugin)
    mainSizer = wx.BoxSizer(wx.VERTICAL)
    mainSizer.Add((0, borderSize))
    lineSizer = wx.BoxSizer(wx.HORIZONTAL)
    x = 0
    y = 0

    def MakeButtonDownFunc(eventstring):
        if not eventstring:
            return
        def OnButtonDown(event):
            event.Skip()
            plugin.lastEvent = plugin.TriggerEnduringEvent(eventstring)
        return OnButtonDown


    for itemType, args in data:
        if itemType is ButtonType:
            kwargs = args
            def GetOption(name):
                return kwargs.get(name, plugin.defaults.get(name))
            label = kwargs.get("label")
            event = kwargs.get("event")
            image = kwargs.get("image", None)
            invisible = kwargs.get("invisible", False)
            size =(GetOption("width"), GetOption("height"))
            if invisible:
                # create a spacer
                button = size
            else:
                if image:
                    stream = StringIO(b64decode(image))
                    bmp = wx.BitmapFromImage(wx.ImageFromStream(stream))
                    stream.close()
                    button = GenBitmapButton(panel, -1, bmp, size=size)
                    button.SetLabel(label)
                else:
                    button = GenButton(panel, -1, label, size=size)
                button.SetFont(
                    wx.FontFromNativeInfoString(GetOption("fontInfo"))
                )

                button.SetBezelWidth(3)
                button.SetBackgroundColour(GetOption("backgroundColour"))
                button.SetForegroundColour(GetOption("foregroundColour"))

                OnButtonDown = MakeButtonDownFunc(event)
                if OnButtonDown:
                    button.Bind(wx.EVT_LEFT_DOWN, OnButtonDown)
                    button.Bind(wx.EVT_LEFT_DCLICK, OnButtonDown)
                    button.Bind(wx.EVT_LEFT_UP, plugin.OnButtonUp)
            if x == 0:
                lineSizer.Add(button)
            else:
                lineSizer.Add(button, 0, wx.LEFT, plugin.rowGap)
            x += 1
        elif itemType is LineType:
            height = args[0]
            mainSizer.Add(
                lineSizer,
                0,
                wx.BOTTOM|wx.ALIGN_CENTER_HORIZONTAL,
                height + plugin.columnGap
            )
            lineSizer = wx.BoxSizer(wx.HORIZONTAL)
            y += 1
            x = 0

    mainSizer.Add(lineSizer, 0, wx.LEFT|wx.RIGHT, borderSize)
    mainSizer.Add((0, borderSize))
    panel.SetSizerAndFit(mainSizer)
    frame.SetClientSize(panel.GetSize())
    frame.Show(True)
    frame.Update()