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
)
160 treeCtrl
.EnsureVisible(parentItem
)
163 class OutlineTreeCtrl(wx
.TreeCtrl
):
164 """ Default Tree Control Class for OutlineView.
165 This class has the added functionality of sorting by the labels
169 #----------------------------------------------------------------------------
171 #----------------------------------------------------------------------------
177 #----------------------------------------------------------------------------
179 #----------------------------------------------------------------------------
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
187 def DeleteAllItems(self
):
188 self
._origOrderIndex
= 0
189 wx
.TreeCtrl
.DeleteAllItems(self
)
192 #----------------------------------------------------------------------------
194 #----------------------------------------------------------------------------
196 def SetSortOrder(self
, sortOrder
= SORT_NONE
):
197 """ Sort Order constants are defined at top of file """
198 self
._sortOrder
= sortOrder
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
207 return (self
.GetPyData(item1
)[self
.ORIG_ORDER
] > self
.GetPyData(item2
)[self
.ORIG_ORDER
]) # unsorted
210 def SortAllChildren(self
, parentItem
):
211 if parentItem
and self
.GetChildrenCount(parentItem
, False):
212 self
.SortChildren(parentItem
)
213 (child
, cookie
) = self
.GetFirstChild(parentItem
)
215 self
.SortAllChildren(child
)
216 (child
, cookie
) = self
.GetNextChild(parentItem
, cookie
)
219 #----------------------------------------------------------------------------
220 # Select Callback Methods
221 #----------------------------------------------------------------------------
223 def CallDoSelectCallback(self
, item
):
224 """ Invoke the DoSelectCallback of the given view to highlight text in the document view
226 data
= self
.GetPyData(item
)
230 view
= data
[self
.VIEW
]
231 cbdata
= data
[self
.CALLBACKDATA
]
233 view
.DoSelectCallback(cbdata
)
236 def SelectClosestItem(self
, position
):
240 self
.FindDistanceToTreeItems(tree
.GetRootItem(), position
, distances
, items
)
243 for index
in range(0, len(distances
)):
244 if distances
[index
] <= mindist
:
245 mindist
= distances
[index
]
249 self
.EnsureVisible(item
)
250 os_view
= wx
.GetApp().GetService(OutlineService
).GetView()
252 os_view
.StopActionOnSelect()
253 self
.SelectItem(item
)
255 os_view
.ResumeActionOnSelect()
258 def FindDistanceToTreeItems(self
, item
, position
, distances
, items
):
259 data
= self
.GetPyData(item
)
262 positionTuple
= data
[2]
263 if position
>= positionTuple
[1]:
265 distances
.append(position
- positionTuple
[1])
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
)
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
280 self
.SetPyData(item
, (self
._origOrderIndex
, view
, callbackdata
))
281 self
._origOrderIndex
= self
._origOrderIndex
+ 1
284 def CallDoLoadOutlineCallback(self
, event
):
285 """ Invoke the DoLoadOutlineCallback
287 rootItem
= self
.GetRootItem()
289 data
= self
.GetPyData(rootItem
)
291 view
= data
[self
.VIEW
]
292 if view
and view
.DoLoadOutlineCallback():
293 self
.SortAllChildren(self
.GetRootItem())
296 def GetCallbackView(self
):
297 rootItem
= self
.GetRootItem()
299 return self
.GetPyData(rootItem
)[self
.VIEW
]
304 class OutlineService(Service
.Service
):
307 #----------------------------------------------------------------------------
309 #----------------------------------------------------------------------------
310 SHOW_WINDOW
= wx
.NewId() # keep this line for each subclass, need unique ID for each Service
312 SORT_ASC
= wx
.NewId()
313 SORT_DESC
= wx
.NewId()
314 SORT_NONE
= wx
.NewId()
317 #----------------------------------------------------------------------------
319 #----------------------------------------------------------------------------
321 def __init__(self
, serviceName
, embeddedWindowLocation
= wx
.lib
.pydocview
.EMBEDDED_WINDOW_BOTTOM
):
322 Service
.Service
.__init
__(self
, serviceName
, embeddedWindowLocation
)
323 self
._validTemplates
= []
326 def _CreateView(self
):
327 return OutlineView(self
)
330 def InstallControls(self
, frame
, menuBar
= None, toolBar
= None, statusBar
= None, document
= None):
331 Service
.Service
.InstallControls(self
, frame
, menuBar
, toolBar
, statusBar
, document
)
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
)
341 if wx
.GetApp().GetDocumentManager().GetFlags() & wx
.lib
.docview
.DOC_SDI
:
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
)
354 #----------------------------------------------------------------------------
355 # Event Processing Methods
356 #----------------------------------------------------------------------------
358 def ProcessEvent(self
, event
):
359 if Service
.Service
.ProcessEvent(self
, event
):
363 if id == OutlineService
.SORT_ASC
:
366 elif id == OutlineService
.SORT_DESC
:
369 elif id == OutlineService
.SORT_NONE
:
376 def ProcessUpdateUIEvent(self
, event
):
377 if Service
.Service
.ProcessUpdateUIEvent(self
, event
):
381 if id == OutlineService
.SORT_ASC
:
384 config
= wx
.ConfigBase_Get()
385 sort
= config
.ReadInt("OutlineSort", SORT_NONE
)
387 self
._outlineSortMenu
.Check(OutlineService
.SORT_ASC
, True)
389 self
._outlineSortMenu
.Check(OutlineService
.SORT_ASC
, False)
392 elif id == OutlineService
.SORT_DESC
:
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)
400 self
._outlineSortMenu
.Check(OutlineService
.SORT_DESC
, False)
403 elif id == OutlineService
.SORT_NONE
:
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)
411 self
._outlineSortMenu
.Check(OutlineService
.SORT_NONE
, False)
418 def OnSort(self
, event
):
420 if id == OutlineService
.SORT_ASC
:
421 wx
.ConfigBase_Get().WriteInt("OutlineSort", SORT_ASC
)
422 self
.GetView().OnSort(SORT_ASC
)
424 elif id == OutlineService
.SORT_DESC
:
425 wx
.ConfigBase_Get().WriteInt("OutlineSort", SORT_DESC
)
426 self
.GetView().OnSort(SORT_DESC
)
428 elif id == OutlineService
.SORT_NONE
:
429 wx
.ConfigBase_Get().WriteInt("OutlineSort", SORT_NONE
)
430 self
.GetView().OnSort(SORT_NONE
)
434 #----------------------------------------------------------------------------
435 # Service specific methods
436 #----------------------------------------------------------------------------
438 def LoadOutline(self
, view
, position
=-1, force
=False):
439 if not self
.GetView():
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()
448 self
.SyncToPosition(position
)
451 def SyncToPosition(self
, position
):
452 if not self
.GetView():
454 self
.GetView().GetTreeCtrl().SelectClosestItem(position
)
457 def OnCloseFrame(self
, event
):
458 Service
.Service
.OnCloseFrame(self
, event
)
459 self
.SaveExpansionState(clear
= True)
464 def SaveExpansionState(self
, clear
= False):
468 expanded
= self
.GetView().GetExpansionState()
469 wx
.ConfigBase_Get().Write("OutlineLastExpanded", expanded
.__repr
__())
472 def LoadExpansionState(self
):
473 expanded
= wx
.ConfigBase_Get().Read("OutlineLastExpanded")
475 self
.GetView().SetExpansionState(eval(expanded
))
478 #----------------------------------------------------------------------------
480 #----------------------------------------------------------------------------
482 def StartBackgroundTimer(self
):
483 self
._timer
= wx
.PyTimer(self
.DoBackgroundRefresh
)
484 self
._timer
.Start(250)
487 def DoBackgroundRefresh(self
):
488 """ Refresh the outline view periodically """
491 foundRegisteredView
= False
493 currView
= wx
.GetApp().GetDocumentManager().GetCurrentView()
495 for template
in self
._validTemplates
:
496 type = template
.GetViewType()
497 if isinstance(currView
, type):
498 self
.LoadOutline(currView
)
499 foundRegisteredView
= True
502 if not foundRegisteredView
:
503 self
.GetView().ClearTreeCtrl()
505 self
._timer
.Start(1000) # 1 second interval
508 def AddTemplateForBackgroundHandler(self
, template
):
509 self
._validTemplates
.append(template
)
512 def GetTemplatesForBackgroundHandler(self
):
513 return self
._validTemplates
516 def RemoveTemplateForBackgroundHandler(self
, template
):
517 self
._validTemplates
.remove(template
)