]> git.saurik.com Git - wxWidgets.git/blame - wxPython/samples/ide/activegrid/tool/OutlineService.py
Docview and IDE patch from Morag Hua with fix for bug #1217890
[wxWidgets.git] / wxPython / samples / ide / activegrid / tool / OutlineService.py
CommitLineData
1f780e48
RD
1#----------------------------------------------------------------------------
2# Name: OutlineService.py
3# Purpose: Outline View Service for pydocview
4#
5# Author: Morgan Hua
6#
7# Created: 8/3/04
8# CVS-ID: $Id$
9# Copyright: (c) 2004-2005 ActiveGrid, Inc.
10# License: wxWindows License
11#----------------------------------------------------------------------------
12
13import wx
14import wx.lib.docview
15import wx.lib.pydocview
16import Service
17_ = wx.GetTranslation
18
19
20#----------------------------------------------------------------------------
21# Constants
22#----------------------------------------------------------------------------
23SORT_NONE = 0
24SORT_ASC = 1
25SORT_DESC = 2
26
27class OutlineView(Service.ServiceView):
28 """ Reusable Outline View for any document.
29 As a default, it uses a modified tree control (OutlineTreeCtrl) that allows sorting.
30 Subclass OutlineTreeCtrl to customize the tree control and call SetTreeCtrl to install a customized tree control.
31 When an item is selected, the document view is called back (with DoSelectCallback) to highlight and display the corresponding item in the document view.
32 """
33
34 #----------------------------------------------------------------------------
35 # Overridden methods
36 #----------------------------------------------------------------------------
37
38 def __init__(self, service):
39 Service.ServiceView.__init__(self, service)
40 self._actionOnSelect = True
41
42
43 def _CreateControl(self, parent, id):
44 treeCtrl = OutlineTreeCtrl(parent, id)
45 wx.EVT_TREE_SEL_CHANGED(treeCtrl, treeCtrl.GetId(), self.DoSelection)
46 wx.EVT_SET_FOCUS(treeCtrl, self.DoSelection)
47 wx.EVT_ENTER_WINDOW(treeCtrl, treeCtrl.CallDoLoadOutlineCallback)
48 wx.EVT_RIGHT_DOWN(treeCtrl, self.OnRightClick)
49
50 return treeCtrl
51
52
53 #----------------------------------------------------------------------------
54 # Service specific methods
55 #----------------------------------------------------------------------------
56
57 def OnRightClick(self, event):
58 menu = wx.Menu()
59
60 menu.AppendRadioItem(OutlineService.SORT_NONE, _("Unsorted"), _("Display items in original order"))
61 menu.AppendRadioItem(OutlineService.SORT_ASC, _("Sort A-Z"), _("Display items in ascending order"))
62 menu.AppendRadioItem(OutlineService.SORT_DESC, _("Sort Z-A"), _("Display items in descending order"))
63
64 config = wx.ConfigBase_Get()
65 sort = config.ReadInt("OutlineSort", SORT_NONE)
66 if sort == SORT_NONE:
67 menu.Check(OutlineService.SORT_NONE, True)
68 elif sort == SORT_ASC:
69 menu.Check(OutlineService.SORT_ASC, True)
70 elif sort == SORT_DESC:
71 menu.Check(OutlineService.SORT_DESC, True)
72
73 self.GetControl().PopupMenu(menu, event.GetPosition())
74 menu.Destroy()
75
76
77 #----------------------------------------------------------------------------
78 # Tree Methods
79 #----------------------------------------------------------------------------
80
81 def DoSelection(self, event):
82 if not self._actionOnSelect:
83 return
84 item = self.GetControl().GetSelection()
85 if item:
86 self.GetControl().CallDoSelectCallback(item)
6f1a3f9c 87 event.Skip()
1f780e48
RD
88
89
90 def ResumeActionOnSelect(self):
91 self._actionOnSelect = True
92
93
94 def StopActionOnSelect(self):
95 self._actionOnSelect = False
96
97
98 def SetTreeCtrl(self, tree):
99 self.SetControl(tree)
100 wx.EVT_TREE_SEL_CHANGED(self.GetControl(), self.GetControl().GetId(), self.DoSelection)
101 wx.EVT_ENTER_WINDOW(self.GetControl(), treeCtrl.CallDoLoadOutlineCallback)
102 wx.EVT_RIGHT_DOWN(self.GetControl(), self.OnRightClick)
103
104
105 def GetTreeCtrl(self):
106 return self.GetControl()
107
108
109 def OnSort(self, sortOrder):
110 treeCtrl = self.GetControl()
111 treeCtrl.SetSortOrder(sortOrder)
112 treeCtrl.SortAllChildren(treeCtrl.GetRootItem())
113
114
115 def ClearTreeCtrl(self):
116 if self.GetControl():
117 self.GetControl().DeleteAllItems()
118
119
120 def GetExpansionState(self):
121 expanded = []
122
123 treeCtrl = self.GetControl()
124 if not treeCtrl:
125 return expanded
126
127 parentItem = treeCtrl.GetRootItem()
128
129 if not parentItem:
130 return expanded
131
132 if not treeCtrl.IsExpanded(parentItem):
133 return expanded
134
135 expanded.append(treeCtrl.GetItemText(parentItem))
136
137 (child, cookie) = treeCtrl.GetFirstChild(parentItem)
138 while child.IsOk():
139 if treeCtrl.IsExpanded(child):
140 expanded.append(treeCtrl.GetItemText(child))
141 (child, cookie) = treeCtrl.GetNextChild(parentItem, cookie)
142 return expanded
143
144
145 def SetExpansionState(self, expanded):
146 if not expanded or len(expanded) == 0:
147 return
148
149 treeCtrl = self.GetControl()
150 parentItem = treeCtrl.GetRootItem()
151 if expanded[0] != treeCtrl.GetItemText(parentItem):
152 return
153
154 (child, cookie) = treeCtrl.GetFirstChild(parentItem)
155 while child.IsOk():
156 if treeCtrl.GetItemText(child) in expanded:
157 treeCtrl.Expand(child)
158 (child, cookie) = treeCtrl.GetNextChild(parentItem, cookie)
159
b792147d
RD
160 if parentItem:
161 treeCtrl.EnsureVisible(parentItem)
1f780e48
RD
162
163
164class OutlineTreeCtrl(wx.TreeCtrl):
165 """ Default Tree Control Class for OutlineView.
166 This class has the added functionality of sorting by the labels
167 """
168
169
170 #----------------------------------------------------------------------------
171 # Constants
172 #----------------------------------------------------------------------------
173 ORIG_ORDER = 0
174 VIEW = 1
175 CALLBACKDATA = 2
176
177
178 #----------------------------------------------------------------------------
179 # Overridden Methods
180 #----------------------------------------------------------------------------
181
182 def __init__(self, parent, id, style=wx.TR_HAS_BUTTONS|wx.TR_DEFAULT_STYLE):
183 wx.TreeCtrl.__init__(self, parent, id, style = style)
184 self._origOrderIndex = 0
185 self._sortOrder = SORT_NONE
186
187
188 def DeleteAllItems(self):
189 self._origOrderIndex = 0
190 wx.TreeCtrl.DeleteAllItems(self)
191
192
193 #----------------------------------------------------------------------------
194 # Sort Methods
195 #----------------------------------------------------------------------------
196
197 def SetSortOrder(self, sortOrder = SORT_NONE):
198 """ Sort Order constants are defined at top of file """
199 self._sortOrder = sortOrder
200
201
202 def OnCompareItems(self, item1, item2):
203 if self._sortOrder == SORT_ASC:
204 return cmp(self.GetItemText(item1).lower(), self.GetItemText(item2).lower()) # sort A-Z
205 elif self._sortOrder == SORT_DESC:
206 return cmp(self.GetItemText(item2).lower(), self.GetItemText(item1).lower()) # sort Z-A
207 else:
208 return (self.GetPyData(item1)[self.ORIG_ORDER] > self.GetPyData(item2)[self.ORIG_ORDER]) # unsorted
209
210
211 def SortAllChildren(self, parentItem):
212 if parentItem and self.GetChildrenCount(parentItem, False):
213 self.SortChildren(parentItem)
214 (child, cookie) = self.GetFirstChild(parentItem)
215 while child.IsOk():
216 self.SortAllChildren(child)
217 (child, cookie) = self.GetNextChild(parentItem, cookie)
218
219
220 #----------------------------------------------------------------------------
221 # Select Callback Methods
222 #----------------------------------------------------------------------------
223
224 def CallDoSelectCallback(self, item):
225 """ Invoke the DoSelectCallback of the given view to highlight text in the document view
226 """
227 data = self.GetPyData(item)
228 if not data:
229 return
230
231 view = data[self.VIEW]
232 cbdata = data[self.CALLBACKDATA]
233 if view:
234 view.DoSelectCallback(cbdata)
235
236
237 def SelectClosestItem(self, position):
238 tree = self
239 distances = []
240 items = []
241 self.FindDistanceToTreeItems(tree.GetRootItem(), position, distances, items)
242 mindist = 1000000
243 mindex = -1
244 for index in range(0, len(distances)):
245 if distances[index] <= mindist:
246 mindist = distances[index]
247 mindex = index
248 if mindex != -1:
249 item = items[mindex]
250 self.EnsureVisible(item)
251 os_view = wx.GetApp().GetService(OutlineService).GetView()
252 if os_view:
253 os_view.StopActionOnSelect()
254 self.SelectItem(item)
255 if os_view:
256 os_view.ResumeActionOnSelect()
257
258
259 def FindDistanceToTreeItems(self, item, position, distances, items):
260 data = self.GetPyData(item)
261 this_dist = 1000000
262 if data and data[2]:
263 positionTuple = data[2]
264 if position >= positionTuple[1]:
265 items.append(item)
266 distances.append(position - positionTuple[1])
267
268 if self.ItemHasChildren(item):
269 child, cookie = self.GetFirstChild(item)
270 while child and child.IsOk():
271 self.FindDistanceToTreeItems(child, position, distances, items)
272 child, cookie = self.GetNextChild(item, cookie)
273 return False
274
275
276 def SetDoSelectCallback(self, item, view, callbackdata):
277 """ When an item in the outline view is selected,
278 a method is called to select the respective text in the document view.
279 The view must define the method DoSelectCallback(self, data) in order for this to work
280 """
281 self.SetPyData(item, (self._origOrderIndex, view, callbackdata))
282 self._origOrderIndex = self._origOrderIndex + 1
283
284
285 def CallDoLoadOutlineCallback(self, event):
286 """ Invoke the DoLoadOutlineCallback
287 """
288 rootItem = self.GetRootItem()
289 if rootItem:
290 data = self.GetPyData(rootItem)
291 if data:
292 view = data[self.VIEW]
293 if view and view.DoLoadOutlineCallback():
294 self.SortAllChildren(self.GetRootItem())
295
296
297 def GetCallbackView(self):
298 rootItem = self.GetRootItem()
299 if rootItem:
300 return self.GetPyData(rootItem)[self.VIEW]
301 else:
302 return None
303
304
305class OutlineService(Service.Service):
306
307
308 #----------------------------------------------------------------------------
309 # Constants
310 #----------------------------------------------------------------------------
311 SHOW_WINDOW = wx.NewId() # keep this line for each subclass, need unique ID for each Service
312 SORT = wx.NewId()
313 SORT_ASC = wx.NewId()
314 SORT_DESC = wx.NewId()
315 SORT_NONE = wx.NewId()
316
317
318 #----------------------------------------------------------------------------
319 # Overridden methods
320 #----------------------------------------------------------------------------
321
322 def __init__(self, serviceName, embeddedWindowLocation = wx.lib.pydocview.EMBEDDED_WINDOW_BOTTOM):
323 Service.Service.__init__(self, serviceName, embeddedWindowLocation)
324 self._validTemplates = []
325
326
327 def _CreateView(self):
328 return OutlineView(self)
329
330
331 def InstallControls(self, frame, menuBar = None, toolBar = None, statusBar = None, document = None):
332 Service.Service.InstallControls(self, frame, menuBar, toolBar, statusBar, document)
333
334 wx.EVT_MENU(frame, OutlineService.SORT_ASC, frame.ProcessEvent)
335 wx.EVT_UPDATE_UI(frame, OutlineService.SORT_ASC, frame.ProcessUpdateUIEvent)
336 wx.EVT_MENU(frame, OutlineService.SORT_DESC, frame.ProcessEvent)
337 wx.EVT_UPDATE_UI(frame, OutlineService.SORT_DESC, frame.ProcessUpdateUIEvent)
338 wx.EVT_MENU(frame, OutlineService.SORT_NONE, frame.ProcessEvent)
339 wx.EVT_UPDATE_UI(frame, OutlineService.SORT_NONE, frame.ProcessUpdateUIEvent)
340
341
342 if wx.GetApp().GetDocumentManager().GetFlags() & wx.lib.docview.DOC_SDI:
343 return True
344
345 viewMenu = menuBar.GetMenu(menuBar.FindMenu(_("&View")))
346 self._outlineSortMenu = wx.Menu()
347 self._outlineSortMenu.AppendRadioItem(OutlineService.SORT_NONE, _("Unsorted"), _("Display items in original order"))
348 self._outlineSortMenu.AppendRadioItem(OutlineService.SORT_ASC, _("Sort A-Z"), _("Display items in ascending order"))
349 self._outlineSortMenu.AppendRadioItem(OutlineService.SORT_DESC, _("Sort Z-A"), _("Display items in descending order"))
350 viewMenu.AppendMenu(wx.NewId(), _("Outline Sort"), self._outlineSortMenu)
351
352 return True
353
354
355 #----------------------------------------------------------------------------
356 # Event Processing Methods
357 #----------------------------------------------------------------------------
358
359 def ProcessEvent(self, event):
360 if Service.Service.ProcessEvent(self, event):
361 return True
362
363 id = event.GetId()
364 if id == OutlineService.SORT_ASC:
365 self.OnSort(event)
366 return True
367 elif id == OutlineService.SORT_DESC:
368 self.OnSort(event)
369 return True
370 elif id == OutlineService.SORT_NONE:
371 self.OnSort(event)
372 return True
373 else:
374 return False
375
376
377 def ProcessUpdateUIEvent(self, event):
378 if Service.Service.ProcessUpdateUIEvent(self, event):
379 return True
380
381 id = event.GetId()
382 if id == OutlineService.SORT_ASC:
383 event.Enable(True)
384
385 config = wx.ConfigBase_Get()
386 sort = config.ReadInt("OutlineSort", SORT_NONE)
387 if sort == SORT_ASC:
388 self._outlineSortMenu.Check(OutlineService.SORT_ASC, True)
389 else:
390 self._outlineSortMenu.Check(OutlineService.SORT_ASC, False)
391
392 return True
393 elif id == OutlineService.SORT_DESC:
394 event.Enable(True)
395
396 config = wx.ConfigBase_Get()
397 sort = config.ReadInt("OutlineSort", SORT_NONE)
398 if sort == SORT_DESC:
399 self._outlineSortMenu.Check(OutlineService.SORT_DESC, True)
400 else:
401 self._outlineSortMenu.Check(OutlineService.SORT_DESC, False)
402
403 return True
404 elif id == OutlineService.SORT_NONE:
405 event.Enable(True)
406
407 config = wx.ConfigBase_Get()
408 sort = config.ReadInt("OutlineSort", SORT_NONE)
409 if sort == SORT_NONE:
410 self._outlineSortMenu.Check(OutlineService.SORT_NONE, True)
411 else:
412 self._outlineSortMenu.Check(OutlineService.SORT_NONE, False)
413
414 return True
415 else:
416 return False
417
418
419 def OnSort(self, event):
420 id = event.GetId()
421 if id == OutlineService.SORT_ASC:
422 wx.ConfigBase_Get().WriteInt("OutlineSort", SORT_ASC)
423 self.GetView().OnSort(SORT_ASC)
424 return True
425 elif id == OutlineService.SORT_DESC:
426 wx.ConfigBase_Get().WriteInt("OutlineSort", SORT_DESC)
427 self.GetView().OnSort(SORT_DESC)
428 return True
429 elif id == OutlineService.SORT_NONE:
430 wx.ConfigBase_Get().WriteInt("OutlineSort", SORT_NONE)
431 self.GetView().OnSort(SORT_NONE)
432 return True
433
434
435 #----------------------------------------------------------------------------
436 # Service specific methods
437 #----------------------------------------------------------------------------
438
439 def LoadOutline(self, view, position=-1, force=False):
440 if not self.GetView():
441 return
442
bbf7159c
RD
443 if hasattr(view, "DoLoadOutlineCallback"):
444 self.SaveExpansionState()
445 if view.DoLoadOutlineCallback(force=force):
446 self.GetView().OnSort(wx.ConfigBase_Get().ReadInt("OutlineSort", SORT_NONE))
447 self.LoadExpansionState()
448 if position >= 0:
449 self.SyncToPosition(position)
1f780e48
RD
450
451
452 def SyncToPosition(self, position):
453 if not self.GetView():
454 return
455 self.GetView().GetTreeCtrl().SelectClosestItem(position)
456
457
458 def OnCloseFrame(self, event):
459 Service.Service.OnCloseFrame(self, event)
460 self.SaveExpansionState(clear = True)
461
462 return True
463
464
465 def SaveExpansionState(self, clear = False):
466 if clear:
467 expanded = []
468 elif self.GetView():
469 expanded = self.GetView().GetExpansionState()
470 wx.ConfigBase_Get().Write("OutlineLastExpanded", expanded.__repr__())
471
472
473 def LoadExpansionState(self):
474 expanded = wx.ConfigBase_Get().Read("OutlineLastExpanded")
475 if expanded:
476 self.GetView().SetExpansionState(eval(expanded))
477
478
479 #----------------------------------------------------------------------------
480 # Timer Methods
481 #----------------------------------------------------------------------------
482
483 def StartBackgroundTimer(self):
484 self._timer = wx.PyTimer(self.DoBackgroundRefresh)
485 self._timer.Start(250)
486
487
488 def DoBackgroundRefresh(self):
489 """ Refresh the outline view periodically """
490 self._timer.Stop()
491
492 foundRegisteredView = False
493 if self.GetView():
494 currView = wx.GetApp().GetDocumentManager().GetCurrentView()
495 if currView:
496 for template in self._validTemplates:
497 type = template.GetViewType()
498 if isinstance(currView, type):
499 self.LoadOutline(currView)
500 foundRegisteredView = True
501 break
502
bbf7159c
RD
503 if not foundRegisteredView:
504 self.GetView().ClearTreeCtrl()
1f780e48
RD
505
506 self._timer.Start(1000) # 1 second interval
507
508
509 def AddTemplateForBackgroundHandler(self, template):
510 self._validTemplates.append(template)
511
512
513 def GetTemplatesForBackgroundHandler(self):
514 return self._validTemplates
515
516
517 def RemoveTemplateForBackgroundHandler(self, template):
518 self._validTemplates.remove(template)
519