1
###############################################################################
2
# Name: ed_statbar.py #
3
# Purpose: Custom statusbar with builtin progress indicator #
4
# Author: Cody Precord <cprecord@editra.org> #
5
# Copyright: (c) 2008 Cody Precord <staff@editra.org> #
6
# License: wxWindows License #
7
###############################################################################
10
Custom StatusBar for Editra that contains a progress bar that responds to
11
messages from ed_msg to display progress of different actions.
13
@summary: Editra's StatusBar class
17
__author__ = "Cody Precord <cprecord@editra.org>"
18
__svnid__ = "$Id: ed_statbar.py 70229 2012-01-01 01:27:10Z CJP $"
19
__revision__ = "$Revision: 70229 $"
21
#--------------------------------------------------------------------------#
31
from syntax.synglob import GetDescriptionFromId
32
from eclib import ProgressStatusBar, EncodingDialog
33
from extern.decorlib import anythread
35
#--------------------------------------------------------------------------#
39
#--------------------------------------------------------------------------#
41
class EdStatBar(ProgressStatusBar):
42
"""Custom status bar that handles dynamic field width adjustment and
43
automatic expiration of status messages.
46
ID_CLEANUP_TIMER = wx.NewId()
47
def __init__(self, parent):
48
super(EdStatBar, self).__init__(parent, style=wx.ST_SIZEGRIP)
51
self._pid = parent.GetId() # Save parents id for filtering msgs
53
self._cleanup_timer = wx.Timer(self, EdStatBar.ID_CLEANUP_TIMER)
54
self._eolmenu = wx.Menu()
56
self._log = wx.GetApp().GetLog()
59
self.SetFieldsCount(6) # Info, vi stuff, line/progress
60
self.SetStatusWidths([-1, 90, 40, 40, 40, 155])
61
self._eolmenu.Append(ed_glob.ID_EOL_MAC, u"CR",
62
_("Change line endings to %s") % u"CR",
64
self._eolmenu.Append(ed_glob.ID_EOL_WIN, u"CRLF",
65
_("Change line endings to %s") % u"CRLF",
67
self._eolmenu.Append(ed_glob.ID_EOL_UNIX, u"LF",
68
_("Change line endings to %s") % u"LF",
72
self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy, self)
73
self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
74
self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
75
self.Bind(wx.EVT_TIMER, self.OnExpireMessage,
76
id=EdStatBar.ID_CLEANUP_TIMER)
79
ed_msg.Subscribe(self.OnProgress, ed_msg.EDMSG_PROGRESS_SHOW)
80
ed_msg.Subscribe(self.OnProgress, ed_msg.EDMSG_PROGRESS_STATE)
81
ed_msg.Subscribe(self.OnUpdateText, ed_msg.EDMSG_UI_SB_TXT)
82
ed_msg.Subscribe(self.OnUpdateDoc, ed_msg.EDMSG_UI_NB_CHANGED)
83
ed_msg.Subscribe(self.OnUpdateDoc, ed_msg.EDMSG_FILE_SAVED)
84
ed_msg.Subscribe(self.OnUpdateDoc, ed_msg.EDMSG_FILE_OPENED)
85
ed_msg.Subscribe(self.OnUpdateDoc, ed_msg.EDMSG_UI_STC_LEXER)
87
def OnDestroy(self, evt):
88
"""Unsubscribe from messages"""
90
self._lexmenu.Destroy()
92
self._eolmenu.Destroy()
93
if evt.GetId() == self.GetId():
94
ed_msg.Unsubscribe(self.OnProgress)
95
ed_msg.Unsubscribe(self.OnUpdateText)
96
ed_msg.Unsubscribe(self.OnUpdateDoc)
99
def __SetStatusText(self, txt, field):
100
"""Safe method to use for setting status text with CallAfter.
106
super(EdStatBar, self).SetStatusText(txt, field)
107
self.AdjustFieldWidths()
109
if field == ed_glob.SB_INFO and txt != u'':
110
# Start the expiration countdown
111
if self._cleanup_timer.IsRunning():
112
self._cleanup_timer.Stop()
113
self._cleanup_timer.Start(10000, True)
114
except wx.PyDeadObjectError, wx.PyAssertionError:
115
# Getting some odd assertion errors on wxMac so just trap
116
# and ignore them for now
117
# glyphCount == (text.length()+1)" failed at graphics.cpp(2048)
118
# in GetPartialTextExtents()
120
except TypeError, err:
121
self._log("[edstatbar][err] Bad status message: %s" % str(txt))
122
self._log("[edstatbar][err] %s" % err)
124
def AdjustFieldWidths(self):
125
"""Adjust each field width of status bar basing on the field text
130
# Calculate required widths
131
# NOTE: Order of fields is important
132
for field in [ed_glob.SB_BUFF,
137
width = self.GetTextExtent(self.GetStatusText(field))[0] + 20
146
# Only update if there are changes
147
if widths != self._widths:
148
self._widths = widths
149
self.SetStatusWidths(self._widths)
151
def GetMainWindow(self):
152
"""Method required for L{ed_msg.mwcontext}"""
153
return self.TopLevelParent
155
def OnExpireMessage(self, evt):
156
"""Handle Expiring the status message when the oneshot timer
157
tells us it has expired.
160
if evt.GetId() == EdStatBar.ID_CLEANUP_TIMER:
161
wx.CallAfter(self.__SetStatusText, u'', ed_glob.SB_INFO)
165
def OnLeftDClick(self, evt):
166
"""Handlers mouse left double click on status bar
167
@param evt: wx.MouseEvent
168
@note: Assumes parent is MainWindow instance
171
pt = evt.GetPosition()
172
if self.GetFieldRect(ed_glob.SB_ROWCOL).Contains(pt):
173
mw = self.GetParent()
174
mpane = mw.GetEditPane()
175
mpane.ShowCommandControl(ed_glob.ID_GOTO_LINE)
179
def OnLeftUp(self, evt):
180
"""Handle left clicks on the status bar
181
@param evt: wx.MouseEvent
184
pt = evt.GetPosition()
185
if self.GetFieldRect(ed_glob.SB_EOL).Contains(pt):
186
rect = self.GetFieldRect(ed_glob.SB_EOL)
187
self.PopupMenu(self._eolmenu, (rect.x, rect.y))
188
elif self.GetFieldRect(ed_glob.SB_ENCODING).Contains(pt):
189
nb = self.GetTopLevelParent().GetNotebook()
190
buff = nb.GetCurrentCtrl()
191
dlg = EncodingDialog(nb,
192
msg=_("Change the encoding of the current document."),
193
title=_("Change Encoding"),
194
default=buff.GetEncoding())
195
bmp = wx.ArtProvider.GetBitmap(str(ed_glob.ID_DOCPROP),
201
# TODO: should add EdFile callbacks for modification events instead
202
# of using explicit statusbar refresh.
203
if dlg.ShowModal() == wx.ID_OK:
204
buff.SetEncoding(dlg.GetEncoding())
207
# NOTE: Got an error report about a PyDeadObject error here. The
208
# error does not make any sense since the dialog is not
209
# destroyed or deleted by anything before this. Add validity
210
# check to ensure reference is still valid.
213
elif self.GetFieldRect(ed_glob.SB_LEXER).Contains(pt):
214
# Change Lexer popup menu
216
self._lexmenu.Destroy()
217
self._lexmenu = wx.Menu()
218
ed_menu.EdMenuBar.PopulateLexerMenu(self._lexmenu)
219
rect = self.GetFieldRect(ed_glob.SB_LEXER)
220
self.PopupMenu(self._lexmenu, (rect.x, rect.y))
224
def OnProgress(self, msg):
225
"""Set the progress bar's state
226
@param msg: Message Object
229
mdata = msg.GetData()
230
# Don't do anything if the message is not for this frame
231
if self._pid != mdata[0]:
234
mtype = msg.GetType()
235
if mtype == ed_msg.EDMSG_PROGRESS_STATE:
236
# May be called from non gui thread so don't do anything with
238
self.SetProgress(mdata[1])
239
self.range = mdata[2]
240
if sum(mdata[1:]) == 0:
242
elif mtype == ed_msg.EDMSG_PROGRESS_SHOW:
246
# TODO: findout where stray stop event is coming from...
250
def OnUpdateDoc(self, msg):
251
"""Update document related fields
252
@param msg: Message Object
256
if msg.GetType() == ed_msg.EDMSG_UI_NB_CHANGED:
257
wx.CallAfter(self.__SetStatusText, u'', ed_glob.SB_INFO)
260
def DoUpdateText(self, msg):
261
"""Thread safe update of status text. Proxy for OnUpdateText because
262
pubsub seems to have issues with passing decorator methods for
264
@param msg: Message Object
267
# Only process if this status bar is in the active window and shown
268
parent = self.GetTopLevelParent()
269
if (parent.IsActive() or wx.GetApp().GetTopWindow() == parent):
270
field, txt = msg.GetData()
272
wx.CallAfter(self.__SetStatusText, txt, field)
274
def OnUpdateText(self, msg):
275
"""Update the status bar text based on the received message
276
@param msg: Message Object
279
self.DoUpdateText(msg)
281
def PushStatusText(self, txt, field):
282
"""Set the status text
283
@param txt: Text to put in bar
287
wx.CallAfter(self.__SetStatusText, txt, field)
289
def SetStatusText(self, txt, field):
290
"""Set the status text
291
@param txt: Text to put in bar
295
wx.CallAfter(self.__SetStatusText, txt, field)
297
def UpdateFields(self):
298
"""Update document fields based on the currently selected
299
document in the editor.
300
@postcondition: encoding and lexer fields are updated
301
@todo: update when readonly hooks are implemented
304
nb = self.GetParent().GetNotebook()
309
cbuff = nb.GetCurrentCtrl()
310
doc = cbuff.GetDocument()
311
wx.CallAfter(self.__SetStatusText, doc.GetEncoding(),
313
wx.CallAfter(self.__SetStatusText,
314
GetDescriptionFromId(cbuff.GetLangId()),
317
eol = { wx.stc.STC_EOL_CR : u"CR",
318
wx.stc.STC_EOL_LF : u"LF",
319
wx.stc.STC_EOL_CRLF : u"CRLF" }
320
wx.CallAfter(self.__SetStatusText,
321
eol[cbuff.GetEOLMode()],
324
except wx.PyDeadObjectError:
325
# May be called asyncronasly after the control is already dead