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