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