trelby_export/src/misc.py

1005 lines
30 KiB
Python
Raw Permalink Blame History

# -*- coding: iso-8859-1 -*-
import gutil
import opts
import util
import os
import os.path
import sys
if "TRELBY_TESTING" in os.environ:
import unittest.mock as mock
wx = mock.Mock()
else:
import wx
TAB_BAR_HEIGHT = 24
version = "2.4.3"
def init(doWX = True):
global isWindows, isUnix, unicodeFS, doDblBuf, progPath, confPath, tmpPrefix
# prefix used for temp files
tmpPrefix = "trelby-tmp-"
isWindows = False
isUnix = False
if sys.platform.startswith("linux") or sys.platform.startswith("darwin"):
isUnix = True
else:
isWindows = True
# does this platform support using Python's unicode strings in various
# filesystem calls; if not, we need to convert filenames to UTF-8
# before using them.
unicodeFS = isWindows
# wxGTK2 does not need us to do double buffering ourselves, others do
doDblBuf = not isUnix
# stupid hack to keep testcases working, since they don't initialize
# opts (the doWX name is just for similarity with util)
if not doWX or opts.isTest:
progPath = "."
confPath = ".trelby"
else:
if isUnix:
progPath = str(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
confPath = str(os.environ["HOME"]) + "/.trelby"
else:
progPath = getPathFromRegistry()
confPath = util.getWindowsUnicodeEnvVar("USERPROFILE") + r"\Trelby\conf"
if not os.path.exists(confPath):
os.makedirs(confPath)
def getPathFromRegistry():
registryPath = r"Software\Microsoft\Windows\CurrentVersion\App Paths\trelby.exe"
try:
import winreg
regPathKey = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, registryPath)
regPathValue, regPathType = winreg.QueryValueEx(regPathKey, "Path")
if regPathType == winreg.REG_SZ:
return regPathValue
else:
raise TypeError
except:
wx.MessageBox("There was an error reading the following registry key: %s.\n"
"You may need to reinstall the program to fix this error." %
registryPath, "Error", wx.OK)
sys.exit()
# convert s, which is returned from the wxWidgets GUI and is an Unicode
# string, to a normal string.
def fromGUI(s):
return s
# convert s, which is an Unicode string, to an object suitable for passing
# to Python's file APIs. this is either the Unicode string itself, if the
# platform supports Unicode-based APIs (and Python has implemented support
# for it), or the Unicode string converted to UTF-8 on other platforms.
def toPath(s):
if unicodeFS:
return s
else:
return s.encode("UTF-8")
# return bitmap created from the given file. argument is as for
# getFullPath.
def getBitmap(filename):
return wx.Bitmap(getFullPath(filename))
# return the absolute path of a file under the install dir. so passing in
# "resources/blaa.png" might return "/opt/trelby/resources/blaa.png" for
# example.
def getFullPath(relative):
return progPath + "/" + relative
# TODO: move all GUI stuff to gutil
class MyColorSample(wx.Window):
def __init__(self, parent, id, size):
wx.Window.__init__(self, parent, id, size = size)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, event):
dc = wx.PaintDC(self)
w, h = self.GetClientSize()
br = wx.Brush(self.GetBackgroundColour())
dc.SetBrush(br)
dc.DrawRectangle(0, 0, w, h)
# Custom "exit fullscreen" button for our tab bar. Used so that we have
# full control over the button's size.
class MyFSButton(wx.Window):
def __init__(self, parent, id, getCfgGui):
wx.Window.__init__(self, parent, id, size = (TAB_BAR_HEIGHT, TAB_BAR_HEIGHT))
self.getCfgGui = getCfgGui
self.fsImage = getBitmap("resources/fullscreen.png")
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_LEFT_DOWN, self.OnMouseDown)
def OnPaint(self, event):
cfgGui = self.getCfgGui()
dc = wx.PaintDC(self)
w, h = self.GetClientSize()
dc.SetBrush(cfgGui.tabNonActiveBgBrush)
dc.SetPen(cfgGui.tabBorderPen)
dc.DrawRectangle(0, 0, w, h)
off = (h - self.fsImage.GetHeight()) // 2
dc.DrawBitmap(self.fsImage, off, off)
def OnMouseDown(self, event):
clickEvent = wx.CommandEvent(wx.wxEVT_COMMAND_BUTTON_CLICKED, self.GetId())
clickEvent.SetEventObject(self)
self.GetEventHandler().ProcessEvent(clickEvent)
# custom status control
class MyStatus(wx.Window):
WIDTH = 280
X_ELEDIVIDER = 100
def __init__(self, parent, id, getCfgGui):
wx.Window.__init__(self, parent, id, size = (MyStatus.WIDTH, TAB_BAR_HEIGHT),
style = wx.FULL_REPAINT_ON_RESIZE)
self.getCfgGui = getCfgGui
self.page = 0
self.pageCnt = 0
self.elemType = ""
self.tabNext = ""
self.enterNext = ""
self.elementFont = util.createPixelFont(
TAB_BAR_HEIGHT // 2 + 6, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.NORMAL)
self.font = util.createPixelFont(
TAB_BAR_HEIGHT // 2 + 2, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.NORMAL)
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, event):
cfgGui = self.getCfgGui()
cy = (TAB_BAR_HEIGHT - 1) // 2
xoff = 5
dc = wx.PaintDC(self)
w, h = self.GetClientSize()
dc.SetBrush(cfgGui.tabBarBgBrush)
dc.SetPen(cfgGui.tabBarBgPen)
dc.DrawRectangle(0, 0, w, h)
dc.SetPen(cfgGui.tabTextPen)
dc.SetTextForeground(cfgGui.tabTextColor)
pageText = "Page %d / %d" % (self.page, self.pageCnt)
dc.SetFont(self.font)
util.drawText(dc, pageText, MyStatus.WIDTH - xoff, cy,
util.ALIGN_RIGHT, util.VALIGN_CENTER)
s1 = "%s [Enter]" % self.enterNext
s2 = "%s [Tab]" % self.tabNext
x = MyStatus.X_ELEDIVIDER + xoff
dc.DrawText(s1, x, 0)
dc.DrawText(s2, x, cy)
x = xoff
s = "%s" % self.elemType
dc.SetFont(self.elementFont)
util.drawText(dc, s, x, cy, valign = util.VALIGN_CENTER)
dc.SetPen(cfgGui.tabBorderPen)
dc.DrawLine(0, h-1, w, h-1)
for x in (MyStatus.X_ELEDIVIDER, 0):
dc.DrawLine(x, 0, x, h-1)
def SetValues(self, page, pageCnt, elemType, tabNext, enterNext):
self.page = page
self.pageCnt = pageCnt
self.elemType = elemType
self.tabNext = tabNext
self.enterNext = enterNext
self.Refresh(False)
# our own version of a tab control, which exists for two reasons: it does
# not care where it is physically located, which allows us to combine it
# with other controls on a horizontal row, and it consumes less vertical
# space than wx.Notebook. note that this control is divided into two parts,
# MyTabCtrl and MyTabCtrl2, and both must be created.
class MyTabCtrl(wx.Window):
def __init__(self, parent, id, getCfgGui):
style = wx.FULL_REPAINT_ON_RESIZE
wx.Window.__init__(self, parent, id, style = style)
self.getCfgGui = getCfgGui
# pages, i.e., [wx.Window, name] lists. note that 'name' must be an
# Unicode string.
self.pages = []
# index of selected page
self.selected = -1
# index of first visible tab
self.firstTab = 0
# how much padding to leave horizontally at the ends of the
# control, and within each tab
self.paddingX = 10
# starting Y-pos of text in labels
self.textY = 5
# width of a single tab
self.tabWidth = 150
# width, height, spacing, y-pos of arrows
self.arrowWidth = 8
self.arrowHeight = 13
self.arrowSpacing = 3
self.arrowY = 5
# initialized in OnPaint since we don't know our height yet
self.font = None
self.boldFont = None
self.SetMinSize(wx.Size(
self.paddingX * 2 + self.arrowWidth * 2 + self.arrowSpacing +\
self.tabWidth + 5,
TAB_BAR_HEIGHT))
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDown)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
# get the ctrl that the tabbed windows should use as a parent
def getTabParent(self):
return self.ctrl2
# get page count
def getPageCount(self):
return len(self.pages)
# get selected page index
def getSelectedPageIndex(self):
return self.selected
# get given page
def getPage(self, i):
return self.pages[i][0]
# MyTabCtrl2 uses this to register itself with us
def add2(self, ctrl2):
self.ctrl2 = ctrl2
# add page
def addPage(self, page, name):
self.pages.append([page, name])
# the new page must be given the correct size and position
self.setPageSizes()
page.Move(0, 0)
self.selectPage(len(self.pages) - 1)
# set all page's sizes
def setPageSizes(self):
size = self.ctrl2.GetClientSize()
for p in self.pages:
p[0].SetClientSize(size.width, size.height)
# select given page
def selectPage(self, page):
self.selected = page
for i in range(len(self.pages)):
w = self.pages[i][0]
if i == self.selected:
w.Show()
else:
w.Hide()
self.pageChangeFunc(self.selected)
self.makeSelectedTabVisible()
self.Refresh(False)
# delete given page
def deletePage(self, i):
self.pages[i][0].Destroy()
del self.pages[i]
self.selectPage(util.clamp(i, 0, len(self.pages) - 1))
# try to change the first visible tag by the given amount.
def scroll(self, delta):
newFirstTab = self.firstTab + delta
if (newFirstTab >= 0) and (newFirstTab < len(self.pages)):
self.firstTab = newFirstTab
self.Refresh(False)
# calculate the maximum number of tabs that we could show with our
# current size.
def calcMaxVisibleTabs(self):
w = self.GetClientSize()[0]
w -= self.paddingX * 2
w -= self.arrowWidth * 2 + self.arrowSpacing
# leave at least 2 pixels between left arrow and last tab
w -= 2
w //= self.tabWidth
# if by some freak accident we're so small that the above results
# in w being negative or positive but too small, guard against us
# ever returning < 1.
return max(1, w)
# get last visible tab
def getLastVisibleTab(self):
return util.clamp(self.firstTab + self.calcMaxVisibleTabs() - 1,
maxVal = len(self.pages) - 1)
# make sure selected tab is visible
def makeSelectedTabVisible(self):
maxTab = self.getLastVisibleTab()
# if already visible, no need to do anything
if (self.selected >= self.firstTab) and (self.selected <= maxTab):
return
# otherwise, position the selected tab as far right as possible
self.firstTab = util.clamp(
self.selected - self.calcMaxVisibleTabs() + 1,
0)
# set text for tab 'i' to 's'
def setTabText(self, i, s):
self.pages[i][1] = s
self.Refresh(False)
# set function to call when page changes. the function gets a single
# integer argument, the index of the new page.
def setPageChangedFunc(self, func):
self.pageChangeFunc = func
def OnLeftDown(self, event):
x = event.GetPosition().x
if x < self.paddingX:
return
w = self.GetClientSize()[0]
# start of left arrow
lx = w - 1 - self.paddingX - self.arrowWidth - self.arrowSpacing \
- self.arrowWidth + 1
if x < lx:
page, pageOffset = divmod(x - self.paddingX, self.tabWidth)
page += self.firstTab
if page < len(self.pages):
hitX = pageOffset >= (self.tabWidth - self.paddingX * 2)
if hitX:
panel = self.pages[page][0]
if not panel.ctrl.canBeClosed():
return
if self.getPageCount() > 1:
self.deletePage(page)
else:
panel.ctrl.createEmptySp()
panel.ctrl.updateScreen()
else:
self.selectPage(page)
else:
if x < (lx + self.arrowWidth):
self.scroll(-1)
# start of right arrow
rx = lx + self.arrowWidth + self.arrowSpacing
if (x >= rx) and (x < (rx + self.arrowWidth)) and \
(self.getLastVisibleTab() < (len(self.pages) - 1)):
self.scroll(1)
def OnSize(self, event):
size = self.GetClientSize()
self.screenBuf = wx.Bitmap(size.width, size.height)
def OnEraseBackground(self, event):
pass
def OnPaint(self, event):
dc = wx.BufferedPaintDC(self, self.screenBuf)
cfgGui = self.getCfgGui()
w, h = self.GetClientSize()
dc.SetBrush(cfgGui.tabBarBgBrush)
dc.SetPen(cfgGui.tabBarBgPen)
dc.DrawRectangle(0, 0, w, h)
dc.SetPen(cfgGui.tabBorderPen)
dc.DrawLine(0,h-1,w,h-1)
xpos = self.paddingX
tabW = self.tabWidth
tabH = h - 2
tabY = h - tabH
if not self.font:
textH = h - self.textY - 1
self.font = util.createPixelFont(
textH, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.NORMAL)
self.boldFont = util.createPixelFont(
textH, wx.FONTFAMILY_DEFAULT, wx.NORMAL, wx.BOLD)
maxTab = self.getLastVisibleTab()
for i in range(self.firstTab, maxTab + 1):
dc.SetFont(self.font)
p = self.pages[i]
dc.DestroyClippingRegion()
dc.SetClippingRegion(xpos, tabY, tabW, tabH)
dc.SetPen(cfgGui.tabBorderPen)
if i == self.selected:
points=((6,1),(tabW-8,1),(tabW-6,2),(tabW-2,tabH),(0,tabH),(4,2))
dc.SetBrush(cfgGui.workspaceBrush)
else:
points=((5,2),(tabW-8,2),(tabW-6,3),(tabW-2,tabH-1),(0,tabH-1),(3,3))
dc.SetBrush(cfgGui.tabNonActiveBgBrush)
dc.DrawPolygon(points,xpos,tabY)
# clip the text to fit within the tabs
dc.DestroyClippingRegion()
dc.SetClippingRegion(xpos, tabY, tabW - self.paddingX * 3, tabH)
dc.SetPen(cfgGui.tabTextPen)
dc.SetTextForeground(cfgGui.tabTextColor)
dc.DrawText(p[1], xpos + self.paddingX, self.textY)
dc.DestroyClippingRegion()
dc.SetFont(self.boldFont)
dc.DrawText("<EFBFBD>", xpos + tabW - self.paddingX * 2, self.textY)
xpos += tabW
# start of right arrow
rx = w - 1 - self.paddingX - self.arrowWidth + 1
if self.firstTab != 0:
dc.DestroyClippingRegion()
dc.SetPen(cfgGui.tabTextPen)
util.drawLine(dc, rx - self.arrowSpacing - 1, self.arrowY,
0, self.arrowHeight)
util.drawLine(dc, rx - self.arrowSpacing - 2, self.arrowY,
-self.arrowWidth + 1, self.arrowHeight // 2 + 1)
util.drawLine(dc, rx - self.arrowSpacing - self.arrowWidth,
self.arrowY + self.arrowHeight // 2,
self.arrowWidth - 1, self.arrowHeight // 2 + 1)
if maxTab < (len(self.pages) - 1):
dc.DestroyClippingRegion()
dc.SetPen(cfgGui.tabTextPen)
util.drawLine(dc, rx, self.arrowY, 0, self.arrowHeight)
util.drawLine(dc, rx + 1, self.arrowY, self.arrowWidth - 1,
self.arrowHeight // 2 + 1)
util.drawLine(dc, rx + 1, self.arrowY + self.arrowHeight - 1,
self.arrowWidth - 1, -(self.arrowHeight // 2 + 1))
# second part of MyTabCtrl
class MyTabCtrl2(wx.Window):
def __init__(self, parent, id, tabCtrl):
wx.Window.__init__(self, parent, id)
# MyTabCtrl
self.tabCtrl = tabCtrl
self.tabCtrl.add2(self)
self.Bind(wx.EVT_PAINT, self.OnPaint)
self.Bind(wx.EVT_SIZE, self.OnSize)
self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
def OnEraseBackground(self, event):
pass
def OnSize(self, event):
self.tabCtrl.setPageSizes()
# we have an OnPaint handler that does nothing in a feeble attempt in
# trying to make sure that in the cases when this does get called, as
# little (useless) work as possible is done.
def OnPaint(self, event):
wx.PaintDC(self)
# dialog that shows two lists of script names, allowing user to choose one
# from both. stores indexes of selections in members named 'sel1' and
# 'sel2' when OK is pressed. 'items' must have at least two items.
class ScriptChooserDlg(wx.Dialog):
def __init__(self, parent, items):
wx.Dialog.__init__(self, parent, -1, "Choose scripts",
style = wx.DEFAULT_DIALOG_STYLE)
vsizer = wx.BoxSizer(wx.VERTICAL)
gsizer = wx.FlexGridSizer(2, 2, 5, 0)
self.addCombo("first", "Compare script", self, gsizer, items, 0)
self.addCombo("second", "to", self, gsizer, items, 1)
vsizer.Add(gsizer)
self.forceCb = wx.CheckBox(self, -1, "Use same configuration")
self.forceCb.SetValue(True)
vsizer.Add(self.forceCb, 0, wx.TOP, 10)
hsizer = wx.BoxSizer(wx.HORIZONTAL)
hsizer.Add((1, 1), 1)
cancelBtn = gutil.createStockButton(self, "Cancel")
hsizer.Add(cancelBtn)
okBtn = gutil.createStockButton(self, "OK")
hsizer.Add(okBtn, 0, wx.LEFT, 10)
vsizer.Add(hsizer, 0, wx.EXPAND | wx.TOP, 10)
util.finishWindow(self, vsizer)
self.Bind(wx.EVT_BUTTON, self.OnCancel, id=cancelBtn.GetId())
self.Bind(wx.EVT_BUTTON, self.OnOK, id=okBtn.GetId())
okBtn.SetFocus()
def addCombo(self, name, descr, parent, sizer, items, sel):
al = wx.ALIGN_CENTER_VERTICAL | wx.RIGHT
if sel == 1:
al |= wx.ALIGN_RIGHT
sizer.Add(wx.StaticText(parent, -1, descr), 0, al, 10)
combo = wx.ComboBox(parent, -1, style = wx.CB_READONLY)
util.setWH(combo, w = 200)
for s in items:
combo.Append(s)
combo.SetSelection(sel)
sizer.Add(combo)
setattr(self, name + "Combo", combo)
def OnOK(self, event):
self.sel1 = self.firstCombo.GetSelection()
self.sel2 = self.secondCombo.GetSelection()
self.forceSameCfg = bool(self.forceCb.GetValue())
self.EndModal(wx.ID_OK)
def OnCancel(self, event):
self.EndModal(wx.ID_CANCEL)
# CheckBoxDlg below handles lists of these
class CheckBoxItem:
def __init__(self, text, selected = True, cdata = None):
self.text = text
self.selected = selected
self.cdata = cdata
# return dict which has keys for all selected items' client data.
# takes a list of CheckBoxItem's as its parameter. note: this is a
# static function.
@staticmethod
def getClientData(cbil):
tmp = {}
for i in range(len(cbil)):
cbi = cbil[i]
if cbi.selected:
tmp[cbi.cdata] = None
return tmp
# shows one or two (one if cbil2 = None) checklistbox widgets with
# contents from cbil1 and possibly cbil2, which are lists of
# CheckBoxItems. btns[12] are bools for whether or not to include helper
# buttons. if OK is pressed, the incoming lists' items' selection status
# will be modified.
class CheckBoxDlg(wx.Dialog):
def __init__(self, parent, title, cbil1, descr1, btns1,
cbil2 = None, descr2 = None, btns2 = None):
wx.Dialog.__init__(self, parent, -1, title,
style = wx.DEFAULT_DIALOG_STYLE)
vsizer = wx.BoxSizer(wx.VERTICAL)
self.cbil1 = cbil1
self.list1 = self.addList(descr1, self, vsizer, cbil1, btns1, True)
if cbil2 != None:
self.cbil2 = cbil2
self.list2 = self.addList(descr2, self, vsizer, cbil2, btns2,
False, 20)
hsizer = wx.BoxSizer(wx.HORIZONTAL)
hsizer.Add((1, 1), 1)
cancelBtn = gutil.createStockButton(self, "Cancel")
hsizer.Add(cancelBtn)
okBtn = gutil.createStockButton(self, "OK")
hsizer.Add(okBtn, 0, wx.LEFT, 10)
vsizer.Add(hsizer, 0, wx.EXPAND | wx.TOP, 10)
util.finishWindow(self, vsizer)
self.Bind(wx.EVT_BUTTON, self.OnCancel, id=cancelBtn.GetId())
self.Bind(wx.EVT_BUTTON, self.OnOK, id=okBtn.GetId())
okBtn.SetFocus()
def addList(self, descr, parent, sizer, items, doBtns, isFirst, pad = 0):
sizer.Add(wx.StaticText(parent, -1, descr), 0, wx.TOP, pad)
if doBtns:
hsizer = wx.BoxSizer(wx.HORIZONTAL)
if isFirst:
funcs = [ self.OnSet1, self.OnClear1, self.OnToggle1 ]
else:
funcs = [ self.OnSet2, self.OnClear2, self.OnToggle2 ]
tmp = wx.Button(parent, -1, "Set")
hsizer.Add(tmp)
self.Bind(wx.EVT_BUTTON, funcs[0], id=tmp.GetId())
tmp = wx.Button(parent, -1, "Clear")
hsizer.Add(tmp, 0, wx.LEFT, 10)
self.Bind(wx.EVT_BUTTON, funcs[1], id=tmp.GetId())
tmp = wx.Button(parent, -1, "Toggle")
hsizer.Add(tmp, 0, wx.LEFT, 10)
self.Bind(wx.EVT_BUTTON, funcs[2], id=tmp.GetId())
sizer.Add(hsizer, 0, wx.TOP | wx.BOTTOM, 5)
tmp = wx.CheckListBox(parent, -1)
longest = -1
for i in range(len(items)):
it = items[i]
tmp.Append(it.text)
tmp.Check(i, it.selected)
if isFirst:
if longest != -1:
if len(it.text) > len(items[longest].text):
longest = i
else:
longest = 0
w = -1
if isFirst:
h = len(items)
if longest != -1:
w = util.getTextExtent(tmp.GetFont(),
"[x] " + items[longest].text)[0] + 15
else:
h = min(10, len(items))
# don't know of a way to get the vertical spacing of items in a
# wx.CheckListBox, so estimate it at font height + 5 pixels, which
# is close enough on everything I've tested.
h *= util.getFontHeight(tmp.GetFont()) + 5
h += 5
h = max(25, h)
util.setWH(tmp, w, h)
sizer.Add(tmp, 0, wx.EXPAND)
return tmp
def storeResults(self, cbil, ctrl):
for i in range(len(cbil)):
cbil[i].selected = bool(ctrl.IsChecked(i))
def setAll(self, ctrl, state):
for i in range(ctrl.GetCount()):
ctrl.Check(i, state)
def toggle(self, ctrl):
for i in range(ctrl.GetCount()):
ctrl.Check(i, not ctrl.IsChecked(i))
def OnSet1(self, event):
self.setAll(self.list1, True)
def OnClear1(self, event):
self.setAll(self.list1, False)
def OnToggle1(self, event):
self.toggle(self.list1)
def OnSet2(self, event):
self.setAll(self.list2, True)
def OnClear2(self, event):
self.setAll(self.list2, False)
def OnToggle2(self, event):
self.toggle(self.list2)
def OnOK(self, event):
self.storeResults(self.cbil1, self.list1)
if hasattr(self, "list2"):
self.storeResults(self.cbil2, self.list2)
self.EndModal(wx.ID_OK)
def OnCancel(self, event):
self.EndModal(wx.ID_CANCEL)
# shows a multi-line string to the user in a scrollable text control.
class TextDlg(wx.Dialog):
def __init__(self, parent, text, title):
wx.Dialog.__init__(self, parent, -1, title,
style = wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
vsizer = wx.BoxSizer(wx.VERTICAL)
tc = wx.TextCtrl(self, -1, size = wx.Size(400, 200),
style = wx.TE_MULTILINE | wx.TE_READONLY | wx.TE_LINEWRAP)
tc.SetValue(text)
vsizer.Add(tc, 1, wx.EXPAND);
vsizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
okBtn = gutil.createStockButton(self, "OK")
vsizer.Add(okBtn, 0, wx.ALIGN_CENTER)
util.finishWindow(self, vsizer)
self.Bind(wx.EVT_BUTTON, self.OnOK, id=okBtn.GetId())
okBtn.SetFocus()
def OnOK(self, event):
self.EndModal(wx.ID_OK)
# helper function for using TextDlg
def showText(parent, text, title = "Message"):
dlg = TextDlg(parent, text, title)
dlg.ShowModal()
dlg.Destroy()
# ask user for a single-line text input.
class TextInputDlg(wx.Dialog):
def __init__(self, parent, text, title, validateFunc = None):
wx.Dialog.__init__(self, parent, -1, title,
style = wx.DEFAULT_DIALOG_STYLE | wx.WANTS_CHARS)
# function to call to validate the input string on OK. can be
# None, in which case it is not called. if it returns "", the
# input is valid, otherwise the string it returns is displayed in
# a message box and the dialog is not closed.
self.validateFunc = validateFunc
vsizer = wx.BoxSizer(wx.VERTICAL)
vsizer.Add(wx.StaticText(self, -1, text), 1, wx.EXPAND | wx.BOTTOM, 5)
self.tc = wx.TextCtrl(self, -1, style = wx.TE_PROCESS_ENTER)
vsizer.Add(self.tc, 1, wx.EXPAND);
vsizer.Add(wx.StaticLine(self, -1), 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
hsizer = wx.BoxSizer(wx.HORIZONTAL)
cancelBtn = gutil.createStockButton(self, "Cancel")
hsizer.Add(cancelBtn)
okBtn = gutil.createStockButton(self, "OK")
hsizer.Add(okBtn, 0, wx.LEFT, 10)
vsizer.Add(hsizer, 0, wx.EXPAND | wx.TOP, 5)
util.finishWindow(self, vsizer)
self.Bind(wx.EVT_BUTTON, self.OnCancel, id=cancelBtn.GetId())
self.Bind(wx.EVT_BUTTON, self.OnOK, id=okBtn.GetId())
self.Bind(wx.EVT_TEXT_ENTER, self.OnOK, id=self.tc.GetId())
self.tc.Bind(wx.EVT_CHAR, self.OnCharEntry)
cancelBtn.Bind(wx.EVT_CHAR, self.OnCharButton)
okBtn.Bind(wx.EVT_CHAR, self.OnCharButton)
self.tc.SetFocus()
def OnCharEntry(self, event):
self.OnChar(event, True)
def OnCharButton(self, event):
self.OnChar(event, False)
def OnChar(self, event, isEntry):
kc = event.GetKeyCode()
if kc == wx.WXK_ESCAPE:
self.OnCancel()
elif (kc == wx.WXK_RETURN) and isEntry:
self.OnOK()
else:
event.Skip()
def OnOK(self, event = None):
self.input = fromGUI(self.tc.GetValue())
if self.validateFunc:
msg = self.validateFunc(self.input)
if msg:
wx.MessageBox(msg, "Error", wx.OK, self)
return
self.EndModal(wx.ID_OK)
def OnCancel(self, event = None):
self.EndModal(wx.ID_CANCEL)
# asks the user for a keypress and stores it.
class KeyDlg(wx.Dialog):
def __init__(self, parent, cmdName):
wx.Dialog.__init__(self, parent, -1, "Key capture",
style = wx.DEFAULT_DIALOG_STYLE)
vsizer = wx.BoxSizer(wx.VERTICAL)
vsizer.Add(wx.StaticText(self, -1, "Press the key combination you\n"
"want to bind to the command\n'%s'." % cmdName))
tmp = KeyDlgWidget(self, -1, (1, 1))
vsizer.Add(tmp)
util.finishWindow(self, vsizer)
tmp.SetFocus()
# used by KeyDlg
class KeyDlgWidget(wx.Window):
def __init__(self, parent, id, size):
wx.Window.__init__(self, parent, id, size = size,
style = wx.WANTS_CHARS)
self.Bind(wx.EVT_CHAR, self.OnKeyChar)
def OnKeyChar(self, ev):
p = self.GetParent()
p.key = util.Key.fromKE(ev)
p.EndModal(wx.ID_OK)
# handles the "Most recently used" list of files in a menu.
class MRUFiles:
def __init__(self, maxCount):
# max number of items
self.maxCount = maxCount
# items (Unicode strings)
self.items = []
for i in range(self.maxCount):
id = wx.NewId()
if i == 0:
# first menu id
self.firstId = id
elif i == (self.maxCount - 1):
# last menu id
self.lastId = id
# use given menu. this must be called before any "add" calls.
def useMenu(self, menu, menuPos):
# menu to use
self.menu = menu
# position in menu to add first item at
self.menuPos = menuPos
# if we already have items, add them to the menu (in reverse order
# to maintain the correct ordering)
tmp = self.items
tmp.reverse()
self.items = []
for it in tmp:
self.add(it)
# return (firstMenuId, lastMenuId).
def getIds(self):
return (self.firstId, self.lastId)
# add item.
def add(self, s):
# remove old menu items
for i in range(self.getCount()):
self.menu.Delete(self.firstId + i)
# if item already exists, remove it
try:
i = self.items.index(s)
del self.items[i]
except ValueError:
pass
# add item to top of list
self.items.insert(0, str(s))
# prune overlong list
if self.getCount() > self.maxCount:
self.items = self.items[:self.maxCount]
# add new menu items
for i in range(self.getCount()):
self.menu.Insert(self.menuPos + i, self.firstId + i,
"&%d %s" % (
i + 1, os.path.basename(str(self.get(i)))))
# return number of items.
def getCount(self):
return len(self.items)
# get item number 'i'.
def get(self, i):
return str(self.items[i])