Added Throbber class from Cliff Wells
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/WX_2_4_BRANCH@18185 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
@@ -2,7 +2,7 @@ CHANGES.txt for wxPython
|
||||
|
||||
----------------------------------------------------------------------
|
||||
|
||||
2.3.3.2
|
||||
2.3.4
|
||||
-------
|
||||
Updated XRCed and wxTimeCtrl contribs.
|
||||
|
||||
@@ -50,6 +50,8 @@ is data waiting to be read from the pipe.
|
||||
|
||||
Fixed method name clash in wxIEHtmlWin, renamed Refresh to RefreshPage.
|
||||
|
||||
Added Throbber from Cliff Wells to the library and the demo.
|
||||
|
||||
|
||||
|
||||
|
||||
|
@@ -26,7 +26,7 @@ _treeList = [
|
||||
# new stuff
|
||||
('New since last release', [
|
||||
'wxRadioButton',
|
||||
|
||||
'Throbber',
|
||||
]),
|
||||
|
||||
# managed windows == things with a caption you can close
|
||||
@@ -80,13 +80,13 @@ _treeList = [
|
||||
'wxRadioBox',
|
||||
'wxRadioButton',
|
||||
'wxSashWindow',
|
||||
'wxSlider',
|
||||
'wxScrolledWindow',
|
||||
'wxSplitterWindow',
|
||||
'wxSlider',
|
||||
'wxSpinButton',
|
||||
'wxSpinCtrl',
|
||||
'wxStaticText',
|
||||
'wxSplitterWindow',
|
||||
'wxStaticBitmap',
|
||||
'wxStaticText',
|
||||
'wxStatusBar',
|
||||
'wxTextCtrl',
|
||||
'wxToggleButton',
|
||||
@@ -97,6 +97,8 @@ _treeList = [
|
||||
|
||||
# controls coming from other librairies
|
||||
('More Windows/Controls', [
|
||||
#'wxFloatBar', deprecated
|
||||
#'wxMVCTree', deprecated
|
||||
'ColourSelect',
|
||||
'ContextHelp',
|
||||
'FancyText',
|
||||
@@ -111,12 +113,10 @@ _treeList = [
|
||||
'wxDynamicSashWindow',
|
||||
'wxEditableListBox',
|
||||
'wxEditor',
|
||||
#'wxFloatBar', deprecated
|
||||
'wxHtmlWindow',
|
||||
'wxIEHtmlWin',
|
||||
'wxLEDNumberCtrl',
|
||||
'wxMimeTypesManager',
|
||||
#'wxMVCTree', deprecated
|
||||
'wxRightTextCtrl',
|
||||
'wxStyledTextCtrl_1',
|
||||
'wxStyledTextCtrl_2',
|
||||
@@ -155,6 +155,7 @@ _treeList = [
|
||||
|
||||
# Images
|
||||
('Images', [
|
||||
'Throbber',
|
||||
'wxDragImage',
|
||||
'wxImage',
|
||||
'wxImageFromStream',
|
||||
@@ -169,6 +170,7 @@ _treeList = [
|
||||
'DrawXXXList',
|
||||
'FontEnumerator',
|
||||
'PrintFramework',
|
||||
'Throbber',
|
||||
'Unicode',
|
||||
'wxFileHistory',
|
||||
'wxJoystick',
|
||||
@@ -204,7 +206,8 @@ class MyLog(wxPyLog):
|
||||
if self.logTime:
|
||||
message = time.strftime("%X", time.localtime(timeStamp)) + \
|
||||
": " + message
|
||||
self.tc.AppendText(message + '\n')
|
||||
if self.tc:
|
||||
self.tc.AppendText(message + '\n')
|
||||
|
||||
|
||||
class MyTP(wxPyTipProvider):
|
||||
|
171
wxPython/demo/Throbber.py
Normal file
171
wxPython/demo/Throbber.py
Normal file
@@ -0,0 +1,171 @@
|
||||
#
|
||||
# Throbber.py - Cliff Wells <clifford.wells@attbi.com>
|
||||
#
|
||||
|
||||
from wxPython.wx import *
|
||||
from wxPython.lib.rcsizer import RowColSizer
|
||||
from wxPython.lib.throbber import Throbber, __doc__ as docString
|
||||
import throbImages # this was created using a modified version of img2py
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
class TestPanel(wxPanel):
|
||||
def __init__(self, parent, log):
|
||||
wxPanel.__init__(self, parent, -1)
|
||||
self.log = log
|
||||
|
||||
# create the throbbers
|
||||
self.throbbers = {
|
||||
'plain': { 'throbber': None,
|
||||
'text': "Plain throbber." },
|
||||
'reverse': { 'throbber': None,
|
||||
'text': "This throbber runs in reverse and faster." },
|
||||
'autoreverse': { 'throbber': None,
|
||||
'text': "This throbber switches direction." },
|
||||
'label': { 'throbber': None,
|
||||
'text': "With a label." },
|
||||
'overlay': { 'throbber': None,
|
||||
'text': "With an overlayed image." },
|
||||
'overlay+text': { 'throbber': None,
|
||||
'text': "With a label and an overlayed image." },
|
||||
}
|
||||
|
||||
images = [throbImages.catalog[i].getBitmap()
|
||||
for i in throbImages.index
|
||||
if i not in ['eclouds', 'logo']]
|
||||
|
||||
self.throbbers['plain']['throbber'] = Throbber(self, -1,
|
||||
images, #size=(36, 36),
|
||||
frameDelay = 0.1)
|
||||
self.throbbers['reverse']['throbber'] = Throbber(self, -1, images, #size=(36, 36),
|
||||
frameDelay = 0.07)
|
||||
self.throbbers['reverse']['throbber'].Reverse()
|
||||
self.throbbers['autoreverse']['throbber'] = Throbber(self, -1,
|
||||
images, #size=(36, 36),
|
||||
frameDelay = 0.1,
|
||||
reverse = true)
|
||||
self.throbbers['autoreverse']['throbber'].sequence.append(0)
|
||||
self.throbbers['label']['throbber'] = Throbber(self, -1,
|
||||
images, #size=(36, 36),
|
||||
frameDelay = 0.1,
|
||||
label = 'Label')
|
||||
self.throbbers['label']['throbber'].SetFont(wxFont(pointSize = 10,
|
||||
family = wxDEFAULT,
|
||||
style = wxNORMAL,
|
||||
weight = wxBOLD))
|
||||
self.throbbers['overlay']['throbber'] = Throbber(self, -1,
|
||||
images, #size=(36, 36),
|
||||
frameDelay = 0.1,
|
||||
overlay = throbImages.catalog['logo'].getBitmap())
|
||||
self.throbbers['overlay+text']['throbber'] = Throbber(self, -1,
|
||||
images, #size=(36, 36),
|
||||
frameDelay = 0.1,
|
||||
overlay = throbImages.catalog['logo'].getBitmap(),
|
||||
label = "Python!")
|
||||
self.throbbers['overlay+text']['throbber'].SetFont(wxFont(pointSize = 8,
|
||||
family = wxDEFAULT,
|
||||
style = wxNORMAL,
|
||||
weight = wxBOLD))
|
||||
|
||||
|
||||
# this throbber is created using a single, composite image
|
||||
self.otherThrobber = Throbber(self, -1,
|
||||
throbImages.catalog['eclouds'].getBitmap(), #size=(48, 48),
|
||||
frameDelay = 0.15,
|
||||
frames = 12,
|
||||
frameWidth = 48,
|
||||
label = "Stop")
|
||||
|
||||
|
||||
EVT_LEFT_DOWN(self.otherThrobber, self.OnClickThrobber)
|
||||
|
||||
staticBox = wxStaticBox(self, -1, "")
|
||||
box = wxStaticBoxSizer(staticBox)
|
||||
sizer = RowColSizer()
|
||||
box.Add(sizer, 1, wxEXPAND|wxALL, 5)
|
||||
sizer.AddGrowableCol(1)
|
||||
|
||||
sizer.Add(self.otherThrobber, row = 0, col = 2, rowspan = 4, flag = wxALIGN_CENTER_VERTICAL)
|
||||
|
||||
row = 2
|
||||
# use a list so we can keep our order
|
||||
for t in ['plain', 'reverse', 'autoreverse', 'label', 'overlay', 'overlay+text']:
|
||||
sizer.Add(self.throbbers[t]['throbber'], row = row, col = 0, flag = wxALIGN_CENTER|wxALL, border=2)
|
||||
sizer.Add(wxStaticText(self, -1, self.throbbers[t]['text']), row = row, col = 1,
|
||||
flag = wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT)
|
||||
row += 1
|
||||
|
||||
# start and stop buttons
|
||||
startButton = wxButton(self, -1, "Start")
|
||||
EVT_BUTTON(self, startButton.GetId(), self.OnStartAnimation)
|
||||
stopButton = wxButton(self, -1, "Stop")
|
||||
EVT_BUTTON(self, stopButton.GetId(), self.OnStopAnimation)
|
||||
|
||||
buttonBox = wxBoxSizer(wxHORIZONTAL)
|
||||
buttonBox.AddMany([
|
||||
(startButton, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5),
|
||||
(stopButton, 0, wxALIGN_CENTER_HORIZONTAL | wxALL, 5),
|
||||
])
|
||||
|
||||
sizer.Add(buttonBox,
|
||||
row = len(self.throbbers) + 3,
|
||||
col = 0,
|
||||
colspan = 3,
|
||||
flag = wxALIGN_CENTER)
|
||||
|
||||
self.SetSizer(box)
|
||||
self.SetAutoLayout(true)
|
||||
self.Layout()
|
||||
sizer.SetSizeHints(self)
|
||||
sizer.Fit(self)
|
||||
|
||||
for t in self.throbbers:
|
||||
self.throbbers[t]['throbber'].Start()
|
||||
self.otherThrobber.Start()
|
||||
self.otherThrobber.Reverse()
|
||||
|
||||
EVT_WINDOW_DESTROY(self, self.OnDestroy)
|
||||
|
||||
def OnDestroy(self, event):
|
||||
self.log.write("got it")
|
||||
event.Skip()
|
||||
|
||||
def OnStartAnimation(self, event):
|
||||
for t in self.throbbers:
|
||||
self.throbbers[t]['throbber'].Start()
|
||||
|
||||
def OnStopAnimation(self, event):
|
||||
for t in self.throbbers:
|
||||
self.throbbers[t]['throbber'].Rest()
|
||||
|
||||
def OnClickThrobber(self, event):
|
||||
if self.otherThrobber.Running():
|
||||
self.otherThrobber.Rest()
|
||||
self.otherThrobber.SetLabel("Start")
|
||||
else:
|
||||
self.otherThrobber.Start()
|
||||
self.otherThrobber.SetLabel("Stop")
|
||||
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
def runTest(frame, nb, log):
|
||||
win = TestPanel(nb, log)
|
||||
return win
|
||||
|
||||
#----------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
overview = """<html><body>
|
||||
<h4><center>Throbber</center></h4>
|
||||
<p>%s</p>
|
||||
</body></html>
|
||||
""" % docString
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys,os
|
||||
import run
|
||||
run.main(['', os.path.basename(sys.argv[0])])
|
5244
wxPython/demo/throbImages.py
Normal file
5244
wxPython/demo/throbImages.py
Normal file
File diff suppressed because it is too large
Load Diff
252
wxPython/wxPython/lib/throbber.py
Normal file
252
wxPython/wxPython/lib/throbber.py
Normal file
@@ -0,0 +1,252 @@
|
||||
"""
|
||||
A throbber displays an animated image that can be
|
||||
started, stopped, reversed, etc. Useful for showing
|
||||
an ongoing process (like most web browsers use) or
|
||||
simply for adding eye-candy to an application.
|
||||
|
||||
Throbbers run in a separate thread so normal application
|
||||
processing can continue unencumbered.
|
||||
"""
|
||||
|
||||
#
|
||||
# throbber.py - Cliff Wells <clifford.wells@attbi.com>
|
||||
#
|
||||
# Thanks to Harald Massa <harald.massa@suedvers.de> for
|
||||
# suggestions and sample code.
|
||||
#
|
||||
# $Id$
|
||||
#
|
||||
|
||||
import threading, os
|
||||
##os.putenv('LANG', 'C') # for running on GTK2
|
||||
from wxPython.wx import *
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
wxEVT_UPDATE_THROBBER = wxNewEventType()
|
||||
def EVT_UPDATE_THROBBER(win, func):
|
||||
win.Connect(-1, -1, wxEVT_UPDATE_THROBBER, func)
|
||||
|
||||
class UpdateThrobberEvent(wxPyEvent):
|
||||
def __init__(self):
|
||||
wxPyEvent.__init__(self)
|
||||
self.SetEventType(wxEVT_UPDATE_THROBBER)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
class Throbber(wxPanel):
|
||||
"""
|
||||
The first argument is either the name of a file that will be split into frames
|
||||
(a composite image) or a list of strings of image names that will be treated
|
||||
as individual frames. If a single (composite) image is given, then additional
|
||||
information must be provided: the number of frames in the image and the width
|
||||
of each frame. The first frame is treated as the "at rest" frame (it is not
|
||||
shown during animation, but only when Throbber.Rest() is called.
|
||||
A second, single image may be optionally specified to overlay on top of the
|
||||
animation. A label may also be specified to show on top of the animation.
|
||||
"""
|
||||
def __init__(self, parent, id,
|
||||
bitmap, # single (composite) bitmap or list of bitmaps
|
||||
pos = wxDefaultPosition,
|
||||
size = wxDefaultSize,
|
||||
frameDelay = 0.1,# time between frames
|
||||
frames = 0, # number of frames (only necessary for composite image)
|
||||
frameWidth = 0, # width of each frame (only necessary for composite image)
|
||||
label = None, # optional text to be displayed
|
||||
overlay = None, # optional image to overlay on animation
|
||||
reverse = 0, # reverse direction at end of animation
|
||||
style = 0, # window style
|
||||
name = "throbber"):
|
||||
wxPanel.__init__(self, parent, id, pos, size, style, name)
|
||||
self.name = name
|
||||
self.label = label
|
||||
_seqTypes = (type([]), type(()))
|
||||
|
||||
# set size, guessing if necessary
|
||||
width, height = size
|
||||
if width == -1:
|
||||
if type(bitmap) in _seqTypes:
|
||||
width = bitmap[0].GetWidth()
|
||||
else:
|
||||
if frameWidth:
|
||||
width = frameWidth
|
||||
if height == -1:
|
||||
if type(bitmap) in _seqTypes:
|
||||
height = bitmap[0].GetHeight()
|
||||
else:
|
||||
height = bitmap.GetHeight()
|
||||
self.width, self.height = width, height
|
||||
|
||||
# double check it
|
||||
assert width != -1 and height != -1, "Unable to guess size"
|
||||
|
||||
if label:
|
||||
extentX, extentY = self.GetTextExtent(label)
|
||||
self.labelX = (width - extentX)/2
|
||||
self.labelY = (height - extentY)/2
|
||||
self.frameDelay = frameDelay
|
||||
self.current = 0
|
||||
self.direction = 1
|
||||
self.autoReverse = reverse
|
||||
self.overlay = overlay
|
||||
if overlay is not None:
|
||||
self.overlay = overlay
|
||||
self.overlayX = (width - self.overlay.GetWidth()) / 2
|
||||
self.overlayY = (height - self.overlay.GetHeight()) / 2
|
||||
self.showOverlay = overlay is not None
|
||||
self.showLabel = label is not None
|
||||
|
||||
# do we have a sequence of images?
|
||||
if type(bitmap) in _seqTypes:
|
||||
self.submaps = bitmap
|
||||
self.frames = len(self.submaps)
|
||||
# or a composite image that needs to be split?
|
||||
else:
|
||||
self.frames = frames
|
||||
self.submaps = []
|
||||
for chunk in range(frames):
|
||||
rect = (chunk * frameWidth, 0, width, height)
|
||||
self.submaps.append(bitmap.GetSubBitmap(rect))
|
||||
|
||||
# self.sequence can be changed, but it's not recommended doing it
|
||||
# while the throbber is running. self.sequence[0] should always
|
||||
# refer to whatever frame is to be shown when 'resting' and be sure
|
||||
# that no item in self.sequence >= self.frames or < 0!!!
|
||||
self.sequence = range(self.frames)
|
||||
|
||||
self.SetClientSize((width, height))
|
||||
|
||||
EVT_PAINT(self, self.OnPaint)
|
||||
EVT_UPDATE_THROBBER(self, self.Rotate)
|
||||
EVT_WINDOW_DESTROY(self, self.OnDestroyWindow)
|
||||
|
||||
self.event = threading.Event()
|
||||
self.event.set() # we start out in the "resting" state
|
||||
|
||||
|
||||
def OnDestroyWindow(self, event):
|
||||
# this is currently broken due to a bug in wxWindows... hopefully
|
||||
# it'll be fixed soon. Meanwhile be sure to explicitly call Stop()
|
||||
# before the throbber is destroyed.
|
||||
self.Stop()
|
||||
event.Skip()
|
||||
|
||||
|
||||
def Draw(self, dc):
|
||||
dc.DrawBitmap(self.submaps[self.sequence[self.current]], 0, 0, true)
|
||||
if self.overlay and self.showOverlay:
|
||||
dc.DrawBitmap(self.overlay, self.overlayX, self.overlayY, true)
|
||||
if self.label and self.showLabel:
|
||||
dc.DrawText(self.label, self.labelX, self.labelY)
|
||||
dc.SetTextForeground(wxWHITE)
|
||||
dc.DrawText(self.label, self.labelX-1, self.labelY-1)
|
||||
|
||||
|
||||
def OnPaint(self, event):
|
||||
self.Draw(wxPaintDC(self))
|
||||
event.Skip()
|
||||
|
||||
|
||||
def UpdateThread(self):
|
||||
try:
|
||||
while not self.event.isSet():
|
||||
wxPostEvent(self, UpdateThrobberEvent())
|
||||
self.event.wait(self.frameDelay)
|
||||
except wxPyDeadObjectError: # BUG: we were destroyed
|
||||
return
|
||||
|
||||
|
||||
def Rotate(self, event):
|
||||
if self.event.isSet():
|
||||
return
|
||||
self.current += self.direction
|
||||
if self.current >= len(self.sequence):
|
||||
if self.autoReverse:
|
||||
self.Reverse()
|
||||
self.current = len(self.sequence) - 1
|
||||
else:
|
||||
self.current = 1
|
||||
if self.current < 1:
|
||||
if self.autoReverse:
|
||||
self.Reverse()
|
||||
self.current = 1
|
||||
else:
|
||||
self.current = len(self.sequence) - 1
|
||||
self.Draw(wxClientDC(self))
|
||||
|
||||
|
||||
# --------- public methods ---------
|
||||
def SetFont(self, font):
|
||||
"""Set the font for the label"""
|
||||
wxPanel.SetFont(self, font)
|
||||
self.SetLabel(self.label)
|
||||
self.Draw(wxClientDC(self))
|
||||
|
||||
|
||||
def Rest(self):
|
||||
"""Stop the animation and return to frame 0"""
|
||||
self.Stop()
|
||||
self.current = 0
|
||||
self.Draw(wxClientDC(self))
|
||||
|
||||
|
||||
def Reverse(self):
|
||||
"""Change the direction of the animation"""
|
||||
self.direction = -self.direction
|
||||
|
||||
|
||||
def Running(self):
|
||||
"""Returns true if the animation is running"""
|
||||
return not self.event.isSet()
|
||||
|
||||
|
||||
def Start(self):
|
||||
"""Start the animation"""
|
||||
if not self.Running():
|
||||
self.event.clear()
|
||||
thread = threading.Thread(target = self.UpdateThread,
|
||||
name = "%s-thread" % self.name)
|
||||
thread.start()
|
||||
|
||||
|
||||
def Stop(self):
|
||||
"""Stop the animation"""
|
||||
self.event.set()
|
||||
|
||||
|
||||
def SetFrameDelay(self, frameDelay = 0.05):
|
||||
"""Delay between each frame"""
|
||||
self.frameDelay = frameDelay
|
||||
|
||||
|
||||
def ToggleOverlay(self, state = None):
|
||||
"""Toggle the overlay image"""
|
||||
if state is None:
|
||||
self.showOverlay = not self.showOverlay
|
||||
else:
|
||||
self.showOverlay = state
|
||||
self.Draw(wxClientDC(self))
|
||||
|
||||
|
||||
def ToggleLabel(self, state = None):
|
||||
"""Toggle the label"""
|
||||
if state is None:
|
||||
self.showLabel = not self.showLabel
|
||||
else:
|
||||
self.showLabel = state
|
||||
self.Draw(wxClientDC(self))
|
||||
|
||||
|
||||
def SetLabel(self, label):
|
||||
"""Change the text of the label"""
|
||||
self.label = label
|
||||
if label:
|
||||
extentX, extentY = self.GetTextExtent(label)
|
||||
self.labelX = (self.width - extentX)/2
|
||||
self.labelY = (self.height - extentY)/2
|
||||
self.Draw(wxClientDC(self))
|
||||
|
||||
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
|
Reference in New Issue
Block a user