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

« back to all changes in this revision

Viewing changes to wxPython/wx/tools/Editra/src/ed_msg.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_msg.py                                                             #
 
3
# Purpose: Provide a messaging/notification system for actions performed in   #
 
4
#          the editor.                                                        #
 
5
# Author: Cody Precord <cprecord@editra.org>                                  #
 
6
# Copyright: (c) 2008 Cody Precord <staff@editra.org>                         #
 
7
# License: wxWindows License                                                  #
 
8
###############################################################################
 
9
 
 
10
"""
 
11
This module provides a light wrapping of a slightly modified pubsub module
 
12
to give it a lighter and simpler syntax for usage. It exports three main
 
13
methods. The first L{PostMessage} which is used to post a message for all
 
14
interested listeners. The second L{Subscribe} which allows an object to
 
15
subscribe its own listener function for a particular message type, all of
 
16
Editra's core message types are defined in this module using a naming
 
17
convention that starts each identifier with I{EDMSG_}. These identifier
 
18
constants can be used to identify the message type by comparing them with the
 
19
value of msg.GetType in a listener method. The third method is L{Unsubscribe}
 
20
which can be used to remove a listener from recieving messages.
 
21
 
 
22
@summary: Message system api and message type definitions
 
23
 
 
24
"""
 
25
 
 
26
__author__ = "Cody Precord <cprecord@editra.org>"
 
27
__svnid__ = "$Id: ed_msg.py 71697 2012-06-08 15:20:22Z CJP $"
 
28
__revision__ = "$Revision: 71697 $"
 
29
 
 
30
__all__ = ['PostMessage', 'Subscribe', 'Unsubscribe']
 
31
 
 
32
#--------------------------------------------------------------------------#
 
33
# Imports
 
34
from wx import PyDeadObjectError
 
35
from extern.pubsub import Publisher
 
36
 
 
37
#--------------------------------------------------------------------------#
 
38
# Message Type Definitions
 
39
 
 
40
#---- General Messages ----#
 
41
 
 
42
# Listen to all messages
 
43
EDMSG_ALL = ('editra',)
 
44
 
 
45
#---- End General Messages ----#
 
46
 
 
47
#---- Log Messages ----#
 
48
# Used internally by the log system. Listed by priority lowest -> highest
 
49
# All message data from these functions are a LogMsg object which is a
 
50
# container object for the message string / timestamp / type
 
51
#
 
52
# Using these message types with the PostMessage method is not suggested for
 
53
# use in user code instead use the logging facilities (wx.GetApp().GetLog() or
 
54
# util.Getlog() ) as they will handle the formatting that is expected by the 
 
55
# log messaging listeners.
 
56
 
 
57
# Recieve all log messages (i.e anything put on the logging system)
 
58
EDMSG_LOG_ALL = EDMSG_ALL + ('log',)
 
59
 
 
60
# Recieve all messages that have been labled (info, events, warnings, errors)
 
61
EDMSG_LOG_INFO = EDMSG_LOG_ALL + ('info',)
 
62
 
 
63
# Messages generated by ui events
 
64
EDMSG_LOG_EVENT = EDMSG_LOG_INFO + ('evt',)
 
65
 
 
66
# Recieve only warning messages
 
67
EDMSG_LOG_WARN = EDMSG_LOG_INFO + ('warn',)
 
68
 
 
69
# Recieve only error messages
 
70
EDMSG_LOG_ERROR = EDMSG_LOG_INFO + ('err',)
 
71
 
 
72
#---- End Log Messages ----#
 
73
 
 
74
#---- Configuration Messages ----#
 
75
 
 
76
# These messages will be sent when there are configuration
 
77
# changes in the current user profile. Messages will be in
 
78
# the format of (editra,config,PROFILE_KEY)
 
79
# mdata == Profile[PROFILE_KEY]
 
80
EDMSG_PROFILE_CHANGE = EDMSG_ALL + ('config',)
 
81
 
 
82
#---- End Configuration Messages ----#
 
83
 
 
84
#---- File Action Messages ----#
 
85
 
 
86
# Recieve notification of all file actions
 
87
EDMSG_FILE_ALL = EDMSG_ALL + ('file',)
 
88
 
 
89
# File open was just requested / msgdata == file path
 
90
EDMSG_FILE_OPENING = EDMSG_FILE_ALL + ('opening',)
 
91
 
 
92
# File was just opened / msgdata == file path
 
93
# context == MainWindows ID
 
94
EDMSG_FILE_OPENED = EDMSG_FILE_ALL + ('opened',)
 
95
 
 
96
# Get a list of all opened files
 
97
# msgdata == list of file paths (out param)
 
98
EDMSG_FILE_GET_OPENED = EDMSG_FILE_ALL + ('allopened',)
 
99
 
 
100
# TODO: using MainWindow as context for now, but may make more sense to use
 
101
#       the buffer instead.
 
102
 
 
103
# File save requested / msgdata == (filename, filetypeId)
 
104
# context == MainWindows ID
 
105
# Note: All listeners of this message are processed *before* the save takes
 
106
#       place. Meaning the listeners block the save action until they are
 
107
#       finished.
 
108
EDMSG_FILE_SAVE = EDMSG_FILE_ALL + ('save',)
 
109
 
 
110
# File just written to disk / msgdata == (filename, filetypeId)
 
111
# context == MainWindows ID
 
112
EDMSG_FILE_SAVED = EDMSG_FILE_ALL + ('saved',)
 
113
 
 
114
#---- End File Action Messages ----#
 
115
 
 
116
#---- UI Action Messages ----#
 
117
 
 
118
# Recieve notification of all ui typed messages
 
119
EDMSG_UI_ALL = EDMSG_ALL + ('ui',)
 
120
 
 
121
#- Receive all Main Notebook Messages
 
122
EDMSG_UI_NB = EDMSG_UI_ALL + ('mnotebook',)
 
123
 
 
124
# MainWindow Activated
 
125
# msgdata == dict(active=bool)
 
126
# context = MainWindow ID
 
127
EDMSG_UI_MW_ACTIVATE = EDMSG_UI_ALL + ('mwactivate',)
 
128
 
 
129
# Notebook page changing
 
130
# msgdata == (ref to notebook, 
 
131
#             index of previous selection,
 
132
#             index of current selection)
 
133
# context == MainWindow ID
 
134
EDMSG_UI_NB_CHANGING = EDMSG_UI_NB + ('pgchanging',)
 
135
 
 
136
# Notebook page changed
 
137
# msgdata == (ref to notebook, index of currently selected page)
 
138
# context == MainWindow ID
 
139
EDMSG_UI_NB_CHANGED = EDMSG_UI_NB + ('pgchanged',)
 
140
 
 
141
# Page is about to close
 
142
# msgdata == (ref to notebook, index of page that is closing)
 
143
# context == MainWindow ID
 
144
EDMSG_UI_NB_CLOSING = EDMSG_UI_NB + ('pgclosing',)
 
145
 
 
146
# Page has just been closed
 
147
# msgdata == (ref to notebook, index of page that is now selected)
 
148
# context == MainWindow ID
 
149
EDMSG_UI_NB_CLOSED = EDMSG_UI_NB + ('pgclosed',)
 
150
 
 
151
# Tab Menu requested
 
152
# msgdata == ContextMenuManager
 
153
# menu = ContextMenuManager.GetMenu()
 
154
# ContextMenuManager.AddHandler(ID_MENU_ID, handler(buffer, event))
 
155
# page = ContextMenuManager.GetUserData("page")
 
156
EDMSG_UI_NB_TABMENU = EDMSG_UI_NB + ('tabmenu',)
 
157
 
 
158
# Post message to show the progress indicator of the MainWindow
 
159
# msgdata == (frame id, True / False)
 
160
EDMSG_PROGRESS_SHOW = EDMSG_UI_ALL + ('statbar', 'progbar', 'show')
 
161
 
 
162
# Post this message to manipulate the state of the MainWindows status bar
 
163
# progress indicator. The message data should be a three tuple of the recipient
 
164
# frames id, current progress and the total range (current, total). If both 
 
165
# values are 0 then the bar will be hidden. If both are negative the bar will 
 
166
# be set into pulse mode. This message can safely be sent from background 
 
167
# threads.
 
168
EDMSG_PROGRESS_STATE = EDMSG_UI_ALL + ('statbar', 'progbar', 'state')
 
169
 
 
170
# Set the status text
 
171
# msgdata == (field id, text)
 
172
EDMSG_UI_SB_TXT = EDMSG_UI_ALL + ('statbar', 'text')
 
173
 
 
174
## Text Buffer ##
 
175
 
 
176
# Root message for the text buffer
 
177
EDMSG_UI_STC_ALL = EDMSG_UI_ALL + ('stc',)
 
178
 
 
179
# msgdata == ((x, y), keycode)
 
180
# context == MainWindows ID
 
181
EDMSG_UI_STC_KEYUP = EDMSG_UI_STC_ALL + ('keyup',)
 
182
 
 
183
# msgdata == dict(lnum=line, cnum=column)
 
184
# context == MainWindows ID
 
185
EDMSG_UI_STC_POS_CHANGED = EDMSG_UI_STC_ALL + ('position',)
 
186
 
 
187
# msgdata == dict(fname=fname,
 
188
#                 prepos=pos, preline=line,
 
189
#                 lnum=cline, pos=cpos)
 
190
# context == MainWindow ID
 
191
EDMSG_UI_STC_POS_JUMPED = EDMSG_UI_STC_ALL + ('jump',)
 
192
 
 
193
# Editor control size restored (msgdata == None)
 
194
EDMSG_UI_STC_RESTORE = EDMSG_UI_STC_ALL + ('restore',)
 
195
 
 
196
# Lexer Changed
 
197
# msgdata == (filename, filetype id)
 
198
# context == MainWindows ID
 
199
EDMSG_UI_STC_LEXER = EDMSG_UI_STC_ALL + ('lexer',)
 
200
 
 
201
# Buffer Changed
 
202
# NOTE: this gets called ALOT so be very efficient in any handlers of it!
 
203
# msgdata == None
 
204
EDMSG_UI_STC_CHANGED = EDMSG_UI_STC_ALL + ('changed',)
 
205
 
 
206
# Customize Context Menu
 
207
# Add custom menu items and handlers to the buffers right click menu
 
208
# msgdata = ContextMenuManager
 
209
# ContextMenuManager.AddHandler(menu_id, handler)
 
210
# menu = ContextMenuManager.GetMenu()
 
211
# def handler(buffer, event_obj)
 
212
# ContextMenuManager.GetData('buffer')
 
213
EDMSG_UI_STC_CONTEXT_MENU = EDMSG_UI_STC_ALL + ('custommenu',)
 
214
 
 
215
# UserList Selection
 
216
# msgdata == dict(ltype=int, text=string, stc=EditraStc)
 
217
EDMSG_UI_STC_USERLIST_SEL = EDMSG_UI_STC_ALL + ('userlistsel',)
 
218
 
 
219
# Mouse Dwell Start
 
220
# mdata = dict(stc=self, pos=position,
 
221
#              line=line_number,
 
222
#              word=word_under_cursor
 
223
#              rdata="")
 
224
# If the handler for this method wants to show a calltip
 
225
# it should set the rdata value
 
226
EDMSG_UI_STC_DWELL_START = EDMSG_UI_STC_ALL + ('dwellstart',)
 
227
 
 
228
# Mouse Dwell End
 
229
# mdata = None
 
230
EDMSG_UI_STC_DWELL_END = EDMSG_UI_STC_ALL + ('dwellend',)
 
231
 
 
232
# Bookmark (added/deleted)
 
233
# mdata = dict(stc=EditraStc, added=bool, line=line, handle=bookmarkhandle)
 
234
# NOTE: if line < 0, then all bookmarks removed
 
235
EDMSG_UI_STC_BOOKMARK = EDMSG_UI_STC_ALL + ('bookmark',)
 
236
 
 
237
# Margin Click
 
238
# mdata = dict(stc=EditraStc, line=line, handled=bool)
 
239
# handled is an out param in the message data. Set to True
 
240
# to indicate that the click was handled.
 
241
EDMSG_UI_STC_MARGIN_CLICK = EDMSG_UI_STC_ALL + ('marginclick',)
 
242
 
 
243
#---- End UI Action Messages ----#
 
244
 
 
245
#---- Menu Messages ----#
 
246
EDMSG_MENU = EDMSG_ALL + ('menu',)
 
247
 
 
248
# Signal to all windows to update keybindings (msgdata == None)
 
249
EDMSG_MENU_REBIND = EDMSG_MENU + ('rebind',)
 
250
 
 
251
# Message to set key profile
 
252
# msgdata == keyprofile name
 
253
EDMSG_MENU_LOADPROFILE = EDMSG_MENU + ('load',)
 
254
 
 
255
# Message to recreate the lexer menu
 
256
# msgdata == None
 
257
EDMSG_CREATE_LEXER_MENU = EDMSG_MENU + ('lexer',)
 
258
 
 
259
#---- End Menu Messages ----#
 
260
 
 
261
#---- Find Actions ----#
 
262
 
 
263
EDMSG_FIND_ALL = EDMSG_ALL + ('find',)
 
264
 
 
265
# Show or modify an existing find dialog
 
266
# msgdata = dict(mw, lookin, searchtxt, replacetxt)
 
267
EDMSG_FIND_SHOW_DLG = EDMSG_FIND_ALL + ('show',)
 
268
 
 
269
# Message to request a search job
 
270
# msgdata == (callable, args, kwargs)
 
271
# msgdata == (callable)
 
272
EDMSG_START_SEARCH = EDMSG_FIND_ALL + ('results',)
 
273
 
 
274
#---- End Find Actions ----#
 
275
 
 
276
#---- Session Related Actions ----#
 
277
 
 
278
# Root Session Message
 
279
EDMSG_SESSION_ALL = ('session',)
 
280
 
 
281
# Initiate a Session Save to save current session under new name.
 
282
# Note: This invokes a UI action to prompt the user on what to name
 
283
#       the session. DO NOT call from background threads.
 
284
#      send(msgdata == None) | context - MainWindow
 
285
EDMSG_SESSION_DO_SAVE = EDMSG_SESSION_ALL + ('dosave',)
 
286
 
 
287
# Initiate loading a session
 
288
# Causes all currently open files to be closed and the files from the
 
289
# specified session to be loaded. Not thread safe.
 
290
# send(msgdata == session_name)
 
291
EDMSG_SESSION_DO_LOAD = EDMSG_SESSION_ALL + ('doload',)
 
292
 
 
293
#---- End Session Related Actions ----#
 
294
 
 
295
#---- Misc Messages ----#
 
296
# Signal that the icon theme has changed. Respond to this to update icon
 
297
# resources from the ArtProvider.
 
298
EDMSG_THEME_CHANGED = EDMSG_ALL + ('theme',) # All theme listeners
 
299
 
 
300
# Update the theme the notebook specifically to the current preferences
 
301
EDMSG_THEME_NOTEBOOK = EDMSG_ALL + ('nb', 'theme')
 
302
 
 
303
# Signal that the font preferences for the ui have changed (msgdata == font)
 
304
EDMSG_DSP_FONT = EDMSG_ALL + ('dfont',)
 
305
 
 
306
# Add file to file history
 
307
# msgdata == filename
 
308
EDMSG_ADD_FILE_HISTORY = EDMSG_ALL + ('filehistory',)
 
309
 
 
310
#---- End Misc Messages ----#
 
311
 
 
312
#--------------------------------------------------------------------------#
 
313
# Public Api
 
314
_ThePublisher = Publisher()
 
315
 
 
316
def PostMessage(msgtype, msgdata=None, context=None):
 
317
    """Post a message containing the msgdata to all listeners that are
 
318
    interested in the given msgtype from the given context. If context
 
319
    is None than default context is assumed.
 
320
    Message is always propagated to the default context.
 
321
    @param msgtype: Message Type EDMSG_*
 
322
    @keyword msgdata: Message data to pass to listener (can be anything)
 
323
    @keyword context: Context of the message.
 
324
 
 
325
    """
 
326
    _ThePublisher.sendMessage(msgtype, msgdata, context=context)
 
327
            
 
328
def Subscribe(callback, msgtype=EDMSG_ALL):
 
329
    """Subscribe your listener function to listen for an action of type msgtype.
 
330
    The callback must be a function or a _bound_ method that accepts one
 
331
    parameter for the actions message. The message that is sent to the callback
 
332
    is a class object that has two attributes, one for the message type and the
 
333
    other for the message data. See below example for how these two values can
 
334
    be accessed.
 
335
      >>> def MyCallback(msg):
 
336
              print "Msg Type: ", msg.GetType(), "Msg Data: ", msg.GetData()
 
337
 
 
338
      >>> class Foo:
 
339
              def MyCallbackMeth(self, msg):
 
340
                  print "Msg Type: ", msg.GetType(), "Msg Data: ", msg.GetData()
 
341
 
 
342
      >>> Subscribe(MyCallback, EDMSG_SOMETHING)
 
343
      >>> myfoo = Foo()
 
344
      >>> Subscribe(myfoo.MyCallBackMeth, EDMSG_SOMETHING)
 
345
 
 
346
    @param callback: Callable function or bound method
 
347
    @keyword msgtype: Message to subscribe to (default to all)
 
348
 
 
349
    """
 
350
    _ThePublisher.subscribe(callback, msgtype)
 
351
 
 
352
def Unsubscribe(callback, messages=None):
 
353
    """Remove a listener so that it doesn't get sent messages for msgtype. If
 
354
    msgtype is not specified the listener will be removed for all msgtypes that
 
355
    it is associated with.
 
356
    @param callback: Function or bound method to remove subscription for
 
357
    @keyword messages: EDMSG_* val or list of EDMSG_* vals
 
358
 
 
359
    """    
 
360
    Publisher().unsubscribe(callback, messages)
 
361
 
 
362
 
 
363
#---- Helper Decorators ----#
 
364
 
 
365
def mwcontext(func):
 
366
    """Helper decorator for checking if the message is in context of the
 
367
    main window. Class that uses this to wrap its message handlers must
 
368
    have a GetMainWindow method that returns a reference to the MainWindow
 
369
    instance that owns the object.
 
370
    @param funct: callable(self, msg)
 
371
 
 
372
    """
 
373
    def ContextWrap(self, msg):
 
374
        """Check and only call the method if the message is in the
 
375
        context of the main window or no context was specified.
 
376
 
 
377
        """
 
378
        if hasattr(self, 'GetMainWindow'):
 
379
            mw = self.GetMainWindow()
 
380
        elif hasattr(self, 'MainWindow'):
 
381
            mw = self.MainWindow
 
382
        else:
 
383
            assert False, "Must declare a GetMainWindow method"
 
384
        context = msg.GetContext()
 
385
        if context is None or mw.GetId() == context:
 
386
            func(self, msg)
 
387
 
 
388
    ContextWrap.__name__ = func.__name__
 
389
    ContextWrap.__doc__ = func.__doc__
 
390
    return ContextWrap
 
391
 
 
392
def wincontext(funct):
 
393
    """Decorator to filter messages based on a window. Class must declare
 
394
    a GetWindow method that returns the window that the messages context
 
395
    should be filtered on.
 
396
    @param funct: callable(self, msg)
 
397
 
 
398
    """
 
399
    def ContextWrap(self, msg):
 
400
        assert hasattr(self, 'GetWindow'), "Must define a GetWindow method"
 
401
        context = msg.GetContext()
 
402
        if isinstance(context, wx.Window) and context is self.GetWindow():
 
403
            funct(self, msg)
 
404
 
 
405
    ContextWrap.__name__ = funct.__name__
 
406
    ContextWrap.__doc__ = funct.__doc__
 
407
    return ContextWrap
 
408
 
 
409
#-----------------------------------------------------------------------------#
 
410
 
 
411
# Request Messages
 
412
EDREQ_ALL = ('editra', 'req')
 
413
 
 
414
EDREQ_DOCPOINTER =  EDREQ_ALL + ('docpointer',)
 
415
 
 
416
#-----------------------------------------------------------------------------#
 
417
 
 
418
class NullValue:
 
419
    """Null value to signify that a callback method should be skipped or that
 
420
    no callback could answer the request.
 
421
 
 
422
    """
 
423
    def __int__(self):
 
424
        return 0
 
425
 
 
426
    def __nonzero__(self):
 
427
        return False
 
428
 
 
429
def RegisterCallback(callback, msgtype):
 
430
    """Register a callback method for the given message type
 
431
    @param callback: callable
 
432
    @param msgtype: message type
 
433
 
 
434
    """
 
435
    if isinstance(msgtype, tuple):
 
436
        mtype = '.'.join(msgtype)
 
437
    else:
 
438
        mtype = msgtype
 
439
 
 
440
    if mtype not in _CALLBACK_REGISTRY:
 
441
        _CALLBACK_REGISTRY[mtype] = list()
 
442
 
 
443
    if callback not in _CALLBACK_REGISTRY[mtype]:
 
444
        _CALLBACK_REGISTRY[mtype].append(callback)
 
445
 
 
446
def RequestResult(msgtype, args=list()):
 
447
    """Request a return value result from a registered function/method.
 
448
    If multiple callbacks have been registered for the given msgtype, the
 
449
    first callback to return a non-NullValue will be used for the return
 
450
    value. If L{NullValue} is returned then no callback could answer the
 
451
    call.
 
452
    @param msgtype: Request message
 
453
    @keyword args: Arguments to pass to the callback
 
454
 
 
455
    """
 
456
    if isinstance(msgtype, tuple):
 
457
        mtype = '.'.join(msgtype)
 
458
    else:
 
459
        mtype = msgtype
 
460
 
 
461
    to_remove = list()
 
462
    rval = NullValue()
 
463
    for idx, meth in enumerate(_CALLBACK_REGISTRY.get(mtype, list())):
 
464
        try:
 
465
            if len(args):
 
466
                rval = meth(args)
 
467
            else:
 
468
                rval = meth()
 
469
        except PyDeadObjectError:
 
470
            to_remove.append(meth)
 
471
 
 
472
        if not isinstance(rval, NullValue):
 
473
            break
 
474
 
 
475
    # Remove any dead objects that may have been found
 
476
    for val in reversed(to_remove):
 
477
        try:
 
478
            _CALLBACK_REGISTRY.get(mtype, list()).pop(val)
 
479
        except:
 
480
            pass
 
481
 
 
482
    return rval
 
483
 
 
484
def UnRegisterCallback(callback):
 
485
    """Un-Register a callback method
 
486
    @param callback: callable
 
487
 
 
488
    """
 
489
    for key, val in _CALLBACK_REGISTRY.iteritems():
 
490
        if callback in val:
 
491
            _CALLBACK_REGISTRY[key].remove(callback)
 
492
 
 
493
# Callback Registry for storing the methods sent in with RegisterCallback
 
494
_CALLBACK_REGISTRY = {}
 
495
 
 
496
#-----------------------------------------------------------------------------#