# -*- coding: utf-8 -*-
#
# plugins/MediaPlayerClassic/__init__.py
#
# Copyright (C) 2006 MonsterMagnet
#
# This file is a plugin for EventGhost.
# Copyright (C) 2005-2012 Lars-Peter Voss <bitmonster@eventghost.org>
#
# EventGhost is free software; you can redistribute it and/or modify it under
# the terms of the GNU General Public License version 2 as published by the
# Free Software Foundation;
#
# 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 this program. If not, see <http://www.gnu.org/licenses/>.
    
# Changelog (in reverse chronological order):
# -------------------------------------------
# 2.9 by Pako 2015-01-12 19:23 UTC+1
#     - bugfix - Find_MPC() function now works also in 64-bit system
# 2.8 by Pako 2014-12-26 10:38 UTC+1
#     - bugfix - the function GetMpcHcPath() improved
# 2.7 by Pako 2014-12-18 08:09 UTC+1
#     - bugfix (Show menu - Test button)
# 2.6 by Pako 2013-02-10 10:56 UTC+1
#     - the function GetMpcHcPath() improved - now works also in x64 environment
# 2.5 by Pako 2013-02-08 19:05 UTC+1
#     - we must wait with the connection, if MPC-HC is "Not responding"
# 2.4 by Pako 2012-12-02 16:01 UTC+1
#     - added "Stop processing event" feature (Menu and GoTo frames)
# 2.3 by Pako 2012-09-10 08:15 UTC+1
#     - the function GetMpcHcPath() changed,
#       due to HKEY_LOCAL_MACHINE -> HKEY_CURRENT_USER change
# 2.2 by Pako 2012-08-19 12:21 UTC+1
#     - Added the event "MPC-HC.Connected"
#     - eg.scheduler.CancelTask added, when plugin is stopped
# 2.1 by Pako 2012-08-19 12:21 UTC+1
#     - bugfixes
# 2.0 by Pako 2012-08-17 12:23 UTC+1
#     - Corrected ID at many actions
#     - Added many new actions
#     - Plugin now automatically triggers events
# 1.7 by Pako 2012-01-06 15:18 UTC+1
#     - Added actions GetWindowState and GetNowPlaying
# 1.6 by Pako 2011-06-05 18:57 UTC+1
#     - Used eg.EVT_VALUE_CHANGED instead of EVT_BUTTON_AFTER
# 1.5 by Pako
#     - bugfix (On Screen GoTo is always on primary monitor)
# 1.4 by Pako
#     - bugfix (ShowMenu when fullscreen is on second monitor)
# 1.3 by Pako
#     - added actions:
#                      Get Times
#                      Show Menu
#                      On Screen GoTo
#     - deleted action:
#                      Get recent files (replaced Show Menu)
# 1.2 by Pako
#     - added actions: Toggle OSD Elapsed Time
#                      Open Directory
#                      Send user message
#                      Get recent files
# 1.1 by bitmonster
#     - changed code to use new AddActionsFromList
# 1.0 by MonsterMagnet
#     - initial version

eg.RegisterPlugin(
    name = "Media Player Classic",
    author = "MonsterMagnet",
    version = "2.9",
    kind = "program",
    guid = "{DD75104D-D586-438A-B63D-3AD01A4D4BD3}",
    createMacrosOnAdd = True,
    description = (
        'Adds actions to control '
        '<br><a href="http://mpc-hc.sourceforge.net/">'
        'Media Player Classic - Home Cinema</a>.'
    ),
    help = """
        For proper functioning of this plugin should be used
        <br><a href="http://mpc-hc.sourceforge.net/">
        Media Player Classic - Home Cinema (x86/x64)</a>
        <br>version 1.6.3.5818 or later.
        <br><br>There must be selected the option
        <br><b> Use the same player for each media file</b>
        <br>in dialogue <i>View/Options.../Player/Open options.</i>""",
    icon = (
        "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAhElEQVR42rWRgQqAIAwF"
        "fV+++eWr1V6kiM6gQaTVHYehJEdV7bUG18hCInIDQMNhA+L7cQHBETQrBWERDXANjcxm"
        "Ee6CyFxd6ArkynZT5l7KK9gFbs3CrGgEPLzM1FonAn9kz59stqhnhdhEwK/j3m0Tgj8K"
        "OPmCr4eYpmMaASt3JS44ADcFoxFdcIMPAAAAAElFTkSuQmCC"
    ),
    url = "http://www.eventghost.net/forum/viewtopic.php?t=694"
)
#===============================================================================

import eg
import wx
import _winreg
from os import environ
from os.path import dirname, join, exists, isfile, split, isabs
from subprocess import Popen
from eg.WinApi import SendMessageTimeout, WM_COMMAND
from eg.WinApi.Utils import GetMonitorDimensions
from win32api import EnumDisplayMonitors
from eg.WinApi.Dynamic import PostMessage
from eg.WinApi.Dynamic import CreateEvent, SetEvent, GetWindowLong
from threading import Timer
from win32gui import GetMenu, GetSubMenu, GetMenuItemCount, GetWindowPlacement 
from win32gui import GetClassName, GetWindowText, IsWindowVisible, GetWindowRect
from win32gui import GetDlgItem, SendMessage, FindWindow, IsWindow, GetWindow
from copy import deepcopy as cpy
from time import sleep, strftime, gmtime
from winsound import PlaySound, SND_ASYNC
from ctypes import create_unicode_buffer, addressof, windll, c_int, c_buffer, byref, sizeof
import wx.grid as gridlib
from eg.WinApi.Dynamic import COPYDATASTRUCT, PCOPYDATASTRUCT, WM_COPYDATA
from ctypes import Structure, cast, wstring_at, c_wchar, c_void_p
from sys import getfilesystemencoding
FSE = getfilesystemencoding()
from eg.Classes.MainFrame.TreeCtrl import DropTarget as EventDropTarget

GWL_EXSTYLE      = -20
WS_EX_WINDOWEDGE = 0x00000100
WM_INITMENUPOPUP = 0x0117
MF_GRAYED        = 1
MF_DISABLED      = 2
MF_CHECKED       = 8
MF_BYPOSITION    = 1024
MF_SEPARATOR     = 2048
SYS_VSCROLL_X    = wx.SystemSettings.GetMetric(wx.SYS_VSCROLL_X)
SYS_HSCROLL_Y    = wx.SystemSettings.GetMetric(wx.SYS_HSCROLL_Y)
arialInfoString  = "0;-35;0;0;0;700;0;0;0;0;3;2;1;34;Arial"
GW_CHILD         = 5
WM_CLOSE         = 16
#===============================================================================

def Find_MPC():
    mpchc = eg.WindowMatcher(
        u'mpc-hc{*}.exe',
        None,
        u'MediaPlayerClassicW',
        None,
        None,
        None,
        True,
        0.0,
        2
    )
    return mpchc()
#===============================================================================

class MPC_OSDDATA(Structure): 
    _fields_ = [
        ("nMsgPos", c_int),       #// screen position constant (see OSD_MESSAGEPOS constants)
        ("nDurationMS", c_int),   #// duration in milliseconds
        ("strMsg", c_wchar * 128) #// message to display thought OSD
    ]
OSDDATA = MPC_OSDDATA()
#===============================================================================

MPC_LOADSTATE = ("Closed", "Loading", "Loaded", "Closing")

MPC_PLAYSTATE = ("Play", "Pause", "Stop", "Unused")

CMD_CONNECT            = 0x50000000 #Par 1 : MPC window handle (command should be send to this HWnd)

CMD_STATE              = 0x50000001 #Par 1 : current state /see MPC_LOADSTATE enum

CMD_PLAYMODE           = 0x50000002 #Par 1 : current play mode (see MPC_PLAYSTATE enum)

CMD_NOWPLAYING		   = 0x50000003 #;   // Send after opening a new file
                                    #   // Par 1 : title
                                    #   // Par 2 : author
                                    #   // Par 3 : description
                                    #   // Par 4 : complete filename (path included)
                                    #   // Par 5 : duration in seconds

CMD_LISTSUBTITLETRACKS = 0x50000004 #   // List of subtitle tracks
                                    #   // Par 1 : Subtitle track name 0
                                    #   // Par 2 : Subtitle track name 1
                                    #   // ...
                                    #   // Par n : Active subtitle track, -1 if subtitles disabled
                                    #   //
                                    #   // if no subtitle track present, returns -1
                                    #   // if no file loaded, returns -2

CMD_LISTAUDIOTRACKS    = 0x50000005 #   // List of audio tracks
                                    #   // Par 1 : Audio track name 0
                                    #   // Par 2 : Audio track name 1
                                    #   // ...
                                    #   // Par n : Active audio track
                                    #   //
                                    #   // if no audio track present, returns -1
                                    #   // if no file loaded, returns -2

CMD_PLAYLIST           = 0x50000006 #   // List of files in the playlist
                                    #   // Par 1 : file path 0
                                    #   // Par 2 : file path 1
                                    #   // ...
                                    #   // Par n : active file, -1 if no active file

CMD_CURRENTPOSITION		= 0x50000007
	 #Send current playback position in responce
	 #of CMD_GETCURRENTPOSITION.
	 #Par 1 : current position in seconds
	

CMD_NOTIFYSEEK			= 0x50000008
	 #Send the current playback position after a jump.
	 #(Automatically sent after a seek event).
	 #Par 1 : new playback position (in seconds).

CMD_NOTIFYENDOFSTREAM	= 0x50000009
	 #Notify the end of current playback
	 #(Automatically sent).
	 #Par 1 : none.

# ==== Commands from host to MPC

# Open new file
# Par 1 : file path
CMD_OPENFILE			= 0xA0000000

# Stop playback but keep file / playlist
CMD_STOP				= 0xA0000001

# Stop playback and close file / playlist
CMD_CLOSEFILE			= 0xA0000002

# Pause or restart playback
CMD_PLAYPAUSE			= 0xA0000003

# Add a new file to playlist (did not start playing)
# Par 1 : file path
CMD_ADDTOPLAYLIST		= 0xA0001000

# Remove all files from playlist
CMD_CLEARPLAYLIST		= 0xA0001001

# Start playing playlist
CMD_STARTPLAYLIST		= 0xA0001002

CMD_REMOVEFROMPLAYLIST	= 0xA0001003	# TODO

# Cue current file to specific position
# Par 1 : new position in seconds
CMD_SETPOSITION			= 0xA0002000

# Set the audio delay
# Par 1 : new audio delay in ms
CMD_SETAUDIODELAY		= 0xA0002001

# Set the subtitle delay
# Par 1 : new subtitle delay in ms
CMD_SETSUBTITLEDELAY	= 0xA0002002

# Set the active file in the playlist
# Par 1 : index of the active file -1 for no file selected
# DOESN'T WORK
CMD_SETINDEXPLAYLIST	= 0xA0002003

# Set the audio track
# Par 1 : index of the audio track
CMD_SETAUDIOTRACK		= 0xA0002004

# Set the subtitle track
# Par 1 : index of the subtitle track -1 for disabling subtitles
CMD_SETSUBTITLETRACK	= 0xA0002005

# Ask for a list of the subtitles tracks of the file
# return a CMD_LISTSUBTITLETRACKS
CMD_GETSUBTITLETRACKS	= 0xA0003000

# Ask for the current playback position
# see CMD_CURRENTPOSITION.
# Par 1 : current position in seconds
CMD_GETCURRENTPOSITION	= 0xA0003004

# Jump forward/backward of N seconds
# Par 1 : seconds (negative values for backward)
CMD_JUMPOFNSECONDS		= 0xA0003005

# Ask for a list of the audio tracks of the file
# return a CMD_LISTAUDIOTRACKS
CMD_GETAUDIOTRACKS		= 0xA0003001

# Ask for the properties of the current loaded file
# return a CMD_NOWPLAYING
CMD_GETNOWPLAYING		= 0xA0003002

# Ask for the current playlist
# return a CMD_PLAYLIST
CMD_GETPLAYLIST			= 0xA0003003

# Toggle FullScreen
CMD_TOGGLEFULLSCREEN	= 0xA0004000

# Jump forward(medium)
CMD_JUMPFORWARDMED		= 0xA0004001

# Jump backward(medium)
CMD_JUMPBACKWARDMED		= 0xA0004002

# Increase Volume
CMD_INCREASEVOLUME		= 0xA0004003

# Decrease volume
CMD_DECREASEVOLUME		= 0xA0004004

# Shader toggle
CMD_SHADER_TOGGLE		= 0xA0004005

# Close App
CMD_CLOSEAPP			= 0xA0004006

# show host defined OSD message string
CMD_OSDSHOWMESSAGE		= 0xA0005000
#===============================================================================

class FixedWidth(wx.FontEnumerator):

    def __init__(self):
        wx.FontEnumerator.__init__(self)
        self.fontList = []

    def OnFacename(self, fontname):
        if not fontname.startswith("@"): 
            self.fontList.append(fontname)
#===============================================================================

def GetSec(timeStr):
    sec = int(timeStr[-2:])
    min = timeStr[-5:-3]
    hr = timeStr[-8:-6]
    if min:
        sec += 60*int(min)
    if hr:
        sec += 3600*int(hr)
    return sec
#===============================================================================

def GetItemList(menu, hWnd):
    SendMessage(hWnd, WM_INITMENUPOPUP, menu, 0) #REFRESH MENU STATE !!!
    itemList = []
    itemName = c_buffer("\000" * 128)
    count = GetMenuItemCount(menu)
    for i in range(count):
        windll.user32.GetMenuStringA(c_int(menu),
                                     c_int(i),
                                     itemName,
                                     c_int(len(itemName)),
                                     MF_BYPOSITION)
        menuState = windll.user32.GetMenuState(c_int(menu),
                                               c_int(i),
                                               MF_BYPOSITION)
        id = windll.user32.GetMenuItemID(c_int(menu), c_int(i))
#        if menuState & (MF_GRAYED|MF_DISABLED|MF_SEPARATOR):
        if menuState & (MF_GRAYED|MF_DISABLED):
            continue
        item = itemName.value.replace("&","").split("\t")[0]
        if item == "" and id == 0:
            continue
        checked = bool(menuState & MF_CHECKED)
        if isabs(item):
            if not isfile(item):
                continue
            else:
                item = split(item)[1]
        itemList.append((item, i, checked, id))
    return itemList
#===============================================================================

class MenuGrid(gridlib.Grid):

    def __init__(self, parent, lngth):
        gridlib.Grid.__init__(self, parent)
        self.SetRowLabelSize(0)
        self.SetColLabelSize(0)
        self.SetDefaultRowSize(16)
        self.SetScrollLineX(1)
        self.SetScrollLineY(1)
        self.EnableEditing(False)
        self.EnableDragColSize(False)
        self.EnableDragRowSize(False)
        self.EnableDragGridSize(False)
        self.EnableGridLines(False)
        self.SetColMinimalAcceptableWidth(8)
        self.CreateGrid(lngth, 3)
        attr = gridlib.GridCellAttr()
        attr.SetAlignment(wx.ALIGN_LEFT, wx.ALIGN_CENTRE)
        self.SetColAttr(1,attr)
        self.SetSelectionMode(gridlib.Grid.wxGridSelectRows)
        self.Bind(gridlib.EVT_GRID_CMD_SELECT_CELL, self.onGridSelectCell, self)


    def SetBackgroundColour(self, colour):
        self.SetDefaultCellBackgroundColour(colour)


    def SetForegroundColour(self, colour):
        self.SetDefaultCellTextColour(colour)


    def SetFont(self, font):
        self.SetDefaultCellFont(font)


    def GetSelection(self):
        return self.GetSelectedRows()[0]


    def Set(self, choices):
        oldLen = self.GetNumberRows()
        newLen = len(choices)
        h = self.GetDefaultRowSize()
        if oldLen > newLen:
            self.DeleteRows(0, oldLen-newLen, False)
        elif oldLen < newLen:
            self.AppendRows(newLen-oldLen, False)
        for i in range(len(choices)):
            chr = u"\u25a0" if choices[i][2] else ""
            self.SetCellValue(i,0,chr)
            self.SetCellValue(i,1," "+choices[i][0])
            chr = u"\u25ba" if choices[i][3] == -1 else ""
            self.SetCellValue(i,2, chr)
            self.SetRowSize(i,h)


    def onGridSelectCell(self, event):
        row = event.GetRow()
        self.SelectRow(row)
        if not self.IsVisible(row,1):
            self.MakeCellVisible(row,1)
        event.Skip()


    def MoveCursor(self, step):
        max = self.GetNumberRows()
        sel = self.GetSelectedRows()[0]
        new = sel + step
        if new < 0:
            new += max
        elif new > max-1:
            new -= max
        self.SetGridCursor(new, 1)
        self.SelectRow(new)
#===============================================================================

class MyTextDropTarget(EventDropTarget):

    def __init__(self, object):
        EventDropTarget.__init__(self, object)
        self.object = object


    def OnDragOver(self, x, y, dragResult):
        return wx.DragMove


    def OnData(self, dummyX, dummyY, dragResult):
        if self.GetData() and self.customData.GetDataSize() > 0:
            txt = self.customData.GetData()
            ix, evtList = self.object.GetEvtList()
            flag = True
            for lst in evtList:
                if txt in lst:
                    flag = False
                    break
            if flag:
                self.object.InsertImageStringItem(len(evtList[ix]), txt, 0)
                self.object.UpdateEvtList(ix, txt)
            else:
                PlaySound('SystemExclamation', SND_ASYNC)


    def OnLeave(self):
        pass
#===============================================================================

class EventListCtrl(wx.ListCtrl):

    def __init__(self, parent, id, evtList, ix, plugin):
        width = 205
        wx.ListCtrl.__init__(self, parent, id, style=wx.LC_REPORT | 
            wx.LC_NO_HEADER | wx.LC_SINGLE_SEL, size = (width, -1))
        self.parent = parent
        self.id = id
        self.evtList = evtList
        self.ix = ix
        self.plugin = plugin
        self.sel = -1
        self.il = wx.ImageList(16, 16)
        self.il.Add(wx.BitmapFromImage(wx.Image(join(eg.imagesDir, "event.png"), wx.BITMAP_TYPE_PNG)))
        self.SetImageList(self.il, wx.IMAGE_LIST_SMALL)
        self.InsertColumn(0, '')
        self.SetColumnWidth(0, width - 5 - SYS_VSCROLL_X)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.OnSelect)
        self.Bind(wx.EVT_SET_FOCUS, self.OnChange) 
        self.Bind(wx.EVT_LIST_INSERT_ITEM, self.OnChange) 
        self.Bind(wx.EVT_LIST_ITEM_DESELECTED, self.OnChange) 
        self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnRightClick)
        self.SetToolTipString(self.plugin.text.toolTip)


    def OnSelect(self, event):
        self.sel = event.GetIndex()
        evt = eg.ValueChangedEvent(self.id, value = self)
        wx.PostEvent(self, evt)
        event.Skip()


    def OnChange(self, event):
        evt = eg.ValueChangedEvent(self.id, value = self)
        wx.PostEvent(self, evt)
        event.Skip()


    def OnRightClick(self, event):
        if not hasattr(self, "popupID1"):
            self.popupID1 = wx.NewId()
            self.popupID2 = wx.NewId()
            self.Bind(wx.EVT_MENU, self.OnDeleteButton, id=self.popupID1)
            self.Bind(wx.EVT_MENU, self.OnDeleteAllButton, id=self.popupID2)
        # make a menu
        menu = wx.Menu()
        # add some items
        menu.Append(self.popupID1, self.plugin.text.popup[0])
        menu.Append(self.popupID2, self.plugin.text.popup[1])
        # Popup the menu.  If an item is selected then its handler
        # will be called before PopupMenu returns.
        self.PopupMenu(menu)
        menu.Destroy()


    def OnDeleteButton(self, event=None):
        self.DeleteItem(self.sel)
        self.evtList[self.ix].pop(self.sel)       
        evt = eg.ValueChangedEvent(self.id, value = self)
        wx.PostEvent(self, evt)
        if event:
            event.Skip()
        

    def OnDeleteAllButton(self, event=None):
        self.DeleteAllItems()
        evt = eg.ValueChangedEvent(self.id, value = self)
        wx.PostEvent(self, evt)
        self.evtList[self.ix] = []
        if event:
            event.Skip()


    def GetEvtList(self):
        return self.ix, self.evtList


    def UpdateEvtList(self, ix, txt):
        self.evtList[ix].append(txt)


    def SetItems(self, evtList):
        for i in range(len(evtList)):
            self.InsertImageStringItem(i, evtList[i], 0) 
#===============================================================================

class GoToFrame(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(
            self,
            None,
            -1,
            'MPCgotoFrame',
            style=wx.STAY_ON_TOP | wx.SIMPLE_BORDER,
        )
        self.GoToCtrl = None
        self.pos = -1
        self.posList = (0,1,3,4,6,7)
        self.gotowin = None
        self.total = None
        self.evtList = [[],[],[],[],[]]
        self.plugin = None


    def UpdateOSD(self, data=None):
        if data:
            self.GoToCtrl.SetValue(data)
        if self.pos > -1:
            pos = self.pos
            self.GoToCtrl.SetStyle(0, pos, wx.TextAttr(self.fore, self.back, self.fnt))
            self.GoToCtrl.SetStyle(pos, pos+1, wx.TextAttr(self.foreSel, self.backSel, self.fnt))
            self.GoToCtrl.SetStyle(pos+1, 8, wx.TextAttr(self.fore, self.back, self.fnt))
            f = self.fore
            b = self.back
        else:
            self.GoToCtrl.SetStyle(0, 8, wx.TextAttr(self.fore, self.back, self.fnt))
            f = self.foreSel
            b = self.backSel
        self.gotoLbl.SetBackgroundColour(b)
        self.gotoLbl.SetForegroundColour(f)
        self.Refresh()


    def MoveCursor(self, step):
        max = len(self.posList)-1
        ix = self.posList.index(self.pos)
        ix += step
        if ix > max:
            ix = 0
        elif ix == -1:
            ix = max    
        self.pos = self.posList[ix]
        wx.CallAfter(self.UpdateOSD)


    def Turn(self, step):
        min = 0
        max = 9
        if self.pos == -1:
            self.GoTo()
            return
        pos = self.pos
        data = list(self.GoToCtrl.GetValue())
        value = int(data[pos])
        if pos == 6:
            max = 5
        elif pos == 4 and len(self.posList)==3:
            max = int(self.total[4])
        elif pos == 3:
            max = 5
            if len(self.posList)==4:
                max = int(self.total[3])
        elif pos == 1 and len(self.posList)==5:
            max = int(self.total[1])
        elif pos == 0 and len(self.posList)==6:
            max = int(self.total[0])
        value += step
        if value < min:
            value = max
        elif value > max:
            value = min
        data[pos] = str(value)
        newTime =''.join(data)
        if newTime<self.total:
            wx.CallAfter(self.UpdateOSD, newTime)


    def ShowGoToFrame(
        self,
        fore,
        back,
        foreSel,
        backSel,
        fontFace,
        fontSize,
        flag,
        plugin,
        event,
        monitor,
        hWnd,
        evtList,
        sizeFlag
    ):
        self.plugin = plugin
        eg.TriggerEvent("OSD.%s" % self.plugin.text.opened, prefix = "MPC")
        self.flag = False
        self.Bind(wx.EVT_CLOSE, self.onClose)
        child = GetDlgItem(hWnd, 10021)
        self.total = "99:99:99"
        if GetClassName(child) ==  "#32770":
            statText = GetDlgItem(child, 12027)
            if GetClassName(statText) ==  "Static":
                try:
                    elaps, total = GetWindowText(statText).split(" / ")
                except:
                    self.plugin.menuDlg = None
                    return
                totalSec = GetSec(total)
                self.total = strftime('%H:%M:%S', gmtime(totalSec))
                if totalSec < 600:                # < 10 min   (skip 3 digits)
                    self.posList = (-1,4,6,7)
                elif totalSec < 3600:             # < 1 hour   (skip 2 digits)
                    self.posList = (-1,3,4,6,7)
                elif totalSec < 360000:           # < 10 hour  (skip 1 digit)
                    self.posList = (-1,1,3,4,6,7)
                else:
                    self.posList = (-1,0,1,3,4,6,7)  # >= 10 hour  (no skip)
        self.back = back
        self.fore = fore
        self.foreSel = foreSel
        self.backSel = backSel
        self.flag = flag
        self.evtList = evtList
        for evt in self.evtList[0]:
            eg.Bind(evt, self.onUp)
        for evt in self.evtList[1]:
            eg.Bind(evt, self.onDown)
        for evt in self.evtList[2]:
            eg.Bind(evt, self.onLeft)
        for evt in self.evtList[3]:
            eg.Bind(evt, self.onRight)
        for evt in self.evtList[4]:
            eg.Bind(evt, self.onEscape)
        label = self.plugin.text.gotoLabel
        self.gotoLbl=wx.StaticText(self, -1, label, pos = (5,5))
        self.gotoLbl.SetBackgroundColour(self.back)
        self.gotoLbl.SetForegroundColour(self.fore)
        fnt = self.gotoLbl.GetFont()
        border = fontSize/3
        fnt.SetPointSize(6*fontSize/10)
        fnt.SetWeight(wx.FONTWEIGHT_BOLD)
        self.gotoLbl.SetFont(fnt)
        labelSize = self.gotoLbl.GetTextExtent(label)
        self.gotoLbl.SetSize(labelSize)
        self.gotoLbl.SetPosition((border,border))
        self.GoToCtrl = wx.TextCtrl(
                    self,
                    -1,
                    style=wx.TE_RICH2|wx.NO_BORDER|wx.TE_READONLY|wx.TE_CENTER,
                )
        fnt.SetFaceName(fontFace)
        fnt.SetPointSize(fontSize)
        self.GoToCtrl.SetFont(fnt)
        self.fnt = fnt
        data = "%s%s" % ("00:00:00"[:8-len(elaps)],elaps)
        gotoSize = self.GoToCtrl.GetTextExtent(data)
        wx.CallAfter(self.UpdateOSD, data)
        if sizeFlag:
            gotoSize = (1.4 * gotoSize[0],gotoSize[1])
        self.GoToCtrl.SetSize(gotoSize)
        self.GoToCtrl.SetPosition((border, 1.5*border+labelSize[1]))
        self.SetSize((4+gotoSize[0]+2*border,2+labelSize[1]+gotoSize[1]+2.5*border))
        self.SetBackgroundColour(self.back)
        self.GoToCtrl.SetBackgroundColour(self.back)
        self.Bind(wx.EVT_CHAR_HOOK, self.onFrameCharHook)
        monDim = GetMonitorDimensions()
        try:
            x,y,ws,hs = monDim[monitor]
        except IndexError:
            x,y,ws,hs = monDim[0]
        width,height = self.GetSizeTuple()
        x_pos = x + (ws - width)/2
        y_pos = y + (hs - height)/2
        self.SetPosition((x_pos,y_pos) )
        self.Show(True)
        self.gotoLbl.SetFocus()
        if self.flag:
            self.timer=MyTimer(t = 5.0, plugin = self.plugin)
        wx.Yield()
        SetEvent(event)


    def onUp(self, event):
        wx.CallAfter(self.Turn, 1)
        return True #stop processing this event !!!


    def onDown(self, event):
        wx.CallAfter(self.Turn, -1)
        return True #stop processing this event !!!


    def onLeft(self, event):
        wx.CallAfter(self.MoveCursor, -1)
        return True #stop processing this event !!!


    def onRight(self, event):
        wx.CallAfter(self.MoveCursor, 1)
        return True #stop processing this event !!!


    def onEscape(self, event):
        wx.CallAfter(self.destroyMenu)
        return True #stop processing this event !!!


    def GoTo(
        self,
    ):
        data = self.GoToCtrl.GetValue()
        if data >= self.total:
            return
        wx.CallAfter(
            self.plugin.SendCopydata,
            CMD_SETPOSITION,
            str(GetSec(data))
        )
        self.destroyMenu()


    def onFrameCharHook(self, event):
        keyCode = event.GetKeyCode()
        if keyCode == wx.WXK_F4:
            if event.AltDown():
                self.destroyMenu()
        elif keyCode == wx.WXK_RETURN or keyCode == wx.WXK_NUMPAD_ENTER:
            self.GoTo()
        elif keyCode == wx.WXK_RIGHT or keyCode == wx.WXK_NUMPAD_RIGHT:
            self.MoveCursor(1)
        elif keyCode == wx.WXK_ESCAPE:
            self.destroyMenu()
        elif keyCode == wx.WXK_UP or keyCode == wx.WXK_NUMPAD_UP:
            self.Turn(1)
        elif keyCode == wx.WXK_DOWN or keyCode == wx.WXK_NUMPAD_DOWN:
            self.Turn(-1)
        elif keyCode == wx.WXK_LEFT or keyCode == wx.WXK_NUMPAD_LEFT:
            self.MoveCursor(-1)
        else:
            event.Skip()


    def onClose(self, event):
        self.Show(False)
        self.Destroy()
        if self.plugin:
            self.plugin.menuDlg = None
        if self.gotowin and IsWindow(self.gotowin):
            PostMessage(self.gotowin, WM_CLOSE, 0, 0)


    def destroyMenu(self):
        if self.flag:
            self.timer.Cancel()
        for evt in self.evtList[0]:
            eg.Unbind(evt, self.onUp)
        for evt in self.evtList[1]:
            eg.Unbind(evt, self.onDown)
        for evt in self.evtList[2]:
            eg.Unbind(evt, self.onLeft)
        for evt in self.evtList[3]:
            eg.Unbind(evt, self.onRight)
        for evt in self.evtList[4]:
            eg.Unbind(evt, self.onEscape)
        eg.TriggerEvent("OSD.%s" % self.plugin.text.closed, prefix = "MPC")
        self.Close()
#===============================================================================

class MenuEventsDialog(wx.MiniFrame):

    def __init__(self, parent, plugin):
        wx.MiniFrame.__init__(
            self,
            parent,
            -1,
            style=wx.CAPTION,
            name="MenuEventsDialog"
        )
        self.panel = parent
        self.plugin = plugin
        self.evtList = cpy(self.panel.evtList)
        self.SetBackgroundColour(wx.NullColour)
        self.ctrl = None
        self.sel = -1


    def ShowMenuEventsDialog(self, title, labels):
        self.panel.Enable(False)
        self.panel.dialog.buttonRow.cancelButton.Enable(False)
        self.panel.EnableButtons(False)
        self.SetTitle(title)
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.SetMinSize((450, 308))
        topSizer=wx.GridBagSizer(2, 20)
        textLbl_0=wx.StaticText(self, -1, labels[0])        
        id = wx.NewId()
        eventsCtrl_0 = EventListCtrl(self, id, self.evtList, 0, self.plugin)
        eventsCtrl_0.SetItems(self.evtList[0])
        dt0 = MyTextDropTarget(eventsCtrl_0)
        eventsCtrl_0.SetDropTarget(dt0)
        textLbl_1=wx.StaticText(self, -1, labels[1])       
        id = wx.NewId()        
        eventsCtrl_1 = EventListCtrl(self, id, self.evtList, 1, self.plugin)
        eventsCtrl_1.SetItems(self.evtList[1])
        dt1 = MyTextDropTarget(eventsCtrl_1)
        eventsCtrl_1.SetDropTarget(dt1)
        textLbl_2=wx.StaticText(self, -1, labels[2])        
        id = wx.NewId()
        eventsCtrl_2 = EventListCtrl(self, id, self.evtList, 2, self.plugin)
        eventsCtrl_2.SetItems(self.evtList[2])
        dt2 = MyTextDropTarget(eventsCtrl_2)
        eventsCtrl_2.SetDropTarget(dt2)
        textLbl_3=wx.StaticText(self, -1, labels[3])        
        id = wx.NewId()        
        eventsCtrl_3 = EventListCtrl(self, id, self.evtList, 3, self.plugin)
        eventsCtrl_3.SetItems(self.evtList[3])
        dt3 = MyTextDropTarget(eventsCtrl_3)
        eventsCtrl_3.SetDropTarget(dt3)
        textLbl_4=wx.StaticText(self, -1, labels[4])        
        id = wx.NewId()
        eventsCtrl_4 = EventListCtrl(self, id, self.evtList, 4, self.plugin)
        eventsCtrl_4.SetItems(self.evtList[4])
        dt4 = MyTextDropTarget(eventsCtrl_4)
        eventsCtrl_4.SetDropTarget(dt4)
        deleteSizer = wx.BoxSizer(wx.VERTICAL)
        delOneBtn = wx.Button(self, -1, self.plugin.text.popup[0])
        delBoxBtn = wx.Button(self, -1, self.plugin.text.popup[1])
        clearBtn  = wx.Button(self, -1, self.plugin.text.clear)
        deleteSizer.Add(delOneBtn, 1, wx.EXPAND)
        deleteSizer.Add(delBoxBtn, 1, wx.EXPAND|wx.TOP,5)
        deleteSizer.Add(clearBtn, 1, wx.EXPAND|wx.TOP,5)
        
        topSizer.Add(textLbl_0, (0,0))
        topSizer.Add(eventsCtrl_0, (1,0), flag = wx.EXPAND)
        topSizer.Add(textLbl_1, (0,1))
        topSizer.Add(eventsCtrl_1, (1,1), flag = wx.EXPAND)
        topSizer.Add(textLbl_2, (2,0),flag = wx.TOP, border = 8)
        topSizer.Add(eventsCtrl_2, (3,0), flag = wx.EXPAND)
        topSizer.Add(textLbl_3, (2,1), flag = wx.TOP, border = 8)
        topSizer.Add(eventsCtrl_3, (3,1), flag = wx.EXPAND)
        topSizer.Add(textLbl_4, (4,0), flag = wx.TOP, border = 8)
        topSizer.Add(eventsCtrl_4, (5,0), flag = wx.EXPAND)
        topSizer.Add(deleteSizer, (5,1), flag = wx.EXPAND)

        line = wx.StaticLine(self, -1, size=(20,-1),pos = (200,0), style=wx.LI_HORIZONTAL)
        btn1 = wx.Button(self, wx.ID_OK)
        btn1.SetLabel(self.plugin.text.ok)
        btn1.SetDefault()
        btn2 = wx.Button(self, wx.ID_CANCEL)
        btn2.SetLabel(self.plugin.text.cancel)
        btnsizer = wx.StdDialogButtonSizer()
        btnsizer.AddButton(btn1)
        btnsizer.AddButton(btn2)
        btnsizer.Realize()
        sizer.Add(topSizer,0,wx.ALL,10)
        sizer.Add(line, 0, wx.EXPAND|wx.ALIGN_CENTER|wx.TOP|wx.BOTTOM,5)
        sizer.Add(btnsizer, 0, wx.EXPAND|wx.RIGHT, 10)
        sizer.Add((1,6))
        self.SetSizer(sizer)
        sizer.Fit(self)


        def onFocus(evt):
            ctrl = evt.GetValue()
            if ctrl != self.ctrl:
                if self.ctrl:
                    self.ctrl.SetItemState(-1, wx.LIST_MASK_STATE, wx.LIST_STATE_SELECTED)
                self.ctrl = ctrl
            sel = self.ctrl.sel
            if sel != -1:
                self.sel = sel
            flag = self.ctrl.GetSelectedItemCount() > 0
            delOneBtn.Enable(flag)
            delBoxBtn.Enable(flag)
            evt.Skip()
        eventsCtrl_0.Bind(eg.EVT_VALUE_CHANGED, onFocus)        
        eventsCtrl_1.Bind(eg.EVT_VALUE_CHANGED, onFocus)        
        eventsCtrl_2.Bind(eg.EVT_VALUE_CHANGED, onFocus)        
        eventsCtrl_3.Bind(eg.EVT_VALUE_CHANGED, onFocus)        
        eventsCtrl_4.Bind(eg.EVT_VALUE_CHANGED, onFocus)      
      

        def onDelOneBtn(evt):
            self.ctrl.OnDeleteButton()
            delOneBtn.Enable(False)
            delBoxBtn.Enable(False)
            evt.Skip()
        delOneBtn.Bind(wx.EVT_BUTTON, onDelOneBtn)
        

        def onDelBoxBtn(evt):
            self.ctrl.OnDeleteAllButton()
            delOneBtn.Enable(False)
            delBoxBtn.Enable(False)
            evt.Skip()
        delBoxBtn.Bind(wx.EVT_BUTTON, onDelBoxBtn)


        def onClearBtn(evt):
            eventsCtrl_0.DeleteAllItems()
            eventsCtrl_1.DeleteAllItems()
            eventsCtrl_2.DeleteAllItems()
            eventsCtrl_3.DeleteAllItems()
            eventsCtrl_4.DeleteAllItems()
            delOneBtn.Enable(False)
            delBoxBtn.Enable(False)
            self.evtList = [[],[],[],[],[]]
            evt.Skip()
        clearBtn.Bind(wx.EVT_BUTTON, onClearBtn)


        def onClose(evt):
            self.panel.Enable(True)
            self.panel.dialog.buttonRow.cancelButton.Enable(True)
            self.panel.EnableButtons(True)
            self.GetParent().GetParent().Raise()
            self.Destroy()
            self.panel.setFocus()
        self.Bind(wx.EVT_CLOSE, onClose)
        

        def onCancel(evt):
            self.panel.Enable(True)
            self.panel.dialog.buttonRow.cancelButton.Enable(True)
            self.panel.EnableButtons(True)
            self.Close()
        btn2.Bind(wx.EVT_BUTTON,onCancel)
        

        def onOK(evt):
            self.panel.evtList = self.evtList
            self.Close()
        btn1.Bind(wx.EVT_BUTTON,onOK)
        
        sizer.Layout()
        self.Raise()
        self.Show()
#===============================================================================

class Menu(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(
            self,
            None,
            -1,
            'MPC_menu',
            style = wx.STAY_ON_TOP|wx.SIMPLE_BORDER
        )
        self.flag = False
        self.monitor = 0
        self.oldMenu = []
        self.messStack = []


    def DrawMenu(self, ix):
        self.Show(False)
        self.menuGridCtrl.SetGridCursor(ix, 1)
        self.menuGridCtrl.SelectRow(ix)
        monDim = GetMonitorDimensions()
        try:
            x,y,ws,hs = monDim[self.monitor]
        except IndexError:
            x,y,ws,hs = monDim[0]
        # menu height calculation:                                
        h=self.GetCharHeight()+4
        for i in range(len(self.choices)):
            self.menuGridCtrl.SetRowSize(i,h)
            self.menuGridCtrl.SetCellValue(i,1," "+self.choices[i])
            if self.items[i][3] == -1:
                self.menuGridCtrl.SetCellValue(i,2, u"\u25ba")
        height0 = len(self.choices)*h
        height1 = h*((hs-20)/h)
        height = min(height0, height1)+6
        # menu width calculation:
        width_lst=[]
        for item in self.choices:
            width_lst.append(self.GetTextExtent(item+' ')[0])
        width = max(width_lst)+8
        self.menuGridCtrl.SetColSize(0,self.w0)
        self.menuGridCtrl.SetColSize(1,width)
        self.menuGridCtrl.SetColSize(2,self.w2)
        self.menuGridCtrl.ForceRefresh()
        width = width + self.w0 + self.w2
        if height1 < height0:
            width += SYS_VSCROLL_X
        if width > ws-50:
            if height + SYS_HSCROLL_Y < hs:
                height += SYS_HSCROLL_Y
            width = ws-50
        width += 6
        x_pos = x + (ws - width)/2
        y_pos = y + (hs - height)/2
        self.SetDimensions(x_pos,y_pos,width,height)
        self.menuGridCtrl.SetDimensions(2,2,width-6,height-6,wx.SIZE_AUTO)
        self.Show(True)
        self.Raise()


    def ShowMenu(
        self,
        fore,
        back,
        foreSel,
        backSel,
        fontInfo,
        flag,
        plugin,
        event,
        monitor,
        hWnd,
        evtList,
        ix = 0
    ):
        self.fore     = fore
        self.back     = back
        self.foreSel  = foreSel
        self.backSel  = backSel
        self.fontInfo = fontInfo
        self.flag     = flag
        self.plugin   = plugin
        self.monitor  = monitor
        self.hWnd     = hWnd
        self.evtList = evtList
        eg.TriggerEvent("OSD.%s" % self.plugin.text.opened, prefix = "MPC")
        for evt in self.evtList[0]:
            eg.Bind(evt, self.onUp)
        for evt in self.evtList[1]:
            eg.Bind(evt, self.onDown)
        for evt in self.evtList[2]:
            eg.Bind(evt, self.onLeft)
        for evt in self.evtList[3]:
            eg.Bind(evt, self.onRight)
        for evt in self.evtList[4]:
            eg.Bind(evt, self.onEscape)
        child = GetWindow(self.hWnd, GW_CHILD)
        clsName = GetClassName(child)
        if clsName[:4] == "Afx:" and len(clsName) == 41:
            childRect = GetWindowRect(child)
            mons = EnumDisplayMonitors()
            fullscreen = False
            for mon in mons:
                if childRect == mon[2]:
                    fullscreen = True
                    break
            if fullscreen: # Fullscreen !
                SendMessageTimeout(self.hWnd, WM_COMMAND, 830, 0)
                self.messStack.append(830)
                sleep(0.1)
            childRect = GetWindowRect(child)
            parRect = GetWindowRect(self.hWnd)
            if childRect[1] - parRect[1] < 16: # Without menu !
                SendMessageTimeout(self.hWnd, WM_COMMAND, 817, 0)
                self.messStack.append(817)
                sleep(0.1)
        self.menu = GetMenu(hWnd)
        self.items = GetItemList(self.menu, self.hWnd)
        self.choices = [item[0] for item in self.items]
        self.menuGridCtrl = MenuGrid(self,len(self.choices))
        mainSizer = wx.BoxSizer(wx.VERTICAL)
        self.SetSizer(mainSizer)
        mainSizer.Add(self.menuGridCtrl, 0, wx.EXPAND)
        self.Bind(wx.EVT_CLOSE, self.onClose)
        self.Bind(gridlib.EVT_GRID_CMD_CELL_LEFT_DCLICK, self.onDoubleClick, self.menuGridCtrl)
        self.Bind(wx.EVT_CHAR_HOOK, self.onFrameCharHook)
        font = wx.FontFromNativeInfoString(fontInfo)
        self.menuGridCtrl.SetFont(font)
        arial = wx.FontFromNativeInfoString(arialInfoString)
        self.SetFont(font)            
        hght = self.GetTextExtent('X')[1]
        for n in range(1,1000):
            arial.SetPointSize(n)
            self.SetFont(arial)
            h = self.GetTextExtent(u"\u25a0")[1]
            if h > hght:
                break
        arial.SetPointSize(2*n/3)
        self.SetFont(arial)            
        self.w0 = 2 * self.GetTextExtent(u"\u25a0")[0]
        attr = gridlib.GridCellAttr()
        attr.SetFont(arial)
        attr.SetAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE)
        self.menuGridCtrl.SetColAttr(0,attr)
        for n in range(1,1000):
            arial.SetPointSize(n)
            self.SetFont(arial)
            h = self.GetTextExtent(u"\u25ba")[1]
            if h > hght:
                break
        arial.SetPointSize(n/2)
        self.SetFont(arial)            
        self.w2 = 2 * self.GetTextExtent(u"\u25ba")[0]
        attr = gridlib.GridCellAttr()
        attr.SetFont(arial)
        attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE)
        self.menuGridCtrl.SetColAttr(2,attr)
        self.SetFont(font)                        
        self.SetBackgroundColour((0, 0, 0))
        self.menuGridCtrl.SetBackgroundColour(self.back)
        self.menuGridCtrl.SetForegroundColour(self.fore)
        self.menuGridCtrl.SetSelectionBackground(self.backSel)
        self.menuGridCtrl.SetSelectionForeground(self.foreSel)
        if self.flag:
            self.timer=MyTimer(t = 5.0, plugin = self.plugin)
        self.DrawMenu(ix)    
        wx.Yield()
        SetEvent(event)


    def UpdateMenu(self, ix=0):
        self.items = GetItemList(self.menu, self.hWnd)
        if len(self.items)==0:
            PlaySound('SystemExclamation', SND_ASYNC)
            self.menu,ix = self.oldMenu.pop()
            self.items = GetItemList(self.menu, self.hWnd)
        self.choices = [item[0] for item in self.items]
        self.menuGridCtrl.Set(self.items)
        self.DrawMenu(ix)    


    def MoveCursor(self, step):
        max=len(self.choices)
        if max > 0:
            self.menuGridCtrl.MoveCursor(step)


    def onUp(self, event):
        wx.CallAfter(self.menuGridCtrl.MoveCursor, -1)
        return True #stop processing this event !!!


    def onDown(self, event):
        wx.CallAfter(self.menuGridCtrl.MoveCursor, 1)
        return True #stop processing this event !!!


    def onLeft(self, event):
        if len(self.oldMenu) > 0:
            self.menu, ix = self.oldMenu.pop()
            wx.CallAfter(self.UpdateMenu, ix)
        else:
            wx.CallAfter(self.destroyMenu)
        return True #stop processing this event !!!


    def onRight(self, event):
        wx.CallAfter(self.DefaultAction)
        return True #stop processing this event !!!


    def onEscape(self, event):
        wx.CallAfter(self.destroyMenu)
        return True #stop processing this event !!!


    def DefaultAction(self):
        sel = self.menuGridCtrl.GetSelection()
        item = self.items[sel]
        id = item[3]
        if id != -1:
            self.destroyMenu()
            SendMessage(self.hWnd, WM_COMMAND, id, 0)
        else:
            self.oldMenu.append((self.menu,sel))
            self.menu = GetSubMenu(self.menu, item[1])
            self.UpdateMenu()


    def onFrameCharHook(self, event):
        keyCode = event.GetKeyCode()
        if keyCode == wx.WXK_F4:
            if event.AltDown():
                self.destroyMenu()
        elif keyCode == wx.WXK_RETURN or keyCode == wx.WXK_NUMPAD_ENTER:
            self.DefaultAction()
        elif keyCode == wx.WXK_RIGHT or keyCode == wx.WXK_NUMPAD_RIGHT:
            self.DefaultAction()
        elif keyCode == wx.WXK_ESCAPE:
            self.destroyMenu()
        elif keyCode == wx.WXK_UP or keyCode == wx.WXK_NUMPAD_UP:
            self.menuGridCtrl.MoveCursor(-1)
        elif keyCode == wx.WXK_DOWN or keyCode == wx.WXK_NUMPAD_DOWN:
            self.menuGridCtrl.MoveCursor(1)
        elif keyCode == wx.WXK_LEFT or keyCode == wx.WXK_NUMPAD_LEFT:
            if len(self.oldMenu) > 0:
                self.menu, ix = self.oldMenu.pop()
                wx.CallAfter(self.UpdateMenu,ix)
            else:
                self.destroyMenu()
        else:
            event.Skip()


    def onDoubleClick(self, event):
        self.DefaultAction()
        event.Skip()


    def onClose(self, event):
        self.Show(False)
        self.Destroy()
        self.plugin.menuDlg = None


    def destroyMenu(self, event = None):
        for i in range(len(self.messStack)):
            mess = self.messStack.pop()
            SendMessageTimeout(self.hWnd, WM_COMMAND, mess, 0)
        for evt in self.evtList[0]:
            eg.Unbind(evt, self.onUp)
        for evt in self.evtList[1]:
            eg.Unbind(evt, self.onDown)
        for evt in self.evtList[2]:
            eg.Unbind(evt, self.onLeft)
        for evt in self.evtList[3]:
            eg.Unbind(evt, self.onRight)
        for evt in self.evtList[4]:
            eg.Unbind(evt, self.onEscape)
        if self.flag:
            self.timer.Cancel()
        eg.TriggerEvent("OSD.%s" % self.plugin.text.closed, prefix = "MPC")
        self.Close()
#===============================================================================

class MyTimer():

    def __init__(self, t, plugin):
        self.timer = Timer(t, self.Run)
        self.plugin = plugin
        self.timer.start()


    def Run(self):
        try:
            self.plugin.menuDlg.destroyMenu()
            self.plugin.menuDlg = None
        except:
            pass


    def Cancel(self):
        self.timer.cancel()
#===============================================================================

class AfterPlaybackOnce(eg.ActionClass):

    class text:
        label = "Select action after playback:"
        choices = (
            "Exit",
            "Stand By",
            "Hibernate",
            "Shutdown",
            "Log Off",
            "Lock"
        )


    def __call__(self, action = -1):
        if action > -1:
            if self.plugin.runFlg and self.plugin.mpcHwnd:
                return SendMessage(self.plugin.mpcHwnd, WM_COMMAND, action + 912, 0)
            else:
                raise self.Exceptions.ProgramNotRunning


    def GetLabel(self, action):
        return "%s: %s" % (self.name, self.text.choices[action])
            

    def Configure(self, action = -1):
        panel = eg.ConfigPanel()
        label = wx.StaticText(panel, -1, self.text.label)
        ctrl = wx.Choice(panel, -1, choices = self.text.choices)
        eg.EqualizeWidths((label, ctrl))        
        ctrl.SetSelection(action)
        panel.sizer.Add(label, 0, wx.TOP, 20)
        panel.sizer.Add(ctrl, 0, wx.TOP, 3)
        while panel.Affirmed():
            panel.SetResult(ctrl.GetSelection())
#===============================================================================

class AfterPlayback(eg.ActionClass):

    class text:
        label = "Select action after playback (every time):"
        choices = (
            "Exit",
            "Do Nothing",
            "Play next in the folder",
        )


    def __call__(self, action = -1):
        if action > -1:
            if self.plugin.runFlg and self.plugin.mpcHwnd:
                ids = (33411, 918, 33412)
                return SendMessageTimeout(
                    self.plugin.mpcHwnd,
                    WM_COMMAND,
                    ids[action],
                    0
                )
            else:
                raise self.Exceptions.ProgramNotRunning
            

    def GetLabel(self, action):
        return "%s: %s" % (self.name, self.text.choices[action])


    def Configure(self, action = -1):
        panel = eg.ConfigPanel()
        label = wx.StaticText(panel, -1, self.text.label)
        ctrl = wx.Choice(panel, -1, choices = self.text.choices)
        eg.EqualizeWidths((label, ctrl))        
        ctrl.SetSelection(action)
        panel.sizer.Add(label, 0, wx.TOP, 20)
        panel.sizer.Add(ctrl, 0, wx.TOP, 3)
        while panel.Affirmed():
            panel.SetResult(ctrl.GetSelection())
#===============================================================================

class UserMessage(eg.ActionClass):

    name = "Send user's message"
    description = u"""<rst>**Sends user's message.**

If you can not find in the menu of features some of the functions you can get it (maybe) add yourself.
Go to the menu "**View - Options... - Player - Hotkeys**".
If you find there a function, what you need, then write the number that is in the column "**ID**".
Type this number into the edit box "**User message ID:**".
You can of course also use an expression such as **{eg.result}** or **{eg.event.payload}**."""


    class text:
        label = "User message ID:"
        error = "ValueError: invalid literal for int() with base 10: '%s'"



    def __call__(self, val=""):
        if self.plugin.runFlg and self.plugin.mpcHwnd:
            try:
                val = eg.ParseString(val)
                val = int(val)
            except:
                raise self.Exception(self.text.error % val)
                return
            return SendMessage(self.plugin.mpcHwnd, WM_COMMAND, val, 0)
        else:
            raise self.Exceptions.ProgramNotRunning


    def Configure(self, val=""):
        panel = eg.ConfigPanel()
        textLabel = wx.StaticText(panel, -1, self.text.label)
        textControl = wx.TextCtrl(panel, -1, val, size = (200,-1))
        panel.sizer.Add(textLabel, 0, wx.TOP, 20)
        panel.sizer.Add(textControl, 0, wx.TOP, 3)
        while panel.Affirmed():
            panel.SetResult(textControl.GetValue())
#===============================================================================

class ActionPrototype(eg.ActionBase):
    
    def __call__(self):
        if self.plugin.runFlg and self.plugin.mpcHwnd:
            wx.CallAfter(SendMessage,self.plugin.mpcHwnd, WM_COMMAND, self.value, 0)
#===============================================================================

class GetWindowState(eg.ActionBase):
    
    class text:
        rbLabel = "Result type choice"
        numVal = "Return a numeric value"
        strVal = "Return a string value"
        boolVal = "Return True when window is "
        triggEvent = "Trigger an event"
        evtPrefix = "Event prefix (or prefix and first suffix):"
        states = (
            "Not running",
            "Tray icon",
            "Normal",
            "Minimized",
            "Maximized",
            "Full screen",
        )

    def __call__(self, mode = 0, state = 5, evnt = False, prefix = "MPCHC.Window"):
        stt = self.plugin.GetWindowState()
        if evnt:
            eg.TriggerEvent(
                "".join([word.capitalize() for word in self.text.states[stt+1].split()]),
                prefix = prefix
            )
        if mode == 0:
            return stt
        elif mode == 1:
            return self.text.states[stt+1]
        else:
            return stt == state-1


    def Configure(self, mode = 0, state = 5, evnt = False, prefix = "MPCHC.Window"):
        self.stt = state
        panel=eg.ConfigPanel(self)
        topSizer = wx.StaticBoxSizer(
            wx.StaticBox(panel, -1, self.text.rbLabel),
            wx.VERTICAL
        )
        boolSizer = wx.BoxSizer(wx.HORIZONTAL)
        rb0 = panel.RadioButton(mode==0, self.text.numVal, style=wx.RB_GROUP)
        rb1 = panel.RadioButton(mode==1, self.text.strVal)
        rb2 = panel.RadioButton(mode==2, self.text.boolVal)
        statChoice = wx.Choice(panel, -1, choices = self.text.states)
        statChoice.Select(state)
        triggCtrl = wx.CheckBox(panel, -1, self.text.triggEvent)
        triggCtrl.SetValue(evnt)
        prefixLabel = wx.StaticText(panel, -1, self.text.evtPrefix)
        prefixCtrl = wx.TextCtrl(panel, -1, prefix)
        prefixSizer = wx.BoxSizer(wx.HORIZONTAL)
        prefixSizer.Add(prefixLabel,0,wx.TOP,3)
        prefixSizer.Add(prefixCtrl,0,wx.LEFT,6)
        boolSizer.Add(rb2,0,wx.TOP,3)
        boolSizer.Add(statChoice)
        topSizer.Add(rb0,0,wx.TOP,3)
        topSizer.Add(rb1,0,wx.TOP,7)
        topSizer.Add(boolSizer,0,wx.TOP,5)
        panel.sizer.Add(topSizer)
        panel.sizer.Add(triggCtrl, 0, wx.TOP, 10)
        panel.sizer.Add(prefixSizer, 0, wx.TOP, 10)

        def onTriggCtrl(evt = None):
            flg = triggCtrl.GetValue()
            prefixLabel.Show(flg)
            prefixCtrl.Show(flg)
            if evt:
                evt.Skip()
        triggCtrl.Bind(wx.EVT_CHECKBOX, onTriggCtrl)
        onTriggCtrl()

        def onState(evt):
            self.stt = evt.GetSelection()
            evt.Skip()
        statChoice.Bind(wx.EVT_CHOICE, onState)

        def onRadio(evt = None):
            flg = rb2.GetValue()
            statChoice.Enable(flg)
            sel = self.stt if flg else -1
            statChoice.SetSelection(sel)
            if evt:
                evt.Skip()
        rb0.Bind(wx.EVT_RADIOBUTTON, onRadio)
        rb1.Bind(wx.EVT_RADIOBUTTON, onRadio)
        rb2.Bind(wx.EVT_RADIOBUTTON, onRadio)
        onRadio()

        while panel.Affirmed():
            state = state if self.stt == -1 else self.stt
            panel.SetResult(
                (rb0.GetValue(),rb1.GetValue(),rb2.GetValue()).index(True),
                state,
                triggCtrl.GetValue(),
                prefixCtrl.GetValue()
            )
#===============================================================================

class GetNowPlaying(eg.ActionBase):
    
    def __call__(self):
        hWnd = Find_MPC()
        if not hWnd:
            raise self.Exceptions.ProgramNotRunning
        hWnd = hWnd[0]
        title = GetWindowText(hWnd)
        if not title.startswith("Media Player Classic"):
            ix = title.rfind(".")
            if ix > -1:
                return title[:ix]
#===============================================================================

class GetTimes(eg.ActionBase):

    name = "Get Times"
    description = "Returns elapsed, remaining and total times."

    def __call__(self):
        if self.plugin.runFlg and self.plugin.mpcHwnd:
            try:
                child = GetDlgItem(self.plugin.mpcHwnd, 10021)
                if GetClassName(child) ==  "#32770":
                    statText = GetDlgItem(child, 12027)
                    if GetClassName(statText) ==  "Static":
                        elaps, total = GetWindowText(statText).split(" / ")
                        elaps = GetSec(elaps)
                        totSec = GetSec(total)
                        rem = strftime('%H:%M:%S', gmtime(totSec-elaps))
                        elaps = strftime('%H:%M:%S', gmtime(elaps))
                        total = strftime('%H:%M:%S', gmtime(totSec))
            except:
                return None, None, None
            return elaps, rem, total
        else:
            eg.programCounter = None
            raise self.Exceptions.ProgramNotRunning
#===============================================================================

class GoTo_OSD(eg.ActionBase):

    name = 'On Screen Go To ...'
    description = 'Show On Screen "Go To ...".'

    panel = None

    class text:
        OSELabel = '"Go To..." show on:'
        menuPreview = '"Go To..." OSD preview:'
        txtColour = 'Text colour'
        background = 'Background colour'
        txtColourSel = 'Selected text colour'
        backgroundSel = 'Selected background colour'
        gotoLabel = 'Go To...'
        dialog = "Events ..."
        btnToolTip = '''Press this button to assign events to control the "Go To..." OSD !!!'''
        evtAssignTitle = '"Go To..." OSD control - events assignement'
        events = (
            "Digit increment:",
            "Digit decrement:",
            "Kursor left:",
            "Kursor right:",
            "Cancel (Escape):",
        )
        fontFace = "Select font face"
        fontSize = "Select font size"
        inverted = "Use inverted colours"

    def __call__(
        self,
        fore,
        back,
        faceFont,
        sizeFont,
        monitor,
        foreSel,
        backSel,
        evtList,
        sizeFlag,
        inverted
        ):
        if self.plugin.runFlg and self.plugin.mpcHwnd:
            if not self.plugin.menuDlg:
                self.plugin.menuDlg = GoToFrame()
                self.event = CreateEvent(None, 0, 0, None)
                wx.CallAfter(self.plugin.menuDlg.ShowGoToFrame,
                    fore,
                    back,
                    foreSel,
                    backSel,
                    faceFont,
                    sizeFont,
                    False,
                    self.plugin,
                    self.event,
                    monitor,
                    self.plugin.mpcHwnd,
                    evtList,
                    sizeFlag
                )
                eg.actionThread.WaitOnEvent(self.event)
        else:
            eg.programCounter = None
            raise self.Exceptions.ProgramNotRunning


    def GetLabel(
        self,
        fore,
        back,
        faceFont,
        sizeFont,
        monitor,
        foreSel,
        backSel,
        evtList,
        sizeFlag,
        inverted
    ):
        return self.name


    def Configure(
        self,
        fore = (75, 75, 75),
        back = (180, 180, 180),
        faceFont = "Courier New",
        sizeFont = 40,
        monitor = 0,
        foreSel = (180, 180, 180),
        backSel = (75, 75, 75),
        evtList = [[],[],[],[],[]],
        sizeFlag = False,
        inverted = True
    ):
        self.fore = fore
        self.back = back
        self.foreSel = foreSel
        self.backSel = backSel
        self.oldSel=0
        self.inverted = inverted
        global panel
        panel = eg.ConfigPanel(self)
        panel.evtList = cpy(evtList)
        previewLbl=wx.StaticText(panel, -1, self.text.menuPreview)
        displayChoice = eg.DisplayChoice(panel, monitor)
        w = displayChoice.GetSize()[0]
        OSElbl = wx.StaticText(panel, -1, self.text.OSELabel)
        #Button Text Colour
        foreLbl=wx.StaticText(panel, -1, self.text.txtColour+':')
        foreColourButton = eg.ColourSelectButton(panel,fore, title = self.text.txtColour)
        #Button Background Colour
        backLbl=wx.StaticText(panel, -1, self.text.background+':')
        backColourButton = eg.ColourSelectButton(panel,back, title = self.text.background)
        #Button Selected Text Colour
        foreSelLbl=wx.StaticText(panel, -1, self.text.txtColourSel+':')
        foreSelColourButton = eg.ColourSelectButton(panel,foreSel, title = self.text.txtColourSel)
        #Button Selected Background Colour
        backSelLbl=wx.StaticText(panel, -1, self.text.backgroundSel+':')
        backSelColourButton = eg.ColourSelectButton(panel,backSel, title = self.text.backgroundSel)
        #Button Dialog "Menu control - assignement of events"
        dialogButton = wx.Button(panel,-1,self.text.dialog, size = (w, -1))
        dialogButton.SetToolTipString(self.text.btnToolTip)
        foreSelLbl.Enable(not inverted)
        foreSelColourButton.Enable(not inverted)
        backSelLbl.Enable(not inverted)
        backSelColourButton.Enable(not inverted)
        #Use inverted colours checkbox
        useInvertedCtrl = wx.CheckBox(panel, -1, self.text.inverted)
        useInvertedCtrl.SetValue(inverted)
        #Sizers
        mainSizer = panel.sizer
        topSizer=wx.GridBagSizer(2, 30)
        mainSizer.Add(topSizer)
        topSizer.Add(previewLbl,(0, 0),flag = wx.TOP,border = 0)
        topSizer.Add((160,-1),(1, 0),(3, 1),flag = wx.EXPAND)    
        topSizer.Add(foreLbl,(0, 1),flag = wx.TOP,border = 0)
        topSizer.Add(foreColourButton,(1, 1),flag = wx.TOP)
        topSizer.Add(backLbl,(2, 1),flag = wx.TOP,border = 8)
        topSizer.Add(backColourButton,(3, 1),flag = wx.TOP)        
        topSizer.Add(foreSelLbl,(4, 1), (1, 2), flag = wx.TOP,border = 8)
        topSizer.Add(foreSelColourButton, (5, 1), flag = wx.TOP)
        topSizer.Add(backSelLbl,(6, 1), (1, 2), flag = wx.TOP,border = 8)
        topSizer.Add(backSelColourButton, (7, 1), flag = wx.TOP)
        topSizer.Add(useInvertedCtrl, (8, 1))
        topSizer.Add(OSElbl,(0, 2), flag = wx.TOP)
        topSizer.Add(displayChoice,(1, 2),flag = wx.TOP)        
        topSizer.Add(dialogButton, (3, 2), flag = wx.TOP)
        #Font face
        fw = FixedWidth()
        fw.EnumerateFacenames(wx.FONTENCODING_SYSTEM,  fixedWidthOnly = True)
        fw.fontList.sort()
        fontFaceLbl=wx.StaticText(panel, -1, self.text.fontFace+':')
        topSizer.Add(fontFaceLbl,(4, 0),(1, 1), flag = wx.TOP,border = 8)
        fontFaceCtrl = wx.Choice(panel, -1, choices = fw.fontList, size =(160,-1))
        fontFaceCtrl.SetStringSelection(faceFont)
        topSizer.Add(fontFaceCtrl,(5, 0),(1, 1), flag = wx.TOP)
        #Font size
        fontSizeLbl=wx.StaticText(panel, -1, self.text.fontSize+':')
        topSizer.Add(fontSizeLbl,(6, 0),(1, 1), flag = wx.TOP,border = 8)

        def LevelCallback(value):
            if value != sizeFont:
                panel.SetIsDirty()
            return str(value)

        fontSizeCtrl = eg.Slider(
            panel, 
            value = sizeFont, 
            min=20, 
            max=120, 
            style = wx.SL_TOP,
            size=(160,-1),
            levelCallback=LevelCallback
        )
        fontSizeCtrl.SetMinSize((160, -1))
        topSizer.Add(fontSizeCtrl,(7, 0),(1, 1), flag = wx.TOP)
        panel.sizer.Layout()
        spacer = topSizer.GetChildren()[1]
        ps = spacer.GetPosition()
        sz = spacer.GetSize()
        previewPanel = wx.Panel(panel, -1, pos = (ps[0], ps[1]+2), size = sz, style = wx.BORDER_SIMPLE)
        gotoLbl=wx.StaticText(previewPanel, -1, self.text.gotoLabel, pos = (5,5))
        gotoLbl.SetBackgroundColour(self.back)
        gotoLbl.SetForegroundColour(self.fore)
        gt = gotoLbl.GetTextExtent(self.text.gotoLabel)
        fnt = gotoLbl.GetFont()
        fnt.SetPointSize(12)
        fnt.SetWeight(wx.FONTWEIGHT_BOLD)
        gotoLbl.SetFont(fnt)
        GoToCtrl = wx.TextCtrl(
                    previewPanel,
                    -1,
                    style=wx.TE_RICH2|wx.NO_BORDER|wx.TE_READONLY|wx.TE_CENTER,
                 )
        GoToCtrl.SetValue("01:25:30")
        fnt.SetPointSize(20)
        GoToCtrl.SetFont(fnt)
        previewPanel.SetBackgroundColour(self.back)
        GoToCtrl.SetBackgroundColour(self.back)

        def OnFontFaceChoice(event = None):
            fnt.SetFaceName(fontFaceCtrl.GetStringSelection())
            GoToCtrl.SetFont(fnt)
            if GoToCtrl.GetTextExtent("01:25:30")[0] < 120:
                self.sizeFlag = True
            else:
                self.sizeFlag = False
            te = GoToCtrl.GetTextExtent("01:25:30")
            GoToCtrl.SetSize((158, te[1]))
            GoToCtrl.SetPosition((1, 5+gt[1]+(sz[1]-gt[1]-te[1])/2))
            pos = 3
            GoToCtrl.SetStyle(0, pos, wx.TextAttr(self.fore, self.back, fnt))
            GoToCtrl.SetStyle(pos, pos+1, wx.TextAttr(self.foreSel, self.backSel, fnt))
            GoToCtrl.SetStyle(pos+1, 8, wx.TextAttr(self.fore, self.back, fnt))
            if event:
                event.Skip()
        fontFaceCtrl.Bind(wx.EVT_CHOICE, OnFontFaceChoice)
        OnFontFaceChoice()


        def OnInverted(evt):
            flag = evt.IsChecked()
            foreSelLbl.Enable(not flag)
            foreSelColourButton.Enable(not flag)
            backSelLbl.Enable(not flag)
            backSelColourButton.Enable(not flag)
            self.inverted = flag
            if flag:
                backSelColourButton.SetValue(foreColourButton.GetValue())
                foreSelColourButton.SetValue(backColourButton.GetValue())
                self.backSel = self.fore
                self.foreSel = self.back
                previewPanel.Refresh()
                OnFontFaceChoice()
            evt.Skip
        useInvertedCtrl.Bind(wx.EVT_CHECKBOX, OnInverted)


        def OnDialogBtn(evt):
            dlg = MenuEventsDialog(
                parent = panel,
                plugin = self.plugin,
            )
            dlg.Centre()
            wx.CallAfter(dlg.ShowMenuEventsDialog, self.text.evtAssignTitle, self.text.events)
            evt.Skip()
        dialogButton.Bind(wx.EVT_BUTTON, OnDialogBtn)


        def OnColourBtn(evt):
            id = evt.GetId()
            value = evt.GetValue()
            if id == foreColourButton.GetId():
                self.fore = value
                gotoLbl.SetForegroundColour(value)
                if self.inverted:
                    self.backSel = value
                    backSelColourButton.SetValue(value)
            elif id == backColourButton.GetId():
                self.back = value
                GoToCtrl.SetBackgroundColour(value)
                previewPanel.SetBackgroundColour(value)
                gotoLbl.SetBackgroundColour(value)
                if self.inverted:
                    self.foreSel = value
                    foreSelColourButton.SetValue(value)
            elif id == foreSelColourButton.GetId():
                self.foreSel = value
            elif id == backSelColourButton.GetId():
                self.backSel = value
            previewPanel.Refresh()
            OnFontFaceChoice()
            evt.Skip()
        foreColourButton.Bind(eg.EVT_VALUE_CHANGED, OnColourBtn)
        backColourButton.Bind(eg.EVT_VALUE_CHANGED, OnColourBtn)
        foreSelColourButton.Bind(eg.EVT_VALUE_CHANGED, OnColourBtn)
        backSelColourButton.Bind(eg.EVT_VALUE_CHANGED, OnColourBtn)

        def setFocus():
            pass
        panel.setFocus = setFocus

        # re-assign the test button
        def OnButton(event):
            if self.plugin.runFlg and self.plugin.mpcHwnd:
                if not self.plugin.menuDlg:
                    self.plugin.menuDlg = GoToFrame()
                    self.event = CreateEvent(None, 0, 0, None)
                    wx.CallAfter(self.plugin.menuDlg.ShowGoToFrame,
                        foreColourButton.GetValue(),
                        backColourButton.GetValue(),
                        foreSelColourButton.GetValue(),
                        backSelColourButton.GetValue(),
                        fontFaceCtrl.GetStringSelection(),
                        fontSizeCtrl.GetValue(),
                        True,
                        self.plugin,
                        self.event,
                        displayChoice.GetSelection(),
                        self.plugin.mpcHwnd,
                        panel.evtList,
                        self.sizeFlag
                    )
                    eg.actionThread.WaitOnEvent(self.event)
            else:
                self.PrintError(eg.Classes.Exceptions.Text.ProgramNotRunning)
        panel.dialog.buttonRow.testButton.Bind(wx.EVT_BUTTON, OnButton)

        while panel.Affirmed():
            panel.SetResult(
            foreColourButton.GetValue(),
            backColourButton.GetValue(),
            fontFaceCtrl.GetStringSelection(),
            fontSizeCtrl.GetValue(),
            displayChoice.GetSelection(),
            foreSelColourButton.GetValue(),
            backSelColourButton.GetValue(),
            panel.evtList,
            self.sizeFlag,
            useInvertedCtrl.GetValue()
        )
#===============================================================================

class ShowMenu(eg.ActionClass):

    name = "Show MPC menu"
    description = "Show MPC menu."
    panel = None

    class text:
        OSELabel = 'Menu show on:'
        menuPreview = 'MPC On Screen Menu preview:'
        menuFont = 'Font:'
        txtColour = 'Text colour'
        background = 'Background colour'
        txtColourSel = 'Selected text colour'
        backgroundSel = 'Selected background colour'
        dialog = "Events ..."
        btnToolTip = """Press this button to assign events to control the menu !!!"""
        evtAssignTitle = "Menu control - events assignement"
        events = (
            "Cursor up:",
            "Cursor down:",
            "Back from the (sub)menu:",
            "Submenu, or select an item:",
            "Cancel (Escape):",
        )
        inverted = "Use inverted colours"


    def __call__(
        self,
        fore,
        back,
        fontInfo = arialInfoString,
        monitor = 0,
        foreSel = (180, 180, 180),
        backSel = (75, 75, 75),
        evtList = [],
        inverted = True
    ):
        if self.plugin.runFlg and self.plugin.mpcHwnd:
            if not self.plugin.menuDlg:
                self.plugin.menuDlg = Menu()
                self.event = CreateEvent(None, 0, 0, None)
                wx.CallAfter(self.plugin.menuDlg.ShowMenu,
                    fore,
                    back,
                    foreSel,
                    backSel,
                    fontInfo,
                    False,
                    self.plugin,
                    self.event,
                    monitor,
                    self.plugin.mpcHwnd,
                    evtList,
                )
                eg.actionThread.WaitOnEvent(self.event)
        else:
            eg.programCounter = None
            raise self.Exceptions.ProgramNotRunning


    def GetLabel(
        self,
        fore,
        back,
        fontInfo,
        monitor,
        foreSel,
        backSel,
        evtList,
        inverted
    ):
        return self.name


    def Configure(
        self,
        fore = (75, 75, 75),
        back = (180, 180, 180),
        fontInfo = arialInfoString,
        monitor = 0,
        foreSel = (180, 180, 180),
        backSel = (75, 75, 75),
        evtList = [[],[],[],[],[]],
        inverted = True
    ):
        self.fontInfo = fontInfo
        self.fore = fore
        self.back = back
        self.foreSel = foreSel
        self.backSel = backSel
        self.oldSel=0
        self.inverted = inverted
        global panel
        panel = eg.ConfigPanel(self)
        panel.evtList = cpy(evtList)
        previewLbl=wx.StaticText(panel, -1, self.text.menuPreview)
        listBoxCtrl = MenuGrid(panel, 3)
        items = (("Blabla_1",0,True,804),
                 ("Blabla_2",1,False,804),
                 ("Blabla_3",2,False,-1),)
        listBoxCtrl.Set(items)
        listBoxCtrl.SetBackgroundColour(self.back)
        listBoxCtrl.SetForegroundColour(self.fore)
        listBoxCtrl.SetSelectionBackground(self.backSel)
        listBoxCtrl.SetSelectionForeground(self.foreSel)
        #Font button
        fontLbl=wx.StaticText(panel, -1, self.text.menuFont)
        fontButton = eg.FontSelectButton(panel, value = fontInfo)
        font = wx.FontFromNativeInfoString(fontInfo)
        for n in range(10,20):
            font.SetPointSize(n)
            fontButton.SetFont(font)
            hght = fontButton.GetTextExtent('X')[1]
            if hght > 20:
                break
        listBoxCtrl.SetDefaultCellFont(font)
        arial = wx.FontFromNativeInfoString(arialInfoString)
        fontButton.SetFont(font)            
        for n in range(1,1000):
            arial.SetPointSize(n)
            fontButton.SetFont(arial)
            h = fontButton.GetTextExtent(u"\u25a0")[1]
            if h > hght:
                break
        arial.SetPointSize(2*n/3)
        fontButton.SetFont(arial)            
        w0 = 2 * fontButton.GetTextExtent(u"\u25a0")[0]
        attr = gridlib.GridCellAttr()
        attr.SetFont(arial)
        attr.SetAlignment(wx.ALIGN_CENTRE, wx.ALIGN_CENTRE)
        listBoxCtrl.SetColAttr(0,attr)
        for n in range(1,1000):
            arial.SetPointSize(n)
            fontButton.SetFont(arial)
            h = fontButton.GetTextExtent(u"\u25ba")[1]
            if h > hght:
                break
        arial.SetPointSize(n/2)
        fontButton.SetFont(arial)            
        w2 = 2 * fontButton.GetTextExtent(u"\u25ba")[0]
        attr = gridlib.GridCellAttr()
        attr.SetFont(arial)
        attr.SetAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTRE)
        listBoxCtrl.SetColAttr(2,attr)
        listBoxCtrl.SetDefaultRowSize(hght+4, True)
        displayChoice = eg.DisplayChoice(panel, monitor)
        w = displayChoice.GetSize()[0]
        OSElbl = wx.StaticText(panel, -1, self.text.OSELabel)
        useInvertedCtrl = wx.CheckBox(panel, -1, self.text.inverted)
        useInvertedCtrl.SetValue(inverted)
        #Button Text Colour
        foreLbl=wx.StaticText(panel, -1, self.text.txtColour+':')
        foreColourButton = eg.ColourSelectButton(panel,fore,title = self.text.txtColour)
        #Button Background Colour
        backLbl=wx.StaticText(panel, -1, self.text.background+':')
        backColourButton = eg.ColourSelectButton(panel,back,title = self.text.background)
        #Button Selected Text Colour
        foreSelLbl=wx.StaticText(panel, -1, self.text.txtColourSel+':')
        foreSelColourButton = eg.ColourSelectButton(panel,foreSel,title = self.text.txtColourSel)
        #Button Selected Background Colour
        backSelLbl=wx.StaticText(panel, -1, self.text.backgroundSel+':')
        backSelColourButton = eg.ColourSelectButton(panel,backSel,title = self.text.backgroundSel)
        #Button Dialog "Menu control - assignement of events"
        dialogButton = wx.Button(panel,-1,self.text.dialog)
        dialogButton.SetToolTipString(self.text.btnToolTip)
        foreSelLbl.Enable(not inverted)
        foreSelColourButton.Enable(not inverted)
        backSelLbl.Enable(not inverted)
        backSelColourButton.Enable(not inverted)
        #Sizers
        mainSizer = panel.sizer
        topSizer=wx.GridBagSizer(2, 30)
        mainSizer.Add(topSizer)
        topSizer.Add(previewLbl,(0, 0),flag = wx.TOP,border = 0)
        topSizer.Add(listBoxCtrl,(1, 0),(4, 1))        
        topSizer.Add(useInvertedCtrl,(6, 0),flag = wx.TOP, border = 8)        
        topSizer.Add(fontLbl,(0, 1),flag = wx.TOP)
        topSizer.Add(fontButton,(1, 1),flag = wx.TOP)        
        topSizer.Add(foreLbl,(2, 1),flag = wx.TOP,border = 8)
        topSizer.Add(foreColourButton,(3, 1),flag = wx.TOP)
        topSizer.Add(backLbl,(4, 1),flag = wx.TOP,border = 8)
        topSizer.Add(backColourButton,(5, 1),flag = wx.TOP)        
        topSizer.Add(OSElbl,(0, 2), flag = wx.TOP)
        topSizer.Add(displayChoice,(1, 2),flag = wx.TOP)        
        topSizer.Add(foreSelLbl,(6, 1), (1, 2), flag = wx.TOP,border = 8)
        topSizer.Add(foreSelColourButton, (7, 1), flag = wx.TOP)
        topSizer.Add(backSelLbl,(8, 1), (1, 2), flag = wx.TOP,border = 8)
        topSizer.Add(backSelColourButton, (9, 1), flag = wx.TOP)
        topSizer.Add(dialogButton, (3, 2), flag = wx.TOP|wx.EXPAND)
        panel.sizer.Layout()
        wdth = 160
        if (hght+4)*listBoxCtrl.GetNumberRows() > listBoxCtrl.GetSize()[1]: #after Layout() !!!
            wdth -=  SYS_VSCROLL_X
        listBoxCtrl.SetColSize(0, w0)
        listBoxCtrl.SetColSize(1, wdth - w0 - w2)
        listBoxCtrl.SetColSize(2, w2)
        listBoxCtrl.SetGridCursor(-1, 1)
        listBoxCtrl.SelectRow(0)


        def OnMonitor(evt):
            listBoxCtrl.SetFocus()
            evt.Skip
        displayChoice.Bind(wx.EVT_CHOICE, OnMonitor)


        def OnInverted(evt):
            flag = evt.IsChecked()
            foreSelLbl.Enable(not flag)
            foreSelColourButton.Enable(not flag)
            backSelLbl.Enable(not flag)
            backSelColourButton.Enable(not flag)
            self.inverted = flag
            if flag:
                self.foreSel = self.back
                self.backSel = self.fore
                backSelColourButton.SetValue(self.backSel)
                foreSelColourButton.SetValue(self.foreSel)
                listBoxCtrl.SetSelectionForeground(self.foreSel)
                listBoxCtrl.SetSelectionBackground(self.backSel)
            listBoxCtrl.SetFocus()
            evt.Skip
        useInvertedCtrl.Bind(wx.EVT_CHECKBOX, OnInverted)


        def OnDialogBtn(evt):
            dlg = MenuEventsDialog(
                parent = panel,
                plugin = self.plugin,
            )
            dlg.Centre()
            wx.CallAfter(dlg.ShowMenuEventsDialog, self.text.evtAssignTitle, self.text.events)
            evt.Skip()
        dialogButton.Bind(wx.EVT_BUTTON, OnDialogBtn)


        def OnFontBtn(evt):
            value = evt.GetValue()
            self.fontInfo = value
            font = wx.FontFromNativeInfoString(value)
            for n in range(10,20):
                font.SetPointSize(n)
                fontButton.SetFont(font)
                hght = fontButton.GetTextExtent('X')[1]
                if hght > 20:
                    break
            listBoxCtrl.SetDefaultCellFont(font)
            listBoxCtrl.SetDefaultRowSize(hght+4, True)
            for i in range(listBoxCtrl.GetNumberRows()):
                listBoxCtrl.SetCellFont(i,1,font)
            listBoxCtrl.SetFocus()
            if evt:
                evt.Skip()
        fontButton.Bind(eg.EVT_VALUE_CHANGED, OnFontBtn)

        def OnColourBtn(evt):
            id = evt.GetId()
            value = evt.GetValue()
            if id == foreColourButton.GetId():
                listBoxCtrl.SetForegroundColour(value)
                if self.inverted:
                    self.backSel = self.fore
                    listBoxCtrl.SetSelectionBackground(value)
                    backSelColourButton.SetValue(value)
            elif id == backColourButton.GetId():
                listBoxCtrl.SetBackgroundColour(value)
                if self.inverted:
                    self.foreSel = self.back
                    listBoxCtrl.SetSelectionForeground(value)
                    foreSelColourButton.SetValue(value)
            elif id == foreSelColourButton.GetId():
                listBoxCtrl.SetSelectionForeground(value)
            elif id == backSelColourButton.GetId():
                listBoxCtrl.SetSelectionBackground(value)
            listBoxCtrl.Refresh()
            listBoxCtrl.SetFocus()
            evt.Skip()
        foreColourButton.Bind(eg.EVT_VALUE_CHANGED, OnColourBtn)
        backColourButton.Bind(eg.EVT_VALUE_CHANGED, OnColourBtn)
        foreSelColourButton.Bind(eg.EVT_VALUE_CHANGED, OnColourBtn)
        backSelColourButton.Bind(eg.EVT_VALUE_CHANGED, OnColourBtn)


        def setFocus():
            listBoxCtrl.SetFocus()
        panel.setFocus = setFocus

        # re-assign the test button
        def OnButton(event):
            if self.plugin.runFlg and self.plugin.mpcHwnd:
                if not self.plugin.menuDlg:
                    self.plugin.menuDlg = Menu()
                    self.event = CreateEvent(None, 0, 0, None)
                    wx.CallAfter(self.plugin.menuDlg.ShowMenu,
                        foreColourButton.GetValue(),
                        backColourButton.GetValue(),
                        foreSelColourButton.GetValue(),
                        backSelColourButton.GetValue(),
                        self.fontInfo, 
                        True,
                        self.plugin,
                        self.event,
                        displayChoice.GetSelection(),
                        self.plugin.mpcHwnd,
                        panel.evtList
                    )
                    eg.actionThread.WaitOnEvent(self.event)
            else:
                self.PrintError(eg.Classes.Exceptions.Text.ProgramNotRunning)
        panel.dialog.buttonRow.testButton.Bind(wx.EVT_BUTTON, OnButton)

        while panel.Affirmed():
            fontInfo = fontButton.GetValue()
            if not fontInfo:
                font = listBoxCtrl.GetFont()
                font.SetPointSize(36)
                fontInfo = font.GetNativeFontInfoDesc()
            panel.SetResult(
            foreColourButton.GetValue(),
            backColourButton.GetValue(),
            fontInfo,
            displayChoice.GetSelection(),
            foreSelColourButton.GetValue(),
            backSelColourButton.GetValue(),
            panel.evtList,
            useInvertedCtrl.GetValue()
        )
#===============================================================================
class Run(eg.ActionBase):

    def __call__(self):
        if self.plugin.mpcPath:
            self.plugin.ConnectMpcHc()        
#===============================================================================

class MediaPlayerClassic(eg.PluginBase):

    mySched = None
    myStart = None
    menuDlg = None
    state = None
    mpcHwnd = None
    event = None
    result = None
    runFlg = False
    strtFlg = False
    connected = False

    class text:
        popup = (
            "Delete item",
            "Delete all items",
        )
        cancel = 'Cancel'
        ok = 'OK'
        clear  = "Clear all"
        toolTip = "Drag-and-drop an event from the log into the box."
        opened = "Opened"
        closed = "Closed"
        label = "Path to MPC-HC executable:"
        fileMask = "MPC-HC executable|mpc-hc*.exe|All EXE files (*.exe)|*.exe"
        gotoLabel = "Go To..."


    def ParseMsg(self, msg):
        msg = msg.replace(u"\\|", u"\xb0*\u2734*\xb0")
        msg = msg.split(u"|")
        for i in range(len(msg)):
            msg[i] = msg[i].replace(u"\xb0*\u2734*\xb0", u"|")
        return msg


    @eg.LogIt
    def Handler(self, hwnd, mesg, wParam, lParam):
        if not self.runFlg:
            return True
        cpyData = cast(lParam, PCOPYDATASTRUCT)
        cmd = cpyData.contents.dwData
        msg = wstring_at(cpyData.contents.lpData)
        if cmd == CMD_CONNECT:
            self.mpcHwnd = int(msg)
            self.connected = True
            eg.TriggerEvent("Connected",prefix="MPC-HC")
        elif cmd == CMD_STATE:
            state = int(msg)
            if self.state != state:
                self.state = state
                eg.TriggerEvent("State."+MPC_LOADSTATE[state],prefix="MPC-HC")
        elif cmd == CMD_NOWPLAYING:
            msg = self.ParseMsg(msg)
            eg.TriggerEvent("NowPlaying",prefix="MPC-HC", payload = msg)

        elif cmd == CMD_PLAYMODE:
            eg.TriggerEvent("Playstate."+MPC_PLAYSTATE[int(msg)],prefix="MPC-HC")

        elif cmd == CMD_NOTIFYSEEK:
            eg.TriggerEvent("Seek",prefix="MPC-HC",payload = int(0.5+float(msg)))

        elif cmd in (
            CMD_CURRENTPOSITION,
            CMD_LISTSUBTITLETRACKS,
            CMD_LISTAUDIOTRACKS,
            CMD_PLAYLIST
        ):
            if self.event:
                self.result = self.ParseMsg(msg)
                SetEvent(self.event)

        elif cmd == CMD_NOTIFYENDOFSTREAM:
            eg.TriggerEvent("EndOfStream",prefix="MPC-HC")
        return True


    def ConnectMpcHc(self):
        if not self.runFlg:
            mp = self.mpcPath
            mp = mp.encode(FSE) if isinstance(mp, unicode) else mp
            if isfile(mp):
                self.runFlg = True
                self.strtFlg = False
                args = [mp]
                args.append("/slave")
                args.append(str(self.mr.hwnd))
                Popen(args)


    def isResponding(self, hwnd):
        try:
            SendMessageTimeout(hwnd, 0, timeout = 1000) # 0 = WM_NULL
            return True
        except:
            return False


    def isRunning(self):
        try:
            return FindWindow(u'MediaPlayerClassicW', None)
        except:
            return False


    def waitBeforeConnect(self):
        hwnd = self.isRunning()
        if hwnd:
            if self.isResponding(hwnd):
                eg.scheduler.AddTask(1, self.ConnectMpcHc)
            else:
                self.myStart = eg.scheduler.AddTask(2, self.waitBeforeConnect)
        else:
            self.strtFlg = False


    def mpcIsRunning(self):
        self.mySched=eg.scheduler.AddTask(2, self.mpcIsRunning) # must run continuously !
        if not self.isRunning(): #user closed MPC-HC ?
            if self.runFlg and self.connected:
                    self.runFlg = False
                    self.connected = False
                    self.strtFlg = False
                    self.myStart = None
                    self.mpcHwnd = None
        elif self.runFlg:
            pass
        elif not self.strtFlg:
            self.strtFlg = True
            self.myStart = eg.scheduler.AddTask(2, self.waitBeforeConnect)
      

    def SendCopydata(self, cmd, txt):
        if self.mpcHwnd is not None:        
            cpyData = create_unicode_buffer(txt)
            cds = COPYDATASTRUCT()
            cds.dwData = cmd
            cds.lpData = cast(cpyData, c_void_p)
            cds.cbData = sizeof(cpyData)
            return SendMessage(self.mpcHwnd, WM_COPYDATA, 0, addressof(cds))


    def __init__(self):
        self.mr = eg.MessageReceiver("MPC-HC_plugin_")
        self.mr.AddHandler(WM_COPYDATA, self.Handler)
        self.mr.Start()
        self.AddActionsFromList(ACTIONS, ActionPrototype)


    def __start__(self, mpcPath=None):
        self.mySched=None
        self.myStart=None
        self.menuDlg = None
        self.state = None
        self.mpcHwnd = None
        self.event = None
        self.result = None
        self.runFlg = False
        self.strtFlg = False
        self.connected = False
        if mpcPath is None:
            mpcPath = self.GetMpcHcPath()
        if not mpcPath or not exists(mpcPath):
            raise self.Exceptions.ProgramNotFound
            return
        self.mpcPath = mpcPath
        hWnd = Find_MPC()
        if hWnd:
            self.mpcHwnd = hWnd[0]
        eg.scheduler.AddTask(1, self.mpcIsRunning)
    

    def __stop__(self):
        if self.mySched:
            try:
                eg.scheduler.CancelTask(self.mySched)
            except:
                pass
        if self.myStart:
            try:
                eg.scheduler.CancelTask(self.myStart)
            except:
                pass

  
    def __close__(self):
        self.mr.RemoveHandler(WM_COPYDATA, self.Handler)
        self.mr.Stop()
        self.mr = None
       

    def Configure(self, mpcPath=None):
        if mpcPath is None:
            mpcPath = self.GetMpcHcPath()
            if mpcPath is None:
                mpcPath = join(
                    eg.folderPath.ProgramFiles, 
                    "MediaPlayerClassic", 
                    "mpc-hc.exe"
                )
        panel = eg.ConfigPanel()
        filepathCtrl = eg.FileBrowseButton(
            panel, 
            size=(320,-1),
            initialValue=mpcPath, 
            startDirectory=eg.folderPath.ProgramFiles,
            labelText="",
            fileMask = self.text.fileMask,
            buttonText=eg.text.General.browse,
        )
        panel.AddLabel(self.text.label)
        panel.AddCtrl(filepathCtrl)
        while panel.Affirmed():
            panel.SetResult(filepathCtrl.GetValue())


    def GetMpcHcPath(self):
        """
        Get the path of MPC-HC's installation directory through querying 
        the Windows registry.
        """
        try:
            if "PROCESSOR_ARCHITEW6432" in environ:
                args = [_winreg.HKEY_CURRENT_USER,            
                    "Software\MPC-HC\MPC-HC"]
                args.extend((0, _winreg.KEY_READ | _winreg.KEY_WOW64_64KEY))
            else:
                args = [_winreg.HKEY_CURRENT_USER,            
                    "Software\Gabest\Media Player Classic"]
            mpc = _winreg.OpenKey(*args)
            mpcPath =_winreg.QueryValueEx(mpc, "ExePath")[0]
            _winreg.CloseKey(mpc)
        except WindowsError:
            mpcPath = None
        return mpcPath


    def GetWindowState(self):
        hWnd = Find_MPC()
        if not hWnd:
            return -1
        else:
            hWnd = hWnd[0]
        if not IsWindowVisible(hWnd):
            return 0
        state = GetWindowPlacement(hWnd)[1]
        border = GetWindowLong(hWnd, GWL_EXSTYLE) & WS_EX_WINDOWEDGE
        if border:
            return state
        rect = GetWindowPlacement(hWnd)[-1]
        mons = EnumDisplayMonitors()
        fullscreen = False
        for mon in mons:
            if rect == mon[2]:
                fullscreen = True
                break
        if fullscreen:
            return 4
        return state
#===============================================================================

class SendCmd(eg.ActionBase):
    def __call__(self):
        self.plugin.SendCopydata(self.value, u"")
#===============================================================================

class GetInfo(eg.ActionBase):
    def __call__(self):
        self.plugin.result = None
        self.plugin.event = CreateEvent(None, 0, 0, None)
        if self.plugin.SendCopydata(self.value, u""):
            eg.actionThread.WaitOnEvent(self.plugin.event)
            self.plugin.event = None
            if self.plugin.result:
                return self.plugin.result
#===============================================================================

class OpenFile(eg.ActionBase):

    class text:
        toolTipFile = 'Type filename or click browse to choose file'
        browseFile = 'Choose a file'


    def __call__(self, filepath = ""):
        if filepath:
            filepath = eg.ParseString(filepath)
            self.plugin.SendCopydata(self.value, filepath)


    def Configure(self, filepath = ""):
        panel = eg.ConfigPanel()    
        folder = split(filepath)[0] if filepath else eg.folderPath.Videos
        filepathLabel = wx.StaticText(panel, -1, "%s:" % self.text.browseFile)
        filepathCtrl = eg.FileBrowseButton(
            panel,
            -1,
            toolTip = self.text.toolTipFile,
            dialogTitle = self.text.browseFile,
            buttonText = eg.text.General.browse,
            startDirectory = folder,
            initialValue = filepath
        )
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(filepathLabel,0,wx.TOP,3)
        sizer.Add(filepathCtrl,1,wx.LEFT|wx.EXPAND,5)
        panel.sizer.Add(sizer,0,wx.ALL|wx.EXPAND,20)
        while panel.Affirmed():
            panel.SetResult(
                filepathCtrl.GetValue(),
            )
#===============================================================================

class GetPosition(eg.ActionBase):

    def __call__(self):
        self.plugin.result = None
        self.plugin.event = CreateEvent(None, 0, 0, None)
        if self.plugin.SendCopydata(CMD_GETCURRENTPOSITION, u""):
            eg.actionThread.WaitOnEvent(self.plugin.event)
            self.plugin.event = None
            if self.plugin.result:
                return int(0.5+float(self.plugin.result[0]))
#===============================================================================

class SetInteger(eg.ActionBase):

    class text:
        labels = (
            ("Jump of", "seconds (negative values for backward)"),
            ("New position:", "seconds"),
            ("Index of the audio track:", ""),
            ("Index of the subtitle track:", "(-1 for disabling subtitles)"),
            ("Index of the active file:", "(-1 for no file selected)"),
            ("New audio delay:", "milliseconds"),
            ("New subtitle delay:", "milliseconds"),
        )


    def __call__(self, value=0):
        value = unicode(value)
        self.plugin.SendCopydata(self.value[0], value)


    def Configure(self, value=0):
        panel = eg.ConfigPanel()
        label_1 = wx.StaticText(panel,-1,self.text.labels[self.value[1]][0])
        label_2 = wx.StaticText(panel,-1,self.text.labels[self.value[1]][1])
        valueCtrl = eg.SpinIntCtrl(panel, -1, value, max=self.value[3],min=self.value[2])
        sizer = wx.FlexGridSizer(1, 3, 5, 10)
        sizer.Add(label_1,0,wx.TOP,3)
        sizer.Add(valueCtrl)
        sizer.Add(label_2,0,wx.TOP,3)
        panel.sizer.Add(sizer,0,wx.ALL|wx.EXPAND,20)
        while panel.Affirmed():
            panel.SetResult(
                valueCtrl.GetValue(),
            )
#===============================================================================

class SendOSD(eg.ActionBase):

    class text:
        osdLabel = "OSD text:"
        durLabel = "Duration [s]:"
        posLabel = "Position:"
        position = (
            "None (clear)",
            "Top left",
            "Top right",
        )


    def __call__(self, osd="", dur=3, pos=1):
        if self.plugin.mpcHwnd is not None:
            osd = eg.ParseString(osd) + "\0"
            OSDDATA.nMsgPos     = pos
            OSDDATA.nDurationMS = 1000*dur
            OSDDATA.strMsg      = osd.encode(eg.systemEncoding)
            cds = COPYDATASTRUCT()
            cds.dwData = CMD_OSDSHOWMESSAGE
            cds.cbData = sizeof(OSDDATA)
            cds.lpData = cast(addressof(OSDDATA), c_void_p)
            SendMessage(self.plugin.mpcHwnd, WM_COPYDATA, 0, addressof(cds))


    def Configure(self, osd="", dur=3, pos=1):
        panel = eg.ConfigPanel()
        osdLabel = wx.StaticText(panel,-1,self.text.osdLabel)
        posLabel = wx.StaticText(panel,-1,self.text.posLabel)
        durLabel = wx.StaticText(panel,-1,self.text.durLabel)
        osdCtrl = wx.TextCtrl(panel,-1,osd, size=(200,-1))
        durCtrl = eg.SpinIntCtrl(panel, -1, dur, max=99999)
        posCtrl = wx.Choice(panel,-1,choices=self.text.position)
        posCtrl.SetSelection(pos)
        sizer = wx.FlexGridSizer(3, 2, 5, 10)
        sizer.Add(osdLabel,0,wx.TOP,3)
        sizer.Add(osdCtrl)
        sizer.Add(durLabel,0,wx.TOP,3)
        sizer.Add(durCtrl)
        sizer.Add(posLabel,0,wx.TOP,3)
        sizer.Add(posCtrl)
        panel.sizer.Add(sizer,0,wx.ALL|wx.EXPAND,20)

        while panel.Affirmed():
            panel.SetResult(
                osdCtrl.GetValue(),
                durCtrl.GetValue(),
                posCtrl.GetSelection(),
            )
#===============================================================================

ACTIONS = (
(eg.ActionGroup, 'GroupMainControls', 'Main controls', None, (
    (Run, "Run", "Run MPC-HC", "Run MPC-HC with its default settings." ,None),
    ('Exit', 'Quit Application', None, 816),
    ('PlayPause', 'Play/Pause', None, 889),
    ('Play', 'Play', None, 887),
    ('Pause', 'Pause', None, 888),
    ('Stop', 'Stop', None, 890),
    ('JumpForwardSmall', 'Jump Forward Small', None, 900),
    ('JumpBackwardSmall', 'Jump Backward Small', None, 899),
    ('JumpForwardMedium', 'Jump Forward Medium', None, 902),
    ('JumpBackwardMedium', 'Jump Backward Medium', None, 901),
    ('JumpForwardLarge', 'Jump Forward Large', None, 904),
    ('JumpBackwardLarge', 'Jump Backward Large', None, 903),
    ('JumpForwardKeyframe', 'Jump Forward Keyframe', None, 898),
    ('JumpBackwardKeyframe', 'Jump Backward Keyframe', None, 897),
    (
        SetInteger,
        "Jump",
        "Jump forward/backward of N seconds",
        "Jumps forward/backward of N seconds.",
        (CMD_JUMPOFNSECONDS,0,-99999,99999)
    ),
    (
        SetInteger,
        "SetPosition",
        "Cue current file to specific position",
        "Cues current file to specific position.",
        (CMD_SETPOSITION,1,1,999999)
    ),    ('IncreaseRate', 'Increase Rate', None, 895),
    ('DecreaseRate', 'Decrease Rate', None, 894),
    ('ResetRate', 'Reset Rate', None, 896),
    ('VolumeUp', 'Volume Up', None, 907),
    ('VolumeDown', 'Volume Down', None, 908),
    ('VolumeMute', 'Volume Mute', None, 909),
    ('BossKey', 'Boss Key', None, 944),
    ('Next', 'Next', None, 922),
    ('Previous', 'Previous', None, 921),
    (SendCmd,"StartPlaylist","Start playing playlist","Starts playing playlist.",CMD_STARTPLAYLIST),
    (SendCmd,"ClearPlaylist","Remove all files from playlist","Removes all files from playlist.",CMD_CLEARPLAYLIST),
    ('NextPlaylistItem', 'Next Playlist Item', None, 920),
    ('PreviousPlaylistItem', 'Previous Playlist Item', None, 919),
    (OpenFile,"AddFile","Add file to playlist","Add a new file to playlist (did not start playing).",CMD_ADDTOPLAYLIST),
    #(
    #    SetInteger,
    #    "SetActiveFile",
    #    "Set the active file in the playlist",
    #    "Sets the active file in the playlist.",
    #    (CMD_SETINDEXPLAYLIST,4,-1,9)
    #),    
    ('OpenDVD', 'Open DVD', None, 801),
    ('OpenFileDialog', 'Show dialog "Open file"', None, 800),
    (OpenFile,"OpenFile","Open file","Opens file.",CMD_OPENFILE),
    ('QuickOpen', 'Quick Open File', None, 969),
    ('OpenDirectory', 'Open Directory', None, 33208),
    ('FrameStep', 'Frame Step', None, 891),
    ('FrameStepBack', 'Frame Step Back', None, 892),
    ('GoTo', 'Go To', None, 893),
    ('AudioDelayAdd10ms', 'Audio Delay +10ms', None, 905),
    ('AudioDelaySub10ms', 'Audio Delay -10ms', None, 906),
    (
        SetInteger,
        "SetAudioDelay",
        "Set the audio delay",
        "Sets the audio delay.",
        (CMD_SETAUDIODELAY,5,-999999,999999)
    ),
)),
(eg.ActionGroup, 'GroupViewModes', 'View modes', None, (
    ('Fullscreen', 'Fullscreen', None, 830),
    ('FullscreenWOR', 'Fullscreen without resolution change', None, 831),
    ('PnSIncSize', 'Pan & Scan Increase Size', None, 862),
    ('PnSDecSize', 'Pan & Scan Decrease Size', None, 863),
    ('PnSTo169', 'Pan & Scan Scale to 16:9', None, 4100),
    ('PnSToWidescreen', 'Pan & Scan to Widescreen', None, 4101),
    ('PnSToUltraWidescreen', 'Pan & Scan to Ultra-Widescreen', None, 4102),
    ('ViewMinimal', 'View Minimal', None, 827),
    ('ViewCompact', 'View Compact', None, 828),
    ('ViewNormal', 'View Normal', None, 829),
    ('AlwaysOnTop', 'Always On Top', None, 884),
    ('Zoom50', 'Zoom 50%', None, 832),
    ('Zoom100', 'Zoom 100%', None, 833),
    ('Zoom200', 'Zoom 200%', None, 834),
    ('VidFrmHalf', 'Video Frame Half', None, 835),
    ('VidFrmNormal', 'Video Frame Normal', None, 836),
    ('VidFrmDouble', 'Video Frame Double', None, 837),
    ('VidFrmStretch', 'Video Frame Stretch', None, 838),
    ('VidFrmInside', 'Video Frame Inside', None, 839),
    ('VidFrmOutside', 'Video Frame Outside', None, 840),
    ('PnSReset', 'Pan & Scan Reset', None, 861),
    ('PnSIncWidth', 'Pan & Scan Increase Width', None, 864),
    ('PnSIncHeight', 'Pan & Scan Increase Height', None, 866),
    ('PnSDecWidth', 'Pan & Scan Decrease Width', None, 865),
    ('PnSDecHeight', 'Pan & Scan Decrease Height', None, 867),
    ('PnSCenter', 'Pan & Scan Center', None, 876),
    ('PnSLeft', 'Pan & Scan Left', None, 868),
    ('PnSRight', 'Pan & Scan Right', None, 869),
    ('PnSUp', 'Pan & Scan Up', None, 870),
    ('PnSDown', 'Pan & Scan Down', None, 871),
    ('PnSUpLeft', 'Pan & Scan Up/Left', None, 872),
    ('PnSUpRight', 'Pan & Scan Up/Right', None, 873),
    ('PnSDownLeft', 'Pan & Scan Down/Left', None, 874),
    ('PnSDownRight', 'Pan & Scan Down/Right', None, 875),
    ('PnSRotateAddX', 'Pan & Scan Rotate X+', None, 877),
    ('PnSRotateSubX', 'Pan & Scan Rotate X-', None, 878),
    ('PnSRotateAddY', 'Pan & Scan Rotate Y+', None, 879),
    ('PnsRotateSubY', 'Pan & Scan Rotate Y-', None, 880),
    ('PnSRotateAddZ', 'Pan & Scan Rotate Z+', None, 881),
    ('PnSRotateSubZ', 'Pan & Scan Rotate Z-', None, 882),
)),
(eg.ActionGroup, 'GroupDvdControls', 'DVD controls', None, (
    ('DVDTitleMenu', 'DVD Title Menu', None, 923),
    ('DVDRootMenu', 'DVD Root Menu', None, 924),
    ('DVDSubtitleMenu', 'DVD Subtitle Menu', None, 925),
    ('DVDAudioMenu', 'DVD Audio Menu', None, 926),
    ('DVDAngleMenu', 'DVD Angle Menu', None, 927),
    ('DVDChapterMenu', 'DVD Chapter Menu', None, 928),
    ('DVDMenuLeft', 'DVD Menu Left', None, 929),
    ('DVDMenuRight', 'DVD Menu Right', None, 930),
    ('DVDMenuUp', 'DVD Menu Up', None, 931),
    ('DVDMenuDown', 'DVD Menu Down', None, 932),
    ('DVDMenuActivate', 'DVD Menu Activate', None, 933),
    ('DVDMenuBack', 'DVD Menu Back', None, 934),
    ('DVDMenuLeave', 'DVD Menu Leave', None, 935),
    ('DVDNextAngle', 'DVD Next Angle', None, 961),
    ('DVDPrevAngle', 'DVD Previous Angle', None, 962),
    ('DVDNextAudio', 'DVD Next Audio', None, 963),
    ('DVDPrevAudio', 'DVD Prev Audio', None, 964),
    ('DVDNextSubtitle', 'DVD Next Subtitle', None, 965),
    ('DVDPrevSubtitle', 'DVD Prev Subtitle', None, 966),
    ('DVDOnOffSubtitle', 'DVD On/Off Subtitle', None, 967),
)),
(eg.ActionGroup, 'GroupExtendedControls', 'Extended controls', None, (
    ('OpenDevice', 'Open Device', None, 802),
    ('SaveAs', 'Save As', None, 805),
    ('SaveImage', 'Save Image', None, 806),
    ('SaveImageAuto', 'Save Image Auto', None, 807),
    ('LoadSubTitle', 'Load Subtitle', None, 809),
    ('SaveSubtitle', 'Save Subtitle', None, 810),
    ('Close', 'Close File', None, 804),
    ('Properties', 'Properties', None, 814),
    ('PlayerMenuShort', 'Player Menu Short', None, 949),
    ('PlayerMenuLong', 'Player Menu Long', None, 950),
    ('FiltersMenu', 'Filters Menu', None, 951),
    ('Options', 'Options', None, 815),
    ('NextAudio', 'Next Audio', None, 952),
    ('PrevAudio', 'Previous Audio', None, 953),
    (
        SetInteger,
        "SetAudioTrack",
        "Set the audio track",
        "Sets the audio track.",
        (CMD_SETAUDIOTRACK,2,0,9)
    ),
    ('NextSubtitle', 'Next Subtitle', None, 954),
    ('PrevSubtitle', 'Prev Subtitle', None, 955),
    (
        SetInteger,
        "SetSubtitlesTrack",
        "Set the subtitle track",
        "Sets the subtitle track.",
        (CMD_SETSUBTITLETRACK,3,-1,9)
    ),
    ('OnOffSubtitle', 'On/Off Subtitle', None, 956),
    ('GotoNextSubtitle', 'Goto Next Subtitle', None, 32781),
    ('GotoPrevSubtitle', 'Goto Prev Subtitle', None, 32780),
    ('SubtitleDelayMinus', 'Subtitle Delay -', None, 24000),
    ('SubtitleDelayPlus', 'Subtitle Delay +', None, 24001),
    (
        SetInteger,
        "SetSubtitleDelay",
        "Set the subtitle delay",
        "Sets the subtitle delay.",
        (CMD_SETSUBTITLEDELAY,6,-999999,999999)
    ),
    ('ReloadSubtitles', 'Reload Subtitles', None, 2302),
    ('NextAudioOGM', 'Next Audio OGM', None, 957),
    ('PrevAudioOGM', 'Previous Audio OGM', None, 958),
    ('NextSubtitleOGM', 'Next Subtitle OGM', None, 959),
    ('PrevSubtitleOGM', 'Previous Subtitle OGM', None, 960),
    (ShowMenu,'ShowMenu','Show MPC menu','Show MPC menu.', None),
    (GoTo_OSD,'GoTo_OSD','On Screen Go To ...','Show On Screen "Go To ...".', None),
    (SendOSD,"SendOSD","Show custom OSD","Shows custom OSD.",None),
    (AfterPlaybackOnce,"AfterPlaybackOnce","Action after playback (once)","The selected action will be made after playback (once).", None),
    (AfterPlayback,"AfterPlayback","Action after playback (every time)","The selected action will be made after playback (every time).", None),
    (UserMessage,'UserMessage',"Send user's message",UserMessage.description, None),
)),
(eg.ActionGroup, 'GroupToggleControls', 'Toggle player controls', None, (
    ('ToggleCaptionMenu', 'Toggle Caption Menu', None, 817),
    ('ToggleSeeker', 'Toggle Seeker', None, 818),
    ('ToggleControls', 'Toggle Controls', None, 819),
    ('ToggleInformation', 'Toggle Information', None, 820),
    ('ToggleStatistics', 'Toggle Statistics', None, 821),
    ('ToggleStatus', 'Toggle Status', None, 822),
    ('ToggleSubresyncBar', 'Toggle Subresync Bar', None, 823),
    ('TogglePlaylistBar', 'Toggle Playlist Bar', None, 824),
    ('ToggleCaptureBar', 'Toggle Capture Bar', None, 825),
    (SendCmd,"ShaderToggle","Toggle shader","Toggles shader.",CMD_SHADER_TOGGLE),
    ('ToggleShaderEditorBar', 'Toggle Shader Editor Bar', None, 826),
    ('ToggleElapsedTime', 'Toggle OSD Elapsed Time', None, 32778),
)),
(eg.ActionGroup, 'VariousInformationRetrieval',"Retrieve various information", None, (
    (GetWindowState,'GetWindowState','Get window state','Gets window state.', None),
    (GetNowPlaying,'GetNowPlaying','Get currently playing file','Gets currently playing file.', None),
    (GetTimes,'GetTimes','Get Times','Returns elapsed, remaining and total times.', None),
    (GetPosition,"GetPosition","Get current position","Returns current position.",None),
    (GetInfo,"GetSubtitles","Get subtitles tracks","Asks for a list of the subtitles tracks of the file.",CMD_GETSUBTITLETRACKS),
    (GetInfo,"GetAdiotracks","Get audio tracks","Asks for a list of the audio tracks of the file.",CMD_GETAUDIOTRACKS),
    (GetInfo,"GetPlaylist","Get playlist","Asks for the current playlist.",CMD_GETPLAYLIST),  
)),
)
#===============================================================================
        