1 #----------------------------------------------------------------------------
2 # Name: OutlineService.py
3 # Purpose: Outline View Service for pydocview
9 # Copyright: (c) 2004-2005 ActiveGrid, Inc.
10 # License: wxWindows License
11 #----------------------------------------------------------------------------
15 import wx
.lib
.pydocview
20 #----------------------------------------------------------------------------
22 #----------------------------------------------------------------------------
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.
34 #----------------------------------------------------------------------------
36 #----------------------------------------------------------------------------
38 def __init__(self
, service
):
39 Service
.ServiceView
.__init
__(self
, service
)
40 self
._actionOnSelect
= True
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
)
53 #----------------------------------------------------------------------------
54 # Service specific methods
55 #----------------------------------------------------------------------------
57 def OnRightClick(self
, event
):
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"))
64 config
= wx
.ConfigBase_Get()
65 sort
= config
.ReadInt("OutlineSort", 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)
73 self
.GetControl().PopupMenu(menu
, event
.GetPosition())
77 #----------------------------------------------------------------------------
79 #----------------------------------------------------------------------------
81 def DoSelection(self
, event
):
82 if not self
._actionOnSelect
:
84 item
= self
.GetControl().GetSelection()
86 self
.GetControl().CallDoSelectCallback(item
)
89 def ResumeActionOnSelect(self
):
90 self
._actionOnSelect
= True
93 def StopActionOnSelect(self
):
94 self
._actionOnSelect
= False
97 def SetTreeCtrl(self
, 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
)
104 def GetTreeCtrl(self
):
105 return self
.GetControl()
108 def OnSort(self
, sortOrder
):
109 treeCtrl
= self
.GetControl()
110 treeCtrl
.SetSortOrder(sortOrder
)
111 treeCtrl
.SortAllChildren(treeCtrl
.GetRootItem())
114 def ClearTreeCtrl(self
):
115 if self
.GetControl():
116 self
.GetControl().DeleteAllItems()
119 def GetExpansionState(self
):
122 treeCtrl
= self
.GetControl()
126 parentItem
= treeCtrl
.GetRootItem()
131 if not treeCtrl
.IsExpanded(parentItem
):
134 expanded
.append(treeCtrl
.GetItemText(parentItem
))
136 (child
, cookie
) = treeCtrl
.GetFirstChild(parentItem
)
138 if treeCtrl
.IsExpanded(child
):
139 expanded
.append(treeCtrl
.GetItemText(child
))
140 (child
, cookie
) = treeCtrl
.GetNextChild(parentItem
, cookie
)
144 def SetExpansionState(self
, expanded
):
145 if not expanded
or len(expanded
) == 0:
148 treeCtrl
= self
.GetControl()
149 parentItem
= treeCtrl
.GetRootItem()
150 if expanded
[0] != treeCtrl
.GetItemText(parentItem
):
153 (child
, cookie
) = treeCtrl
.GetFirstChild(parentItem
)
155 if treeCtrl
.GetItemText(child
) in expanded
:
156 treeCtrl
.Expand(child
)
157 (child
, cookie
) = treeCtrl
.GetNextChild(parentItem
, cookie
)
159 # wxBug: This causes a crash, tried using ScrollTo which crashed as well. Then tried calling it with wx.CallAfter and that crashed as well, with both EnsureVisible and ScrollTo
160 # self.GetControl().EnsureVisible(self.GetControl().GetRootItem())
161 # So doing the following massive hack which forces the treectrl to scroll up to the top item
162 treeCtrl
.Collapse(parentItem
)
163 treeCtrl
.Expand(parentItem
)
166 class OutlineTreeCtrl(wx
.TreeCtrl
):
167 """ Default Tree Control Class for OutlineView.
168 This class has the added functionality of sorting by the labels
172 #----------------------------------------------------------------------------
174 #----------------------------------------------------------------------------
180 #----------------------------------------------------------------------------
182 #----------------------------------------------------------------------------
184 def __init__(self
, parent
, id, style
=wx
.TR_HAS_BUTTONS|wx
.TR_DEFAULT_STYLE
):
185 wx
.TreeCtrl
.__init
__(self
, parent
, id, style
= style
)
186 self
._origOrderIndex
= 0
187 self
._sortOrder
= SORT_NONE
190 def DeleteAllItems(self
):
191 self
._origOrderIndex
= 0
192 wx
.TreeCtrl
.DeleteAllItems(self
)
195 #----------------------------------------------------------------------------
197 #----------------------------------------------------------------------------
199 def SetSortOrder(self
, sortOrder
= SORT_NONE
):
200 """ Sort Order constants are defined at top of file """
201 self
._sortOrder
= sortOrder
204 def OnCompareItems(self
, item1
, item2
):
205 if self
._sortOrder
== SORT_ASC
:
206 return cmp(self
.GetItemText(item1
).lower(), self
.GetItemText(item2
).lower()) # sort A-Z
207 elif self
._sortOrder
== SORT_DESC
:
208 return cmp(self
.GetItemText(item2
).lower(), self
.GetItemText(item1
).lower()) # sort Z-A
210 return (self
.GetPyData(item1
)[self
.ORIG_ORDER
] > self
.GetPyData(item2
)[self
.ORIG_ORDER
]) # unsorted
213 def SortAllChildren(self
, parentItem
):
214 if parentItem
and self
.GetChildrenCount(parentItem
, False):
215 self
.SortChildren(parentItem
)
216 (child
, cookie
) = self
.GetFirstChild(parentItem
)
218 self
.SortAllChildren(child
)
219 (child
, cookie
) = self
.GetNextChild(parentItem
, cookie
)
222 #----------------------------------------------------------------------------
223 # Select Callback Methods
224 #----------------------------------------------------------------------------
226 def CallDoSelectCallback(self
, item
):
227 """ Invoke the DoSelectCallback of the given view to highlight text in the document view
229 data
= self
.GetPyData(item
)
233 view
= data
[self
.VIEW
]
234 cbdata
= data
[self
.CALLBACKDATA
]
236 view
.DoSelectCallback(cbdata
)
239 def SelectClosestItem(self
, position
):
243 self
.FindDistanceToTreeItems(tree
.GetRootItem(), position
, distances
, items
)
246 for index
in range(0, len(distances
)):
247 if distances
[index
] <= mindist
:
248 mindist
= distances
[index
]
252 self
.EnsureVisible(item
)
253 os_view
= wx
.GetApp().GetService(OutlineService
).GetView()
255 os_view
.StopActionOnSelect()
256 self
.SelectItem(item
)
258 os_view
.ResumeActionOnSelect()
261 def FindDistanceToTreeItems(self
, item
, position
, distances
, items
):
262 data
= self
.GetPyData(item
)
265 positionTuple
= data
[2]
266 if position
>= positionTuple
[1]:
268 distances
.append(position
- positionTuple
[1])
270 if self
.ItemHasChildren(item
):
271 child
, cookie
= self
.GetFirstChild(item
)
272 while child
and child
.IsOk():
273 self
.FindDistanceToTreeItems(child
, position
, distances
, items
)
274 child
, cookie
= self
.GetNextChild(item
, cookie
)
278 def SetDoSelectCallback(self
, item
, view
, callbackdata
):
279 """ When an item in the outline view is selected,
280 a method is called to select the respective text in the document view.
281 The view must define the method DoSelectCallback(self, data) in order for this to work
283 self
.SetPyData(item
, (self
._origOrderIndex
, view
, callbackdata
))
284 self
._origOrderIndex
= self
._origOrderIndex
+ 1
287 def CallDoLoadOutlineCallback(self
, event
):
288 """ Invoke the DoLoadOutlineCallback
290 rootItem
= self
.GetRootItem()
292 data
= self
.GetPyData(rootItem
)
294 view
= data
[self
.VIEW
]
295 if view
and view
.DoLoadOutlineCallback():
296 self
.SortAllChildren(self
.GetRootItem())
299 def GetCallbackView(self
):
300 rootItem
= self
.GetRootItem()
302 return self
.GetPyData(rootItem
)[self
.VIEW
]
307 class OutlineService(Service
.Service
):
310 #----------------------------------------------------------------------------
312 #----------------------------------------------------------------------------
313 SHOW_WINDOW
= wx
.NewId() # keep this line for each subclass, need unique ID for each Service
315 SORT_ASC
= wx
.NewId()
316 SORT_DESC
= wx
.NewId()
317 SORT_NONE
= wx
.NewId()
320 #----------------------------------------------------------------------------
322 #----------------------------------------------------------------------------
324 def __init__(self
, serviceName
, embeddedWindowLocation
= wx
.lib
.pydocview
.EMBEDDED_WINDOW_BOTTOM
):
325 Service
.Service
.__init
__(self
, serviceName
, embeddedWindowLocation
)
326 self
._validTemplates
= []
329 def _CreateView(self
):
330 return OutlineView(self
)
333 def InstallControls(self
, frame
, menuBar
= None, toolBar
= None, statusBar
= None, document
= None):
334 Service
.Service
.InstallControls(self
, frame
, menuBar
, toolBar
, statusBar
, document
)
336 wx
.EVT_MENU(frame
, OutlineService
.SORT_ASC
, frame
.ProcessEvent
)
337 wx
.EVT_UPDATE_UI(frame
, OutlineService
.SORT_ASC
, frame
.ProcessUpdateUIEvent
)
338 wx
.EVT_MENU(frame
, OutlineService
.SORT_DESC
, frame
.ProcessEvent
)
339 wx
.EVT_UPDATE_UI(frame
, OutlineService
.SORT_DESC
, frame
.ProcessUpdateUIEvent
)
340 wx
.EVT_MENU(frame
, OutlineService
.SORT_NONE
, frame
.ProcessEvent
)
341 wx
.EVT_UPDATE_UI(frame
, OutlineService
.SORT_NONE
, frame
.ProcessUpdateUIEvent
)
344 if wx
.GetApp().GetDocumentManager().GetFlags() & wx
.lib
.docview
.DOC_SDI
:
347 viewMenu
= menuBar
.GetMenu(menuBar
.FindMenu(_("&View")))
348 self
._outlineSortMenu
= wx
.Menu()
349 self
._outlineSortMenu
.AppendRadioItem(OutlineService
.SORT_NONE
, _("Unsorted"), _("Display items in original order"))
350 self
._outlineSortMenu
.AppendRadioItem(OutlineService
.SORT_ASC
, _("Sort A-Z"), _("Display items in ascending order"))
351 self
._outlineSortMenu
.AppendRadioItem(OutlineService
.SORT_DESC
, _("Sort Z-A"), _("Display items in descending order"))
352 viewMenu
.AppendMenu(wx
.NewId(), _("Outline Sort"), self
._outlineSortMenu
)
357 #----------------------------------------------------------------------------
358 # Event Processing Methods
359 #----------------------------------------------------------------------------
361 def ProcessEvent(self
, event
):
362 if Service
.Service
.ProcessEvent(self
, event
):
366 if id == OutlineService
.SORT_ASC
:
369 elif id == OutlineService
.SORT_DESC
:
372 elif id == OutlineService
.SORT_NONE
:
379 def ProcessUpdateUIEvent(self
, event
):
380 if Service
.Service
.ProcessUpdateUIEvent(self
, event
):
384 if id == OutlineService
.SORT_ASC
:
387 config
= wx
.ConfigBase_Get()
388 sort
= config
.ReadInt("OutlineSort", SORT_NONE
)
390 self
._outlineSortMenu
.Check(OutlineService
.SORT_ASC
, True)
392 self
._outlineSortMenu
.Check(OutlineService
.SORT_ASC
, False)
395 elif id == OutlineService
.SORT_DESC
:
398 config
= wx
.ConfigBase_Get()
399 sort
= config
.ReadInt("OutlineSort", SORT_NONE
)
400 if sort
== SORT_DESC
:
401 self
._outlineSortMenu
.Check(OutlineService
.SORT_DESC
, True)
403 self
._outlineSortMenu
.Check(OutlineService
.SORT_DESC
, False)
406 elif id == OutlineService
.SORT_NONE
:
409 config
= wx
.ConfigBase_Get()
410 sort
= config
.ReadInt("OutlineSort", SORT_NONE
)
411 if sort
== SORT_NONE
:
412 self
._outlineSortMenu
.Check(OutlineService
.SORT_NONE
, True)
414 self
._outlineSortMenu
.Check(OutlineService
.SORT_NONE
, False)
421 def OnSort(self
, event
):
423 if id == OutlineService
.SORT_ASC
:
424 wx
.ConfigBase_Get().WriteInt("OutlineSort", SORT_ASC
)
425 self
.GetView().OnSort(SORT_ASC
)
427 elif id == OutlineService
.SORT_DESC
:
428 wx
.ConfigBase_Get().WriteInt("OutlineSort", SORT_DESC
)
429 self
.GetView().OnSort(SORT_DESC
)
431 elif id == OutlineService
.SORT_NONE
:
432 wx
.ConfigBase_Get().WriteInt("OutlineSort", SORT_NONE
)
433 self
.GetView().OnSort(SORT_NONE
)
437 #----------------------------------------------------------------------------
438 # Service specific methods
439 #----------------------------------------------------------------------------
441 def LoadOutline(self
, view
, position
=-1, force
=False):
442 if not self
.GetView():
445 if hasattr(view
, "DoLoadOutlineCallback"):
446 self
.SaveExpansionState()
447 if view
.DoLoadOutlineCallback(force
=force
):
448 self
.GetView().OnSort(wx
.ConfigBase_Get().ReadInt("OutlineSort", SORT_NONE
))
449 self
.LoadExpansionState()
451 self
.SyncToPosition(position
)
454 def SyncToPosition(self
, position
):
455 if not self
.GetView():
457 self
.GetView().GetTreeCtrl().SelectClosestItem(position
)
460 def OnCloseFrame(self
, event
):
461 Service
.Service
.OnCloseFrame(self
, event
)
462 self
.SaveExpansionState(clear
= True)
467 def SaveExpansionState(self
, clear
= False):
471 expanded
= self
.GetView().GetExpansionState()
472 wx
.ConfigBase_Get().Write("OutlineLastExpanded", expanded
.__repr
__())
475 def LoadExpansionState(self
):
476 expanded
= wx
.ConfigBase_Get().Read("OutlineLastExpanded")
478 self
.GetView().SetExpansionState(eval(expanded
))
481 #----------------------------------------------------------------------------
483 #----------------------------------------------------------------------------
485 def StartBackgroundTimer(self
):
486 self
._timer
= wx
.PyTimer(self
.DoBackgroundRefresh
)
487 self
._timer
.Start(250)
490 def DoBackgroundRefresh(self
):
491 """ Refresh the outline view periodically """
494 foundRegisteredView
= False
496 currView
= wx
.GetApp().GetDocumentManager().GetCurrentView()
498 for template
in self
._validTemplates
:
499 type = template
.GetViewType()
500 if isinstance(currView
, type):
501 self
.LoadOutline(currView
)
502 foundRegisteredView
= True
505 if not foundRegisteredView
:
506 self
.GetView().ClearTreeCtrl()
508 self
._timer
.Start(1000) # 1 second interval
511 def AddTemplateForBackgroundHandler(self
, template
):
512 self
._validTemplates
.append(template
)
515 def GetTemplatesForBackgroundHandler(self
):
516 return self
._validTemplates
519 def RemoveTemplateForBackgroundHandler(self
, template
):
520 self
._validTemplates
.remove(template
)