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
)
90 def ResumeActionOnSelect(self
):
91 self
._actionOnSelect
= True
94 def StopActionOnSelect(self
):
95 self
._actionOnSelect
= False
98 def SetTreeCtrl(self
, 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
)
105 def GetTreeCtrl(self
):
106 return self
.GetControl()
109 def OnSort(self
, sortOrder
):
110 treeCtrl
= self
.GetControl()
111 treeCtrl
.SetSortOrder(sortOrder
)
112 treeCtrl
.SortAllChildren(treeCtrl
.GetRootItem())
115 def ClearTreeCtrl(self
):
116 if self
.GetControl():
117 self
.GetControl().DeleteAllItems()
120 def GetExpansionState(self
):
123 treeCtrl
= self
.GetControl()
127 parentItem
= treeCtrl
.GetRootItem()
132 if not treeCtrl
.IsExpanded(parentItem
):
135 expanded
.append(treeCtrl
.GetItemText(parentItem
))
137 (child
, cookie
) = treeCtrl
.GetFirstChild(parentItem
)
139 if treeCtrl
.IsExpanded(child
):
140 expanded
.append(treeCtrl
.GetItemText(child
))
141 (child
, cookie
) = treeCtrl
.GetNextChild(parentItem
, cookie
)
145 def SetExpansionState(self
, expanded
):
146 if not expanded
or len(expanded
) == 0:
149 treeCtrl
= self
.GetControl()
151 parentItem
= treeCtrl
.GetRootItem()
155 if expanded
[0] != treeCtrl
.GetItemText(parentItem
):
158 (child
, cookie
) = treeCtrl
.GetFirstChild(parentItem
)
160 if treeCtrl
.GetItemText(child
) in expanded
:
161 treeCtrl
.Expand(child
)
162 (child
, cookie
) = treeCtrl
.GetNextChild(parentItem
, cookie
)
164 treeCtrl
.EnsureVisible(parentItem
)
167 class OutlineTreeCtrl(wx
.TreeCtrl
):
168 """ Default Tree Control Class for OutlineView.
169 This class has the added functionality of sorting by the labels
173 #----------------------------------------------------------------------------
175 #----------------------------------------------------------------------------
181 #----------------------------------------------------------------------------
183 #----------------------------------------------------------------------------
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
191 def DeleteAllItems(self
):
192 self
._origOrderIndex
= 0
193 wx
.TreeCtrl
.DeleteAllItems(self
)
196 #----------------------------------------------------------------------------
198 #----------------------------------------------------------------------------
200 def SetSortOrder(self
, sortOrder
= SORT_NONE
):
201 """ Sort Order constants are defined at top of file """
202 self
._sortOrder
= sortOrder
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
211 return (self
.GetPyData(item1
)[self
.ORIG_ORDER
] > self
.GetPyData(item2
)[self
.ORIG_ORDER
]) # unsorted
214 def SortAllChildren(self
, parentItem
):
215 if parentItem
and self
.GetChildrenCount(parentItem
, False):
216 self
.SortChildren(parentItem
)
217 (child
, cookie
) = self
.GetFirstChild(parentItem
)
219 self
.SortAllChildren(child
)
220 (child
, cookie
) = self
.GetNextChild(parentItem
, cookie
)
223 #----------------------------------------------------------------------------
224 # Select Callback Methods
225 #----------------------------------------------------------------------------
227 def CallDoSelectCallback(self
, item
):
228 """ Invoke the DoSelectCallback of the given view to highlight text in the document view
230 data
= self
.GetPyData(item
)
234 view
= data
[self
.VIEW
]
235 cbdata
= data
[self
.CALLBACKDATA
]
237 view
.DoSelectCallback(cbdata
)
240 def SelectClosestItem(self
, position
):
244 self
.FindDistanceToTreeItems(tree
.GetRootItem(), position
, distances
, items
)
247 for index
in range(0, len(distances
)):
248 if distances
[index
] <= mindist
:
249 mindist
= distances
[index
]
253 self
.EnsureVisible(item
)
254 os_view
= wx
.GetApp().GetService(OutlineService
).GetView()
256 os_view
.StopActionOnSelect()
257 self
.SelectItem(item
)
259 os_view
.ResumeActionOnSelect()
262 def FindDistanceToTreeItems(self
, item
, position
, distances
, items
):
263 data
= self
.GetPyData(item
)
266 positionTuple
= data
[2]
267 if position
>= positionTuple
[1]:
269 distances
.append(position
- positionTuple
[1])
271 if self
.ItemHasChildren(item
):
272 child
, cookie
= self
.GetFirstChild(item
)
274 self
.FindDistanceToTreeItems(child
, position
, distances
, items
)
275 child
, cookie
= self
.GetNextChild(item
, cookie
)
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
284 self
.SetPyData(item
, (self
._origOrderIndex
, view
, callbackdata
))
285 self
._origOrderIndex
= self
._origOrderIndex
+ 1
288 def CallDoLoadOutlineCallback(self
, event
):
289 """ Invoke the DoLoadOutlineCallback
291 rootItem
= self
.GetRootItem()
293 data
= self
.GetPyData(rootItem
)
295 view
= data
[self
.VIEW
]
296 if view
and view
.DoLoadOutlineCallback():
297 self
.SortAllChildren(self
.GetRootItem())
300 def GetCallbackView(self
):
301 rootItem
= self
.GetRootItem()
303 return self
.GetPyData(rootItem
)[self
.VIEW
]
308 class OutlineService(Service
.Service
):
311 #----------------------------------------------------------------------------
313 #----------------------------------------------------------------------------
314 SHOW_WINDOW
= wx
.NewId() # keep this line for each subclass, need unique ID for each Service
316 SORT_ASC
= wx
.NewId()
317 SORT_DESC
= wx
.NewId()
318 SORT_NONE
= wx
.NewId()
321 #----------------------------------------------------------------------------
323 #----------------------------------------------------------------------------
325 def __init__(self
, serviceName
, embeddedWindowLocation
= wx
.lib
.pydocview
.EMBEDDED_WINDOW_BOTTOM
):
326 Service
.Service
.__init
__(self
, serviceName
, embeddedWindowLocation
)
327 self
._validViewTypes
= []
330 def _CreateView(self
):
331 return OutlineView(self
)
334 def InstallControls(self
, frame
, menuBar
= None, toolBar
= None, statusBar
= None, document
= None):
335 Service
.Service
.InstallControls(self
, frame
, menuBar
, toolBar
, statusBar
, document
)
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
)
345 if wx
.GetApp().GetDocumentManager().GetFlags() & wx
.lib
.docview
.DOC_SDI
:
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
)
358 #----------------------------------------------------------------------------
359 # Event Processing Methods
360 #----------------------------------------------------------------------------
362 def ProcessEvent(self
, event
):
363 if Service
.Service
.ProcessEvent(self
, event
):
367 if id == OutlineService
.SORT_ASC
:
370 elif id == OutlineService
.SORT_DESC
:
373 elif id == OutlineService
.SORT_NONE
:
380 def ProcessUpdateUIEvent(self
, event
):
381 if Service
.Service
.ProcessUpdateUIEvent(self
, event
):
385 if id == OutlineService
.SORT_ASC
:
388 config
= wx
.ConfigBase_Get()
389 sort
= config
.ReadInt("OutlineSort", SORT_NONE
)
391 self
._outlineSortMenu
.Check(OutlineService
.SORT_ASC
, True)
393 self
._outlineSortMenu
.Check(OutlineService
.SORT_ASC
, False)
396 elif id == OutlineService
.SORT_DESC
:
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)
404 self
._outlineSortMenu
.Check(OutlineService
.SORT_DESC
, False)
407 elif id == OutlineService
.SORT_NONE
:
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)
415 self
._outlineSortMenu
.Check(OutlineService
.SORT_NONE
, False)
422 def OnSort(self
, event
):
424 if id == OutlineService
.SORT_ASC
:
425 wx
.ConfigBase_Get().WriteInt("OutlineSort", SORT_ASC
)
426 self
.GetView().OnSort(SORT_ASC
)
428 elif id == OutlineService
.SORT_DESC
:
429 wx
.ConfigBase_Get().WriteInt("OutlineSort", SORT_DESC
)
430 self
.GetView().OnSort(SORT_DESC
)
432 elif id == OutlineService
.SORT_NONE
:
433 wx
.ConfigBase_Get().WriteInt("OutlineSort", SORT_NONE
)
434 self
.GetView().OnSort(SORT_NONE
)
438 #----------------------------------------------------------------------------
439 # Service specific methods
440 #----------------------------------------------------------------------------
442 def LoadOutline(self
, view
, position
=-1, force
=False):
443 if not self
.GetView():
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()
452 self
.SyncToPosition(position
)
455 def SyncToPosition(self
, position
):
456 if not self
.GetView():
458 self
.GetView().GetTreeCtrl().SelectClosestItem(position
)
461 def OnCloseFrame(self
, event
):
462 Service
.Service
.OnCloseFrame(self
, event
)
463 self
.SaveExpansionState(clear
= True)
468 def SaveExpansionState(self
, clear
= False):
472 expanded
= self
.GetView().GetExpansionState()
473 wx
.ConfigBase_Get().Write("OutlineLastExpanded", expanded
.__repr
__())
476 def LoadExpansionState(self
):
477 expanded
= wx
.ConfigBase_Get().Read("OutlineLastExpanded")
479 self
.GetView().SetExpansionState(eval(expanded
))
482 #----------------------------------------------------------------------------
484 #----------------------------------------------------------------------------
486 def StartBackgroundTimer(self
):
487 self
._timer
= wx
.PyTimer(self
.DoBackgroundRefresh
)
488 self
._timer
.Start(250)
491 def DoBackgroundRefresh(self
):
492 """ Refresh the outline view periodically """
495 foundRegisteredView
= False
497 currView
= wx
.GetApp().GetDocumentManager().GetCurrentView()
499 for viewType
in self
._validViewTypes
:
500 if isinstance(currView
, viewType
):
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 AddViewTypeForBackgroundHandler(self
, viewType
):
512 self
._validViewTypes
.append(viewType
)
515 def GetViewTypesForBackgroundHandler(self
):
516 return self
._validViewTypes
519 def RemoveViewTypeForBackgroundHandler(self
, viewType
):
520 self
._validViewTypes
.remove(viewType
)