root/branches/mbutscher/work/lib/pwiki/DocStructureCtrl.py @ 263

Revision 263, 10.8 kB (checked in by mbutscher, 4 years ago)

branches/stable-2.1:
* Support for URL appendix "prnr" to create a relative link which is

not relocated (modified) when exported as HTML to a different
destination (backported to 2.1 to provide some backward compatibility
to old behavior)

* Bug fixed: Favorite wiki icons may open wrong wiki
* Bug fixed: Misleading error message and bad error handling for

corrupted wiki config file

* Internal: Default maximum length of compatible filename reduced from

250 to 120

branches/mbutscher/work:
* Less jumping around of selection in doc structure window when adding

text (thanks to Christian Ziemski)

* Support for spaces in bracketed URLs
* Option to control type of URL (bracketed or not) on drag&drop
* Support for URL appendix "prnr" to create a relative link which is

not relocated (modified) when exported as HTML to a different
destination

* Option to sort list in "Open Wiki Word" dialog in reverse

alphabetical order

* Shortcuts introduced to move one or more selected logical lines one

line up or down

* Bug fixed: Favorite wiki icons may open wrong wiki
* Bug fixed: Misleading error message and bad error handling for

corrupted wiki config file

* Internal: Deprecated makeRelUrlAbsolute() and makeAbsPathRelUrl() in

PersonalWikiFrame?, call functions in WikiDocument? instead

* Internal: Default maximum length of compatible filename reduced from

250 to 120

* File cleanup now in usable state (orphaned files complete, missing

files need work yet)

Line 
1from __future__ import with_statement
2
3# import hotshot
4# _prof = hotshot.Profile("hotshot.prf")
5
6import os, traceback, codecs, bisect
7import threading
8from time import sleep
9
10import wx
11
12import Utilities
13from Utilities import DUMBTHREADSTOP
14# from MiscEvent import KeyFunctionSinkAR
15from WikiExceptions import NotCurrentThreadException
16
17from wxHelper import EnhancedListControl, wxKeyFunctionSink, WindowUpdateLocker
18
19
20class DocStructureCtrl(EnhancedListControl):
21    def __init__(self, parent, ID, mainControl):
22        EnhancedListControl.__init__(self, parent, ID,
23                style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_NO_HEADER)
24
25        self.mainControl = mainControl
26
27        self.InsertColumn(0, u"", width=3000)
28
29        self.updatingThreadHolder = Utilities.ThreadHolder()
30        self.tocList = [] # List of tuples (char. start in text, headLevel, heading text)
31        self.tocListStarts = []   # List of the char. start items of self.tocList
32        self.mainControl.getMiscEvent().addListener(self)
33        self.sizeVisible = True   # False if this window has a size
34                # that it can't be read (one dim. less than 5 pixels)
35        self.ignoreOnChange = False
36
37        self.docPagePresenterSink = wxKeyFunctionSink((
38                ("loaded current doc page", self.onUpdateNeeded),
39                ("changed live text", self.onUpdateNeeded)
40#                 ("options changed", self.onUpdateNeeded)
41        ))
42
43        self.__sinkApp = wxKeyFunctionSink((
44                ("options changed", self.onUpdateNeeded),
45        ), wx.GetApp().getMiscEvent(), self)
46
47#         if not self.mainControl.isMainWindowConstructed():
48#             # Install event handler to wait for construction
49#             self.__sinkMainFrame = wxKeyFunctionSink((
50#                     ("constructed main window", self.onConstructedMainWindow),
51#             ), self.mainControl.getMiscEvent(), self)
52#         else:
53#             self.onConstructedMainWindow(None)
54
55        self.__sinkMainFrame = wxKeyFunctionSink((
56                ("idle visible", self.onIdleVisible),
57        ), self.mainControl.getMiscEvent(), self)
58
59        currPres = self.mainControl.getCurrentDocPagePresenter()
60        if currPres is not None:
61            self.docPagePresenterSink.setEventSource(currPres.getMiscEvent())
62       
63        self.lastSelection = (-1, -1)
64
65        self.updateList()
66
67        wx.EVT_WINDOW_DESTROY(self, self.OnDestroy)
68        wx.EVT_LIST_ITEM_SELECTED(self, self.GetId(), self.OnItemSelected)
69        wx.EVT_LIST_ITEM_ACTIVATED(self, self.GetId(), self.OnItemActivated)
70        wx.EVT_SIZE(self, self.OnSize)
71       
72        wx.EVT_KILL_FOCUS(self, self.OnKillFocus)
73#         wx.EVT_LEFT_UP(self, self.OnLeftUp)
74
75
76    def close(self):
77        """
78        """
79        self.updatingThreadHolder.setThread(None)
80        self.docPagePresenterSink.disconnect()
81        self.__sinkApp.disconnect()
82
83
84    def isVisibleEffect(self):
85        """
86        Is this control effectively visible?
87        """
88        return self.sizeVisible
89
90
91    def handleVisibilityChange(self):
92        """
93        Only call after isVisibleEffect() really changed its value.
94        The new value is taken from isVisibleEffect(), the old is assumed
95        to be the opposite.
96        """
97        self.__sinkMainFrame.enable(self.isVisibleEffect())
98
99        if self.isVisibleEffect():
100            presenter = self.mainControl.getCurrentDocPagePresenter()
101            if presenter is not None:
102                self.docPagePresenterSink.setEventSource(presenter.getMiscEvent())
103            else:
104                self.docPagePresenterSink.setEventSource(None)
105            self.updateList()
106        else:
107            self.docPagePresenterSink.disconnect()
108            if wx.Window.FindFocus() is self:
109                self.mainControl.getMainAreaPanel().SetFocus()
110
111
112    def OnDestroy(self, evt):
113        self.close()
114
115
116    def OnSize(self, evt):
117        evt.Skip()
118        oldVisible = self.isVisibleEffect()
119        size = evt.GetSize()
120        self.sizeVisible = size.GetHeight() >= 5 and size.GetWidth() >= 5
121       
122        if oldVisible != self.isVisibleEffect():
123            self.handleVisibilityChange()
124
125
126#     def onConstructedMainWindow(self, evt):
127#         """
128#         Now we can register idle handler.
129#         """
130#         wx.EVT_IDLE(self, self.OnIdle)
131
132
133    def onIdleVisible(self, evt):
134        self.checkSelectionChanged()
135       
136       
137    def checkSelectionChanged(self, callAlways=False):
138        if not self.isVisibleEffect():
139            return
140
141        presenter = self.mainControl.getCurrentDocPagePresenter()
142        if presenter is None:
143            return
144
145        subCtrl = presenter.getSubControl("textedit")
146        if subCtrl is None:
147            return
148       
149        sel = subCtrl.LineFromPosition(subCtrl.GetCurrentPos())
150       
151        if sel != self.lastSelection or callAlways:
152            self.lastSelection = sel
153            self.onSelectionChanged(subCtrl.GetSelectionCharPos())
154
155
156    def onSelectionChanged(self, sel):
157        """
158        This is not directly supported by Scintilla, but called by OnIdle().
159        """
160        if not self.mainControl.getConfig().getboolean("main",
161                "docStructure_autofollow"):
162            return
163           
164        idx = bisect.bisect_right(self.tocListStarts, sel[0]) - 1
165
166        self.ignoreOnChange = True
167        try:
168            self.SelectSingle(idx, scrollVisible=True)
169        finally:   
170            self.ignoreOnChange = False
171
172
173
174    def miscEventHappened(self, miscevt):
175        """
176        Handle misc events
177        """
178        if self.sizeVisible and miscevt.getSource() is self.mainControl:
179            if miscevt.has_key("changed current presenter"):
180                presenter = self.mainControl.getCurrentDocPagePresenter()
181                if presenter is not None:
182                    self.docPagePresenterSink.setEventSource(presenter.getMiscEvent())
183                else:
184                    self.docPagePresenterSink.setEventSource(None)
185
186                self.updateList()
187
188
189    def onUpdateNeeded(self, miscevt):
190        self.updateList()
191
192
193    def updateList(self):
194#         if self.mainControl.getConfig().getboolean("main",
195#                 "docStructure_autofollow"):
196#             self.tocListStarts = []
197#             self.SelectSingle(-1)
198
199        presenter = self.mainControl.getCurrentDocPagePresenter()
200
201        if presenter is None:
202            self.tocList = []
203            self.tocListStarts = []
204            self.applyTocList()
205            return
206
207#         print "updateList"
208        text = presenter.getLiveText()
209        docPage = presenter.getDocPage()
210
211        # Asynchronous update
212        uth = self.updatingThreadHolder
213
214        depth = presenter.getConfig().getint(
215                "main", "docStructure_depth")
216       
217        t = threading.Thread(None, self.buildTocList,
218                args = (text, docPage, depth, uth))
219        uth.setThread(t)
220        t.start()
221
222
223    def buildTocList(self, text, docPage, depth, threadstop=DUMBTHREADSTOP):
224        """
225        Build toc list and put data in self.tocList. Finally call applyTocList()
226        to show data.
227        """
228        try:
229            if docPage is None:
230                self.tocList = []
231                self.tocListStarts = []
232                Utilities.callInMainThread(self.applyTocList)
233                return
234
235            sleep(0.3)   # Make configurable?
236            threadstop.testRunning()
237
238            depth = min(depth, 15)
239            depth = max(depth, 1)
240
241            pageAst = docPage.getLivePageAst(threadstop=threadstop)
242
243            result = []
244            for node in pageAst.iterFlatByName("heading"):
245                threadstop.testRunning()
246                if node.level > depth:
247                    continue
248
249                title = u"  " * (node.level - 1) + node.contentNode.getString()
250                while title.endswith(u"\n"):
251                    title = title[:-1]
252                result.append((node.pos, node.level, title))
253
254            threadstop.testRunning()
255
256            self.tocList = result
257            self.tocListStarts = [r[0] for r in result]
258
259            Utilities.callInMainThread(self.applyTocList)
260
261        except NotCurrentThreadException:
262            return
263
264
265    def applyTocList(self):
266        """
267        Show the content of self.tocList in the ListCtrl
268        """
269        with WindowUpdateLocker(self):
270            self.DeleteAllItems()
271            for start, headLevel, text in self.tocList:
272                self.InsertStringItem(self.GetItemCount(), text)
273#             self.SetColumnWidth(0, wx.LIST_AUTOSIZE)
274            self.autosizeColumn(0)
275            self.checkSelectionChanged(callAlways=True)
276
277
278    def OnKillFocus(self, evt):
279#         self.SelectSingle(-1)
280        evt.Skip()
281       
282       
283#     def OnLeftUp(self, evt):
284#         print "OnLeftUp"
285#         if self.FindFocus() is self:
286#             evt.Skip()
287#         # Consume event otherwise
288
289
290    def displayInSubcontrol(self, start):   # , focusToSubctrl
291        """
292        Display title in subcontrol of current presenter which
293        starts at char position  start  in page text.
294
295        focusToSubctrl -- True iff subcontrol should become focused after
296                displaying is done
297        """
298
299        presenter = self.mainControl.getCurrentDocPagePresenter()
300        if presenter is None:
301            return
302
303        # Find out which subcontrol is currently active
304        scName = presenter.getCurrentSubControlName()
305        subCtrl = presenter.getSubControl(scName)
306
307        if scName == "textedit":
308            # Text editor is active
309            subCtrl.gotoCharPos(start)
310        elif scName == "preview":
311            # HTML preview
312            subCtrl.gotoAnchor(u".h%i" % start)
313
314#         if focusToSubctrl:
315#             subCtrl.SetFocus()
316#             # wx.CallAfter(presenter.SetFocus)
317
318
319    def OnItemSelected(self, evt):
320        if self.ignoreOnChange:
321            return
322
323        start = self.tocListStarts[evt.GetIndex()]
324        self.displayInSubcontrol(start)
325
326
327
328    def OnItemActivated(self, evt):
329        presenter = self.mainControl.getCurrentDocPagePresenter()
330        if presenter is None:
331            return
332
333        # Find out which subcontrol is currently active
334        scName = presenter.getCurrentSubControlName()
335        subCtrl = presenter.getSubControl(scName)
336
337        if self.mainControl.getConfig().getboolean("main",
338                "docStructure_autohide", False):
339            # Auto-hide tree
340            self.mainControl.setShowDocStructure(False)
341
342        subCtrl.SetFocus()
343        # wx.CallAfter(presenter.SetFocus)
344
345           
346       
347
Note: See TracBrowser for help on using the browser.