cf0d4bfc63
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@43739 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
476 lines
15 KiB
Python
476 lines
15 KiB
Python
|
|
import wx
|
|
import wx.combo
|
|
import os
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
class NullLog:
|
|
def write(*args):
|
|
pass
|
|
|
|
|
|
# This class is used to provide an interface between a ComboCtrl and a
|
|
# ListCtrl that is used as the popoup for the combo widget. In this
|
|
# case we use multiple inheritance to derive from both wx.ListCtrl and
|
|
# wx.ComboPopup, but it also works well when deriving from just
|
|
# ComboPopup and using a has-a relationship with the popup control,
|
|
# you just need to be sure to return the control itself from the
|
|
# GetControl method.
|
|
|
|
class ListCtrlComboPopup(wx.ListCtrl, wx.combo.ComboPopup):
|
|
|
|
def __init__(self, log=None):
|
|
if log:
|
|
self.log = log
|
|
else:
|
|
self.log = NullLog()
|
|
|
|
|
|
# Since we are using multiple inheritance, and don't know yet
|
|
# which window is to be the parent, we'll do 2-phase create of
|
|
# the ListCtrl instead, and call its Create method later in
|
|
# our Create method. (See Create below.)
|
|
self.PostCreate(wx.PreListCtrl())
|
|
|
|
# Also init the ComboPopup base class.
|
|
wx.combo.ComboPopup.__init__(self)
|
|
|
|
|
|
def AddItem(self, txt):
|
|
self.InsertStringItem(self.GetItemCount(), txt)
|
|
|
|
def OnMotion(self, evt):
|
|
item, flags = self.HitTest(evt.GetPosition())
|
|
if item >= 0:
|
|
self.Select(item)
|
|
self.curitem = item
|
|
|
|
def OnLeftDown(self, evt):
|
|
self.value = self.curitem
|
|
self.Dismiss()
|
|
|
|
|
|
# The following methods are those that are overridable from the
|
|
# ComboPopup base class. Most of them are not required, but all
|
|
# are shown here for demonstration purposes.
|
|
|
|
|
|
# This is called immediately after construction finishes. You can
|
|
# use self.GetCombo if needed to get to the ComboCtrl instance.
|
|
def Init(self):
|
|
self.log.write("ListCtrlComboPopup.Init")
|
|
self.value = -1
|
|
self.curitem = -1
|
|
|
|
|
|
# Create the popup child control. Return true for success.
|
|
def Create(self, parent):
|
|
self.log.write("ListCtrlComboPopup.Create")
|
|
wx.ListCtrl.Create(self, parent,
|
|
style=wx.LC_LIST|wx.LC_SINGLE_SEL|wx.SIMPLE_BORDER)
|
|
self.Bind(wx.EVT_MOTION, self.OnMotion)
|
|
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
|
|
return True
|
|
|
|
|
|
# Return the widget that is to be used for the popup
|
|
def GetControl(self):
|
|
#self.log.write("ListCtrlComboPopup.GetControl")
|
|
return self
|
|
|
|
# Called just prior to displaying the popup, you can use it to
|
|
# 'select' the current item.
|
|
def SetStringValue(self, val):
|
|
self.log.write("ListCtrlComboPopup.SetStringValue")
|
|
idx = self.FindItem(-1, val)
|
|
if idx != wx.NOT_FOUND:
|
|
self.Select(idx)
|
|
|
|
# Return a string representation of the current item.
|
|
def GetStringValue(self):
|
|
self.log.write("ListCtrlComboPopup.GetStringValue")
|
|
if self.value >= 0:
|
|
return self.GetItemText(self.value)
|
|
return ""
|
|
|
|
# Called immediately after the popup is shown
|
|
def OnPopup(self):
|
|
self.log.write("ListCtrlComboPopup.OnPopup")
|
|
wx.combo.ComboPopup.OnPopup(self)
|
|
|
|
# Called when popup is dismissed
|
|
def OnDismiss(self):
|
|
self.log.write("ListCtrlComboPopup.OnDismiss")
|
|
wx.combo.ComboPopup.OnDismiss(self)
|
|
|
|
# This is called to custom paint in the combo control itself
|
|
# (ie. not the popup). Default implementation draws value as
|
|
# string.
|
|
def PaintComboControl(self, dc, rect):
|
|
self.log.write("ListCtrlComboPopup.PaintComboControl")
|
|
wx.combo.ComboPopup.PaintComboControl(self, dc, rect)
|
|
|
|
# Receives key events from the parent ComboCtrl. Events not
|
|
# handled should be skipped, as usual.
|
|
def OnComboKeyEvent(self, event):
|
|
self.log.write("ListCtrlComboPopup.OnComboKeyEvent")
|
|
wx.combo.ComboPopup.OnComboKeyEvent(self, event)
|
|
|
|
# Implement if you need to support special action when user
|
|
# double-clicks on the parent wxComboCtrl.
|
|
def OnComboDoubleClick(self):
|
|
self.log.write("ListCtrlComboPopup.OnComboDoubleClick")
|
|
wx.combo.ComboPopup.OnComboDoubleClick(self)
|
|
|
|
# Return final size of popup. Called on every popup, just prior to OnPopup.
|
|
# minWidth = preferred minimum width for window
|
|
# prefHeight = preferred height. Only applies if > 0,
|
|
# maxHeight = max height for window, as limited by screen size
|
|
# and should only be rounded down, if necessary.
|
|
def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
|
|
self.log.write("ListCtrlComboPopup.GetAdjustedSize: %d, %d, %d" % (minWidth, prefHeight, maxHeight))
|
|
return wx.combo.ComboPopup.GetAdjustedSize(self, minWidth, prefHeight, maxHeight)
|
|
|
|
# Return true if you want delay the call to Create until the popup
|
|
# is shown for the first time. It is more efficient, but note that
|
|
# it is often more convenient to have the control created
|
|
# immediately.
|
|
# Default returns false.
|
|
def LazyCreate(self):
|
|
self.log.write("ListCtrlComboPopup.LazyCreate")
|
|
return wx.combo.ComboPopup.LazyCreate(self)
|
|
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
# This class is a popup containing a TreeCtrl. This time we'll use a
|
|
# has-a style (instead of is-a like above.)
|
|
|
|
class TreeCtrlComboPopup(wx.combo.ComboPopup):
|
|
|
|
# overridden ComboPopup methods
|
|
|
|
def Init(self):
|
|
self.value = None
|
|
self.curitem = None
|
|
|
|
|
|
def Create(self, parent):
|
|
self.tree = wx.TreeCtrl(parent, style=wx.TR_HIDE_ROOT
|
|
|wx.TR_HAS_BUTTONS
|
|
|wx.TR_SINGLE
|
|
|wx.TR_LINES_AT_ROOT
|
|
|wx.SIMPLE_BORDER)
|
|
self.tree.Bind(wx.EVT_MOTION, self.OnMotion)
|
|
self.tree.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
|
|
|
|
|
|
def GetControl(self):
|
|
return self.tree
|
|
|
|
|
|
def GetStringValue(self):
|
|
if self.value:
|
|
return self.tree.GetItemText(self.value)
|
|
return ""
|
|
|
|
|
|
def OnPopup(self):
|
|
if self.value:
|
|
self.tree.EnsureVisible(self.value)
|
|
self.tree.SelectItem(self.value)
|
|
|
|
|
|
def SetStringValue(self, value):
|
|
# this assumes that item strings are unique...
|
|
root = self.tree.GetRootItem()
|
|
if not root:
|
|
return
|
|
found = self.FindItem(root, value)
|
|
if found:
|
|
self.value = found
|
|
self.tree.SelectItem(found)
|
|
|
|
|
|
def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
|
|
return wx.Size(minWidth, min(200, maxHeight))
|
|
|
|
|
|
# helpers
|
|
|
|
def FindItem(self, parentItem, text):
|
|
item, cookie = self.tree.GetFirstChild(parentItem)
|
|
while item:
|
|
if self.tree.GetItemText(item) == text:
|
|
return item
|
|
if self.tree.ItemHasChildren(item):
|
|
item = self.FindItem(item, text)
|
|
item, cookie = self.tree.GetNextChild(parentItem, cookie)
|
|
return wx.TreeItemId();
|
|
|
|
|
|
def AddItem(self, value, parent=None):
|
|
if not parent:
|
|
root = self.tree.GetRootItem()
|
|
if not root:
|
|
root = self.tree.AddRoot("<hidden root>")
|
|
parent = root
|
|
|
|
item = self.tree.AppendItem(parent, value)
|
|
return item
|
|
|
|
|
|
def OnMotion(self, evt):
|
|
# have the selection follow the mouse, like in a real combobox
|
|
item, flags = self.tree.HitTest(evt.GetPosition())
|
|
if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
|
|
self.tree.SelectItem(item)
|
|
self.curitem = item
|
|
evt.Skip()
|
|
|
|
|
|
def OnLeftDown(self, evt):
|
|
# do the combobox selection
|
|
item, flags = self.tree.HitTest(evt.GetPosition())
|
|
if item and flags & wx.TREE_HITTEST_ONITEMLABEL:
|
|
self.curitem = item
|
|
self.value = item
|
|
self.Dismiss()
|
|
evt.Skip()
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
# Here we subclass wx.combo.ComboCtrl to do some custom popup animation
|
|
|
|
CUSTOM_COMBOBOX_ANIMATION_DURATION = 200
|
|
|
|
class ComboCtrlWithCustomPopupAnim(wx.combo.ComboCtrl):
|
|
def __init__(self, *args, **kw):
|
|
wx.combo.ComboCtrl.__init__(self, *args, **kw)
|
|
self.Bind(wx.EVT_TIMER, self.OnTimer)
|
|
self.aniTimer = wx.Timer(self)
|
|
|
|
|
|
def AnimateShow(self, rect, flags):
|
|
self.aniStart = wx.GetLocalTimeMillis()
|
|
self.aniRect = wx.Rect(*rect)
|
|
self.aniFlags = flags
|
|
|
|
dc = wx.ScreenDC()
|
|
bmp = wx.EmptyBitmap(rect.width, rect.height)
|
|
mdc = wx.MemoryDC(bmp)
|
|
if "wxMac" in wx.PlatformInfo:
|
|
pass
|
|
else:
|
|
mdc.Blit(0, 0, rect.width, rect.height, dc, rect.x, rect.y)
|
|
del mdc
|
|
self.aniBackBitmap = bmp
|
|
|
|
self.aniTimer.Start(10, wx.TIMER_CONTINUOUS)
|
|
self.OnTimer(None)
|
|
return False
|
|
|
|
|
|
def OnTimer(self, evt):
|
|
stopTimer = False
|
|
popup = self.GetPopupControl().GetControl()
|
|
rect = self.aniRect
|
|
dc = wx.ScreenDC()
|
|
|
|
if self.IsPopupWindowState(self.Hidden):
|
|
stopTimer = True
|
|
else:
|
|
pos = wx.GetLocalTimeMillis() - self.aniStart
|
|
if pos < CUSTOM_COMBOBOX_ANIMATION_DURATION:
|
|
# Actual animation happens here
|
|
width = rect.width
|
|
height = rect.height
|
|
|
|
center_x = rect.x + (width/2)
|
|
center_y = rect.y + (height/2)
|
|
|
|
dc.SetPen( wx.BLACK_PEN )
|
|
dc.SetBrush( wx.TRANSPARENT_BRUSH )
|
|
|
|
w = (((pos*256)/CUSTOM_COMBOBOX_ANIMATION_DURATION)*width)/256
|
|
ratio = float(w) / float(width)
|
|
h = int(height * ratio)
|
|
|
|
dc.DrawBitmap( self.aniBackBitmap, rect.x, rect.y )
|
|
dc.DrawRectangle( center_x - w/2, center_y - h/2, w, h )
|
|
else:
|
|
stopTimer = True
|
|
|
|
if stopTimer:
|
|
dc.DrawBitmap( self.aniBackBitmap, rect.x, rect.y )
|
|
popup.Move( (0, 0) )
|
|
self.aniTimer.Stop()
|
|
self.DoShowPopup( rect, self.aniFlags )
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
# FileSelectorCombo displays a dialog instead of a popup control, it
|
|
# also uses a custom bitmap on the combo button.
|
|
|
|
class FileSelectorCombo(wx.combo.ComboCtrl):
|
|
def __init__(self, *args, **kw):
|
|
wx.combo.ComboCtrl.__init__(self, *args, **kw)
|
|
|
|
# make a custom bitmap showing "..."
|
|
bw, bh = 14, 16
|
|
bmp = wx.EmptyBitmap(bw,bh)
|
|
dc = wx.MemoryDC(bmp)
|
|
|
|
# clear to a specific background colour
|
|
bgcolor = wx.Colour(255,254,255)
|
|
dc.SetBackground(wx.Brush(bgcolor))
|
|
dc.Clear()
|
|
|
|
# draw the label onto the bitmap
|
|
label = "..."
|
|
font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT)
|
|
font.SetWeight(wx.FONTWEIGHT_BOLD)
|
|
dc.SetFont(font)
|
|
tw,th = dc.GetTextExtent(label)
|
|
dc.DrawText(label, (bw-tw)/2, (bw-tw)/2)
|
|
del dc
|
|
|
|
# now apply a mask using the bgcolor
|
|
bmp.SetMaskColour(bgcolor)
|
|
|
|
# and tell the ComboCtrl to use it
|
|
self.SetButtonBitmaps(bmp, True)
|
|
|
|
|
|
# Overridden from ComboCtrl, called when the combo button is clicked
|
|
def OnButtonClick(self):
|
|
path = ""
|
|
name = ""
|
|
if self.GetValue():
|
|
path, name = os.path.split(self.GetValue())
|
|
|
|
dlg = wx.FileDialog(self, "Choose File", path, name,
|
|
"All files (*.*)|*.*", wx.FD_OPEN)
|
|
if dlg.ShowModal() == wx.ID_OK:
|
|
self.SetValue(dlg.GetPath())
|
|
dlg.Destroy()
|
|
self.SetFocus()
|
|
|
|
# Overridden from ComboCtrl to avoid assert since there is no ComboPopup
|
|
def DoSetPopupControl(self, popup):
|
|
pass
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
|
|
class TestPanel(wx.Panel):
|
|
def __init__(self, parent, log):
|
|
self.log = log
|
|
wx.Panel.__init__(self, parent, -1)
|
|
|
|
fgs = wx.FlexGridSizer(cols=3, hgap=10, vgap=10)
|
|
|
|
cc = self.MakeLCCombo(log=self.log)
|
|
fgs.Add(cc)
|
|
fgs.Add((10,10))
|
|
fgs.Add(wx.StaticText(self, -1, "wx.ComboCtrl with a ListCtrl popup"))
|
|
|
|
cc = self.MakeLCCombo(style=wx.CB_READONLY)
|
|
fgs.Add(cc)
|
|
fgs.Add((10,10))
|
|
fgs.Add(wx.StaticText(self, -1, " Read-only"))
|
|
|
|
cc = self.MakeLCCombo()
|
|
cc.SetButtonPosition(side=wx.LEFT)
|
|
fgs.Add(cc)
|
|
fgs.Add((10,10))
|
|
fgs.Add(wx.StaticText(self, -1, " Button on the left"))
|
|
|
|
cc = self.MakeLCCombo()
|
|
cc.SetPopupMaxHeight(250)
|
|
fgs.Add(cc)
|
|
fgs.Add((10,10))
|
|
fgs.Add(wx.StaticText(self, -1, " Max height of popup set"))
|
|
|
|
cc = wx.combo.ComboCtrl(self, size=(250,-1))
|
|
tcp = TreeCtrlComboPopup()
|
|
cc.SetPopupControl(tcp)
|
|
fgs.Add(cc)
|
|
fgs.Add((10,10))
|
|
fgs.Add(wx.StaticText(self, -1, "TreeCtrl popup"))
|
|
# add some items to the tree
|
|
for i in range(5):
|
|
item = tcp.AddItem('Item %d' % (i+1))
|
|
for j in range(15):
|
|
tcp.AddItem('Subitem %d-%d' % (i+1, j+1), parent=item)
|
|
|
|
cc = ComboCtrlWithCustomPopupAnim(self, size=(250, -1))
|
|
popup = ListCtrlComboPopup()
|
|
cc.SetPopupMaxHeight(150)
|
|
cc.SetPopupControl(popup)
|
|
fgs.Add(cc)
|
|
fgs.Add((10,10))
|
|
fgs.Add(wx.StaticText(self, -1, "Custom popup animation"))
|
|
for word in "How cool was that!? Way COOL!".split():
|
|
popup.AddItem(word)
|
|
if "wxMac" in wx.PlatformInfo:
|
|
cc.SetValue("Sorry, animation not working yet on Mac")
|
|
|
|
|
|
cc = FileSelectorCombo(self, size=(250, -1))
|
|
fgs.Add(cc)
|
|
fgs.Add((10,10))
|
|
fgs.Add(wx.StaticText(self, -1, "Custom popup action, and custom button bitmap"))
|
|
|
|
box = wx.BoxSizer()
|
|
box.Add(fgs, 1, wx.EXPAND|wx.ALL, 20)
|
|
self.SetSizer(box)
|
|
|
|
|
|
def MakeLCCombo(self, log=None, style=0):
|
|
# Create a ComboCtrl
|
|
cc = wx.combo.ComboCtrl(self, style=style, size=(250,-1))
|
|
|
|
# Create a Popup
|
|
popup = ListCtrlComboPopup(log)
|
|
|
|
# Associate them with each other. This also triggers the
|
|
# creation of the ListCtrl.
|
|
cc.SetPopupControl(popup)
|
|
|
|
# Add some items to the listctrl.
|
|
for x in range(75):
|
|
popup.AddItem("Item-%02d" % x)
|
|
|
|
return cc
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
def runTest(frame, nb, log):
|
|
win = TestPanel(nb, log)
|
|
return win
|
|
|
|
#----------------------------------------------------------------------
|
|
|
|
|
|
|
|
overview = """<html><body>
|
|
<h2><center>wx.combo.ComboCtrl</center></h2>
|
|
|
|
A combo control is a generic combobox that allows a totally custom
|
|
popup. In addition it has other customization features. For instance,
|
|
position and size of the dropdown button can be changed.
|
|
|
|
</body></html>
|
|
"""
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
import sys,os
|
|
import run
|
|
run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
|