40
40
from zim.index import LINK_DIR_BACKWARD
41
41
from zim.config import data_file, config_file, data_dirs, ListDict, value_is_coord
42
42
from zim.parsing import url_encode, URL_ENCODE_DATA, is_win32_share_re
43
from zim.history import History, HistoryRecord
43
from zim.history import History, HistoryPath
44
44
from zim.gui.pathbar import NamespacePathBar, RecentPathBar, HistoryPathBar
45
45
from zim.gui.pageindex import PageIndex
46
46
from zim.gui.pageview import PageView
47
47
from zim.gui.widgets import ui_environment, gtk_window_set_default_icon, \
48
48
Button, MenuButton, \
50
ErrorDialog, QuestionDialog, FileDialog, ProgressBarDialog, MessageDialog
50
ErrorDialog, QuestionDialog, FileDialog, ProgressBarDialog, MessageDialog, \
51
PromptExistingFileDialog
51
52
from zim.gui.clipboard import Clipboard
52
53
from zim.gui.applications import ApplicationManager, CustomToolManager
274
277
'open-page': (gobject.SIGNAL_RUN_LAST, None, (object, object)),
275
278
'close-page': (gobject.SIGNAL_RUN_LAST, None, (object,)),
279
'new-window': (gobject.SIGNAL_RUN_LAST, None, (object,)),
276
280
'preferences-changed': (gobject.SIGNAL_RUN_LAST, None, ()),
277
281
'readonly-changed': (gobject.SIGNAL_RUN_LAST, None, ()),
278
282
'quit': (gobject.SIGNAL_RUN_LAST, None, ()),
333
337
if not self.preferences['GtkInterface'].get(type):
334
338
default = manager.get_default_helper(type)
336
self.preferences['GtkInterface'].setdefault(type, default.key)
340
self.preferences['GtkInterface'][type] = default.key
342
self.preferences['GtkInterface'][type] = None
338
343
logger.warn('No helper application defined for %s', type)
340
345
self.mainwindow = MainWindow(self, fullscreen, geometry)
685
690
register.setdefault(category, [])
686
691
register[category].append((section, key, type, label))
694
def register_new_window(self, window):
695
'''Called by windows and dialog to register themselves with
696
the application. Used e.g. by plugins that want to add some
697
widget to specific windows.
699
#~ print 'WINDOW:', window
700
self.emit('new-window', window)
702
def do_new_window(self, window):
703
pass # TODO: keep register of pageviews
688
705
def get_path_context(self):
689
706
'''Returns the current 'context' for actions that want a path to start
690
707
with. Asks the mainwindow for a selected page, defaults to the
793
810
def open_page(self, path=None):
794
811
'''Emit the open-page signal. The argument 'path' can either be a Page
795
812
or a Path object. If 'page' is None a dialog is shown
796
to specify the page. If 'path' is a HistoryRecord we assume that this
813
to specify the page. If 'path' is a HistoryPath we assume that this
797
814
call is the result of a history action and the page is not added to
798
815
the history. The original path object is given as the second argument
799
816
in the signal, so handlers can inspect how this method was called.
950
967
name, _ = name.split('\n', 1)
951
968
name = self.notebook.cleanup_pathname(name.replace(':', ''), purge=True)
969
elif isinstance(name, Path):
971
name = self.notebook.cleanup_pathname(name, purge=True)
953
973
name = self.notebook.cleanup_pathname(name, purge=True)
954
975
path = self.notebook.resolve_path(name)
955
976
page = self.notebook.get_new_page(path)
956
page.parse('plain', text)
977
page.parse('wiki', text) # FIXME format hard coded
957
978
self.notebook.store_page(page)
959
980
self.open_page(page)
962
983
def append_text_to_page(self, name, text):
963
984
'''Append text to an (exising) page'''
985
if isinstance(name, Path):
964
987
path = self.notebook.resolve_path(name)
965
988
page = self.notebook.get_page(path)
966
page.parse('plain', text, append=True)
989
page.parse('wiki', text, append=True) # FIXME format hard coded
967
990
self.notebook.store_page(page)
969
992
def open_new_window(self, page=None):
1157
1180
self.open_page(self.notebook.get_page(self.page))
1159
1182
def attach_file(self, path=None):
1183
'''Show the AttachFileDialog'''
1160
1184
AttachFileDialog(self, path=path).run()
1186
def do_attach_file(self, path, file, force_overwrite=False):
1187
'''Callback for AttachFileDialog and InsertImageDialog
1188
When 'force_overwrite' is False the user will be prompted in
1189
case the new file has the same name as an existing attachment.
1190
Returns the (new) filename or None when the action was canceled.
1193
dir = self.notebook.get_attachments_dir(path)
1195
raise Error, '%s does not have an attachments dir' % path
1197
dest = dir.file(file.basename)
1198
if dest.exists() and not force_overwrite:
1199
dialog = PromptExistingFileDialog(self, dest)
1202
return None # dialog was cancelled
1162
1207
def open_file(self, file):
1163
1208
'''Open either a File or a Dir in the file browser'''
1164
1209
assert isinstance(file, (File, Dir))
1524
1569
return True # Do not destroy - let close() handle it
1525
1570
self.connect('delete-event', do_delete_event)
1572
# setup the window layout
1573
from zim.gui.widgets import TOP, BOTTOM, TOP_PANE, LEFT_PANE
1530
1575
# setup menubar and toolbar
1531
1576
self.add_accel_group(ui.uimanager.get_accel_group())
1532
1577
self.menubar = ui.uimanager.get_widget('/menubar')
1533
1578
self.toolbar = ui.uimanager.get_widget('/toolbar')
1534
1579
self.toolbar.connect('popup-context-menu', self.do_toolbar_popup)
1535
vbox.pack_start(self.menubar, False)
1536
vbox.pack_start(self.toolbar, False)
1580
self.add_bar(self.menubar, TOP)
1581
self.add_bar(self.toolbar, TOP)
1538
# split window in side pane and editor
1539
self.hpane = gtk.HPaned()
1540
self.hpane.set_position(175)
1541
vbox.add(self.hpane)
1542
self.sidepane = gtk.Notebook()
1543
self.hpane.add1(self.sidepane)
1583
self.sidepane = self._zim_window_left # FIXME - get rid of sidepane attribute
1545
1585
self.sidepane.connect('key-press-event',
1546
1586
lambda o, event: event.keyval == KEYVAL_ESC
1547
1587
and self.toggle_sidepane())
1549
1589
self.pageindex = PageIndex(ui)
1550
self.sidepane.append_page(self.pageindex, None)
1553
self.hpane.add2(vbox2)
1590
self.add_tab(_('Index'), self.pageindex, LEFT_PANE)
1592
def check_focus_sidepane(window, widget):
1593
focus = widget == self.pageindex
1594
# FIXME - what if we have more widgets in side pane ?
1596
self.on_sidepane_lost_focus()
1598
self.connect('set-focus', check_focus_sidepane)
1555
1600
self.pathbar = None
1556
1601
self.pathbar_box = gtk.HBox() # FIXME other class for this ?
1557
1602
self.pathbar_box.set_border_width(3)
1558
vbox2.pack_start(self.pathbar_box, False)
1603
self.add_widget(self.pathbar_box, TOP_PANE, TOP)
1560
1605
self.pageview = PageView(ui)
1561
1606
self.pageview.view.connect_after(
1562
1607
'toggle-overwrite', self.do_textview_toggle_overwrite)
1563
vbox2.add(self.pageview)
1608
self.add(self.pageview)
1565
1610
# create statusbar
1566
1611
hbox = gtk.HBox(spacing=0)
1567
vbox.pack_start(hbox, False, True, False)
1612
self.add_bar(hbox, BOTTOM)
1569
1614
self.statusbar = gtk.Statusbar()
1570
1615
if ui_environment['platform'] == 'maemo':
1652
1697
group = gtk.AccelGroup()
1653
1698
group.connect_group( # <Alt><Space>
1654
1699
space, gtk.gdk.MOD1_MASK, gtk.ACCEL_VISIBLE,
1655
self.do_switch_focus)
1700
self.toggle_focus_sidepane)
1657
1702
if self.ui.preferences['GtkInterface']['toggle_on_ctrlspace']:
1658
1703
group.connect_group( # <Ctrl><Space>
1659
1704
space, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE,
1660
self.do_switch_focus)
1705
self.toggle_focus_sidepane)
1662
1707
self.add_accel_group(group)
1663
1708
self._switch_focus_accelgroup = group
1784
1829
self.sidepane.set_no_show_all(False)
1785
1830
self.sidepane.show_all()
1786
self.hpane.set_position(self.uistate['sidepane_pos'])
1831
self._zim_window_left_pane.set_position(self.uistate['sidepane_pos'])
1787
1832
self.pageindex.grab_focus()
1789
self.uistate['sidepane_pos'] = self.hpane.get_position()
1834
self.uistate['sidepane_pos'] = self._zim_window_left_pane.get_position()
1790
1835
self.sidepane.hide_all()
1791
1836
self.sidepane.set_no_show_all(True)
1792
1837
self.pageview.grab_focus()
1794
1839
self._sidepane_autoclose = False
1795
1840
self.uistate['show_sidepane'] = show
1797
def do_switch_focus(self, *a):
1842
def toggle_focus_sidepane(self, *a):
1843
'''Switch focus between the textview and the sidepane.
1844
Automatically opens the sidepane if it is closed
1845
(but sets a property to automatically close it again).
1798
1847
action = self.actiongroup.get_action('toggle_sidepane')
1799
1848
if action.get_active():
1800
1849
# side pane open
1814
1863
return True # we are called from an event handler
1865
def on_sidepane_lost_focus(self):
1866
action = self.actiongroup.get_action('toggle_sidepane')
1867
if self._sidepane_autoclose and action.get_active():
1868
# Sidepane open and should close automatic
1869
self.toggle_sidepane(show=False)
1816
1871
def set_pathbar(self, style):
1817
1872
'''Set the pathbar. Style can be either PATHBAR_NONE,
1818
1873
PATHBAR_RECENT, PATHBAR_HISTORY or PATHBAR_PATH.
2172
2227
ErrorDialog.run(self)
2173
2228
gobject.source_remove(id)
2176
2230
class OpenPageDialog(Dialog):
2177
2231
'''Dialog to go to a specific page. Also known as the "Jump to" dialog.
2178
2232
Prompts for a page name and navigate to that page on 'Ok'.
2181
def __init__(self, ui, namespace=None):
2235
def __init__(self, ui):
2182
2236
Dialog.__init__(self, ui, _('Jump to'), # T: Dialog title
2183
2237
button=(None, gtk.STOCK_JUMP_TO),
2184
path_context = ui.page,
2185
fields=[('name', 'page', _('Jump to Page'), None)] # T: Label for page input
2241
[('page', 'page', _('Jump to Page'), ui.page)] # T: Label for page input
2188
2244
def do_response_ok(self):
2189
path = self.get_field('name')
2245
path = self.form['page']
2191
2247
self.ui.open_page(path)
2209
2265
'Please note that linking to a non-existing page\n'
2210
2266
'also creates a new page automatically.'),
2211
2267
# T: Dialog text in 'new page' dialog
2214
#~ ('namespace', 'namespace', _('Namespace'), path.parent), # T: Input label
2215
('name', 'page', _('Page Name'), None), # T: Input label
2217
2268
help=':Help:Pages'
2272
('page', 'page', _('Page Name'), (path or ui.page)), # T: Input label
2221
self.inputs['name'].force_child = True
2224
#~ self.inputs['name'].force_child = True
2225
#~ self.inputs['namespace'].set_path(path)
2226
#~ self.inputs['namespace'].set_sensitive(False)
2228
#~ self.inputs['name'].grab_focus()
2230
# FIXME using namespace here need integration between pagename
2231
# and namespace widget
2276
self.form.widgets['page'].force_child = True
2233
2278
def do_response_ok(self):
2234
path = self.get_field('name')
2279
path = self.form['page']
2236
2281
page = self.ui.notebook.get_page(path)
2237
2282
if page.hascontent or page.haschildren:
2238
ErrorDialog(self, _('Page exists')+': %s' % page.name).run() # T: error message
2283
raise Error, _('Page exists')+': %s' % page.name
2241
2285
template = self.ui.notebook.get_template(page)
2242
2286
tree = template.process_to_parsetree(self.ui.notebook, page)
2316
2360
raise AssertionError, 'Could not save page'
2317
2361
# assert statement could be optimized away
2319
i = self.ui.notebook.index.n_list_links_to_tree(
2320
self.path, zim.index.LINK_DIR_BACKWARD )
2322
2363
self.vbox.add(gtk.Label(_('Move page "%s"') % self.path.name))
2323
2364
# T: Heading in 'move page' dialog - %s is the page name
2324
linkslabel = ngettext(
2366
indexpath = self.ui.notebook.index.lookup_path(self.path)
2368
i = self.ui.notebook.index.n_list_links_to_tree(
2369
indexpath, zim.index.LINK_DIR_BACKWARD )
2325
2374
'Update %i page linking to this page',
2326
2375
'Update %i pages linking to this page', i) % i
2327
2376
# T: label in MovePage dialog - %i is number of backlinks
2328
2377
# TODO update lable to reflect that links can also be to child pages
2329
2378
self.context_page = self.path.parent
2331
2380
('parent', 'namespace', _('Namespace'), self.context_page),
2332
2381
# T: Input label for namespace to move a file to
2333
('links', 'bool', linkslabel, True),
2382
('update', 'bool', label),
2334
2383
# T: option in 'move page' dialog
2338
self.inputs['links'].set_active(False)
2339
self.inputs['links'].set_sensitive(False)
2387
self.form['update'] = False
2388
self.form.widgets['update'].set_sensitive(False)
2390
self.form['update'] = True
2341
2392
def do_response_ok(self):
2342
parent = self.get_field('parent')
2343
links = self.get_field('links')
2393
parent = self.form['parent']
2394
update = self.form['update']
2344
2395
newpath = parent + self.path.basename
2345
2396
self.hide() # hide this dialog before showing the progressbar
2346
ok = self.ui.do_move_page(self.path, newpath, update_links=links)
2397
ok = self.ui.do_move_page(self.path, newpath, update)
2364
2415
page = self.ui.notebook.get_page(self.path)
2365
2416
existing = (page.hascontent or page.haschildren)
2367
i = self.ui.notebook.index.n_list_links_to_tree(
2368
self.path, zim.index.LINK_DIR_BACKWARD )
2370
2418
self.vbox.add(gtk.Label(_('Rename page "%s"') % self.path.name))
2371
2419
# T: label in 'rename page' dialog - %s is the page name
2372
linkslabel = ngettext(
2421
indexpath = self.ui.notebook.index.lookup_path(self.path)
2423
i = self.ui.notebook.index.n_list_links_to_tree(
2424
indexpath, zim.index.LINK_DIR_BACKWARD )
2373
2429
'Update %i page linking to this page',
2374
2430
'Update %i pages linking to this page', i) % i
2375
2431
# T: label in MovePage dialog - %i is number of backlinks
2376
# TODO update lable to reflect that links can also be to child pages
2378
('name', 'string', _('Name'), self.path.basename),
2432
# TODO update label to reflect that links can also be to child pages
2435
('name', 'string', _('Name')),
2379
2436
# T: Input label in the 'rename page' dialog for the new name
2380
('head', 'bool', _('Update the heading of this page'), existing),
2381
# T: Option in the 'rename page' dialog
2382
('links', 'bool', linkslabel, True),
2383
# T: Option in the 'rename page' dialog
2437
('head', 'bool', _('Update the heading of this page')),
2438
# T: Option in the 'rename page' dialog
2439
('update', 'bool', label),
2440
# T: Option in the 'rename page' dialog
2442
'name': self.path.basename,
2386
2447
if not existing:
2387
self.inputs['head'].set_active(False)
2388
self.inputs['head'].set_sensitive(False)
2448
self.form['head'] = False
2449
self.form.widgets['head'].set_sensitive(False)
2391
self.inputs['links'].set_active(False)
2392
self.inputs['links'].set_sensitive(False)
2452
self.form['update'] = False
2453
self.form.widgets['update'].set_sensitive(False)
2394
2455
def do_response_ok(self):
2395
name = self.get_field('name')
2396
head = self.get_field('head')
2397
links = self.get_field('links')
2456
name = self.form['name']
2457
head = self.form['head']
2458
update = self.form['update']
2398
2459
self.hide() # hide this dialog before showing the progressbar
2399
ok = self.ui.do_rename_page(
2400
self.path, newbasename=name, update_heading=head, update_links=links)
2460
ok = self.ui.do_rename_page(self.path, name, head, update)
2432
2492
label.set_markup('<b>'+short+'</b>\n\n'+long)
2433
2493
vbox.pack_start(label, False)
2435
i = self.ui.notebook.index.n_list_links_to_tree(
2436
self.path, zim.index.LINK_DIR_BACKWARD )
2437
linkslabel = ngettext(
2495
indexpath = self.ui.notebook.index.lookup_path(self.path)
2497
i = self.ui.notebook.index.n_list_links_to_tree(
2498
indexpath, zim.index.LINK_DIR_BACKWARD )
2438
2503
'Remove links from %i page linking to this page',
2439
2504
'Remove links from %i pages linking to this page', i) % i
2440
2505
# T: label in DeletePage dialog - %i is number of backlinks
2441
2506
# TODO update lable to reflect that links can also be to child pages
2442
self.links_checkbox = gtk.CheckButton(label=linkslabel)
2507
self.links_checkbox = gtk.CheckButton(label=label)
2443
2508
vbox.pack_start(self.links_checkbox, False)