Painting is done in tileddrawwidget.py.
"""
-MYPAINT_VERSION="0.9.1+git"
+MYPAINT_VERSION="1.0.0"
-import os, math, time
+import os, math, time, webbrowser
from gettext import gettext as _
import gtk, gobject
from gtk import gdk, keysyms
+import pango
-import colorselectionwindow, historypopup, stategroup, colorpicker, windowing, layout
+import colorselectionwindow, historypopup, stategroup, colorpicker, windowing, layout, toolbar
import dialogs
from lib import helpers
import stock
return True
# Dispatch regular GTK events.
- for ag in drawwindow.action_group, doc.action_group:
- action = ag.get_action(action_name)
- if action is not None:
- action.activate()
- return True
+ action = drawwindow.app.find_action(action_name)
+ if action is not None:
+ action.activate()
+ return True
def button_release_cb_abstraction(win, event, doc):
#print event.device, event.button
class Window (windowing.MainWindow, layout.MainWindow):
+ MENUISHBAR_RADIO_MENUBAR = 1
+ MENUISHBAR_RADIO_MAIN_TOOLBAR = 2
+ MENUISHBAR_RADIO_BOTH_BARS = 3
+
def __init__(self, app):
windowing.MainWindow.__init__(self, app)
self.app = app
lm = app.layout_manager
layout.MainWindow.__init__(self, lm)
+
+ # Park the focus on the main tdw rather than on the toolbar. Default
+ # activation doesn't really mean much for MyPaint's main window, so
+ # it's safe to do this and it looks better.
+ self.main_widget.set_can_default(True)
+ self.main_widget.set_can_focus(True)
+ self.main_widget.grab_focus()
+
self.main_widget.connect("button-press-event", self.button_press_cb)
self.main_widget.connect("button-release-event",self.button_release_cb)
self.main_widget.connect("scroll-event", self.scroll_cb)
('ColorMenu', None, _('Color')),
('ColorPickerPopup', gtk.STOCK_COLOR_PICKER, _('Pick Color'), 'r', None, self.popup_cb),
('ColorHistoryPopup', None, _('Color History'), 'x', None, self.popup_cb),
- ('ColorChangerPopup', None, _('Color Changer'), 'v', None, self.popup_cb),
+ ('ColorChangerCrossedBowlPopup', None, _('Color Changer (crossed bowl)'), 'v', None, self.popup_cb),
+ ('ColorChangerWashPopup', None, _('Color Changer (washed)'), 'c', None, self.popup_cb),
('ColorRingPopup', None, _('Color Ring'), None, None, self.popup_cb),
+ ('ColorDetailsDialog', None, _("Color Details"), None, None, self.color_details_dialog_cb),
('ContextMenu', None, _('Brushkeys')),
('ContextHelp', gtk.STOCK_HELP, _('Help!'), None, None, self.show_infodialog_cb),
# Scratchpad menu items
('ScratchMenu', None, _('Scratchpad')),
- ('ScratchNew', gtk.STOCK_NEW, _('New scratchpad'), '', None, self.new_scratchpad_cb),
- ('ScratchLoad', gtk.STOCK_OPEN, _('Load into scratchpad'), '', None, self.load_scratchpad_cb),
+ ('ScratchNew', gtk.STOCK_NEW, _('New Scratchpad'), '', None, self.new_scratchpad_cb),
+ ('ScratchLoad', gtk.STOCK_OPEN, _('Load Scratchpad...'), '', None, self.load_scratchpad_cb),
('ScratchSaveNow', gtk.STOCK_SAVE, _('Save Scratchpad Now'), '', None, self.save_current_scratchpad_cb),
('ScratchSaveAs', gtk.STOCK_SAVE_AS, _('Save Scratchpad As...'), '', None, self.save_as_scratchpad_cb),
- ('ScratchRevert', gtk.STOCK_UNDO, _('Revert scratchpad'), '', None, self.revert_current_scratchpad_cb),
- ('ScratchSaveAsDefault', None, _('Save Scratchpad As Default'), None, None, self.save_scratchpad_as_default_cb),
+ ('ScratchRevert', gtk.STOCK_UNDO, _('Revert Scratchpad'), '', None, self.revert_current_scratchpad_cb),
+ ('ScratchSaveAsDefault', None, _('Save Scratchpad as Default'), None, None, self.save_scratchpad_as_default_cb),
('ScratchClearDefault', None, _('Clear the Default Scratchpad'), None, None, self.clear_default_scratchpad_cb),
- ('ScratchLoadPalette', None, _('Draw a palette in the current Scratchpad'), None, None, self.draw_palette_cb),
- ('ScratchPaletteOptions', None, _('Draw a palette...')),
- ('ScratchDrawSatPalette', None, _('Draw a saturation palette of current color'), None, None, self.draw_sat_spectrum_cb),
- ('ScratchCopyBackground', None, _('Match scratchpad bg to canvas bg'), None, None, self.scratchpad_copy_background_cb),
+ ('ScratchPaletteOptions', None, _('Render a Palette')),
+ ('ScratchLoadPalette', None, _('Load Palette File...'), None, None, self.draw_palette_cb),
+ ('ScratchDrawSatPalette', None, _('Different Saturations of the current Color'), None, None, self.draw_sat_spectrum_cb),
+ ('ScratchCopyBackground', None, _('Copy Background to Scratchpad'), None, None, self.scratchpad_copy_background_cb),
('BrushMenu', None, _('Brush')),
- ('ImportBrushPack', gtk.STOCK_OPEN, _('Import brush package...'), '', None, self.import_brush_pack_cb),
+ ('BrushChooserPopup', stock.TOOL_BRUSH, _("Change Brush..."), 'b', None, self.brush_chooser_popup_cb),
+ ('DownloadBrushPack', gtk.STOCK_OPEN, _('Download more brushes (in web browser)'), '', None, self.download_brush_pack_cb),
+ ('ImportBrushPack', gtk.STOCK_OPEN, _('Import brush package...'), '', None, self.import_brush_pack_cb),
('HelpMenu', None, _('Help')),
('Docu', gtk.STOCK_INFO, _('Where is the Documentation?'), None, None, self.show_infodialog_cb),
('ViewMenu', None, _('View')),
+ ('MenuishBarMenu', None, _('Toolbars')),
('ShowPopupMenu', None, _('Popup Menu'), 'Menu', None, self.popupmenu_show_cb),
- ('Fullscreen', gtk.STOCK_FULLSCREEN, _('Fullscreen'), 'F11', None, self.fullscreen_cb),
+ ('Fullscreen', gtk.STOCK_FULLSCREEN, None, 'F11', None, self.fullscreen_cb),
('ViewHelp', gtk.STOCK_HELP, _('Help'), None, None, self.show_infodialog_cb),
]
ag = self.action_group = gtk.ActionGroup('WindowActions')
+ self.app.add_action_group(ag)
ag.add_actions(actions)
+ self.update_fullscreen_action()
# Toggle actions
toggle_actions = [
('BackgroundWindow', gtk.STOCK_PAGE_SETUP,
_('Background'), None, None, self.toggle_window_cb),
('BrushSelectionWindow', stock.TOOL_BRUSH,
- None, None, _("Toggle the Brush selector"),
+ None, None,
+ _("Edit and reorganise Brush Lists"),
self.toggle_window_cb),
('BrushSettingsWindow', gtk.STOCK_PROPERTIES,
- _('Brush Editor'), '<control>b', None,
+ _('Brush Settings Editor'), '<control>b',
+ _("Change Brush Settings in detail"),
self.toggle_window_cb),
('ColorSelectionWindow', stock.TOOL_COLOR_SELECTOR,
None, None, _("Toggle the Colour Triangle"),
# More toggle actions - ones which don't control windows.
toggle_actions = [
- ('ToggleToolbar', None, _('Toolbar'), None,
- _("Show toolbar"), self.toggle_toolbar_cb,
- self.get_show_toolbar()),
('ToggleSubwindows', None, _('Subwindows'), 'Tab',
_("Show subwindows"), self.toggle_subwindows_cb,
self.get_show_subwindows()),
]
ag.add_toggle_actions(toggle_actions)
+ # Radio actions
+ menuishbar_radio_actions = [
+ ('MenuishBarRadioMenubar', None, _('Menubar only'), None,
+ _("Show menu bar"),
+ self.MENUISHBAR_RADIO_MENUBAR),
+ ('MenuishBarRadioMainToolbar', None, _('Toolbar only'), None,
+ _("Show toolbar"),
+ self.MENUISHBAR_RADIO_MAIN_TOOLBAR),
+ ('MenuishBarRadioMenubarAndMainToolbar', None, _('Both'), None,
+ _("Show both the menu bar and the toolbar"),
+ self.MENUISHBAR_RADIO_BOTH_BARS),
+ ]
+ menuishbar_state = 0
+ if self.get_ui_part_enabled("menubar"):
+ menuishbar_state += self.MENUISHBAR_RADIO_MENUBAR
+ if self.get_ui_part_enabled("main_toolbar"):
+ menuishbar_state += self.MENUISHBAR_RADIO_MAIN_TOOLBAR
+ if menuishbar_state == 0:
+ menuishbar_state = self.MENUISHBAR_RADIO_MAIN_TOOLBAR
+ ag.add_radio_actions(menuishbar_radio_actions,
+ menuishbar_state, self.on_menuishbar_radio_change)
+ gobject.idle_add(lambda: self.update_ui_parts())
+
# Keyboard handling
for action in self.action_group.list_actions():
self.app.kbm.takeover_action(action)
- self.app.ui_manager.insert_action_group(ag, -1)
def init_stategroups(self):
sg = stategroup.StateGroup()
p2s = sg.create_popup_state
- changer = p2s(colorselectionwindow.ColorChangerPopup(self.app))
+ changer_crossed_bowl = p2s(colorselectionwindow.ColorChangerCrossedBowlPopup(self.app))
+ changer_wash = p2s(colorselectionwindow.ColorChangerWashPopup(self.app))
ring = p2s(colorselectionwindow.ColorRingPopup(self.app))
hist = p2s(historypopup.HistoryPopup(self.app, self.app.doc.model))
pick = self.colorpick_state = p2s(colorpicker.ColorPicker(self.app, self.app.doc.model))
self.popup_states = {
- 'ColorChangerPopup': changer,
+ 'ColorChangerCrossedBowlPopup': changer_crossed_bowl,
+ 'ColorChangerWashPopup': changer_wash,
'ColorRingPopup': ring,
'ColorHistoryPopup': hist,
'ColorPickerPopup': pick,
}
- changer.next_state = ring
- ring.next_state = changer
- changer.autoleave_timeout = None
+
+ # not sure how useful this is; we can't cycle at the moment
+ changer_crossed_bowl.next_state = ring
+ ring.next_state = changer_wash
+ changer_wash.next_state = ring
+
+ changer_wash.autoleave_timeout = None
+ changer_crossed_bowl.autoleave_timeout = None
ring.autoleave_timeout = None
pick.max_key_hit_duration = 0.0
menupath = os.path.join(self.app.datapath, 'gui/menu.xml')
menubar_xml = open(menupath).read()
self.app.ui_manager.add_ui_from_string(menubar_xml)
- self._init_popupmenu(menubar_xml)
+ self.popupmenu = self._clone_menu(menubar_xml, 'PopupMenu', self.app.doc.tdw)
self.menubar = self.app.ui_manager.get_widget('/Menubar')
def init_toolbar(self):
- toolbarpath = os.path.join(self.app.datapath, 'gui/toolbar.xml')
- toolbarbar_xml = open(toolbarpath).read()
- self.app.ui_manager.add_ui_from_string(toolbarbar_xml)
- self.toolbar = self.app.ui_manager.get_widget('/toolbar1')
- if not self.get_show_toolbar():
- gobject.idle_add(self.toolbar.hide)
+ self.toolbar_manager = toolbar.ToolbarManager(self)
+ self.toolbar = self.toolbar_manager.toolbar1
-
- def _init_popupmenu(self, xml):
+ def _clone_menu(self, xml, name, owner=None):
"""
Hopefully temporary hack for converting UIManager XML describing the
main menubar into a rebindable popup menu. UIManager by itself doesn't
"""
ui_elt = ET.fromstring(xml)
rootmenu_elt = ui_elt.find("menubar")
- rootmenu_elt.attrib["name"] = "PopupMenu"
- ## XML-style menu jiggling. No need for this really though.
- #for menu_elt in rootmenu_elt.findall("menu"):
- # for item_elt in menu_elt.findall("menuitem"):
- # if item_elt.attrib.get("action", "") == "ShowPopupMenu":
- # menu_elt.remove(item_elt)
- ## Maybe shift a small number of frequently-used items to the top?
+ rootmenu_elt.attrib["name"] = name
xml = ET.tostring(ui_elt)
self.app.ui_manager.add_ui_from_string(xml)
- tmp_menubar = self.app.ui_manager.get_widget('/PopupMenu')
- self.popupmenu = gtk.Menu()
+ tmp_menubar = self.app.ui_manager.get_widget('/' + name)
+ popupmenu = gtk.Menu()
for item in tmp_menubar.get_children():
tmp_menubar.remove(item)
- self.popupmenu.append(item)
- self.popupmenu.attach_to_widget(self.app.doc.tdw, None)
- #self.popupmenu.set_title("MyPaint")
- #self.popupmenu.set_take_focus(True)
- self.popupmenu.connect("selection-done", self.popupmenu_done_cb)
- self.popupmenu.connect("deactivate", self.popupmenu_done_cb)
- self.popupmenu.connect("cancel", self.popupmenu_done_cb)
+ popupmenu.append(item)
+ if owner is not None:
+ popupmenu.attach_to_widget(owner, None)
+ popupmenu.set_title("MyPaint")
+ popupmenu.connect("selection-done", self.popupmenu_done_cb)
+ popupmenu.connect("deactivate", self.popupmenu_done_cb)
+ popupmenu.connect("cancel", self.popupmenu_done_cb)
self.popupmenu_last_active = None
+ return popupmenu
def update_title(self, filename):
state.activate(action)
- # Show Toolbar
- # Saved in the user prefs between sessions.
- # Controlled via its ToggleAction only.
+ def brush_chooser_popup_cb(self, action):
+ # It may be even nicer to do this as a real popup state with
+ # mouse-out to cancel. The Action is named accordingly. For now
+ # though a modal dialog will do as an implementation.
+ dialogs.change_current_brush_quick(self.app)
+
+ def color_details_dialog_cb(self, action):
+ dialogs.change_current_color_detailed(self.app)
+
+
+ # User-toggleable UI pieces: things like toolbars, status bars, menu bars.
+ # Saved between sessions.
+
- def set_show_toolbar(self, show_toolbar):
- """Programatically set the Show Toolbar option.
+ def get_ui_part_enabled(self, part_name):
+ """Returns whether the named UI part is enabled in the prefs.
"""
- action = self.action_group.get_action("ToggleToolbar")
- if show_toolbar:
- if not action.get_active():
- action.set_active(True)
- self.app.preferences["ui.toolbar"] = True
- else:
- if action.get_active():
- action.set_active(False)
- self.app.preferences["ui.toolbar"] = False
+ parts = self.app.preferences["ui.parts"]
+ return bool(parts.get(part_name, False))
- def get_show_toolbar(self):
- return self.app.preferences.get("ui.toolbar", True)
- def toggle_toolbar_cb(self, action):
- active = action.get_active()
- if active:
+ def update_ui_parts(self, **updates):
+ """Updates the UI part prefs, then hide/show widgets to match.
+
+ Called without arguments, this updates the UI to match the
+ boolean-valued hash ``ui.parts`` in the app preferences. With keyword
+ arguments, the prefs are updated first, then changes are reflected in
+ the set of visible widgets. Current known parts:
+
+ :``main_toolbar``:
+ The primary toolbar and its menu button.
+ :``menubar``:
+ A conventional menu bar.
+
+ Currently the user cannot turn off both the main toolbar and the
+ menubar: the toolbar will be forced on if an attempt is made.
+ """
+ new_state = self.app.preferences["ui.parts"].copy()
+ new_state.update(updates)
+ # Menu bar
+ if new_state.get("menubar", False):
+ self.menubar.show_all()
+ else:
+ self.menubar.hide()
+ if not new_state.get("main_toolbar", False):
+ new_state["main_toolbar"] = True
+ # Toolbar
+ if new_state.get("main_toolbar", False):
self.toolbar.show_all()
else:
self.toolbar.hide()
- self.app.preferences["ui.toolbar"] = active
+ self.app.preferences["ui.parts"] = new_state
+ self.update_menu_button()
+
+
+ def update_menu_button(self):
+ """Updates the menu button to match toolbar and menubar visibility.
+
+ The menu button is visible when the menu bar is hidden. Since the user
+ must have either a toolbar or a menu or both, this ensures that a menu
+ is on-screen at all times in non-fullscreen mode.
+ """
+ toolbar_visible = self.toolbar.get_property("visible")
+ menubar_visible = self.menubar.get_property("visible")
+ if toolbar_visible and menubar_visible:
+ self.toolbar_manager.menu_button.hide()
+ else:
+ self.toolbar_manager.menu_button.show_all()
+
+
+ def on_menuishbar_radio_change(self, radioaction, current):
+ """Respond to a change of the 'menu bar/toolbar' radio menu items.
+ """
+ value = radioaction.get_current_value()
+ if value == self.MENUISHBAR_RADIO_MENUBAR:
+ self.update_ui_parts(main_toolbar=False, menubar=True)
+ elif value == self.MENUISHBAR_RADIO_MAIN_TOOLBAR:
+ self.update_ui_parts(main_toolbar=True, menubar=False)
+ else:
+ self.update_ui_parts(main_toolbar=True, menubar=True)
# Show Subwindows
while gtk.events_pending():
gtk.main_iteration()
if getattr(self, "_restore_menubar_on_unfullscreen", False):
- self.menubar.show()
+ if self.get_ui_part_enabled("menubar"):
+ self.menubar.show()
del self._restore_menubar_on_unfullscreen
if getattr(self, "_restore_toolbar_on_unfullscreen", False):
- if self.get_show_toolbar():
+ if self.get_ui_part_enabled("main_toolbar"):
self.toolbar.show()
del self._restore_toolbar_on_unfullscreen
if getattr(self, "_restore_subwindows_on_unfullscreen", False):
self.set_show_subwindows(True)
del self._restore_subwindows_on_unfullscreen
+ self.update_menu_button()
+ self.update_fullscreen_action()
+
+ def update_fullscreen_action(self):
+ action = self.action_group.get_action("Fullscreen")
+ if self.is_fullscreen:
+ action.set_stock_id(gtk.STOCK_LEAVE_FULLSCREEN)
+ action.set_tooltip(_("Leave Fullscreen Mode"))
+ action.set_label(_("UnFullscreen"))
+ else:
+ action.set_stock_id(gtk.STOCK_FULLSCREEN)
+ action.set_tooltip(_("Enter Fullscreen Mode"))
+ action.set_label(_("Fullscreen"))
def popupmenu_show_cb(self, action):
self.show_popupmenu()
def show_popupmenu(self, event=None):
self.menubar.set_sensitive(False) # excessive feedback?
+ self.toolbar_manager.menu_button.set_sensitive(False)
button = 1
time = 0
if event is not None:
def popupmenu_done_cb(self, *a, **kw):
# Not sure if we need to bother with this level of feedback,
- # but it actaully looks quite nice to see one menu taking over
+ # but it actually looks quite nice to see one menu taking over
# the other. Makes it clear that the popups are the same thing as
# the full menu, maybe.
self.menubar.set_sensitive(True)
+ self.toolbar_manager.menu_button.set_sensitive(True)
self.popupmenu_last_active = self.popupmenu.get_active()
# BEGIN -- Scratchpad menu options
def save_scratchpad_as_default_cb(self, action):
self.app.filehandler.save_scratchpad(self.app.filehandler.get_scratchpad_default(), export = True)
-
+
def clear_default_scratchpad_cb(self, action):
self.app.filehandler.delete_default_scratchpad()
bg = self.app.doc.model.background
if self.app.scratchpad_doc:
self.app.scratchpad_doc.model.set_background(bg)
-
+
self.app.scratchpad_filename = self.app.preferences['scratchpad.last_opened'] = self.app.filehandler.get_scratchpad_autosave()
def load_scratchpad_cb(self, action):
self.app.filehandler.save_scratchpad_as_dialog()
def revert_current_scratchpad_cb(self, action):
- if os.path.isfile(self.app.scratchpad_filename):
+ if os.path.isfile(self.app.scratchpad_filename):
self.app.filehandler.open_scratchpad(self.app.scratchpad_filename)
print "Reverted to %s" % self.app.scratchpad_filename
else:
print "No file to revert to yet."
-
+
def save_current_scratchpad_cb(self, action):
self.app.filehandler.save_scratchpad(self.app.scratchpad_filename)
enabled = self.app.doc.model.frame_enabled
self.app.doc.model.set_frame_enabled(not enabled)
+ def download_brush_pack_cb(self, *junk):
+ url = 'http://wiki.mypaint.info/index.php?title=Brush_Packages/redirect_mypaint_1.0_gui'
+ print 'URL:', url
+ webbrowser.open(url)
+
def import_brush_pack_cb(self, *junk):
format_id, filename = dialogs.open_dialog(_("Import brush package..."), self,
[(_("MyPaint brush package (*.zip)"), "*.zip")])
d.set_authors([
# (in order of appearance)
u"Martin Renold (%s)" % _('programming'),
- u"Artis Rozentāls (%s)" % _('brushes'),
u"Yves Combe (%s)" % _('portability'),
- u"Popolon (%s)" % _('brushes, programming'),
+ u"Popolon (%s)" % _('programming'),
u"Clement Skau (%s)" % _('programming'),
- u"Marcelo 'Tanda' Cerviño (%s)" % _('patterns, brushes'),
u"Jon Nordby (%s)" % _('programming'),
u"Álinson Santos (%s)" % _('programming'),
u"Tumagonx (%s)" % _('portability'),
u"Ilya Portnov (%s)" % _('programming'),
- u"David Revoy (%s)" % _('brushes'),
- u"Ramón Miranda (%s)" % _('brushes, patterns'),
- u"Enrico Guarnieri 'Ico_dY' (%s)" % _('brushes'),
u"Jonas Wagner (%s)" % _('programming'),
u"Luka Čehovin (%s)" % _('programming'),
u"Andrew Chadwick (%s)" % _('programming'),
u"Till Hartmann (%s)" % _('programming'),
- u"Nicola Lunghi (%s)" % _('patterns'),
- u"Toni Kasurinen (%s)" % _('brushes'),
- u"Сан Саныч (%s)" % _('patterns'),
u'David Grundberg (%s)' % _('programming'),
u"Krzysztof Pasek (%s)" % _('programming'),
+ u"Ben O'Steen (%s)" % _('programming'),
+ u"Ferry Jérémie (%s)" % _('programming'),
+ u"しげっち 'sigetch' (%s)" % _('programming'),
])
d.set_artists([
+ u"Artis Rozentāls (%s)" % _('brushes'),
+ u"Popolon (%s)" % _('brushes'),
+ u"Marcelo 'Tanda' Cerviño (%s)" % _('patterns, brushes'),
+ u"David Revoy (%s)" % _('brushes'),
+ u"Ramón Miranda (%s)" % _('brushes, patterns'),
+ u"Enrico Guarnieri 'Ico_dY' (%s)" % _('brushes'),
u'Sebastian Kraft (%s)' % _('desktop icon'),
+ u"Nicola Lunghi (%s)" % _('patterns'),
+ u"Toni Kasurinen (%s)" % _('brushes'),
+ u"Сан Саныч 'MrMamurk' (%s)" % _('patterns'),
+ u"Andrew Chadwick (%s)" % _('tool icons'),
+ u"Ben O'Steen (%s)" % _('tool icons'),
])
# list all translators, not only those of the current language
d.set_translator_credits(
"\n"),
}
self.app.message_dialog(text[action.get_name()])
+
+