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