1# This file is part of the Frescobaldi project, http://www.frescobaldi.org/
2#
3# Copyright (c) 2011 - 2014 by Wilbert Berendsen
4#
5# This program is free software; you can redistribute it and/or
6# modify it under the terms of the GNU General Public License
7# as published by the Free Software Foundation; either version 2
8# of the License, or (at your option) any later version.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, write to the Free Software
17# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18# See http://www.gnu.org/licenses/ for more information.
19
20"""
21The sidebar in the editor View.
22"""
23
24
25import sys
26
27from PyQt5.QtCore import QSettings
28from PyQt5.QtWidgets import QAction, QApplication
29
30import app
31import actioncollection
32import actioncollectionmanager
33import plugin
34
35
36class SideBarManager(plugin.MainWindowPlugin):
37    def __init__(self, mainwindow):
38        self.actionCollection = ac = Actions()
39        actioncollectionmanager.manager(mainwindow).addActionCollection(ac)
40        ac.view_linenumbers.triggered.connect(self.toggleLineNumbers)
41        ac.folding_enable.triggered.connect(self.toggleFolding)
42        ac.folding_fold_current.triggered.connect(self.foldCurrent)
43        ac.folding_fold_top.triggered.connect(self.foldTop)
44        ac.folding_unfold_current.triggered.connect(self.unfoldCurrent)
45        ac.folding_fold_all.triggered.connect(self.foldAll)
46        ac.folding_unfold_all.triggered.connect(self.unfoldAll)
47        mainwindow.viewManager.activeViewSpaceChanged.connect(self.updateActions)
48        app.viewSpaceCreated.connect(self.newViewSpace)
49        # there is always one ViewSpace, initialize it
50        self.manager().loadSettings()
51        self.updateActions()
52
53    def manager(self, viewspace=None):
54        """Returns the ViewSpaceSideBarManager for the (current) ViewSpace."""
55        if viewspace is None:
56            viewspace  = self.mainwindow().viewManager.activeViewSpace()
57        return ViewSpaceSideBarManager.instance(viewspace)
58
59    def toggleLineNumbers(self):
60        manager = self.manager()
61        manager.setLineNumbersVisible(not manager.lineNumbersVisible())
62        manager.saveSettings()
63
64    def toggleFolding(self):
65        """Toggle folding in the current view."""
66        viewspace = self.mainwindow().viewManager.activeViewSpace()
67        manager = self.manager(viewspace)
68        enable = not manager.foldingVisible()
69        document = viewspace.document()
70
71        # do it for all managers that display our document
72        for m in manager.instances():
73            if m.viewSpace().document() is document:
74                m.setFoldingVisible(enable)
75        # and also update in other windows
76        for i in self.instances():
77            i.updateActions()
78        manager.saveSettings()
79        # unfold the document if disabled
80        if not enable:
81            self.folder().unfold_all()
82
83    def folder(self):
84        """Get the Folder for the current document."""
85        import folding
86        return folding.Folder.get(self.mainwindow().currentDocument())
87
88    def foldCurrent(self):
89        """Fold current region."""
90        self.folder().fold(self.mainwindow().textCursor().block())
91
92    def foldTop(self):
93        """Fold current region to toplevel."""
94        self.folder().fold(self.mainwindow().textCursor().block(), -1)
95
96    def unfoldCurrent(self):
97        """Unfold current region."""
98        block = self.mainwindow().textCursor().block()
99        folder = self.folder()
100        folder.ensure_visible(block)
101        folder.unfold(block)
102
103    def foldAll(self):
104        """Fold the whole document."""
105        self.folder().fold_all()
106
107    def unfoldAll(self):
108        """Unfold the whole document."""
109        self.folder().unfold_all()
110
111    def updateActions(self):
112        manager = self.manager()
113        ac = self.actionCollection
114        ac.view_linenumbers.setChecked(manager.lineNumbersVisible())
115        folding = manager.foldingVisible()
116        ac.folding_enable.setChecked(folding)
117        ac.folding_fold_current.setEnabled(folding)
118        ac.folding_fold_top.setEnabled(folding)
119        ac.folding_unfold_current.setEnabled(folding)
120        ac.folding_fold_all.setEnabled(folding)
121        ac.folding_unfold_all.setEnabled(folding)
122
123    def newViewSpace(self, viewspace):
124        viewmanager = viewspace.manager()
125        if viewmanager and viewmanager.window() is self.mainwindow():
126            # let the new viewspace take over the settings of the currently
127            # active viewspace
128            self.manager(viewspace).copySettings(self.manager())
129
130
131class ViewSpaceSideBarManager(plugin.ViewSpacePlugin):
132    """Manages SideBar settings and behaviour for one ViewSpace."""
133    def __init__(self, viewspace):
134        self._line_numbers = False
135        self._linenumberarea = None
136        self._folding = False
137        self._foldingarea = None
138        viewspace.viewChanged.connect(self.updateView)
139
140    def loadSettings(self):
141        """Loads the settings from config."""
142        s = QSettings()
143        s.beginGroup("sidebar")
144        line_numbers = s.value("line_numbers", self._line_numbers, bool)
145        self.setLineNumbersVisible(line_numbers)
146        folding = s.value("folding", self._folding, bool)
147        self.setFoldingVisible(folding)
148
149    def saveSettings(self):
150        """Saves the settings to config."""
151        s = QSettings()
152        s.beginGroup("sidebar")
153        s.setValue("line_numbers", self.lineNumbersVisible())
154        s.setValue("folding",self.foldingVisible())
155
156    def copySettings(self, other):
157        """Takes over the settings from another viewspace's manager."""
158        self.setLineNumbersVisible(other.lineNumbersVisible())
159        self.setFoldingVisible(other.foldingVisible())
160
161    def setLineNumbersVisible(self, visible):
162        """Set whether line numbers are to be shown."""
163        if visible == self._line_numbers:
164            return
165        self._line_numbers = visible
166        self.updateView()
167
168    def lineNumbersVisible(self):
169        """Returns whether line numbers are shown."""
170        return self._line_numbers
171
172    def setFoldingVisible(self, visible):
173        """Set whether folding indicators are to be shown."""
174        if visible == self._folding:
175            return
176        self._folding = visible
177        self.updateView()
178
179    def foldingVisible(self):
180        """Return whether folding indicators are to be shown."""
181        return self._folding
182
183    def updateView(self):
184        """Adjust the sidebar in the current View in the sidebar."""
185        view = self.viewSpace().activeView()
186        if not view:
187            return
188
189        def add(widget):
190            """Adds a widget to the side of the view."""
191            from gadgets import borderlayout
192            if QApplication.isRightToLeft():
193                side = borderlayout.RIGHT
194            else:
195                side = borderlayout.LEFT
196            borderlayout.BorderLayout.get(view).addWidget(widget, side)
197
198        # add or remove the folding widget
199        if self._folding:
200            if not self._foldingarea:
201                import folding
202                self._foldingarea = folding.FoldingArea()
203                self._foldingarea.setPalette(QApplication.palette())
204            add(self._foldingarea)
205            self._foldingarea.setTextEdit(view)
206        elif self._foldingarea:
207            self._foldingarea.deleteLater()
208            self._foldingarea = None
209
210        # add of remove the linenumbers widget
211        if self._line_numbers:
212            if not self._linenumberarea:
213                from widgets import linenumberarea
214                self._linenumberarea = linenumberarea.LineNumberArea()
215            add(self._linenumberarea)
216            self._linenumberarea.setTextEdit(view)
217        elif self._linenumberarea:
218            self._linenumberarea.deleteLater()
219            self._linenumberarea = None
220
221        # display horizontal lines where text is collapsed
222        if self._folding:
223            import folding
224            view.viewport().installEventFilter(folding.line_painter)
225        else:
226            # don't import the folding module if it was not loaded anyway
227            folding = sys.modules.get('folding')
228            if folding:
229                view.viewport().removeEventFilter(folding.line_painter)
230
231
232class Actions(actioncollection.ActionCollection):
233    name = "sidebar"
234    def createActions(self, parent):
235        self.view_linenumbers = QAction(parent, checkable=True)
236        self.folding_enable = QAction(parent, checkable=True)
237        self.folding_fold_current = QAction(parent)
238        self.folding_fold_top = QAction(parent)
239        self.folding_unfold_current = QAction(parent)
240        self.folding_fold_all = QAction(parent)
241        self.folding_unfold_all = QAction(parent)
242
243    def translateUI(self):
244        self.view_linenumbers.setText(_("&Line Numbers"))
245        self.folding_enable.setText(_("&Enable Folding"))
246        self.folding_fold_current.setText(_("&Fold Current Region"))
247        self.folding_fold_top.setText(_("Fold &Top Region"))
248        self.folding_unfold_current.setText(_("&Unfold Current Region"))
249        self.folding_fold_all.setText(_("Fold &All"))
250        self.folding_unfold_all.setText(_("U&nfold All"))
251
252
253