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
)