~brian-sidebotham/wxwidgets-cmake/wxpython-2.9.4

« back to all changes in this revision

Viewing changes to wxPython/wx/tools/Editra/src/ed_statbar.py

  • Committer: Brian Sidebotham
  • Date: 2013-08-03 14:30:08 UTC
  • Revision ID: brian.sidebotham@gmail.com-20130803143008-c7806tkych1tp6fc
Initial import into Bazaar

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
###############################################################################
 
8
 
 
9
"""
 
10
Custom StatusBar for Editra that contains a progress bar that responds to
 
11
messages from ed_msg to display progress of different actions.
 
12
 
 
13
@summary: Editra's StatusBar class
 
14
 
 
15
"""
 
16
 
 
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 $"
 
20
 
 
21
#--------------------------------------------------------------------------#
 
22
# Imports
 
23
import wx
 
24
import wx.stc
 
25
 
 
26
# Editra Libraries
 
27
import ed_glob
 
28
import util
 
29
import ed_msg
 
30
import ed_menu
 
31
from syntax.synglob import GetDescriptionFromId
 
32
from eclib import ProgressStatusBar, EncodingDialog
 
33
from extern.decorlib import anythread
 
34
 
 
35
#--------------------------------------------------------------------------#
 
36
 
 
37
_ = wx.GetTranslation
 
38
 
 
39
#--------------------------------------------------------------------------#
 
40
 
 
41
class EdStatBar(ProgressStatusBar):
 
42
    """Custom status bar that handles dynamic field width adjustment and
 
43
    automatic expiration of status messages.
 
44
 
 
45
    """
 
46
    ID_CLEANUP_TIMER = wx.NewId()
 
47
    def __init__(self, parent):
 
48
        super(EdStatBar, self).__init__(parent, style=wx.ST_SIZEGRIP)
 
49
 
 
50
        # Attributes
 
51
        self._pid = parent.GetId() # Save parents id for filtering msgs
 
52
        self._widths = list()
 
53
        self._cleanup_timer = wx.Timer(self, EdStatBar.ID_CLEANUP_TIMER)
 
54
        self._eolmenu = wx.Menu()
 
55
        self._lexmenu = None
 
56
        self._log = wx.GetApp().GetLog()
 
57
 
 
58
        # Setup
 
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",
 
63
                             kind=wx.ITEM_CHECK)
 
64
        self._eolmenu.Append(ed_glob.ID_EOL_WIN, u"CRLF",
 
65
                             _("Change line endings to %s") % u"CRLF",
 
66
                             kind=wx.ITEM_CHECK)
 
67
        self._eolmenu.Append(ed_glob.ID_EOL_UNIX, u"LF",
 
68
                             _("Change line endings to %s") % u"LF",
 
69
                             kind=wx.ITEM_CHECK)
 
70
 
 
71
        # Event Handlers
 
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)
 
77
 
 
78
        # Messages
 
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)
 
86
 
 
87
    def OnDestroy(self, evt):
 
88
        """Unsubscribe from messages"""
 
89
        if self._lexmenu:
 
90
            self._lexmenu.Destroy()
 
91
        if self._eolmenu:
 
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)
 
97
        evt.Skip()
 
98
 
 
99
    def __SetStatusText(self, txt, field):
 
100
        """Safe method to use for setting status text with CallAfter.
 
101
        @param txt: string
 
102
        @param field: int
 
103
 
 
104
        """
 
105
        try:
 
106
            super(EdStatBar, self).SetStatusText(txt, field)
 
107
            self.AdjustFieldWidths()
 
108
 
 
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()
 
119
            pass
 
120
        except TypeError, err:
 
121
            self._log("[edstatbar][err] Bad status message: %s" % str(txt))
 
122
            self._log("[edstatbar][err] %s" % err)
 
123
 
 
124
    def AdjustFieldWidths(self):
 
125
        """Adjust each field width of status bar basing on the field text
 
126
        @return: None
 
127
 
 
128
        """
 
129
        widths = [-1]
 
130
        # Calculate required widths
 
131
        # NOTE: Order of fields is important
 
132
        for field in [ed_glob.SB_BUFF,
 
133
                      ed_glob.SB_LEXER,
 
134
                      ed_glob.SB_ENCODING,
 
135
                      ed_glob.SB_EOL,
 
136
                      ed_glob.SB_ROWCOL]:
 
137
            width = self.GetTextExtent(self.GetStatusText(field))[0] + 20
 
138
            if width == 20:
 
139
                width = 0
 
140
            widths.append(width)
 
141
 
 
142
        # Adjust widths
 
143
        if widths[-1] < 155:
 
144
            widths[-1] = 155
 
145
 
 
146
        # Only update if there are changes
 
147
        if widths != self._widths:
 
148
            self._widths = widths
 
149
            self.SetStatusWidths(self._widths)
 
150
 
 
151
    def GetMainWindow(self):
 
152
        """Method required for L{ed_msg.mwcontext}"""
 
153
        return self.TopLevelParent
 
154
 
 
155
    def OnExpireMessage(self, evt):
 
156
        """Handle Expiring the status message when the oneshot timer
 
157
        tells us it has expired.
 
158
 
 
159
        """
 
160
        if evt.GetId() == EdStatBar.ID_CLEANUP_TIMER:
 
161
            wx.CallAfter(self.__SetStatusText, u'', ed_glob.SB_INFO)
 
162
        else:
 
163
            evt.Skip()
 
164
 
 
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
 
169
 
 
170
        """
 
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)
 
176
        else:
 
177
            evt.Skip()
 
178
 
 
179
    def OnLeftUp(self, evt):
 
180
        """Handle left clicks on the status bar
 
181
        @param evt: wx.MouseEvent
 
182
 
 
183
        """
 
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),
 
196
                                           wx.ART_OTHER)
 
197
            if bmp.IsOk():
 
198
                dlg.SetBitmap(bmp)
 
199
            dlg.CenterOnParent()
 
200
 
 
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())
 
205
                self.UpdateFields()
 
206
 
 
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.
 
211
            if dlg:
 
212
                dlg.Destroy()
 
213
        elif self.GetFieldRect(ed_glob.SB_LEXER).Contains(pt):
 
214
            # Change Lexer popup menu
 
215
            if self._lexmenu:
 
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))
 
221
        else:
 
222
            evt.Skip()
 
223
 
 
224
    def OnProgress(self, msg):
 
225
        """Set the progress bar's state
 
226
        @param msg: Message Object
 
227
 
 
228
        """
 
229
        mdata = msg.GetData()
 
230
        # Don't do anything if the message is not for this frame
 
231
        if self._pid != mdata[0]:
 
232
            return
 
233
 
 
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
 
237
            # the gui here.
 
238
            self.SetProgress(mdata[1])
 
239
            self.range = mdata[2]
 
240
            if sum(mdata[1:]) == 0:
 
241
                self.Stop()
 
242
        elif mtype == ed_msg.EDMSG_PROGRESS_SHOW:
 
243
            if mdata[1]:
 
244
                self.Start(75)
 
245
            else:
 
246
                # TODO: findout where stray stop event is coming from...
 
247
                self.Stop()
 
248
 
 
249
    @ed_msg.mwcontext
 
250
    def OnUpdateDoc(self, msg):
 
251
        """Update document related fields
 
252
        @param msg: Message Object
 
253
 
 
254
        """
 
255
        self.UpdateFields()
 
256
        if msg.GetType() == ed_msg.EDMSG_UI_NB_CHANGED:
 
257
            wx.CallAfter(self.__SetStatusText, u'', ed_glob.SB_INFO)
 
258
 
 
259
    @anythread
 
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
 
263
        listeners.
 
264
        @param msg: Message Object
 
265
 
 
266
        """
 
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()
 
271
            self.UpdateFields()
 
272
            wx.CallAfter(self.__SetStatusText, txt, field)
 
273
 
 
274
    def OnUpdateText(self, msg):
 
275
        """Update the status bar text based on the received message
 
276
        @param msg: Message Object
 
277
 
 
278
        """
 
279
        self.DoUpdateText(msg)
 
280
 
 
281
    def PushStatusText(self, txt, field):
 
282
        """Set the status text
 
283
        @param txt: Text to put in bar
 
284
        @param field: int
 
285
 
 
286
        """
 
287
        wx.CallAfter(self.__SetStatusText, txt, field)
 
288
 
 
289
    def SetStatusText(self, txt, field):
 
290
        """Set the status text
 
291
        @param txt: Text to put in bar
 
292
        @param field: int
 
293
 
 
294
        """
 
295
        wx.CallAfter(self.__SetStatusText, txt, field)
 
296
 
 
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
 
302
 
 
303
        """
 
304
        nb = self.GetParent().GetNotebook()
 
305
        if nb is None:
 
306
            return
 
307
 
 
308
        try:
 
309
            cbuff = nb.GetCurrentCtrl()
 
310
            doc = cbuff.GetDocument()
 
311
            wx.CallAfter(self.__SetStatusText, doc.GetEncoding(),
 
312
                         ed_glob.SB_ENCODING)
 
313
            wx.CallAfter(self.__SetStatusText,
 
314
                         GetDescriptionFromId(cbuff.GetLangId()),
 
315
                         ed_glob.SB_LEXER)
 
316
 
 
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()],
 
322
                         ed_glob.SB_EOL)
 
323
 
 
324
        except wx.PyDeadObjectError:
 
325
            # May be called asyncronasly after the control is already dead
 
326
            return