]>
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) | |
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 | ||
164 | class 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 | ||
305 | class 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 |