~jaap.karssenberg/zim/pyzim-gtk3

« back to all changes in this revision

Viewing changes to zim/plugins/tableofcontents.py

  • Committer: Jaap Karssenberg
  • Date: 2014-03-08 11:47:43 UTC
  • mfrom: (668.1.49 pyzim-refactor)
  • Revision ID: jaap.karssenberg@gmail.com-20140308114743-fero6uvy9zirbb4o
Merge branch with refactoring

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# -*- coding: utf-8 -*-
2
2
 
3
 
# Copyright 2012-2013 Jaap Karssenberg <jaap.karssenberg@gmail.com>
4
 
 
5
 
##### TODO TODO - connect the "show h1" preference !
 
3
# Copyright 2012, 2013 Jaap Karssenberg <jaap.karssenberg@gmail.com>
6
4
 
7
5
from __future__ import with_statement
8
6
 
13
11
import re
14
12
import datetime
15
13
 
16
 
from zim.plugins import PluginClass
 
14
from zim.plugins import PluginClass, WindowExtension, extends
17
15
from zim.notebook import Path
 
16
from zim.formats import HEADING
18
17
from zim.gui.widgets import LEFT_PANE, PANE_POSITIONS, BrowserTreeView, populate_popup_add_separator
19
18
from zim.gui.pageview import FIND_REGEX, SCROLL_TO_MARK_MARGIN, _is_heading_tag
20
19
from zim.signals import ConnectorMixin
82
81
                        # T: option for plugin preferences
83
82
                ('floating', 'bool', _('Show ToC as floating widget instead of in sidepane'), True),
84
83
                        # T: option for plugin preferences
85
 
                #~ ('show_h1', 'bool', _('Show the page title heading in the ToC'), False),
 
84
                ('show_h1', 'bool', _('Show the page title heading in the ToC'), False),
86
85
                        # T: option for plugin preferences
87
86
        )
88
87
        # TODO disable pane setting if not embedded
89
88
 
90
 
        def __init__(self, ui):
91
 
                PluginClass.__init__(self, ui)
92
 
                self.floating_widget = None
93
 
                self.sidepane_widget = None
94
 
 
95
 
        def finalize_notebook(self, ui):
96
 
                self.do_preferences_changed()
97
 
 
98
 
        def destroy(self):
99
 
                self.disconnect_sidepane()
100
 
                self.disconnect_floating()
101
 
                PluginClass.destroy(self)
102
 
 
103
 
        def do_preferences_changed(self):
104
 
                if self.ui.ui_type != 'gtk':
 
89
        def __init__(self, config=None):
 
90
                PluginClass.__init__(self, config)
 
91
                self.on_preferences_changed(self.preferences)
 
92
                self.preferences.connect('changed', self.on_preferences_changed)
 
93
 
 
94
        def on_preferences_changed(self, preferences):
 
95
                if preferences['floating']:
 
96
                        self.set_extension_class('MainWindow', MainWindowExtensionFloating)
 
97
                else:
 
98
                        self.set_extension_class('MainWindow', MainWindowExtensionEmbedded)
 
99
 
 
100
 
 
101
@extends('MainWindow', autoload=False)
 
102
class MainWindowExtensionEmbedded(WindowExtension):
 
103
 
 
104
        def __init__(self, plugin, window):
 
105
                WindowExtension.__init__(self, plugin, window)
 
106
                self.widget = SidePaneToC(self.window.ui, self.window.pageview) # XXX
 
107
 
 
108
                self.on_preferences_changed(plugin.preferences)
 
109
                self.connectto(plugin.preferences, 'changed', self.on_preferences_changed)
 
110
 
 
111
        def on_preferences_changed(self, preferences):
 
112
                if self.widget is None:
105
113
                        return
106
114
 
107
 
                if self.preferences['floating']:
108
 
                        self.disconnect_sidepane()
109
 
                        self.connect_floating()
110
 
                else:
111
 
                        self.disconnect_floating()
112
 
                        self.connect_sidepane()
113
 
 
114
 
        def connect_sidepane(self):
115
 
                if not self.sidepane_widget:
116
 
                        self.sidepane_widget = SidePaneToC(self.ui)
117
 
                else:
118
 
                        self.ui.mainwindow.remove(self.sidepane_widget)
119
 
 
120
 
                self.ui.mainwindow.add_tab(
121
 
                        _('ToC'), self.sidepane_widget, self.preferences['pane'])
 
115
                try:
 
116
                        self.window.remove(self.widget)
 
117
                except ValueError:
 
118
                        pass
 
119
 
 
120
                self.window.add_tab(
 
121
                        _('ToC'), self.widget, preferences['pane'])
122
122
                        # T: widget label
123
 
                self.sidepane_widget.show_all()
124
 
 
125
 
        def disconnect_sidepane(self):
126
 
                if self.sidepane_widget:
127
 
                        self.ui.mainwindow.remove(self.sidepane_widget)
128
 
                        self.sidepane_widget.disconnect_all()
129
 
                        self.sidepane_widget.destroy()
130
 
                        self.sidepane_widget = None
131
 
 
132
 
        def connect_floating(self):
133
 
                if not self.floating_widget:
134
 
                        textview = self.ui.mainwindow.pageview.view
135
 
                        self.floating_widget = FloatingToC(self.ui, textview)
136
 
 
137
 
        def disconnect_floating(self):
138
 
                if self.floating_widget:
139
 
                        self.floating_widget.disconnect_all()
140
 
                        self.floating_widget.destroy()
141
 
                        self.floating_widget = None
 
123
                self.widget.show_all()
 
124
 
 
125
                self.widget.set_show_h1(preferences['show_h1'])
 
126
 
 
127
        def teardown(self):
 
128
                self.window.remove(self.widget)
 
129
                self.widget.disconnect_all()
 
130
                self.widget = None
 
131
 
 
132
 
 
133
@extends('MainWindow', autoload=False)
 
134
class MainWindowExtensionFloating(WindowExtension):
 
135
 
 
136
        def __init__(self, plugin, window):
 
137
                WindowExtension.__init__(self, plugin, window)
 
138
                self.widget = FloatingToC(self.window.ui, self.window.pageview) # XXX
 
139
 
 
140
                self.on_preferences_changed(plugin.preferences)
 
141
                self.connectto(plugin.preferences, 'changed', self.on_preferences_changed)
 
142
 
 
143
        def on_preferences_changed(self, preferences):
 
144
                self.widget.set_show_h1(preferences['show_h1'])
 
145
 
 
146
        def teardown(self):
 
147
                self.widget.disconnect_all()
 
148
                self.widget.destroy()
 
149
 
142
150
 
143
151
 
144
152
TEXT_COL = 0
161
169
                self.append_column(column)
162
170
 
163
171
 
 
172
 
164
173
class ToCTreeModel(gtk.TreeStore):
165
174
 
166
175
        def __init__(self):
167
176
                gtk.TreeStore.__init__(self, str) # TEXT_COL
168
177
 
169
 
        def populate(self, parsetree):
 
178
        def populate(self, parsetree, show_h1):
170
179
                self.clear()
171
180
                headings = []
172
 
                for el in parsetree.findall('h'):
173
 
                        headings.append( (int(el.attrib['level']), el.text) )
174
 
 
175
 
                if headings \
 
181
                for heading in parsetree.findall(HEADING):
 
182
                        headings.append( (int(heading.attrib['level']), heading.gettext()) )
 
183
 
 
184
 
 
185
                if not show_h1 \
 
186
                and headings \
176
187
                and headings[0][0] == 1 \
177
188
                and all(h[0] > 1 for h in headings[1:]):
178
189
                        headings.pop(0) # do not show first heading
187
198
                        stack.append((level, iter))
188
199
 
189
200
 
 
201
 
 
202
 
190
203
class ToCWidget(ConnectorMixin, gtk.ScrolledWindow):
191
204
 
192
 
        def __init__(self, ui, ellipsis):
 
205
        def __init__(self, ui, pageview, ellipsis, show_h1=False):
193
206
                gtk.ScrolledWindow.__init__(self)
194
 
 
195
 
                self.ui = ui
196
 
                self.page = None
 
207
                self.show_h1 = show_h1
197
208
 
198
209
                self.treeview = ToCTreeView(ellipsis)
199
 
                self.add(self.treeview)
200
 
 
201
210
                self.treeview.connect('row-activated', self.on_heading_activated)
202
211
                self.treeview.connect('populate-popup', self.on_populate_popup)
 
212
                self.add(self.treeview)
203
213
 
 
214
                # XXX remove ui - use signals from pageview for this
204
215
                self.connectto(ui, 'open-page')
205
216
                self.connectto(ui.notebook, 'stored-page')
206
 
                if ui.page:
207
 
                        self.on_open_page(ui, ui.page, Path(ui.page.name))
 
217
 
 
218
                self.pageview = pageview
 
219
                if self.pageview.page:
 
220
                        self.load_page(self.pageview.page)
 
221
 
 
222
        def set_show_h1(self, show_h1):
 
223
                if show_h1 != self.show_h1:
 
224
                        self.show_h1 = show_h1
 
225
                        if self.pageview.page:
 
226
                                self.load_page(self.pageview.page)
208
227
 
209
228
        def on_open_page(self, ui, page, path):
210
 
                self.page = page
211
 
                self._load_page(page)
 
229
                self.load_page(page)
212
230
 
213
231
        def on_stored_page(self, notebook, page):
214
 
                if page == self.page:
215
 
                        self._load_page(page)
 
232
                if page == self.pageview.page:
 
233
                        self.load_page(page)
216
234
 
217
 
        def _load_page(self, page):
 
235
        def load_page(self, page):
218
236
                model = self.treeview.get_model()
219
237
                tree = page.get_parsetree()
220
238
                if tree is None:
221
239
                        model.clear()
222
240
                else:
223
 
                        model.populate(tree)
 
241
                        model.populate(tree, self.show_h1)
224
242
                self.treeview.expand_all()
225
243
 
226
244
        def on_heading_activated(self, treeview, path, column):
233
251
                model = self.treeview.get_model()
234
252
                text = model[path][TEXT_COL].decode('utf-8')
235
253
 
236
 
                textview = self.ui.mainwindow.pageview.view # FIXME nicer interface for this
 
254
                textview = self.pageview.view
237
255
                buffer = textview.get_buffer()
238
256
                if select_heading(buffer, text):
239
257
                        textview.scroll_to_mark(buffer.get_insert(), SCROLL_TO_MARK_MARGIN)
255
273
                else:
256
274
                        endtext = None
257
275
 
258
 
                textview = self.ui.mainwindow.pageview.view # FIXME nicer interface for this
 
276
                textview = self.pageview.view
259
277
                buffer = textview.get_buffer()
260
278
                start = find_heading(buffer, starttext)
261
279
                if endtext:
262
280
                        end = find_heading(buffer, endtext)
263
281
                else:
264
282
                        end = buffer.get_end_iter()
 
283
 
265
284
                if start and end:
266
 
                        buffer.select_range(startiter, enditer)
 
285
                        buffer.select_range(start, end)
267
286
 
268
287
        def on_populate_popup(self, treeview, menu):
269
288
                model, paths = treeview.get_selection().get_selected_rows()
304
323
                        for i in self._walk(model, iter):
305
324
                                p = model.get_path(i)
306
325
                                if not p in seen:
307
 
                                        newlevel = len(p) - 1
 
326
                                        if self.show_h1:
 
327
                                                newlevel = len(p) - 1
 
328
                                        else:
 
329
                                                newlevel = len(p)
308
330
                                        self._format(p, newlevel)
309
331
                                seen.add(p)
310
332
 
311
 
                self._load_page(self.page)
 
333
                self.load_page(self.pageview.page)
312
334
                return True
313
335
 
314
336
        def can_demote(self, paths):
343
365
                        for i in self._walk(model, iter):
344
366
                                p = model.get_path(i)
345
367
                                if not p in seen:
346
 
                                        newlevel = len(p) + 1
 
368
                                        if self.show_h1:
 
369
                                                newlevel = len(p) + 1
 
370
                                        else:
 
371
                                                newlevel = len(p) + 2
 
372
 
347
373
                                        self._format(p, newlevel)
348
374
                                seen.add(p)
349
375
 
350
 
                self._load_page(self.page)
 
376
                self.load_page(self.pageview.page)
351
377
                return True
352
378
 
353
379
 
358
384
                while child:
359
385
                        for i in self._walk(model, child):
360
386
                                yield i
361
 
                        child = model.iter_next(iter)
 
387
                        child = model.iter_next(child)
362
388
 
363
389
        def _format(self, path, level):
364
390
                assert level > 0 and level < 7
365
391
                if self.select_heading(path):
366
 
                        self.ui.mainwindow.pageview.toggle_format('h' + str(level))
 
392
                        self.pageview.toggle_format('h' + str(level))
367
393
 
368
394
 
369
395
class SidePaneToC(ToCWidget):
370
396
 
371
 
        def __init__(self, ui):
372
 
                ToCWidget.__init__(self, ui, ellipsis=True)
 
397
        def __init__(self, ui, pageview):
 
398
                ToCWidget.__init__(self, ui, pageview, ellipsis=True)
373
399
                self.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
374
400
                self.set_shadow_type(gtk.SHADOW_IN)
375
401
                self.set_size_request(-1, 200) # Fixed Height
424
450
gobject.type_register(BoxWidget)
425
451
 
426
452
 
427
 
import collections
428
 
 
429
453
class FloatingToC(BoxWidget, ConnectorMixin):
430
454
 
431
455
        # This class does all the work to keep the floating window in
434
458
 
435
459
        TEXTVIEW_OFFSET = 5
436
460
 
437
 
        def __init__(self, ui, textview):
 
461
        def __init__(self, ui, pageview):
438
462
                BoxWidget.__init__(self)
439
463
 
440
464
                hscroll = gtk.HScrollbar(gtk.Adjustment())
443
467
                self.head = gtk.Label(_('ToC'))
444
468
                self.head.set_padding(5, 1)
445
469
 
446
 
                self.widget = ToCWidget(ui, ellipsis=False)
 
470
                self.widget = ToCWidget(ui, pageview, ellipsis=False)
447
471
                self.widget.set_shadow_type(gtk.SHADOW_NONE)
448
472
                self.widget.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
449
473
                        # Setting horizontal scroll automatic as well
461
485
                # Need to wrap in event box to make widget visible
462
486
                # probably because Containers normally don't have their own
463
487
                # gdk window. So would paint directly on background window.
464
 
                self.textview = textview
465
 
                self._text_view_allocation = (textview.allocation.width, textview.allocation.height)
 
488
                self.textview = pageview.view
 
489
                self._text_view_allocation = (
 
490
                        self.textview.allocation.width,
 
491
                        self.textview.allocation.height
 
492
                )
466
493
                self._event_box = gtk.EventBox()
467
494
                self._event_box.add(self)
468
495
 
469
 
                textview.add_child_in_window(self._event_box, gtk.TEXT_WINDOW_WIDGET, 0, 0)
470
 
                self.connectto(textview,
 
496
                self.textview.add_child_in_window(self._event_box, gtk.TEXT_WINDOW_WIDGET, 0, 0)
 
497
                self.connectto(self.textview,
471
498
                        'size-allocate',
472
499
                        handler=self.on_size_allocate_textview,
473
500
                )
478
505
 
479
506
                self._event_box.show_all()
480
507
 
 
508
        def set_show_h1(self, show_h1):
 
509
                self.widget.set_show_h1(show_h1)
 
510
 
481
511
        def disconnect_all(self):
482
512
                self.widget.disconnect_all()
483
513
                ConnectorMixin.disconnect_all(self)
487
517
                BoxWidget.destroy(self)
488
518
 
489
519
        def on_toggle(self, *a):
490
 
                self.widget.set_visible(
491
 
                        not self.widget.get_visible()
 
520
                self.widget.set_property('visible',
 
521
                        not self.widget.get_property('visible')
492
522
                )
493
523
                self.queue_draw()
494
524
 
508
538
                border = self.get_border_width()
509
539
                spacing = self.get_spacing()
510
540
 
511
 
                if self.widget.get_visible():
 
541
                if self.widget.get_property('visible'):
512
542
                        tree_w, tree_h = self.widget.treeview.size_request()
513
543
                        tree_h = max(tree_h, head_h) # always show empty space if no content
514
544
                        tree_w += 1 # Allow minimal frame for scrolledwindow
548
578
                        height=head_height
549
579
                ))
550
580
 
551
 
                if self.widget.get_visible():
 
581
                if self.widget.get_property('visible'):
552
582
                        body_w = allocation.width - 2*border
553
583
                        body_h = allocation.height - 2*border - spacing - head_height
554
584
 
562
592
                                height=body_h
563
593
                        ))
564
594
 
565
 
 
566
595
        def on_size_allocate_textview(self, textview, a):
567
596
                new_allocation = (a.width, a.height)
568
597
                if new_allocation != self._text_view_allocation: