OSDN Git Service

b258fc2e3a3b0568fc9aa89316cf3a6de84c8355
[mypaint-anime/master.git] / gui / drawwindow.py
1 # -*- coding: utf-8 -*-
2 #
3 # This file is part of MyPaint.
4 # Copyright (C) 2007-2008 by Martin Renold <martinxyz@gmx.ch>
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10
11 """
12 This is the main drawing window, containing menu actions.
13 Painting is done in tileddrawwidget.py.
14 """
15
16 MYPAINT_VERSION="0.9.1+git"
17
18 import os, math, time
19 from gettext import gettext as _
20
21 import gtk, gobject
22 from gtk import gdk, keysyms
23
24 import colorselectionwindow, historypopup, stategroup, colorpicker, windowing, layout
25 import dialogs
26 from lib import helpers
27 import stock
28
29 import xml.etree.ElementTree as ET
30
31 # TODO: put in a helper file?
32 def with_wait_cursor(func):
33     """python decorator that adds a wait cursor around a function"""
34     def wrapper(self, *args, **kwargs):
35         toplevels = [t for t in gtk.window_list_toplevels()
36                      if t.window is not None]
37         for toplevel in toplevels:
38             toplevel.window.set_cursor(gdk.Cursor(gdk.WATCH))
39             toplevel.set_sensitive(False)
40         self.app.doc.tdw.grab_add()
41         try:
42             func(self, *args, **kwargs)
43             # gtk main loop may be called in here...
44         finally:
45             for toplevel in toplevels:
46                 toplevel.set_sensitive(True)
47                 # ... which is why we need this check:
48                 if toplevel.window is not None:
49                     toplevel.window.set_cursor(None)
50             self.app.doc.tdw.grab_remove()
51     return wrapper
52
53
54 class Window (windowing.MainWindow, layout.MainWindow):
55
56     def __init__(self, app):
57         windowing.MainWindow.__init__(self, app)
58         self.app = app
59
60         # Window handling
61         self._updating_toggled_item = False
62         self._show_subwindows = True
63         self.is_fullscreen = False
64
65         # Enable drag & drop
66         self.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
67                             gtk.DEST_DEFAULT_HIGHLIGHT |
68                             gtk.DEST_DEFAULT_DROP,
69                             [("text/uri-list", 0, 1),
70                              ("application/x-color", 0, 2)],
71                             gtk.gdk.ACTION_DEFAULT|gtk.gdk.ACTION_COPY)
72
73         # Connect events
74         self.connect('delete-event', self.quit_cb)
75         self.connect('key-press-event', self.key_press_event_cb_before)
76         self.connect('key-release-event', self.key_release_event_cb_before)
77         self.connect_after('key-press-event', self.key_press_event_cb_after)
78         self.connect_after('key-release-event', self.key_release_event_cb_after)
79         self.connect("drag-data-received", self.drag_data_received)
80         self.connect("window-state-event", self.window_state_event_cb)
81
82         self.app.filehandler.current_file_observers.append(self.update_title)
83
84         self.init_actions()
85
86         lm = app.layout_manager
87         layout.MainWindow.__init__(self, lm)
88         self.main_widget.connect("button-press-event", self.button_press_cb)
89         self.main_widget.connect("button-release-event",self.button_release_cb)
90         self.main_widget.connect("scroll-event", self.scroll_cb)
91
92         kbm = self.app.kbm
93         kbm.add_extra_key('Menu', 'ShowPopupMenu')
94         kbm.add_extra_key('Tab', 'ToggleSubwindows')
95
96         self.init_stategroups()
97
98     #XXX: Compatability
99     def get_doc(self):
100         print "DeprecationWarning: Use app.doc instead"
101         return self.app.doc
102     def get_tdw(self):
103         print "DeprecationWarning: Use app.doc.tdw instead"
104         return self.app.doc.tdw
105     tdw, doc = property(get_tdw), property(get_doc)
106
107     def init_actions(self):
108         actions = [
109             # name, stock id, label, accelerator, tooltip, callback
110             ('FileMenu',    None, _('File')),
111             ('Quit',         gtk.STOCK_QUIT, _('Quit'), '<control>q', None, self.quit_cb),
112             ('FrameToggle',  None, _('Toggle Document Frame'), None, None, self.toggle_frame_cb),
113
114             ('EditMenu',        None, _('Edit')),
115
116             ('ColorMenu',    None, _('Color')),
117             ('ColorPickerPopup',    gtk.STOCK_COLOR_PICKER, _('Pick Color'), 'r', None, self.popup_cb),
118             ('ColorHistoryPopup',  None, _('Color History'), 'x', None, self.popup_cb),
119             ('ColorChangerPopup', None, _('Color Changer'), 'v', None, self.popup_cb),
120             ('ColorRingPopup',  None, _('Color Ring'), None, None, self.popup_cb),
121
122             ('ContextMenu',  None, _('Brushkeys')),
123             ('ContextHelp',  gtk.STOCK_HELP, _('Help!'), None, None, self.show_infodialog_cb),
124
125             ('LayerMenu',    None, _('Layers')),
126
127             ('BrushMenu',    None, _('Brush')),
128             ('ImportBrushPack',       gtk.STOCK_OPEN, _('Import brush package...'), '', None, self.import_brush_pack_cb),
129
130             ('HelpMenu',   None, _('Help')),
131             ('Docu', gtk.STOCK_INFO, _('Where is the Documentation?'), None, None, self.show_infodialog_cb),
132             ('ShortcutHelp',  gtk.STOCK_INFO, _('Change the Keyboard Shortcuts?'), None, None, self.show_infodialog_cb),
133             ('About', gtk.STOCK_ABOUT, _('About MyPaint'), None, None, self.about_cb),
134
135             ('DebugMenu',    None, _('Debug')),
136             ('PrintMemoryLeak',  None, _('Print Memory Leak Info to Console (Slow!)'), None, None, self.print_memory_leak_cb),
137             ('RunGarbageCollector',  None, _('Run Garbage Collector Now'), None, None, self.run_garbage_collector_cb),
138             ('StartProfiling',  gtk.STOCK_EXECUTE, _('Start/Stop Python Profiling (cProfile)'), None, None, self.start_profiling_cb),
139             ('GtkInputDialog',  None, _('GTK input device dialog'), None, None, self.gtk_input_dialog_cb),
140
141
142             ('ViewMenu', None, _('View')),
143             ('ShowPopupMenu',    None, _('Popup Menu'), 'Menu', None, self.popupmenu_show_cb),
144             ('Fullscreen',   gtk.STOCK_FULLSCREEN, _('Fullscreen'), 'F11', None, self.fullscreen_cb),
145             ('ViewHelp',  gtk.STOCK_HELP, _('Help'), None, None, self.show_infodialog_cb),
146             ]
147         ag = self.action_group = gtk.ActionGroup('WindowActions')
148         ag.add_actions(actions)
149
150         # Toggle actions
151         toggle_actions = [
152             ('PreferencesWindow', gtk.STOCK_PREFERENCES,
153                     _('Preferences'), None, None, self.toggle_window_cb),
154             ('InputTestWindow',  None,
155                     _('Test input devices'), None, None, self.toggle_window_cb),
156             ('FrameWindow',  None,
157                     _('Document Frame...'), None, None, self.toggle_window_cb),
158             ('LayersWindow', stock.TOOL_LAYERS,
159                     None, None, _("Toggle the Layers list"),
160                     self.toggle_window_cb),
161             ('BackgroundWindow', gtk.STOCK_PAGE_SETUP,
162                     _('Background'), None, None, self.toggle_window_cb),
163             ('BrushSelectionWindow', stock.TOOL_BRUSH,
164                     None, None, _("Toggle the Brush selector"),
165                     self.toggle_window_cb),
166             ('BrushSettingsWindow', gtk.STOCK_PROPERTIES,
167                     _('Brush Editor'), '<control>b', None,
168                     self.toggle_window_cb),
169             ('ColorSelectionWindow', stock.TOOL_COLOR_SELECTOR,
170                     None, None, _("Toggle the Colour Triangle"),
171                     self.toggle_window_cb),
172             ('ColorSamplerWindow', stock.TOOL_COLOR_SAMPLER,
173                     None, None, _("Toggle the advanced Colour Sampler"),
174                     self.toggle_window_cb),
175             ]
176         ag.add_toggle_actions(toggle_actions)
177
178         # Reflect changes from other places (like tools' close buttons) into
179         # the proxys' visible states.
180         lm = self.app.layout_manager
181         lm.tool_visibility_observers.append(self.update_toggled_item_visibility)
182         lm.subwindow_visibility_observers.append(self.update_subwindow_visibility)
183
184         # Initial toggle state
185         for spec in toggle_actions:
186             name = spec[0]
187             action = ag.get_action(name)
188             role = name[0].lower() + name[1:]
189             visible = not lm.get_window_hidden_by_role(role)
190             # The sidebar machinery won't be up yet, so reveal windows that
191             # should be initially visible only in an idle handler
192             gobject.idle_add(action.set_active, visible)
193
194         # More toggle actions - ones which don't control windows.
195         toggle_actions = [
196             ('ToggleToolbar', None, _('Toolbar'), None,
197                     _("Show toolbar"), self.toggle_toolbar_cb,
198                     self.get_show_toolbar()),
199             ('ToggleSubwindows', None, _('Subwindows'), 'Tab',
200                     _("Show subwindows"), self.toggle_subwindows_cb,
201                     self.get_show_subwindows()),
202             ]
203         ag.add_toggle_actions(toggle_actions)
204
205         # Keyboard handling
206         for action in self.action_group.list_actions():
207             self.app.kbm.takeover_action(action)
208         self.app.ui_manager.insert_action_group(ag, -1)
209
210     def init_stategroups(self):
211         sg = stategroup.StateGroup()
212         p2s = sg.create_popup_state
213         changer = p2s(colorselectionwindow.ColorChangerPopup(self.app))
214         ring = p2s(colorselectionwindow.ColorRingPopup(self.app))
215         hist = p2s(historypopup.HistoryPopup(self.app, self.app.doc.model))
216         pick = self.colorpick_state = p2s(colorpicker.ColorPicker(self.app, self.app.doc.model))
217
218         self.popup_states = {
219             'ColorChangerPopup': changer,
220             'ColorRingPopup': ring,
221             'ColorHistoryPopup': hist,
222             'ColorPickerPopup': pick,
223             }
224         changer.next_state = ring
225         ring.next_state = changer
226         changer.autoleave_timeout = None
227         ring.autoleave_timeout = None
228
229         pick.max_key_hit_duration = 0.0
230         pick.autoleave_timeout = None
231
232         hist.autoleave_timeout = 0.600
233         self.history_popup_state = hist
234
235     def init_main_widget(self):  # override
236         self.main_widget = self.app.doc.tdw
237
238     def init_menubar(self):   # override
239         # Load Menubar, duplicate into self.popupmenu
240         menupath = os.path.join(self.app.datapath, 'gui/menu.xml')
241         menubar_xml = open(menupath).read()
242         self.app.ui_manager.add_ui_from_string(menubar_xml)
243         self._init_popupmenu(menubar_xml)
244         self.menubar = self.app.ui_manager.get_widget('/Menubar')
245
246     def init_toolbar(self):
247         action_groups = self.app.ui_manager.get_action_groups()
248         def findaction(name):
249             for group in action_groups:
250                 action = group.get_action(name)
251                 if action is not None:
252                     return action
253             return None
254         bar = gtk.Toolbar()
255         bar.insert(findaction("New").create_tool_item(), -1)
256         bar.insert(findaction("Open").create_tool_item(), -1)
257         bar.insert(findaction("Save").create_tool_item(), -1)
258         bar.insert(findaction("Undo").create_tool_item(), -1)
259         bar.insert(findaction("Redo").create_tool_item(), -1)
260
261         bar.insert(gtk.SeparatorToolItem(), -1)
262         bar.insert(findaction("ResetView").create_tool_item(), -1)
263         bar.insert(findaction("ZoomIn").create_tool_item(), -1)
264         bar.insert(findaction("ZoomOut").create_tool_item(), -1)
265         bar.insert(findaction("RotateLeft").create_tool_item(), -1)
266         bar.insert(findaction("RotateRight").create_tool_item(), -1)
267         bar.insert(findaction("MirrorVertical").create_tool_item(), -1)
268         bar.insert(findaction("MirrorHorizontal").create_tool_item(), -1)
269
270         bar.insert(gtk.SeparatorToolItem(), -1)
271         bar.insert(findaction("BlendModeNormal").create_tool_item(), -1)
272         bar.insert(findaction("BlendModeEraser").create_tool_item(), -1)
273         bar.insert(findaction("BlendModeLockAlpha").create_tool_item(), -1)
274
275         expander = gtk.SeparatorToolItem()
276         expander.set_expand(True)
277         expander.set_draw(False)
278         bar.insert(expander, -1)
279
280         for name in ["ColorSelectionWindow", "ColorSamplerWindow",
281                      "BrushSelectionWindow", "LayersWindow"]:
282             action = self.action_group.get_action(name)
283             tool_item = action.create_tool_item()
284             bar.insert(tool_item, -1)
285         self.toolbar = bar
286
287         if not self.get_show_toolbar():
288             gobject.idle_add(self.toolbar.hide)
289
290
291     def _init_popupmenu(self, xml):
292         """
293         Hopefully temporary hack for converting UIManager XML describing the
294         main menubar into a rebindable popup menu. UIManager by itself doesn't
295         let you do this, by design, but we need a bigger menu than the little
296         things it allows you to build.
297         """
298         ui_elt = ET.fromstring(xml)
299         rootmenu_elt = ui_elt.find("menubar")
300         rootmenu_elt.attrib["name"] = "PopupMenu"
301         ## XML-style menu jiggling. No need for this really though.
302         #for menu_elt in rootmenu_elt.findall("menu"):
303         #    for item_elt in menu_elt.findall("menuitem"):
304         #        if item_elt.attrib.get("action", "") == "ShowPopupMenu":
305         #            menu_elt.remove(item_elt)
306         ## Maybe shift a small number of frequently-used items to the top?
307         xml = ET.tostring(ui_elt)
308         self.app.ui_manager.add_ui_from_string(xml)
309         tmp_menubar = self.app.ui_manager.get_widget('/PopupMenu')
310         self.popupmenu = gtk.Menu()
311         for item in tmp_menubar.get_children():
312             tmp_menubar.remove(item)
313             self.popupmenu.append(item)
314         self.popupmenu.attach_to_widget(self.app.doc.tdw, None)
315         #self.popupmenu.set_title("MyPaint")
316         #self.popupmenu.set_take_focus(True)
317         self.popupmenu.connect("selection-done", self.popupmenu_done_cb)
318         self.popupmenu.connect("deactivate", self.popupmenu_done_cb)
319         self.popupmenu.connect("cancel", self.popupmenu_done_cb)
320         self.popupmenu_last_active = None
321
322
323     def update_title(self, filename):
324         if filename:
325             self.set_title("MyPaint - %s" % os.path.basename(filename))
326         else:
327             self.set_title("MyPaint")
328
329     # INPUT EVENT HANDLING
330     def drag_data_received(self, widget, context, x, y, selection, info, t):
331         if info == 1:
332             if selection.data:
333                 uri = selection.data.split("\r\n")[0]
334                 fn = helpers.uri2filename(uri)
335                 if os.path.exists(fn):
336                     if self.app.filehandler.confirm_destructive_action():
337                         self.app.filehandler.open_file(fn)
338         elif info == 2: # color
339             color = [((ord(selection.data[v]) | (ord(selection.data[v+1]) << 8)) / 65535.0)  for v in range(0,8,2)]
340             self.app.brush.set_color_rgb(color[:3])
341             self.app.ch.push_color(self.app.brush.get_color_hsv())
342             # Don't popup the color history for now, as I haven't managed to get it to cooperate.
343
344     def print_memory_leak_cb(self, action):
345         helpers.record_memory_leak_status(print_diff = True)
346
347     def run_garbage_collector_cb(self, action):
348         helpers.run_garbage_collector()
349
350     def start_profiling_cb(self, action):
351         if getattr(self, 'profiler_active', False):
352             self.profiler_active = False
353             return
354
355         def doit():
356             import cProfile
357             profile = cProfile.Profile()
358
359             self.profiler_active = True
360             print '--- GUI Profiling starts ---'
361             while self.profiler_active:
362                 profile.runcall(gtk.main_iteration, False)
363                 if not gtk.events_pending():
364                     time.sleep(0.050) # ugly trick to remove "user does nothing" from profile
365             print '--- GUI Profiling ends ---'
366
367             profile.dump_stats('profile_fromgui.pstats')
368             #print 'profile written to mypaint_profile.pstats'
369             os.system('gprof2dot.py -f pstats profile_fromgui.pstats | dot -Tpng -o profile_fromgui.png && feh profile_fromgui.png &')
370
371         gobject.idle_add(doit)
372
373     def gtk_input_dialog_cb(self, action):
374         d = gtk.InputDialog()
375         d.show()
376
377     def key_press_event_cb_before(self, win, event):
378         key = event.keyval
379         ctrl = event.state & gdk.CONTROL_MASK
380         shift = event.state & gdk.SHIFT_MASK
381         alt = event.state & gdk.MOD1_MASK
382         #ANY_MODIFIER = gdk.SHIFT_MASK | gdk.MOD1_MASK | gdk.CONTROL_MASK
383         #if event.state & ANY_MODIFIER:
384         #    # allow user shortcuts with modifiers
385         #    return False
386         if key == keysyms.space:
387             if shift:
388                 self.app.doc.tdw.start_drag(self.app.doc.dragfunc_rotate)
389             elif ctrl:
390                 self.app.doc.tdw.start_drag(self.app.doc.dragfunc_zoom)
391             elif alt:
392                 self.app.doc.tdw.start_drag(self.app.doc.dragfunc_frame)
393             else:
394                 self.app.doc.tdw.start_drag(self.app.doc.dragfunc_translate)
395         else: return False
396         return True
397
398     def key_release_event_cb_before(self, win, event):
399         if event.keyval == keysyms.space:
400             self.app.doc.tdw.stop_drag(self.app.doc.dragfunc_translate)
401             self.app.doc.tdw.stop_drag(self.app.doc.dragfunc_rotate)
402             self.app.doc.tdw.stop_drag(self.app.doc.dragfunc_zoom)
403             self.app.doc.tdw.stop_drag(self.app.doc.dragfunc_frame)
404             return True
405         return False
406
407     def key_press_event_cb_after(self, win, event):
408         key = event.keyval
409         if self.is_fullscreen and key == keysyms.Escape:
410             self.fullscreen_cb()
411         else:
412             return False
413         return True
414
415     def key_release_event_cb_after(self, win, event):
416         return False
417
418     def button_press_cb(self, win, event):
419         #print event.device, event.button
420
421         ## Ignore accidentals
422         # Single button-presses only, not 2ble/3ple
423         if event.type != gdk.BUTTON_PRESS:
424             # ignore the extra double-click event
425             return False
426
427         if event.button != 1:
428             # check whether we are painting (accidental)
429             if event.state & gdk.BUTTON1_MASK:
430                 # Do not allow dragging in the middle of
431                 # painting. This often happens by accident with wacom
432                 # tablet's stylus button.
433                 #
434                 # However we allow dragging if the user's pressure is
435                 # still below the click threshold.  This is because
436                 # some tablet PCs are not able to produce a
437                 # middle-mouse click without reporting pressure.
438                 # https://gna.org/bugs/index.php?15907
439                 return False
440
441         # Pick a suitable config option
442         ctrl = event.state & gdk.CONTROL_MASK
443         alt  = event.state & gdk.MOD1_MASK
444         shift = event.state & gdk.SHIFT_MASK
445         if shift:
446             modifier_str = "_shift"
447         elif alt or ctrl:
448             modifier_str = "_ctrl"
449         else:
450             modifier_str = ""
451         prefs_name = "input.button%d%s_action" % (event.button, modifier_str)
452         action_name = self.app.preferences.get(prefs_name, "no_action")
453
454         # No-ops
455         if action_name == 'no_action':
456             return True  # We handled it by doing nothing
457
458         # Straight line
459         # Really belongs in the tdw, but this is the only object with access
460         # to the application preferences.
461         if action_name == 'straight_line':
462             self.app.doc.tdw.straight_line_from_last_pos(is_sequence=False)
463             return True
464         if action_name == 'straight_line_sequence':
465             self.app.doc.tdw.straight_line_from_last_pos(is_sequence=True)
466             return True
467
468         # View control
469         if action_name.endswith("_canvas"):
470             dragfunc = None
471             if action_name == "pan_canvas":
472                 dragfunc = self.app.doc.dragfunc_translate
473             elif action_name == "zoom_canvas":
474                 dragfunc = self.app.doc.dragfunc_zoom
475             elif action_name == "rotate_canvas":
476                 dragfunc = self.app.doc.dragfunc_rotate
477             if dragfunc is not None:
478                 self.app.doc.tdw.start_drag(dragfunc)
479                 return True
480             return False
481
482         # Application menu
483         if action_name == 'popup_menu':
484             self.show_popupmenu(event=event)
485             return True
486
487         if action_name in self.popup_states:
488             state = self.popup_states[action_name]
489             state.activate(event)
490             return True
491
492         # Dispatch regular GTK events.
493         for ag in self.action_group, self.app.doc.action_group:
494             action = ag.get_action(action_name)
495             if action is not None:
496                 action.activate()
497                 return True
498
499     def button_release_cb(self, win, event):
500         #print event.device, event.button
501         tdw = self.app.doc.tdw
502         if tdw.dragfunc is not None:
503             tdw.stop_drag(self.app.doc.dragfunc_translate)
504             tdw.stop_drag(self.app.doc.dragfunc_rotate)
505             tdw.stop_drag(self.app.doc.dragfunc_zoom)
506         return False
507
508     def scroll_cb(self, win, event):
509         d = event.direction
510         if d == gdk.SCROLL_UP:
511             if event.state & gdk.SHIFT_MASK:
512                 self.app.doc.rotate('RotateLeft')
513             else:
514                 self.app.doc.zoom('ZoomIn')
515         elif d == gdk.SCROLL_DOWN:
516             if event.state & gdk.SHIFT_MASK:
517                 self.app.doc.rotate('RotateRight')
518             else:
519                 self.app.doc.zoom('ZoomOut')
520         elif d == gdk.SCROLL_RIGHT:
521             self.app.doc.rotate('RotateRight')
522         elif d == gdk.SCROLL_LEFT:
523             self.app.doc.rotate('RotateLeft')
524
525     # WINDOW HANDLING
526     def toggle_window_cb(self, action):
527         if self._updating_toggled_item:
528             return
529         s = action.get_name()
530         active = action.get_active()
531         window_name = s[0].lower() + s[1:] # WindowName -> windowName
532         # If it's a tool, get it to hide/show itself
533         t = self.app.layout_manager.get_tool_by_role(window_name)
534         if t is not None:
535             t.set_hidden(not active)
536             return
537         # Otherwise, if it's a regular subwindow hide/show+present it.
538         w = self.app.layout_manager.get_subwindow_by_role(window_name)
539         if w is None:
540             return
541         onscreen = w.window is not None and w.window.is_visible()
542         if active:
543             if onscreen:
544                 return
545             w.show_all()
546             w.present()
547         else:
548             if not onscreen:
549                 return
550             w.hide()
551
552     def update_subwindow_visibility(self, window, active):
553         # Responds to non-tool subwindows being hidden and shown
554         role = window.get_role()
555         self.update_toggled_item_visibility(role, active)
556
557     def update_toggled_item_visibility(self, role, active, *a, **kw):
558         # Responds to any item with a role being hidden or shown by
559         # silently updating its ToggleAction to match.
560         action_name = role[0].upper() + role[1:]
561         action = self.action_group.get_action(action_name)
562         if action is None:
563             warn("Unable to find action %s" % action_name, RuntimeWarning, 1)
564             return
565         if action.get_active() != active:
566             self._updating_toggled_item = True
567             action.set_active(active)
568             self._updating_toggled_item = False
569
570     def popup_cb(self, action):
571         state = self.popup_states[action.get_name()]
572         state.activate(action)
573
574
575     # Show Toolbar
576     # Saved in the user prefs between sessions.
577     # Controlled via its ToggleAction only.
578
579     def set_show_toolbar(self, show_toolbar):
580         """Programatically set the Show Toolbar option.
581         """
582         action = self.action_group.get_action("ToggleToolbar")
583         if show_toolbar:
584             if not action.get_active():
585                 action.set_active(True)
586             self.app.preferences["ui.toolbar"] = True
587         else:
588             if action.get_active():
589                 action.set_active(False)
590             self.app.preferences["ui.toolbar"] = False
591
592     def get_show_toolbar(self):
593         return self.app.preferences.get("ui.toolbar", True)
594
595     def toggle_toolbar_cb(self, action):
596         active = action.get_active()
597         if active:
598             self.toolbar.show_all()
599         else:
600             self.toolbar.hide()
601         self.app.preferences["ui.toolbar"] = active
602
603
604     # Show Subwindows
605     # Not saved between sessions, defaults to on.
606     # Controlled via its ToggleAction, and entering or leaving fullscreen mode
607     # according to the setting of ui.hide_in_fullscreen in prefs.
608
609     def set_show_subwindows(self, show_subwindows):
610         """Programatically set the Show Subwindows option.
611         """
612         action = self.action_group.get_action("ToggleSubwindows")
613         currently_showing = action.get_active()
614         if show_subwindows != currently_showing:
615             action.set_active(show_subwindows)
616         self._show_subwindows = self._show_subwindows
617
618     def get_show_subwindows(self):
619         return self._show_subwindows
620
621     def toggle_subwindows_cb(self, action):
622         active = action.get_active()
623         lm = self.app.layout_manager
624         if active:
625             lm.toggle_user_tools(on=True)
626         else:
627             lm.toggle_user_tools(on=False)
628         self._show_subwindows = active
629
630
631     # Fullscreen mode
632     # This implementation requires an ICCCM and EWMH-compliant window manager
633     # which supports the _NET_WM_STATE_FULLSCREEN hint. There are several
634     # available.
635
636     def fullscreen_cb(self, *junk):
637         if not self.is_fullscreen:
638             self.fullscreen()
639         else:
640             self.unfullscreen()
641
642     def window_state_event_cb(self, widget, event):
643         # Respond to changes of the fullscreen state only
644         if not event.changed_mask & gdk.WINDOW_STATE_FULLSCREEN:
645             return
646         lm = self.app.layout_manager
647         self.is_fullscreen = event.new_window_state & gdk.WINDOW_STATE_FULLSCREEN
648         if self.is_fullscreen:
649             # Subwindow hiding 
650             if self.app.preferences.get("ui.hide_subwindows_in_fullscreen", True):
651                 self.set_show_subwindows(False)
652                 self._restore_subwindows_on_unfullscreen = True
653             if self.app.preferences.get("ui.hide_menubar_in_fullscreen", True):
654                 self.menubar.hide()
655                 self._restore_menubar_on_unfullscreen = True
656             if self.app.preferences.get("ui.hide_toolbar_in_fullscreen", True):
657                 self.toolbar.hide()
658                 self._restore_toolbar_on_unfullscreen = True
659             # fix for fullscreen problem on Windows, https://gna.org/bugs/?15175
660             # on X11/Metacity it also helps a bit against flickering during the switch
661             while gtk.events_pending():
662                 gtk.main_iteration()
663         else:
664             while gtk.events_pending():
665                 gtk.main_iteration()
666             if getattr(self, "_restore_menubar_on_unfullscreen", False):
667                 self.menubar.show()
668                 del self._restore_menubar_on_unfullscreen
669             if getattr(self, "_restore_toolbar_on_unfullscreen", False):
670                 if self.get_show_toolbar():
671                     self.toolbar.show()
672                 del self._restore_toolbar_on_unfullscreen
673             if getattr(self, "_restore_subwindows_on_unfullscreen", False):
674                 self.set_show_subwindows(True)
675                 del self._restore_subwindows_on_unfullscreen
676
677     def popupmenu_show_cb(self, action):
678         self.show_popupmenu()
679
680     def show_popupmenu(self, event=None):
681         self.menubar.set_sensitive(False)   # excessive feedback?
682         button = 1
683         time = 0
684         if event is not None:
685             if event.type == gdk.BUTTON_PRESS:
686                 button = event.button
687                 time = event.time
688         self.popupmenu.popup(None, None, None, button, time)
689         if event is None:
690             # We're responding to an Action, most probably the menu key.
691             # Open out the last highlighted menu to speed key navigation up.
692             if self.popupmenu_last_active is None:
693                 self.popupmenu.select_first(True) # one less keypress
694             else:
695                 self.popupmenu.select_item(self.popupmenu_last_active)
696
697     def popupmenu_done_cb(self, *a, **kw):
698         # Not sure if we need to bother with this level of feedback,
699         # but it actaully looks quite nice to see one menu taking over
700         # the other. Makes it clear that the popups are the same thing as
701         # the full menu, maybe.
702         self.menubar.set_sensitive(True)
703         self.popupmenu_last_active = self.popupmenu.get_active()
704
705     def quit_cb(self, *junk):
706         self.app.doc.model.split_stroke()
707         self.app.save_gui_config() # FIXME: should do this periodically, not only on quit
708
709         if not self.app.filehandler.confirm_destructive_action(title=_('Quit'), question=_('Really Quit?')):
710             return True
711
712         gtk.main_quit()
713         return False
714
715     def toggle_frame_cb(self, action):
716         enabled = self.app.doc.model.frame_enabled
717         self.app.doc.model.set_frame_enabled(not enabled)
718
719     def import_brush_pack_cb(self, *junk):
720         format_id, filename = dialogs.open_dialog(_("Import brush package..."), self,
721                                  [(_("MyPaint brush package (*.zip)"), "*.zip")])
722         if filename:
723             self.app.brushmanager.import_brushpack(filename,  self)
724
725     # INFORMATION
726     # TODO: Move into dialogs.py?
727     def about_cb(self, action):
728         d = gtk.AboutDialog()
729         d.set_transient_for(self)
730         d.set_program_name("MyPaint")
731         d.set_version(MYPAINT_VERSION)
732         d.set_copyright(_("Copyright (C) 2005-2010\nMartin Renold and the MyPaint Development Team"))
733         d.set_website("http://mypaint.info/")
734         d.set_logo(self.app.pixmaps.mypaint_logo)
735         d.set_license(
736             _(u"This program is free software; you can redistribute it and/or modify "
737               u"it under the terms of the GNU General Public License as published by "
738               u"the Free Software Foundation; either version 2 of the License, or "
739               u"(at your option) any later version.\n"
740               u"\n"
741               u"This program is distributed in the hope that it will be useful, "
742               u"but WITHOUT ANY WARRANTY. See the COPYING file for more details.")
743             )
744         d.set_wrap_license(True)
745         d.set_authors([
746             # (in order of appearance)
747             u"Martin Renold (%s)" % _('programming'),
748             u"Artis Rozentāls (%s)" % _('brushes'),
749             u"Yves Combe (%s)" % _('portability'),
750             u"Popolon (%s)" % _('brushes, programming'),
751             u"Clement Skau (%s)" % _('programming'),
752             u"Marcelo 'Tanda' Cerviño (%s)" % _('patterns, brushes'),
753             u"Jon Nordby (%s)" % _('programming'),
754             u"Álinson Santos (%s)" % _('programming'),
755             u"Tumagonx (%s)" % _('portability'),
756             u"Ilya Portnov (%s)" % _('programming'),
757             u"David Revoy (%s)" % _('brushes'),
758             u"Ramón Miranda (%s)" % _('brushes, patterns'),
759             u"Enrico Guarnieri 'Ico_dY' (%s)" % _('brushes'),
760             u"Jonas Wagner (%s)" % _('programming'),
761             u"Luka Čehovin (%s)" % _('programming'),
762             u"Andrew Chadwick (%s)" % _('programming'),
763             u"Till Hartmann (%s)" % _('programming'),
764             u"Nicola Lunghi (%s)" % _('patterns'),
765             u"Toni Kasurinen (%s)" % _('brushes'),
766             u"Сан Саныч (%s)" % _('patterns'),
767             u'David Grundberg (%s)' % _('programming'),
768             u"Krzysztof Pasek (%s)" % _('programming'),
769             ])
770         d.set_artists([
771             u'Sebastian Kraft (%s)' % _('desktop icon'),
772             ])
773         # list all translators, not only those of the current language
774         d.set_translator_credits(
775             u'Ilya Portnov (ru)\n'
776             u'Popolon (fr, zh_CN, ja)\n'
777             u'Jon Nordby (nb)\n'
778             u'Griatch (sv)\n'
779             u'Tobias Jakobs (de)\n'
780             u'Martin Tabačan (cs)\n'
781             u'Tumagonx (id)\n'
782             u'Manuel Quiñones (es)\n'
783             u'Gergely Aradszki (hu)\n'
784             u'Lamberto Tedaldi (it)\n'
785             u'Dong-Jun Wu (zh_TW)\n'
786             u'Luka Čehovin (sl)\n'
787             u'Geuntak Jeong (ko)\n'
788             u'Łukasz Lubojański (pl)\n'
789             u'Daniel Korostil (uk)\n'
790             u'Julian Aloofi (de)\n'
791             u'Tor Egil Hoftun Kvæstad (nn_NO)\n'
792             u'João S. O. Bueno (pt_BR)\n'
793             u'David Grundberg (sv)\n'
794             u'Elliott Sales de Andrade (en_CA)\n'
795             )
796
797         d.run()
798         d.destroy()
799
800     def show_infodialog_cb(self, action):
801         text = {
802         'ShortcutHelp':
803                 _("Move your mouse over a menu entry, then press the key to assign."),
804         'ViewHelp':
805                 _("You can also drag the canvas with the mouse while holding the middle "
806                 "mouse button or spacebar. Or with the arrow keys."
807                 "\n\n"
808                 "In contrast to earlier versions, scrolling and zooming are harmless now and "
809                 "will not make you run out of memory. But you still require a lot of memory "
810                 "if you paint all over while fully zoomed out."),
811         'ContextHelp':
812                 _("Brushkeys are used to quickly save/restore brush settings "
813                  "using keyboard shortcuts. You can paint with one hand and "
814                  "change brushes with the other without interruption."
815                  "\n\n"
816                  "There are 10 memory slots to hold brush settings.\n"
817                  "They are anonymous brushes, which are not visible in the "
818                  "brush selector list. But they are remembered even if you "
819                  "quit."),
820         'Docu':
821                 _("There is a tutorial available on the MyPaint homepage. It "
822                  "explains some features which are hard to discover yourself."
823                  "\n\n"
824                  "Comments about the brush settings (opaque, hardness, etc.) and "
825                  "inputs (pressure, speed, etc.) are available as tooltips. "
826                  "Put your mouse over a label to see them. "
827                  "\n"),
828         }
829         self.app.message_dialog(text[action.get_name()])