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()
150 parentItem
= treeCtrl
.GetRootItem()
151 if expanded
[0] != treeCtrl
.GetItemText(parentItem
):
154 (child
, cookie
) = treeCtrl
.GetFirstChild(parentItem
)
156 if treeCtrl
.GetItemText(child
) in expanded
:
157 treeCtrl
.Expand(child
)
158 (child
, cookie
) = treeCtrl
.GetNextChild(parentItem
, cookie
)
161 treeCtrl
.EnsureVisible(parentItem
)
164 class OutlineTreeCtrl(wx
.TreeCtrl
):
165 """ Default Tree Control Class for OutlineView.
166 This class has the added functionality of sorting by the labels
170 #----------------------------------------------------------------------------
172 #----------------------------------------------------------------------------
178 #----------------------------------------------------------------------------
180 #----------------------------------------------------------------------------
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
188 def DeleteAllItems(self
):
189 self
._origOrderIndex
= 0
190 wx
.TreeCtrl
.DeleteAllItems(self
)
193 #----------------------------------------------------------------------------
195 #----------------------------------------------------------------------------
197 def SetSortOrder(self
, sortOrder
= SORT_NONE
):
198 """ Sort Order constants are defined at top of file """
199 self
._sortOrder
= sortOrder
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
208 return (self
.GetPyData(item1
)[self
.ORIG_ORDER
] > self
.GetPyData(item2
)[self
.ORIG_ORDER
]) # unsorted
211 def SortAllChildren(self
, parentItem
):
212 if parentItem
and self
.GetChildrenCount(parentItem
, False):
213 self
.SortChildren(parentItem
)
214 (child
, cookie
) = self
.GetFirstChild(parentItem
)
216 self
.SortAllChildren(child
)
217 (child
, cookie
) = self
.GetNextChild(parentItem
, cookie
)
220 #----------------------------------------------------------------------------
221 # Select Callback Methods
222 #----------------------------------------------------------------------------
224 def CallDoSelectCallback(self
, item
):
225 """ Invoke the DoSelectCallback of the given view to highlight text in the document view
227 data
= self
.GetPyData(item
)
231 view
= data
[self
.VIEW
]
232 cbdata
= data
[self
.CALLBACKDATA
]
234 view
.DoSelectCallback(cbdata
)
237 def SelectClosestItem(self
, position
):
241 self
.FindDistanceToTreeItems(tree
.GetRootItem(), position
, distances
, items
)
244 for index
in range(0, len(distances
)):
245 if distances
[index
] <= mindist
:
246 mindist
= distances
[index
]
250 self
.EnsureVisible(item
)
251 os_view
= wx
.GetApp().GetService(OutlineService
).GetView()
253 os_view
.StopActionOnSelect()
254 self
.SelectItem(item
)
256 os_view
.ResumeActionOnSelect()
259 def FindDistanceToTreeItems(self
, item
, position
, distances
, items
):
260 data
= self
.GetPyData(item
)
263 positionTuple
= data
[2]
264 if position
>= positionTuple
[1]:
266 distances
.append(position
- positionTuple
[1])
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
)
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
281 self
.SetPyData(item
, (self
._origOrderIndex
, view
, callbackdata
))
282 self
._origOrderIndex
= self
._origOrderIndex
+ 1
285 def CallDoLoadOutlineCallback(self
, event
):
286 """ Invoke the DoLoadOutlineCallback
288 rootItem
= self
.GetRootItem()
290 data
= self
.GetPyData(rootItem
)
292 view
= data
[self
.VIEW
]
293 if view
and view
.DoLoadOutlineCallback():
294 self
.SortAllChildren(self
.GetRootItem())
297 def GetCallbackView(self
):
298 rootItem
= self
.GetRootItem()
300 return self
.GetPyData(rootItem
)[self
.VIEW
]
305 class OutlineService(Service
.Service
):
308 #----------------------------------------------------------------------------
310 #----------------------------------------------------------------------------
311 SHOW_WINDOW
= wx
.NewId() # keep this line for each subclass, need unique ID for each Service
313 SORT_ASC
= wx
.NewId()
314 SORT_DESC
= wx
.NewId()
315 SORT_NONE
= wx
.NewId()
318 #----------------------------------------------------------------------------
320 #----------------------------------------------------------------------------
322 def __init__(self
, serviceName
, embeddedWindowLocation
= wx
.lib
.pydocview
.EMBEDDED_WINDOW_BOTTOM
):
323 Service
.Service
.__init
__(self
, serviceName
, embeddedWindowLocation
)
324 self
._validViewTypes
= []
327 def _CreateView(self
):
328 return OutlineView(self
)
331 def InstallControls(self
, frame
, menuBar
= None, toolBar
= None, statusBar
= None, document
= None):
332 Service
.Service
.InstallControls(self
, frame
, menuBar
, toolBar
, statusBar
, document
)
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
)
342 if wx
.GetApp().GetDocumentManager().GetFlags() & wx
.lib
.docview
.DOC_SDI
:
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
)
355 #----------------------------------------------------------------------------
356 # Event Processing Methods
357 #----------------------------------------------------------------------------
359 def ProcessEvent(self
, event
):
360 if Service
.Service
.ProcessEvent(self
, event
):
364 if id == OutlineService
.SORT_ASC
:
367 elif id == OutlineService
.SORT_DESC
:
370 elif id == OutlineService
.SORT_NONE
:
377 def ProcessUpdateUIEvent(self
, event
):
378 if Service
.Service
.ProcessUpdateUIEvent(self
, event
):
382 if id == OutlineService
.SORT_ASC
:
385 config
= wx
.ConfigBase_Get()
386 sort
= config
.ReadInt("OutlineSort", SORT_NONE
)
388 self
._outlineSortMenu
.Check(OutlineService
.SORT_ASC
, True)
390 self
._outlineSortMenu
.Check(OutlineService
.SORT_ASC
, False)
393 elif id == OutlineService
.SORT_DESC
:
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)
401 self
._outlineSortMenu
.Check(OutlineService
.SORT_DESC
, False)
404 elif id == OutlineService
.SORT_NONE
:
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)
412 self
._outlineSortMenu
.Check(OutlineService
.SORT_NONE
, False)
419 def OnSort(self
, event
):
421 if id == OutlineService
.SORT_ASC
:
422 wx
.ConfigBase_Get().WriteInt("OutlineSort", SORT_ASC
)
423 self
.GetView().OnSort(SORT_ASC
)
425 elif id == OutlineService
.SORT_DESC
:
426 wx
.ConfigBase_Get().WriteInt("OutlineSort", SORT_DESC
)
427 self
.GetView().OnSort(SORT_DESC
)
429 elif id == OutlineService
.SORT_NONE
:
430 wx
.ConfigBase_Get().WriteInt("OutlineSort", SORT_NONE
)
431 self
.GetView().OnSort(SORT_NONE
)
435 #----------------------------------------------------------------------------
436 # Service specific methods
437 #----------------------------------------------------------------------------
439 def LoadOutline(self
, view
, position
=-1, force
=False):
440 if not self
.GetView():
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()
449 self
.SyncToPosition(position
)
452 def SyncToPosition(self
, position
):
453 if not self
.GetView():
455 self
.GetView().GetTreeCtrl().SelectClosestItem(position
)
458 def OnCloseFrame(self
, event
):
459 Service
.Service
.OnCloseFrame(self
, event
)
460 self
.SaveExpansionState(clear
= True)
465 def SaveExpansionState(self
, clear
= False):
469 expanded
= self
.GetView().GetExpansionState()
470 wx
.ConfigBase_Get().Write("OutlineLastExpanded", expanded
.__repr
__())
473 def LoadExpansionState(self
):
474 expanded
= wx
.ConfigBase_Get().Read("OutlineLastExpanded")
476 self
.GetView().SetExpansionState(eval(expanded
))
479 #----------------------------------------------------------------------------
481 #----------------------------------------------------------------------------
483 def StartBackgroundTimer(self
):
484 self
._timer
= wx
.PyTimer(self
.DoBackgroundRefresh
)
485 self
._timer
.Start(250)
488 def DoBackgroundRefresh(self
):
489 """ Refresh the outline view periodically """
492 foundRegisteredView
= False
494 currView
= wx
.GetApp().GetDocumentManager().GetCurrentView()
496 for viewType
in self
._validViewTypes
:
497 if isinstance(currView
, viewType
):
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 AddViewTypeForBackgroundHandler(self
, viewType
):
509 self
._validViewTypes
.append(viewType
)
512 def GetViewTypesForBackgroundHandler(self
):
513 return self
._validViewTypes
516 def RemoveViewTypeForBackgroundHandler(self
, viewType
):
517 self
._validViewTypes
.remove(viewType
)