Andrew Lim avatar Andrew Lim committed 11171d2

Systray and script plugin system

Comments (0)

Files changed (8)

+User scripts and hotkeys are defined in scripts\script.py 
+
+Run mptpaste.exe - it will stay in the Systray.
+
+If you change the scripts just exit it and reopen.
+from script import *
+
+from pysystray import *
+
+from vk import *
+
+
+import itertools, glob
+import functools
+import os
+
+def bye(sysTrayIcon): pass
+def hello(hk,sysTrayIcon): 
+    sysTrayIcon.handlehot(hk)
+
+icons = itertools.cycle(glob.glob('*.ico'))
+hover_text = "Modplug Paste Tools"
+
+script = os.path.expanduser("script\\script.py")
+
+execfile(script,globals())
+
+menu_options = ()
+for hk in hotmap:
+    menu_options += ((hk['title']+'\t'+hk['short'], None, functools.partial(hello,hk)),)
+
+systray = SysTrayIcon(icons.next(), hover_text, menu_options, on_quit=bye, default_menu_index=1, hot_map=hotmap)
+
+systray.go()
+

pastelib.py

-import win32gui
-import win32con
-import win32api
-import win32clipboard
-
-from ctypes import *
-import os
-import struct
-import wave
-
-'''
-ModPlug Tracker  IT
-|C-530v54...|D#530v54...|C-402v54...
-|........122|...........|...........
-|........444|...........|...........
-|.....v08...|.....v08...|===02......
-|........ZGE|...........|...........
-|...........|...........|...........
-'''
-
-#Register Hotkey Constants
-
-MOD_ALT = 1
-MOD_CONTROL = 2
-MOD_SHIFT = 4
-MOD_WIN = 8
-
-VK_LBUTTON = 0x01; VK_RBUTTON = 0x02; VK_CANCEL = 0x03; VK_MBUTTON = 0x04; VK_XBUTTON1 = 0x05; 
-VK_XBUTTON2 = 0x06; VK_BACK = 0x08; VK_TAB = 0x09; VK_CLEAR = 0x0C; VK_RETURN = 0x0D; 
-VK_SHIFT = 0x10; VK_CONTROL = 0x11; VK_MENU = 0x12; VK_PAUSE = 0x13; VK_CAPITAL = 0x14; 
-VK_KANA = 0x15; VK_HANGUEL = 0x15; VK_HANGUL = 0x15; VK_JUNJA = 0x17; VK_FINAL = 0x18; 
-VK_HANJA = 0x19; VK_KANJI = 0x19; VK_ESCAPE = 0x1B; VK_CONVERT = 0x1C; VK_NONCONVERT = 0x1D; 
-VK_ACCEPT = 0x1E; VK_MODECHANGE = 0x1F; VK_SPACE = 0x20; VK_PRIOR = 0x21; VK_NEXT = 0x22; 
-VK_END = 0x23; VK_HOME = 0x24; VK_LEFT = 0x25; VK_UP = 0x26; VK_RIGHT = 0x27; VK_DOWN = 0x28; 
-VK_SELECT = 0x29; VK_PRINT = 0x2A; VK_EXECUTE = 0x2B; VK_SNAPSHOT = 0x2C; VK_INSERT = 0x2D; 
-VK_DELETE = 0x2E; VK_HELP = 0x2F; 
-
-VK_0 = 0x30; VK_1 = 0x31; VK_2 = 0x32; VK_3 = 0x33
-VK_4 = 0x34; VK_5 = 0x35; VK_6 = 0x36; VK_7 = 0x37
-VK_8 = 0x38; VK_9 = 0x39; VK_A = 0x41; VK_B = 0x42
-VK_C = 0x43; VK_D = 0x44; VK_E = 0x45; VK_F = 0x46
-VK_G = 0x47; VK_H = 0x48; VK_I = 0x49; VK_J = 0x4A
-VK_K = 0x4B; VK_L = 0x4C; VK_M = 0x4D; VK_N = 0x4E
-VK_O = 0x4F; VK_P = 0x50; VK_Q = 0x51; VK_R = 0x52
-VK_S = 0x53; VK_T = 0x54; VK_U = 0x55; VK_V = 0x56
-VK_W = 0x57; VK_X = 0x58; VK_Y = 0x59; VK_Z = 0x5A
-
-VK_SLEEP = 0x5F; VK_NUMPAD0 = 0x60; VK_NUMPAD1 = 0x61; VK_NUMPAD2 = 0x62; VK_NUMPAD3 = 0x63; 
-VK_NUMPAD4 = 0x64; VK_NUMPAD5 = 0x65; VK_NUMPAD6 = 0x66; VK_NUMPAD7 = 0x67; VK_NUMPAD8 = 0x68;
-VK_NUMPAD9 = 0x69; VK_MULTIPLY = 0x6A; VK_ADD = 0x6B; VK_SEPARATOR = 0x6C; VK_SUBTRACT = 0x6D; 
-VK_DECIMAL = 0x6E; VK_DIVIDE = 0x6F; VK_F1 = 0x70; VK_F2 = 0x71; VK_F3 = 0x72; VK_F4 = 0x73; 
-VK_F5 = 0x74; VK_F6 = 0x75; VK_F7 = 0x76; VK_F8 = 0x77; VK_F9 = 0x78; VK_F10 = 0x79; 
-VK_F11 = 0x7A; VK_F12 = 0x7B; VK_F13 = 0x7C; VK_F14 = 0x7D; VK_F15 = 0x7E; VK_F16 = 0x7F; 
-VK_F17 = 0x80; VK_F18 = 0x81; VK_F19 = 0x82; VK_F20 = 0x83; VK_F21 = 0x84; VK_F22 = 0x85; 
-VK_F23 = 0x86; VK_F24 = 0x87; 
-
-VK_NUMLOCK = 0x90; VK_SCROLL = 0x91; VK_LSHIFT = 0xA0; VK_RSHIFT = 0xA1; VK_LCONTROL = 0xA2; 
-VK_RCONTROL = 0xA3; VK_LMENU = 0xA4; VK_RMENU = 0xA5; VK_BROWSER_BACK = 0xA6; 
-VK_BROWSER_FORWARD = 0xA7; VK_BROWSER_REFRESH = 0xA8; VK_BROWSER_STOP = 0xA9; 
-VK_BROWSER_SEARCH = 0xAA; VK_BROWSER_FAVORITES = 0xAB; VK_BROWSER_HOME = 0xAC; 
-VK_VOLUME_MUTE = 0xAD; VK_VOLUME_DOWN = 0xAE; VK_VOLUME_UP = 0xAF; VK_MEDIA_NEXT_TRACK = 0xB0; 
-VK_MEDIA_PREV_TRACK = 0xB1; VK_MEDIA_STOP = 0xB2; VK_MEDIA_PLAY_PAUSE = 0xB3; 
-VK_LAUNCH_MAIL = 0xB4; VK_LAUNCH_MEDIA_SELECT = 0xB5; VK_LAUNCH_APP1 = 0xB6; 
-VK_LAUNCH_APP2 = 0xB7; VK_OEM_1 = 0xBA; VK_OEM_PLUS = 0xBB; VK_OEM_COMMA = 0xBC; 
-VK_OEM_MINUS = 0xBD; VK_OEM_PERIOD = 0xBE; VK_OEM_2 = 0xBF; VK_OEM_3 = 0xC0; VK_OEM_4 = 0xDB; 
-VK_OEM_5 = 0xDC; VK_OEM_6 = 0xDD; VK_OEM_7 = 0xDE; VK_OEM_8 = 0xDF; VK_OEM_102 = 0xE2; 
-VK_PROCESSKEY = 0xE5; VK_PACKET = 0xE7; VK_ATTN = 0xF6; VK_CRSEL = 0xF7; VK_EXSEL = 0xF8; 
-VK_EREOF = 0xF9; VK_PLAY = 0xFA; VK_ZOOM = 0xFB; VK_NONAME = 0xFC; VK_PA1 = 0xFD; 
-VK_OEM_CLEAR = 0xFE; 
-
-
-NOTE_OFF = 255
-NOTE_CUT = 254
-NOTE_FADE = 253
-NOTE_NONE = -1
-notenames = ['C-','C#','D-','D#','E-','F-','F#','G-','G#','A-','A#','B-']
-
-
-def mpt_get_note_number(data):
-    nn = NOTE_NONE
-    if data in ['',"..."]:
-        nn = NOTE_NONE
-    elif data == "===":
-        nn = NOTE_OFF
-    elif data == "^^^":
-        nn = NOTE_CUT
-    elif data == "~~~":
-        nn = NOTE_FADE
-    else:
-        n = notenames.index(data[0:2])
-        o = int(data[2:3],16)
-        nn = o * 12 + n
-    return nn
-
-def mpt_get_note_name(nn):
-    if nn == NOTE_NONE: 
-        return "..."
-    elif nn == NOTE_OFF:
-        return "==="
-    elif nn == NOTE_CUT:
-        return "^^^"
-    elif nn == NOTE_FADE:
-        return "~~~"
-    else:
-        n = nn % 12
-        o = nn // 12
-        return "%s%X" % (notenames[n],o)
-
-def mpt_get_note_info(nn):
-    return { 'n': nn % 12, 'o': nn // 12, 'off': (nn == NOTE_OFF), 'cut': (nn == NOTE_CUT), 'fade': (nn == NOTE_FADE), 'none': (nn == NOTE_NONE) }
-
-def mpt_split_pattern(data):
-    '''
-    Returns
-    [ #row
-        [ #col
-            { note: int,
-              inst: int,
-              volcmd: str,
-              volval: int,
-              effcmd: str,
-              effval: int }
-        ]
-    ]
-    '''
-    lines = data.splitlines()
-    if lines[0] != "ModPlug Tracker  IT":
-        return None
-    rows = []
-    for row in lines[1:]:
-        columns = row[1:].split("|")
-        row = []
-        for col in columns:
-            colm = {}
-            colm['note'] = mpt_get_note_number(col[0:3])
-            colm['inst'] = int(col[3:5]) if col[3:5] not in ['','..'] else -1
-            colm['volcmd'] = col[5:6]
-            colm['volval'] = int(col[6:8]) if col[6:8] not in ['','..'] else -1
-            colm['effcmd'] = col[8:9]
-            colm['effval'] = int(col[9:11],16) if col[9:11] not in ['','..'] else -1
-            row.append(colm)
-        rows.append(row)
-    return rows
-
-def mpt_join_pattern(rows):
-    lines = []
-    lines.append("ModPlug Tracker  IT")
-    for rowm in rows:
-        row = ""
-        for colm in rowm:
-            row += "|"
-            row += mpt_get_note_name(colm['note'])
-            row += "%02d" % colm['inst'] if colm['inst'] > 0 else ".."
-            row += colm['volcmd'] if colm['volcmd'] != '' else '.'
-            row += "%02d" % colm['volval'] if colm['volval'] > -1 else ".."
-            row += colm['effcmd'] if colm['effcmd'] != '' else '.'
-            row += "%02X" % colm['effval'] if colm['effval'] > -1 else '..'
-        lines.append(row)
-    return '\r\n'.join(lines)
-
-def mpt_split_env(data):
-    '''
-    Returns 
-    { sustain_start: int,
-      sustain_end: int,
-      loop_start: int,
-      loop_end: int,
-      has_sustain: bool,
-      has_loop: bool,
-      has_carry: bool 
-      points: [ { x: int, y: int }] }
-    '''
-    lines = data.splitlines()
-    if lines[0] != "Modplug Tracker Envelope":
-        return None
-    headervals = lines[1].split(",")
-    nodes = int(headervals[0])
-    header = {}
-
-    for x in xrange(1,5):
-        header[['sustain_start','sustain_end','loop_start','loop_end'][x-1]] = int(headervals[x])
-    for x in xrange(5,8):
-        header[['has_sustain','has_loop','has_carry'][x-5]] = (int(headervals[x]) == 1)
-    off = 2
-    points = []
-    for n in xrange(nodes):
-        x,y = [int(x) for x in lines[off+n].split(",")]
-        points.append({ "x": x, "y": y })
-    header['points'] = points
-    return header
-
-def mpt_join_env(header):
-    lines = []
-    points = header['points']
-    lines.append("Modplug Tracker Envelope")
-    lines.append( 
-        ','.join(
-        ["%d" % len(points)] + 
-        ["%d" % header[key] for key in ['sustain_start','sustain_end','loop_start','loop_end']] +
-        [("1" if header[key] else "0") for key in ['has_sustain','has_loop','has_carry']]
-        )
-        )
-    for n in points:
-        lines.append("%d,%d" % (n['x'],n['y']))
-    lines.append("255")
-    return '\r\n'.join(lines)
-
-def mpt_new_env():
-    return {
-        'sustain_start': 0,
-        'sustain_end': 0,
-        'loop_start': 0,
-        'loop_end': 0,
-        'has_sustain': False,
-        'has_loop': False,
-        'has_carry': False,
-        'points': []
-    }
-def mpt_new_point(x,y):
-    return {'x':x,'y':y}
-
-class MainWindow:
-    def cleanwav(self):
-     if os.path.exists(self.tempfn):
-      os.remove(self.tempfn)
-
-    def __init__(self):
-       win32gui.InitCommonControls()
-       self.hinst = win32api.GetModuleHandle(None)
-    def CreateWindow(self):
-       className = self.RegisterClass()
-       self.BuildWindow(className)
-
-    def RegisterClass(self):
-       className = "TeSt"
-       message_map = {
-          win32con.WM_DESTROY: self.OnDestroy,
-          win32con.WM_SHOWWINDOW: self.OnCreate,
-          win32con.WM_HOTKEY: self.OnHotkey
-       }
-       wc = win32gui.WNDCLASS()
-       wc.style = win32con.CS_HREDRAW | win32con.CS_VREDRAW
-       wc.lpfnWndProc = message_map
-       wc.cbWndExtra = 0
-       wc.hCursor = win32gui.LoadCursor( 0, win32con.IDC_ARROW )
-       wc.hbrBackground = win32con.COLOR_WINDOW + 1
-       wc.hIcon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
-       wc.lpszClassName = className
-       # C code: wc.cbWndExtra = DLGWINDOWEXTRA + sizeof(HBRUSH) + (sizeof(COLORREF));
-       wc.cbWndExtra = win32con.DLGWINDOWEXTRA + struct.calcsize("Pi")
-       #wc.hIconSm = 0
-       classAtom = win32gui.RegisterClass(wc)
-       return className
-
-    def BuildWindow(self, className):
-       style = win32con.WS_OVERLAPPEDWINDOW
-       xstyle = win32con.WS_EX_LEFT
-       self.hwnd = win32gui.CreateWindow(className,
-                             "Modplug Paste Tool",
-                             style,
-                             win32con.CW_USEDEFAULT,
-                             win32con.CW_USEDEFAULT,
-                             500,
-                             400,
-                             0,
-                             0,
-                             self.hinst,
-                             None)
-       win32gui.ShowWindow(self.hwnd, win32con.SW_SHOW)
-
-    def Activate(self):
-        win32gui.SetForegroundWindow(self.hwnd)
-
-    def RegisterHotkeys(self):
-        for hk in self.hotmap:
-            windll.user32.RegisterHotKey(self.hwnd,-1,hk['mod'],hk['vk'])
-    def MsgBox(self,text="",title="",msgtype=0L):
-        win32gui.MessageBox(self.hwnd,text,title,msgtype)
-
-    def GetClipboard(self):
-        win32clipboard.OpenClipboard()
-        data = win32clipboard.GetClipboardData()
-        win32clipboard.CloseClipboard()
-        return data
-    def SetClipboard(self,data):
-        win32clipboard.OpenClipboard()
-        win32clipboard.SetClipboardText(data)
-        win32clipboard.CloseClipboard()
-    def OnDestroy(self, hwnd, message, wparam, lparam):
-        win32gui.PostQuitMessage(0)
-        return True
-    def OnCreate(self, hwnd, message, wparam, lparam):
-        self.hotmap = hotmap
-        self.RegisterHotkeys()
-    def OnHotkey(self, hwnd, message, wparam, lparam):
-        for hk in self.hotmap:
-            if (hk['mod'] == (lparam & 0xFFFF)) and (hk['vk'] == ((lparam >> 16) & 0xFFFF)):
-               print "Handling: " + hk['title']
-               data = self.GetClipboard()
-               data = hk['handler'](data)
-               if data: self.SetClipboard(data)
-               return
-
-def MsgBox(text="",title="",msgtype=0L):
-    win32gui.MessageBox(win32gui.GetForegroundWindow(),text,title,msgtype)
-
-hotmap = []
+#!/usr/bin/env python
+# Module     : SysTrayIcon.py
+# Synopsis   : Windows System tray icon.
+# Programmer : Simon Brunning - simon@brunningonline.net
+# Date       : 11 April 2005
+# Notes      : Based on (i.e. ripped off from) Mark Hammond's
+#              win32gui_taskbar.py and win32gui_menu.py demos from PyWin32
+
+import pywintypes
+import win32con
+import win32api
+import win32clipboard
+import win32com.client as comclt
+wsh= comclt.Dispatch("WScript.Shell")
+
+from ctypes import *
+import struct
+import wave
+         
+import os
+import sys
+try:
+    import winxpgui as win32gui
+except ImportError:
+    import win32gui
+
+import win32gui_struct
+
+
+'''TODO
+
+For now, the demo at the bottom shows how to use it...'''
+
+class SysTrayIcon(object):
+    '''TODO'''
+    QUIT = 'QUIT'
+    SPECIAL_ACTIONS = [QUIT]
+    
+    FIRST_ID = 1023
+    
+    def __init__(self,
+                 icon,
+                 hover_text,
+                 menu_options,
+                 on_quit=None,
+                 default_menu_index=None,
+                 window_class_name=None,
+                 hot_map=None,):
+        self.hotmap = hot_map
+        self.icon = icon
+        self.hover_text = hover_text
+        self.on_quit = on_quit
+        
+        menu_options = menu_options + (('Quit', None, self.QUIT),)
+        self._next_action_id = self.FIRST_ID
+        self.menu_actions_by_id = set()
+        self.menu_options = self._add_ids_to_menu_options(list(menu_options))
+        self.menu_actions_by_id = dict(self.menu_actions_by_id)
+        del self._next_action_id
+        
+        
+        self.default_menu_index = (default_menu_index or 0)
+        self.window_class_name = window_class_name or "SysTrayIconPy"
+        
+        message_map = {win32gui.RegisterWindowMessage("TaskbarCreated"): self.restart,
+                       win32con.WM_DESTROY: self.destroy,
+                       win32con.WM_SHOWWINDOW: self.OnCreate,
+                       win32con.WM_HOTKEY: self.OnHotkey,
+                       win32con.WM_COMMAND: self.command,
+                       win32con.WM_USER+20 : self.notify,}
+        # Register the Window class.
+        window_class = win32gui.WNDCLASS()
+        hinst = window_class.hInstance = win32gui.GetModuleHandle(None)
+        window_class.lpszClassName = self.window_class_name
+        window_class.style = win32con.CS_VREDRAW | win32con.CS_HREDRAW;
+        window_class.hCursor = win32gui.LoadCursor(0, win32con.IDC_ARROW)
+        window_class.hbrBackground = win32con.COLOR_WINDOW
+        window_class.lpfnWndProc = message_map # could also specify a wndproc.
+        classAtom = win32gui.RegisterClass(window_class)
+        # Create the Window.
+        style = win32con.WS_OVERLAPPED | win32con.WS_SYSMENU
+        self.hwnd = win32gui.CreateWindow(classAtom,
+                                          self.window_class_name,
+                                          style,
+                                          0,
+                                          0,
+                                          win32con.CW_USEDEFAULT,
+                                          win32con.CW_USEDEFAULT,
+                                          0,
+                                          0,
+                                          hinst,
+                                          None)
+        self.RegisterHotkeys()
+        win32gui.UpdateWindow(self.hwnd)
+        self.notify_id = None
+        self.refresh_icon()
+        #win32gui.ShowWindow(self.hwnd, win32con.SW_SHOW)
+        
+    def go(self):
+        win32gui.PumpMessages()
+
+    def OnCreate(self, hwnd, msg, wparam, lparam):
+        pass
+        #win32gui.ShowWindow(self.hwnd, win32con.SW_HIDE)
+
+
+    def _add_ids_to_menu_options(self, menu_options):
+        result = []
+        for menu_option in menu_options:
+            option_text, option_icon, option_action = menu_option
+            if callable(option_action) or option_action in self.SPECIAL_ACTIONS:
+                self.menu_actions_by_id.add((self._next_action_id, option_action))
+                result.append(menu_option + (self._next_action_id,))
+            elif non_string_iterable(option_action):
+                result.append((option_text,
+                               option_icon,
+                               self._add_ids_to_menu_options(option_action),
+                               self._next_action_id))
+            else:
+                print 'Unknown item', option_text, option_icon, option_action
+            self._next_action_id += 1
+        return result
+        
+    def refresh_icon(self):
+        # Try and find a custom icon
+        hinst = win32gui.GetModuleHandle(None)
+        if os.path.isfile(self.icon):
+            icon_flags = win32con.LR_LOADFROMFILE | win32con.LR_DEFAULTSIZE
+            hicon = win32gui.LoadImage(hinst,
+                                       self.icon,
+                                       win32con.IMAGE_ICON,
+                                       0,
+                                       0,
+                                       icon_flags)
+        else:
+            print "Can't find icon file - using default."
+            hicon = win32gui.LoadIcon(0, win32con.IDI_APPLICATION)
+
+        if self.notify_id: message = win32gui.NIM_MODIFY
+        else: message = win32gui.NIM_ADD
+        self.notify_id = (self.hwnd,
+                          0,
+                          win32gui.NIF_ICON | win32gui.NIF_MESSAGE | win32gui.NIF_TIP,
+                          win32con.WM_USER+20,
+                          hicon,
+                          self.hover_text)
+        win32gui.Shell_NotifyIcon(message, self.notify_id)
+
+    def restart(self, hwnd, msg, wparam, lparam):
+        self.refresh_icon()
+
+    def destroy(self, hwnd, msg, wparam, lparam):
+        if self.on_quit: self.on_quit(self)
+        nid = (self.hwnd, 0)
+        win32gui.Shell_NotifyIcon(win32gui.NIM_DELETE, nid)
+        win32gui.PostQuitMessage(0) # Terminate the app.
+
+    def notify(self, hwnd, msg, wparam, lparam):
+        if lparam==win32con.WM_LBUTTONDBLCLK:
+            self.execute_menu_option(self.default_menu_index + self.FIRST_ID)
+        elif lparam==win32con.WM_RBUTTONUP:
+            self.show_menu()
+        elif lparam==win32con.WM_LBUTTONUP:
+            pass
+        return True
+        
+    def show_menu(self):
+        menu = win32gui.CreatePopupMenu()
+        self.create_menu(menu, self.menu_options)
+        #win32gui.SetMenuDefaultItem(menu, 1000, 0)
+        
+        pos = win32gui.GetCursorPos()
+        # See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/menus_0hdi.asp
+        win32gui.SetForegroundWindow(self.hwnd)
+        win32gui.TrackPopupMenu(menu,
+                                win32con.TPM_LEFTALIGN,
+                                pos[0],
+                                pos[1],
+                                0,
+                                self.hwnd,
+                                None)
+        win32gui.PostMessage(self.hwnd, win32con.WM_NULL, 0, 0)
+    
+    def create_menu(self, menu, menu_options):
+        for option_text, option_icon, option_action, option_id in menu_options[::-1]:
+            if option_icon:
+                option_icon = self.prep_menu_icon(option_icon)
+            
+            if option_id in self.menu_actions_by_id:                
+                item, extras = win32gui_struct.PackMENUITEMINFO(text=option_text,
+                                                                hbmpItem=option_icon,
+                                                                wID=option_id)
+                win32gui.InsertMenuItem(menu, 0, 1, item)
+            else:
+                submenu = win32gui.CreatePopupMenu()
+                self.create_menu(submenu, option_action)
+                item, extras = win32gui_struct.PackMENUITEMINFO(text=option_text,
+                                                                hbmpItem=option_icon,
+                                                                hSubMenu=submenu)
+                win32gui.InsertMenuItem(menu, 0, 1, item)
+
+    def prep_menu_icon(self, icon):
+        # First load the icon.
+        ico_x = win32api.GetSystemMetrics(win32con.SM_CXSMICON)
+        ico_y = win32api.GetSystemMetrics(win32con.SM_CYSMICON)
+        hicon = win32gui.LoadImage(0, icon, win32con.IMAGE_ICON, ico_x, ico_y, win32con.LR_LOADFROMFILE)
+
+        hdcBitmap = win32gui.CreateCompatibleDC(0)
+        hdcScreen = win32gui.GetDC(0)
+        hbm = win32gui.CreateCompatibleBitmap(hdcScreen, ico_x, ico_y)
+        hbmOld = win32gui.SelectObject(hdcBitmap, hbm)
+        # Fill the background.
+        brush = win32gui.GetSysColorBrush(win32con.COLOR_MENU)
+        win32gui.FillRect(hdcBitmap, (0, 0, 16, 16), brush)
+        # unclear if brush needs to be feed.  Best clue I can find is:
+        # "GetSysColorBrush returns a cached brush instead of allocating a new
+        # one." - implies no DeleteObject
+        # draw the icon
+        win32gui.DrawIconEx(hdcBitmap, 0, 0, hicon, ico_x, ico_y, 0, 0, win32con.DI_NORMAL)
+        win32gui.SelectObject(hdcBitmap, hbmOld)
+        win32gui.DeleteDC(hdcBitmap)
+        
+        return hbm
+
+    def command(self, hwnd, msg, wparam, lparam):
+        id = win32gui.LOWORD(wparam)
+        self.execute_menu_option(id)
+        
+    def execute_menu_option(self, id):
+        menu_action = self.menu_actions_by_id[id]      
+        if menu_action == self.QUIT:
+            win32gui.DestroyWindow(self.hwnd)
+        else:
+            menu_action(self)
+
+    def RegisterHotkey(self,hk):
+        self.hotmap.append(hk)
+    def RegisterHotkeys(self):
+        for hk in self.hotmap:
+            print "Registered: %s" % hk
+            windll.user32.RegisterHotKey(self.hwnd,-1,hk['mod'],hk['vk'])
+
+    def OnHotkey(self, hwnd, message, wparam, lparam):
+        #if not CurrentWindowTitle().startswith("OpenMPT"): return False
+        for hk in self.hotmap:
+            if (hk['mod'] == (lparam & 0xFFFF)) and (hk['vk'] == ((lparam >> 16) & 0xFFFF)):
+               return self.handlehot(hk)
+
+    def handlehot(self,hk):
+        print "Handling: " + hk['title']
+        data = GetClipboard()
+        data = hk['handler'](data)
+        if data: 
+            SetClipboard(data)
+        return True
+
+def GetClipboard():
+    win32clipboard.OpenClipboard()
+    data = win32clipboard.GetClipboardData()
+    win32clipboard.CloseClipboard()
+    return data
+def SetClipboard(data):
+    win32clipboard.OpenClipboard()
+    win32clipboard.SetClipboardText(data)
+    win32clipboard.CloseClipboard()
+
+            
+def non_string_iterable(obj):
+    try:
+        iter(obj)
+    except TypeError:
+        return False
+    else:
+        return not isinstance(obj, basestring)
+
+def CurrentWindowTitle():
+    return win32gui.GetWindowText(win32gui.GetForegroundWindow())
+def DoCopy():
+    wsh.AppActivate("OpenMPT")
+    wsh.SendKeys("^c")
+def DoPaste():
+    wsh.AppActivate("OpenMPT")
+    wsh.SendKeys("^v")
+
+def MsgBox(text="",title="",msgtype=0L):
+    win32gui.MessageBox(win32gui.GetForegroundWindow(),text,title,msgtype)
+
+
+

script/pastelib.py

+
+'''
+ModPlug Tracker  IT
+|C-530v54...|D#530v54...|C-402v54...
+|........122|...........|...........
+|........444|...........|...........
+|.....v08...|.....v08...|===02......
+|........ZGE|...........|...........
+|...........|...........|...........
+'''
+
+#Register Hotkey Constants
+
+NOTE_OFF = 255
+NOTE_CUT = 254
+NOTE_FADE = 253
+NOTE_NONE = -1
+notenames = ['C-','C#','D-','D#','E-','F-','F#','G-','G#','A-','A#','B-']
+
+
+def mpt_get_note_number(data):
+    nn = NOTE_NONE
+    if data in ['',"..."]:
+        nn = NOTE_NONE
+    elif data == "===":
+        nn = NOTE_OFF
+    elif data == "^^^":
+        nn = NOTE_CUT
+    elif data == "~~~":
+        nn = NOTE_FADE
+    else:
+        n = notenames.index(data[0:2])
+        o = int(data[2:3],16)
+        nn = o * 12 + n
+    return nn
+
+def mpt_get_note_name(nn):
+    if nn == NOTE_NONE: 
+        return "..."
+    elif nn == NOTE_OFF:
+        return "==="
+    elif nn == NOTE_CUT:
+        return "^^^"
+    elif nn == NOTE_FADE:
+        return "~~~"
+    else:
+        n = nn % 12
+        o = nn // 12
+        return "%s%X" % (notenames[n],o)
+
+def mpt_get_note_info(nn):
+    return { 'n': nn % 12, 'o': nn // 12, 'off': (nn == NOTE_OFF), 'cut': (nn == NOTE_CUT), 'fade': (nn == NOTE_FADE), 'none': (nn == NOTE_NONE) }
+
+ORDER_DIVIDER = 254
+ORDER_END = 255
+
+def mpt_split_song(data):
+    '''
+    Returns
+    { speed: int
+      tempo: int
+      rpb: int
+      rpm: int
+      title: str
+      channels: 
+      [{ vol: int
+        pan: int
+        title: str }]
+      orderlist: [ int ]
+      patterns 
+      [{ rpb: int
+        rpm: int
+        title: str
+        rows = [ see mpt_split_pattern ] }]
+    }
+    '''
+    lines = data.splitlines()
+    if lines[0] != "Modplug Tracker Song Data":
+        return None
+    h = [int(x) for x in lines[1].split(",")]
+    song = {}
+    chanCount = h[0]
+    ordCount = h[1]
+    patCount = h[2]
+    for k,v in zip(['speed','tempo','rpb','rpm'],[3,4,5,6]):
+      song[k] = h[v]
+    song['title'] = lines[2]
+    chanvols = [int(x) for x in lines[3].split(',')]
+    chanpans = [int(x) for x in lines[4].split(',')]
+    chantitles = lines[5:(5+chanCount)]
+    song['channels'] = [{'vol': v, 'pan': p, 'title': t} for v,p,t in zip(chanvols,chanpans,chantitles)]
+    lnum = 5+chanCount
+    song['orderlist'] = [int(x) for x in line[lnum].split(',')]
+    lnum += 1
+    pats = []
+    for i in xrange(patCount):
+      pat = {}
+      h = [int(x) for x in lines[lnum]]
+      rowCount = h[0]
+      lnum += 1
+      pat['rpb'] = h[1]
+      pat['rpm'] = h[2]
+      pat['title'] = lines[lnum]
+      lnum += 1
+      pat['rows'] = '\r\n'.join(lines[lnum:(lnum+rowCount)])
+      lnum += rowCount
+      pats.append(pat)
+    song['patterns'] = pats
+    return song
+
+def mpt_split_pattern(data):
+    '''
+    Returns
+    [ #row
+        [ #col
+            { note: int,
+              inst: int,
+              volcmd: str,
+              volval: int,
+              effcmd: str,
+              effval: int }
+        ]
+    ]
+    '''
+    lines = data.splitlines()
+    if lines[0] != "ModPlug Tracker  IT":
+        return None
+    rows = []
+    for row in lines[1:]:
+        columns = row[1:].split("|")
+        row = []
+        for col in columns:
+            colm = {}
+            colm['note'] = mpt_get_note_number(col[0:3])
+            colm['inst'] = int(col[3:5]) if col[3:5] not in ['','..'] else -1
+            colm['volcmd'] = col[5:6]
+            colm['volval'] = int(col[6:8]) if col[6:8] not in ['','..'] else -1
+            colm['effcmd'] = col[8:9]
+            colm['effval'] = int(col[9:11],16) if col[9:11] not in ['','..'] else -1
+            row.append(colm)
+        rows.append(row)
+    return rows
+
+def mpt_join_pattern(rows):
+    lines = []
+    lines.append("ModPlug Tracker  IT")
+    for rowm in rows:
+        row = ""
+        for colm in rowm:
+            row += "|"
+            row += mpt_get_note_name(colm['note'])
+            row += "%02d" % colm['inst'] if colm['inst'] > 0 else ".."
+            row += colm['volcmd'] if colm['volcmd'] != '' else '.'
+            row += "%02d" % colm['volval'] if colm['volval'] > -1 else ".."
+            row += colm['effcmd'] if colm['effcmd'] != '' else '.'
+            row += "%02X" % colm['effval'] if colm['effval'] > -1 else '..'
+        lines.append(row)
+    return '\r\n'.join(lines)
+
+
+ENV_HEADER_1 = ['sustain_start','sustain_end','loop_start','loop_end']
+ENV_HEADER_2 = ['has_sustain','has_loop','has_carry']
+
+def mpt_split_env(data):
+    '''
+    Returns 
+    { sustain_start: int,
+      sustain_end: int,
+      loop_start: int,
+      loop_end: int,
+      has_sustain: bool,
+      has_loop: bool,
+      has_carry: bool 
+      points: [ { x: int, y: int }] }
+    '''
+    lines = data.splitlines()
+    if lines[0] != "Modplug Tracker Envelope":
+        return None
+    headervals = lines[1].split(",")
+    nodes = int(headervals[0])
+    header = {}
+
+    for x in xrange(1,5):
+        header[ENV_HEADER_1[x-1]] = int(headervals[x])
+    for x in xrange(5,8):
+        header[ENV_HEADER_2[x-5]] = (int(headervals[x]) == 1)
+    off = 2
+    points = []
+    for n in xrange(nodes):
+        x,y = [int(x) for x in lines[off+n].split(",")]
+        points.append({ "x": x, "y": y })
+    header['points'] = points
+    return header
+
+def mpt_join_env(header):
+    lines = []
+    points = header['points']
+    lines.append("Modplug Tracker Envelope")
+    lines.append( 
+        ','.join(
+        ["%d" % len(points)] + 
+        ["%d" % header[key] for key in ENV_HEADER_1] +
+        [("1" if header[key] else "0") for key in ENV_HEADER_2]
+        )
+        )
+    for n in points:
+        lines.append("%d,%d" % (n['x'],n['y']))
+    lines.append("255")
+    return '\r\n'.join(lines)
+
+def mpt_new_env():
+    return {
+        'sustain_start': 0,
+        'sustain_end': 0,
+        'loop_start': 0,
+        'loop_end': 0,
+        'has_sustain': False,
+        'has_loop': False,
+        'has_carry': False,
+        'points': []
+    }
+def mpt_new_point(x,y):
+    return {'x':x,'y':y}
+from pastelib import *
+
+def NotesToPitchEnv(data):
+    rows = mpt_split_pattern(data)
+    if not rows:
+        MsgBox("Need Pattern Data! Copy pattern pls")
+        return
+    col1 = [x[0] for x in rows]
+    basenote = -1
+    x = 0
+    env = mpt_new_env()
+    startpoint = 0
+    endpoint = 0
+    ly = -1
+    inturd = -1
+    steplen = -1
+    for row in col1:
+        if row['effcmd'] == 'S':
+            if row['effval'] == 0xB0:
+                startpoint = len(env['points'])
+            elif row['effval'] == 0xBF:
+                env['has_carry'] = True
+            elif row['effval'] == 0xB1:
+                endpoint = len(env['points'])+2
+                env['has_loop'] = True
+            else:
+                endpoint = len(env['points'])+2
+                env['has_sustain'] = True
+
+        if basenote == -1:
+            basenote = row['note']
+        if row['inst'] > 0:
+            steplen = row['inst']
+        if row['note'] not in [NOTE_OFF,NOTE_NONE,NOTE_FADE,NOTE_CUT]:
+            print row['note']
+            inturd = 32 + ((row['note'] - basenote) * 2)
+        if inturd < 0: inturd = 0
+        if inturd > 64: inturd = 64
+
+        if ly > -1:
+            env['points'].append(mpt_new_point(x,ly))
+        if ly != inturd:
+            env['points'].append(mpt_new_point(x,inturd))
+
+        ly = inturd
+        x += steplen
+
+    env['points'].append(mpt_new_point(x,ly))
+    if env['has_loop']:
+        env['loop_start'] = startpoint
+        env['loop_end'] = endpoint
+    elif env['has_sustain']:
+        env['sustain_start'] = startpoint
+        env['sustain_end'] = endpoint
+    return mpt_join_env(env)
+
+def SuperCpy2(data): pass
+
+def SuperCpy(data):
+    rows = mpt_split_pattern(data)
+    rowcount = len(rows)
+    colcount = len(rows[0])
+    colprops = []
+    for i,c in enumerate(map(list, zip(*rows))):
+        #find first note
+        for j,r in enumerate(c):
+            if r['note'] not in [NOTE_OFF,NOTE_NONE,NOTE_FADE,NOTE_CUT]:
+                col = {}
+                col['offset'] = j
+                col['note'] = r['note']
+                col['volval'] = r['volval'] if r['volcmd'] in ['v','o','p'] else -1
+                col['volcmd'] = r['volcmd'] if r['volcmd'] in ['v','o','p'] else ''
+                col['effval'] = r['effval'] if r['effcmd'] in ['O','S'] else -1
+                col['effcmd'] = r['effcmd'] if r['effcmd'] in ['O','S'] else ''
+                col['inst'] = r['inst']
+                colprops.append(col)
+                break
+
+    col1 = [x[0] for x in rows]
+    cp = colprops[0]
+    for j,r in enumerate(col1):
+        for i,c in enumerate(colprops):
+            if i == 0: continue
+            offset = c['offset'] - cp['offset']
+            if j+offset >= rowcount: continue
+
+            trow = rows[j+offset][i]
+            if r['note'] not in [NOTE_OFF,NOTE_NONE,NOTE_FADE,NOTE_CUT]:
+                trow['note'] = (c['note'] - cp['note']) + r['note']
+            else:
+                trow['note'] = r['note']
+            if c['inst'] > 0 and r['inst'] > 0:
+                trow['inst'] = c['inst']
+            else:
+                trow['inst'] = r['inst']
+            trow['volcmd'] = r['volcmd']
+            if c['volval'] > -1 and c['volcmd'] == 'v' and (r['note'] not in [NOTE_OFF,NOTE_NONE,NOTE_FADE,NOTE_CUT] or  r['volcmd'] == 'v'):
+                volval = 64
+                if r['volcmd'] == 'v' and r['volval'] > -1: volval = r['volval']
+                trow['volcmd'] = 'v'
+                pvolval = 64
+                if (cp['volcmd'] == 'v' and cp['volval'] > -1): pvolval = cp['volval']
+                trow['volval'] = int((float(c['volval']) / float(pvolval)) * float(volval))
+            elif c['volcmd'] != '' and c['volval'] > -1 and r['note'] not in [NOTE_OFF,NOTE_NONE,NOTE_FADE,NOTE_CUT]:
+                trow['volcmd'] = c['volcmd']
+                trow['volval'] = c['volval']
+            else:
+                trow['volval'] = r['volval']
+            if c['effcmd'] != '':
+                trow['effval'] = c['effval']
+                trow['effcmd'] = c['effcmd']
+            elif r['effcmd'] not in ['A','B','C']:
+                trow['effval'] = r['effval']
+                trow['effcmd'] = r['effcmd']
+            else:
+                trow['effcmd'] = ''
+                trow['effval'] = -1
+    return mpt_join_pattern(rows)
+
+
+
+def SuperCpy3(data):
+    rowx = mpt_split_pattern(data)
+    xcount = len(map(list, zip(*rowx)))
+    for mmm in xrange(0,xcount,2):
+        trsp = map(list, zip(*rowx))[mmm:mmm+2]
+        rows = map(list, zip(*trsp))
+        rowcount = len(rows)
+        colcount = len(rows[0])
+        colprops = []
+        for i,c in enumerate(map(list, zip(*rows))):
+            #find first note
+            for j,r in enumerate(c):
+                if r['note'] not in [NOTE_OFF,NOTE_NONE,NOTE_FADE,NOTE_CUT]:
+                    col = {}
+                    col['offset'] = j
+                    col['note'] = r['note']
+                    col['volval'] = r['volval'] if r['volcmd'] in ['v','o','p'] else -1
+                    col['volcmd'] = r['volcmd'] if r['volcmd'] in ['v','o','p'] else ''
+                    col['effval'] = r['effval'] if r['effcmd'] in ['O','S'] else -1
+                    col['effcmd'] = r['effcmd'] if r['effcmd'] in ['O','S'] else ''
+                    col['inst'] = r['inst']
+                    colprops.append(col)
+                    break
+
+        col1 = [x[0] for x in rows]
+        cp = colprops[0]
+        for j,r in enumerate(col1):
+            for i,c in enumerate(colprops):
+                if i == 0: continue
+                offset = c['offset'] - cp['offset']
+                if j+offset >= rowcount: continue
+
+                trow = rows[j+offset][i]
+                if r['note'] not in [NOTE_OFF,NOTE_NONE,NOTE_FADE,NOTE_CUT]:
+                    trow['note'] = (c['note'] - cp['note']) + r['note']
+                else:
+                    trow['note'] = r['note']
+                if c['inst'] > 0 and r['inst'] > 0:
+                    trow['inst'] = c['inst']
+                else:
+                    trow['inst'] = r['inst']
+                trow['volcmd'] = r['volcmd']
+                if c['volval'] > -1 and c['volcmd'] == 'v' and (r['note'] not in [NOTE_OFF,NOTE_NONE,NOTE_FADE,NOTE_CUT] or  r['volcmd'] == 'v'):
+                    volval = 64
+                    if r['volcmd'] == 'v' and r['volval'] > -1: volval = r['volval']
+                    trow['volcmd'] = 'v'
+                    pvolval = 64
+                    if (cp['volcmd'] == 'v' and cp['volval'] > -1): pvolval = cp['volval']
+                    trow['volval'] = int((float(c['volval']) / float(pvolval)) * float(volval))
+                elif c['volcmd'] != '' and c['volval'] > -1 and r['note'] not in [NOTE_OFF,NOTE_NONE,NOTE_FADE,NOTE_CUT]:
+                    trow['volcmd'] = c['volcmd']
+                    trow['volval'] = c['volval']
+                else:
+                    trow['volval'] = r['volval']
+                if c['effcmd'] != '':
+                    trow['effval'] = c['effval']
+                    trow['effcmd'] = c['effcmd']
+                elif r['effcmd'] not in ['A','B','C']:
+                    trow['effval'] = r['effval']
+                    trow['effcmd'] = r['effcmd']
+                else:
+                    trow['effcmd'] = ''
+                    trow['effval'] = -1
+    return mpt_join_pattern(rowx)
+
+
+hotmap = []
+hotmap.append({ 
+        'title': 'Replace Note Cuts with Note Offs', 
+        'short': 'Ctrl+Shift+1',
+        'mod': MOD_CONTROL + MOD_SHIFT, 'vk': VK_1, 
+        'handler': lambda x: x.replace("^^^","===") })
+hotmap.append({ 
+        'title': 'Generate Pitch Envelope With Notes', 
+        'short': 'Ctrl+Shift+5',
+        'mod': MOD_CONTROL + MOD_SHIFT, 'vk': VK_5, 
+        'handler': NotesToPitchEnv })
+hotmap.append({ 
+        'title': 'SuperCPY 2', 
+        'short': 'Ctrl+Shift+2',
+        'mod': MOD_CONTROL + MOD_SHIFT, 'vk': VK_2, 
+        'handler': SuperCpy2 })
+hotmap.append({ 
+        'title': 'SuperCPY', 
+        'short': 'Ctrl+Shift+3',
+        'mod': MOD_CONTROL + MOD_SHIFT, 'vk': VK_3, 
+        'handler': SuperCpy })
+hotmap.append({ 
+        'title': 'SuperCPY Parallel', 
+        'short': 'Ctrl+Shift+4',
+        'mod': MOD_CONTROL + MOD_SHIFT, 'vk': VK_4, 
+        'handler': SuperCpy3 })

stuff.py

-from pastelib import *
-
-def NotesToPitchEnv(data):
-    rows = mpt_split_pattern(data)
-    if not rows:
-        MsgBox("Need Pattern Data! Copy pattern pls")
-        return
-    col1 = [x[0] for x in rows]
-    basenote = -1
-    x = 0
-    env = mpt_new_env()
-    startpoint = 0
-    endpoint = 0
-    ly = -1
-    inturd = -1
-    steplen = -1
-    for row in col1:
-        if row['effcmd'] == 'S':
-            if row['effval'] == 0xB0:
-                startpoint = len(env['points'])
-            elif row['effval'] == 0xBF:
-                env['has_carry'] = True
-            elif row['effval'] == 0xB1:
-                endpoint = len(env['points'])+2
-                env['has_loop'] = True
-            else:
-                endpoint = len(env['points'])+2
-                env['has_sustain'] = True
-
-        if basenote == -1:
-            basenote = row['note']
-        if row['inst'] > 0:
-            steplen = row['inst']
-        if row['note'] not in [NOTE_OFF,NOTE_NONE,NOTE_FADE,NOTE_CUT]:
-            print row['note']
-            inturd = 32 + ((row['note'] - basenote) * 2)
-        if inturd < 0: inturd = 0
-        if inturd > 64: inturd = 64
-
-        if ly > -1:
-            env['points'].append(mpt_new_point(x,ly))
-        if ly != inturd:
-            env['points'].append(mpt_new_point(x,inturd))
-
-        ly = inturd
-        x += steplen
-
-    env['points'].append(mpt_new_point(x,ly))
-    if env['has_loop']:
-        env['loop_start'] = startpoint
-        env['loop_end'] = endpoint
-    elif env['has_sustain']:
-        env['sustain_start'] = startpoint
-        env['sustain_end'] = endpoint
-    return mpt_join_env(env)
-
-def SuperCpy(data):
-    rows = mpt_split_pattern(data)
-    rowcount = len(rows)
-    colcount = len(rows[0])
-    colprops = []
-    for i,c in enumerate(map(list, zip(*rows))):
-        #find first note
-        for j,r in enumerate(c):
-            if r['note'] not in [NOTE_OFF,NOTE_NONE,NOTE_FADE,NOTE_CUT]:
-                col = {}
-                col['offset'] = j
-                col['note'] = r['note']
-                col['volval'] = r['volval'] if r['volcmd'] in ['v','o','p'] else -1
-                col['volcmd'] = r['volcmd'] if r['volcmd'] in ['v','o','p'] else ''
-                col['effval'] = r['effval'] if r['effcmd'] in ['O','S'] else -1
-                col['effcmd'] = r['effcmd'] if r['effcmd'] in ['O','S'] else ''
-                col['inst'] = r['inst']
-                colprops.append(col)
-                break
-
-    col1 = [x[0] for x in rows]
-    cp = colprops[0]
-    for j,r in enumerate(col1):
-        for i,c in enumerate(colprops):
-            if i == 0: continue
-            offset = c['offset'] - cp['offset']
-            if j+offset >= rowcount: continue
-
-            trow = rows[j+offset][i]
-            if r['note'] not in [NOTE_OFF,NOTE_NONE,NOTE_FADE,NOTE_CUT]:
-                trow['note'] = (c['note'] - cp['note']) + r['note']
-            else:
-                trow['note'] = r['note']
-            if c['inst'] > 0 and r['inst'] > 0:
-                trow['inst'] = c['inst']
-            else:
-                trow['inst'] = r['inst']
-            trow['volcmd'] = r['volcmd']
-            if c['volval'] > -1 and c['volcmd'] == 'v' and (r['note'] not in [NOTE_OFF,NOTE_NONE,NOTE_FADE,NOTE_CUT] or  r['volcmd'] == 'v'):
-                volval = 64
-                if r['volcmd'] == 'v' and r['volval'] > -1: volval = r['volval']
-                trow['volcmd'] = 'v'
-                pvolval = 64
-                if (cp['volcmd'] == 'v' and cp['volval'] > -1): pvolval = cp['volval']
-                trow['volval'] = int((float(c['volval']) / float(pvolval)) * float(volval))
-            elif c['volcmd'] != '' and c['volval'] > -1 and r['note'] not in [NOTE_OFF,NOTE_NONE,NOTE_FADE,NOTE_CUT]:
-                trow['volcmd'] = c['volcmd']
-                trow['volval'] = c['volval']
-            else:
-                trow['volval'] = r['volval']
-            if c['effcmd'] != '':
-                trow['effval'] = c['effval']
-                trow['effcmd'] = c['effcmd']
-            elif r['effcmd'] not in ['A','B','C']:
-                trow['effval'] = r['effval']
-                trow['effcmd'] = r['effcmd']
-            else:
-                trow['effcmd'] = ''
-                trow['effval'] = -1
-    return mpt_join_pattern(rows)
-
-hotmap.append({ 
-        'title': 'Replace Note Offs with Note Cuts', 
-        'mod': MOD_WIN , 'vk': VK_X, 
-        'handler': lambda x: x.replace("===","^^^") })
-hotmap.append({ 
-        'title': 'Generate Pitch Envelope With Notes', 
-        'mod': MOD_WIN + MOD_ALT, 'vk': VK_Z, 
-        'handler': NotesToPitchEnv })
-hotmap.append({ 
-        'title': 'SuperCPY', 
-        'mod': MOD_WIN + MOD_ALT, 'vk': VK_C, 
-        'handler': SuperCpy })
-
-
-
-
-w = MainWindow()
-w.CreateWindow()
-win32gui.PumpMessages()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.