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
)