def quit_cb(self, *junk): self.app.doc.model.sync_pending_changes() self.app.save_gui_config( ) # FIXME: should do this periodically, not only on quit ok_to_quit = self.app.filehandler.confirm_destructive_action( title=C_( "Quit confirm dialog: title", u"Really Quit?", ), confirm=C_( "Quit confirm dialog: continue button", u"_Quit", ), ) if not ok_to_quit: return True self.app.doc.model.cleanup() self.app.profiler.cleanup() Gtk.main_quit() return False
def tool_widget_properties(self): """Run the properties dialog""" if not self._dialog: # TRANSLATORS: properties dialog for the current brush group buttons = (Gtk.STOCK_CANCEL, Gtk.ResponseType.REJECT) dia = Gtk.Dialog(title=C_( "brush group properties dialog: title", u"Group \u201C{group_name}\u201D", ).format(group_name=self._group, ), modal=True, destroy_with_parent=True, window_position=Gtk.WindowPosition.MOUSE, buttons=buttons) btn = Gtk.Button( C_( "brush group properties dialog: action buttons", "Rename Group", )) btn.connect("clicked", self._rename_cb) dia.vbox.pack_start(btn, False, False, 0) btn = Gtk.Button( C_( "brush group properties dialog: action buttons", "Export as Zipped Brushset", )) btn.connect("clicked", self._export_cb) dia.vbox.pack_start(btn, False, False, 0) btn = Gtk.Button( C_( "brush group properties dialog: action buttons", "Delete Group", )) btn.connect("clicked", self._delete_cb) dia.vbox.pack_start(btn, False, False, 0) dia.vbox.show_all() self._dialog = dia self._dialog.set_transient_for(self.get_toplevel()) self._dialog.run() self._dialog.hide()
def _set_name_entry_warning_flag(self, show_warning): entry = self.view.layer_name_entry pos = Gtk.EntryIconPosition.SECONDARY warning_showing = entry.get_icon_name(pos) if show_warning: if not warning_showing: entry.set_icon_from_icon_name(pos, "dialog-warning") text = entry.get_text() if text.strip() == u"": msg = C_( "layer properties dialog: name entry: icon tooltip", u"Layer names cannot be empty.", ) else: msg = C_( "layer properties dialog: name entry: icon tooltip", u"Layer name is not unique.", ) entry.set_icon_tooltip_text(pos, msg) elif warning_showing: entry.set_icon_from_icon_name(pos, None) entry.set_icon_tooltip_text(pos, None)
def update_title(self, filename): if filename: # TRANSLATORS: window title for use with a filename title_base = _("%s - MyPaint") % os.path.basename(filename) else: # TRANSLATORS: window title for use without a filename title_base = _("MyPaint") # Show whether legacy 1.x compatibility mode is active if self.app.compat_mode == compatibility.C1X: compat_str = " (%s)" % C_("Prefs Dialog|Compatibility", "1.x") else: compat_str = "" self.set_title(title_base + compat_str)
def open_scrap_cb(self, action): groups = self.list_scraps_grouped() if not groups: msg = _('There are no scrap files named "%s" yet.') % \ (self.get_scrap_prefix() + '[0-9]*') self.app.message_dialog(msg, Gtk.MessageType.WARNING) return next = action.get_name() == 'NextScrap' if next: dialog_title = C_( u'File→Open Next/Prev Scrap confirm dialog: ' u'title', u"Open Next Scrap?" ) idx = 0 delta = 1 else: dialog_title = C_( u'File→Open Next/Prev Scrap confirm dialog: ' u'title', u"Open Previous Scrap?" ) idx = -1 delta = -1 ok_to_open = self.app.filehandler.confirm_destructive_action( title = dialog_title, confirm = C_( u'File→Open Next/Prev Scrap confirm dialog: ' u'continue button', u"_Open" ), ) if not ok_to_open: return for i, group in enumerate(groups): if self.active_scrap_filename in group: idx = i + delta filename = groups[idx % len(groups)][-1] self.open_file(filename)
class HSVSaturationSlider(SliderColorAdjuster): STATIC_TOOLTIP_TEXT = C_( "color component slider: tooltip", u"HSV Saturation", ) def get_color_for_bar_amount(self, amt): col = HSVColor(color=self.get_managed_color()) col.s = amt return col def get_bar_amount_for_color(self, col): return col.s
def _update_brush_header(self, modified=False): """Updates the header strip with the current brush's icon and name""" mb = None if self.app: mb = self.app.brushmanager.selected_brush # Brush name label if mb: if mb.name: name = mb.name.replace("_", " ") else: name = C_( "brush settings editor: header: fallback name", "(Unnamed brush)", ) else: name = "(Not running as part of MyPaint)" if modified: name = C_( "brush settings editor: header: is-modified hint", "{brush_name} [unsaved]", ).format( brush_name = name, ) label = self._builder.get_object("brush_name_label") label.set_text(name) # Brush icon image = self._builder.get_object("brush_preview_image") w = image.get_allocated_width() h = image.get_allocated_height() if mb: pixbuf = mb.preview else: pixbuf = None if pixbuf: pixbuf = pixbuf.scale_simple(w, h, GdkPixbuf.InterpType.BILINEAR) if not pixbuf: pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, w, h) image.set_from_pixbuf(pixbuf)
def _switch_page_cb(self, notebook, page, page_num): tool_widget = page.get_child() has_properties = hasattr(tool_widget, "tool_widget_properties") self._properties_button.set_sensitive(has_properties) title = _tool_widget_get_title(tool_widget) close_tooltip = C_( "workspace: sidebar tabs: button tooltips", u"{tab_title}: close tab", ).format(tab_title=title) props_tooltip = C_( "workspace: sidebar tabs: button tooltips", u"{tab_title}: tab options and properties", ).format(tab_title=title) swap_tooltip = C_( "workspace: sidebar tabs: button tooltips", u"{tab_title}: move tab to other sidebar", ).format(tab_title=title) if not has_properties: props_tooltip = u"" self._properties_button.set_tooltip_text(props_tooltip) self._close_button.set_tooltip_text(close_tooltip) self._sidebar_swap_button.set_tooltip_text(swap_tooltip)
def _remove_cb(self, menuitem): bl = self._brushlist brush = self._brush msg = C_( "brush group: context menu: remove from group", u"Really remove brush “{brush_name}” " u"from group “{group_name}”?").format( brush_name=brush.name, group_name=bl.group, ) if not dialogs.confirm(bl, msg): return bl.remove_brush(brush)
def __init__(self): super(BrushGroupsMenu, self).__init__() from gui.application import get_app self.app = get_app() # Static items item = Gtk.SeparatorMenuItem() self.append(item) item = Gtk.MenuItem(label=C_("brush groups menu", u"New Group…")) item.connect("activate", self._new_brush_group_cb) self.append(item) item = Gtk.MenuItem(label=C_("brush groups menu", u"Import Brushes…")) item.connect("activate", self.app.drawWindow.import_brush_pack_cb) self.append(item) item = Gtk.MenuItem( label=C_("brush groups menu", u"Get More Brushes…")) item.connect("activate", self.app.drawWindow.download_brush_pack_cb) self.append(item) # Dynamic items bm = self.app.brushmanager self._items = {} self._update(bm) bm.groups_changed += self._update
def _new_brush_group_cb(self, widget): # XXX should be moved somewhere more sensible than this toplevel = self.app.drawWindow name = dialogs.ask_for_name( toplevel, C_("new brush group dialog: title", 'Create Group'), '', ) if name is None: return name = name.strip() if name: bm = self.app.brushmanager bm.create_group(name)
class RGBBlueSlider(SliderColorAdjuster): STATIC_TOOLTIP_TEXT = C_("color component slider: tooltip", "RGB Blue") def get_background_validity(self): col = self.get_managed_color() r, g, b = col.get_rgb() return r, g def get_color_for_bar_amount(self, amt): col = RGBColor(color=self.get_managed_color()) col.b = amt return col def get_bar_amount_for_color(self, col): return col.b
def _clone_cb(self, menuitem): bl = self._brushlist brush = self._brush # Pick a nice unique name new_name = C_( "brush group: context menu: unique names for cloned brushes", u"{original_name} copy").format(original_name=brush.name, ) uniquifier = 0 while bl.bm.get_brush_by_name(new_name): uniquifier += 1 new_name = C_( "brush group: context menu: unique names for cloned brushes", u"{original_name} copy {n}").format( name=brush.name, n=uniquifier, ) # Make a copy and insert it near the original brush_copy = brush.clone(new_name) index = bl.brushes.index(brush) + 1 bl.insert_brush(index, brush_copy) brush_copy.save() bl.bm.save_brushorder() # Select the copy, for highlighting bl.bm.select_brush(brush_copy)
def _query_tooltip_cb(self, da, x, y, keyboard_mode, tooltip): s = self._TOOLTIP_ICON_SIZE scaled_pixbuf = self._get_scaled_pixbuf(s) tooltip.set_icon(scaled_pixbuf) brush_name = self._brush_name if not brush_name: brush_name = self._DEFAULT_BRUSH_DISPLAY_NAME # Rare cases, see https://github.com/mypaint/mypaint/issues/402. # Probably just after init. template_params = {"brush_name": lib.xml.escape(brush_name)} markup_template = C_( "current brush indicator: tooltip (no-description case)", u"<b>{brush_name}</b>", ) if self._brush_desc: markup_template = C_( "current brush indicator: tooltip (description case)", u"<b>{brush_name}</b>\n{brush_desc}", ) template_params["brush_desc"] = lib.xml.escape(self._brush_desc) markup = markup_template.format(**template_params) tooltip.set_markup(markup) # TODO: summarize changes? return True
def save_button_clicked_cb(self, button): """Save the current brush settings (overwrites)""" bm = self.app.brushmanager b = bm.selected_brush if not b.name: msg = C_( 'brush settings editor: save brush: error message', 'No brush selected, please use “Add As New” instead.', ) dialogs.error(self, msg) return b.brushinfo = self.app.brush.clone() b.save() self._mark_all_settings_unmodified_in_treeview() self._update_brush_header(modified=False)
class CIECAMChromaSlider (SliderColorAdjuster): STATIC_TOOLTIP_TEXT = C_("color component slider: tooltip", "CIECAM Colorfulness/Chroma/Saturation") draw_background = True @property def samples(self): alloc = self.get_allocation() len = self.vertical and alloc.height or alloc.width len -= self.BORDER_WIDTH * 2 return min(int(len // 3), 16) def get_color_for_bar_amount(self, amt): col = self._get_app_brush_color() col.s = max(0.0, amt) * 120 col.gamutmapping = "highlightC" col.cachedrgb = None return col def get_bar_amount_for_color(self, col): col = self._get_app_brush_color() if col.limit_purity is not None: return min(col.s, col.limit_purity) / 120 else: return max(0.0, col.s) / 120 def get_background_validity(self): from gui.application import get_app app = get_app() cm = self.get_color_manager() prefs = cm.get_prefs() try: if app.brush.get_setting('cie_v') == '': return True limit_purity = prefs['color.limit_purity'] vsh = ( int(app.brush.get_setting('cie_v') * 100), int(app.brush.get_setting('cie_s') * 100), int(app.brush.get_setting('cie_h') * 100)) cieaxes = app.brush.get_setting('cieaxes'), lightsource = ( app.brush.get_setting('lightsource_X'), app.brush.get_setting('lightsource_Y'), app.brush.get_setting('lightsource_Z')) except KeyError: return True return vsh, cieaxes, lightsource, limit_purity
def import_layers_cb(self, action): """Action callback: import layers from multiple files.""" dialog = Gtk.FileChooserDialog(title=C_( u'Layers→Import Layers: files-chooser dialog: title', u"Import Layers", ), parent=self.app.drawWindow, action=Gtk.FileChooserAction.OPEN, buttons=[ Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OPEN, Gtk.ResponseType.OK, ]) dialog.set_default_response(Gtk.ResponseType.OK) dialog.set_select_multiple(True) # TODO: decide how well the preview plays with multiple-select. preview = Gtk.Image() dialog.set_preview_widget(preview) dialog.connect("update-preview", self.update_preview_cb, preview) _add_filters_to_dialog(self.file_filters, dialog) # Choose the most recent save folder. self._update_recent_items() for item in reversed(self._recent_items): uri = item.get_uri() fn, _h = lib.glib.filename_from_uri(uri) dn = os.path.dirname(fn) if os.path.isdir(dn): dialog.set_current_folder(dn) break filenames = [] try: if dialog.run() == Gtk.ResponseType.OK: dialog.hide() filenames = dialog.get_filenames() finally: dialog.destroy() if filenames: filenames = [f.decode('utf-8') for f in filenames] self.import_layers(filenames)
def __load_clicked(self, button): preview = HCYMaskPreview() preview.set_size_request(128, 128) target_mgr = self.target.get_color_manager() prefs_ro = deepcopy(target_mgr.get_prefs()) datapath = target_mgr.get_data_path() mgr = ColorManager(prefs=prefs_ro, datapath=datapath) preview.set_color_manager(mgr) preview.set_managed_color(self.editor.get_managed_color()) dialog_title = C_( "HCY Gamut Mask load dialog: window title", u"Load Mask from a GIMP Palette", ) pal = palette_load_via_dialog(title=dialog_title, parent=self, preview=preview) if pal is None: return self.editor.set_mask_from_palette(pal)
def __init__(self): app = None if __name__ != '__main__': from gui.application import get_app app = get_app() self._brush = app.brush bm = app.brushmanager bm.brush_selected += self.brush_selected_cb else: self._brush = lib.brush.BrushInfo() self._brush.load_defaults() SubWindow.__init__(self, app, key_input=True) # Tracking vars for updating the treestore in response to # brush setting changes and loading new brushes. self._treestore = None self._setting_treepath = {} # {cname: Gtk.TreePath}, path for setting self._group_treepath = {} # {groupid: Gtk.TreePath}, path for group self._setting_group = {} # {cname: groupid}, group containing setting # Adjusters: may be shared with those of the app self._base_adj = {} #: setting cname => base value adj self._input_y_adj = {} #: input name => scale y range (+-) adj self._input_xmin_adj = {} #: input name => scale x min adj self._input_xmax_adj = {} #: input name => scale x min adj self._disable_input_adj_changed_cb = False self._init_adjustments() self.set_title( C_( "brush settings editor: subwindow title", "Brush Settings Editor", )) self._scales = [] self._setting = None self._builder = Gtk.Builder() self._builder.set_translation_domain("mypaint") self._build_ui() self._base_value_scale = self._builder.get_object("base_value_scale") self.connect_after("show", self._post_show_cb) self.connect('button-press-event', self._clear_focus) editor = self._builder.get_object("brush_editor") self.add(editor) self._brush.observers.append(self.brush_modified_cb) self._live_update_idle_cb_id = None self._updating_metadata_ui = False self.set_default_size(1000, 800)
def __init__(self, root_stack): super(FlatLayerList, self).__init__() root_stack.layer_properties_changed += self._layer_props_changed_cb root_stack.layer_inserted += self._layer_inserted_cb root_stack.layer_deleted += self._layer_deleted_cb self.root = root_stack # Column data : name, layer_path, layer self.set_column_types((str, object, object)) default_selection = C_( "fill option: default option in the Source Layer dropdown", u"Selected Layer") # Add default option and separator self.append((default_selection, None, None)) self.append((None, None, None)) # Flatten layer tree into rows for layer in root_stack: self._initalize(layer)
def _rename_button_clicked_cb(self, button): old_name = self._lvm.current_view_name if old_name is None: return new_name = gui.dialogs.ask_for_name( title=C_( "view controls: rename view dialog", u"Rename View", ), widget=self._app.drawWindow, default=old_name, ) if new_name is None: return new_name = new_name.strip() if new_name == old_name: return doc = self._docmodel cmd = lib.layervis.RenameActiveLayerView(doc, new_name) doc.do(cmd)
def render_language_names(_, name_cell, model, it): locale, lang_en, lang_nat = model[it][:3] # Mark default with bold font if locale is None: name_cell.set_property("markup", "<b>{lang_en}</b>".format(lang_en=lang_en)) # If a language does not have its native spelling # available, only show its name in english. elif lang_en == lang_nat or lang_nat == "": name_cell.set_property("text", lang_en) else: name_cell.set_property( "text", C_( "Prefs Dialog|View|Interface - menu entries", # TRANSLATORS: lang_en is the english name of the language # TRANSLATORS: lang_nat is the native name of the language # TRANSLATORS: in that _same language_. # TRANSLATORS: This can just be copied most of the time. "{lang_en} - ({lang_nat})").format(lang_en=lang_en, lang_nat=lang_nat))
class HCYLumaSlider (SliderColorAdjuster): STATIC_TOOLTIP_TEXT = C_("color component slider: tooltip", "HCY Luma (Y')") @property def samples(self): alloc = self.get_allocation() len = self.vertical and alloc.height or alloc.width len -= self.BORDER_WIDTH * 2 return min(int(len / 3), 64) def get_color_for_bar_amount(self, amt): col = HCYColor(color=self.get_managed_color()) col.y = amt return col def get_bar_amount_for_color(self, col): col = HCYColor(color=col) return col.y def get_background_validity(self): col = HCYColor(color=self.get_managed_color()) return int(col.h * 1000), int(col.c * 1000)
def __save_clicked(self, button): pal = Palette() mask = self.editor.get_mask() for i, shape in enumerate(mask): for j, col in enumerate(shape): col_name = "mask#%d primary#%d" % (i, j) # NOT localised pal.append(col, col_name) preview = HCYMaskPreview() preview.set_size_request(128, 128) target_mgr = self.target.get_color_manager() prefs_ro = deepcopy(target_mgr.get_prefs()) datapath = target_mgr.get_data_path() mgr = ColorManager(prefs=prefs_ro, datapath=datapath) preview.set_color_manager(mgr) preview.set_managed_color(self.editor.get_managed_color()) palette_save_via_dialog( pal, title=C_("HCY Gamut Mask load dialog: window title", u"Save Mask as a GIMP Palette"), parent=self, preview=preview, )
def init_view(self): self._init_model() store = Gtk.ListStore(str, str) # columns: <our_id, display_markup> markup = C_( "view controls: dropdown: item markup", u"<i>{builtin_view_name}</i>", ).format(builtin_view_name=escape( lib.layervis.UNSAVED_VIEW_DISPLAY_NAME), ) # None has a special meaning for GTK combo ID columns, # so we substitute the empty string. Corrolory: you can't name # views to the empty string. store.append([u"", markup]) store.set_sort_column_id(0, Gtk.SortType.ASCENDING) self._store = store combo = self.view.current_view_combo combo.set_model(store) combo.set_id_column(0) cell = self.view.layer_text_cell combo.add_attribute(cell, "markup", 1) self._refresh_ui()
import os import platform from gi.repository import Gtk from gi.repository import GdkPixbuf from gi.repository import GLib import cairo from lib.gettext import C_ import lib.meta from lib.xml import escape ## Program-related string constants COPYRIGHT_STRING = C_( "About dialog: copyright statement", u"Copyright (C) 2005-2016\n" u"Martin Renold and the MyPaint Development Team") WEBSITE_URI = "http://mypaint.org" LICENSE_SUMMARY = C_( "About dialog: license summary", u"This program is free software; you can redistribute it and/or modify " u"it under the terms of the GNU General Public License as published by " u"the Free Software Foundation; either version 2 of the License, or " u"(at your option) any later version.\n" u"\n" u"This program is distributed in the hope that it will be useful, " u"but WITHOUT ANY WARRANTY. See the COPYING file for more details.") ## Credits-related string constants # Strings for specific tasks, all translated
def import_brushpack(self, path, window): """Import a brushpack from a zipfile, with confirmation dialogs. :param path: Brush pack zipfile path :type path: str :param window: Parent window, for dialogs to set. :type window: GtkWindow :returns: Set of imported group names :rtype: set """ with zipfile.ZipFile(path) as zf: names = zf.namelist() # zipfile does utf-8 decoding on its own; this is just to make # sure we have only unicode objects as brush names. names = [s.decode('utf-8') for s in names] readme = None if _BRUSHPACK_README in names: readme = zf.read(_BRUSHPACK_README) if _BRUSHPACK_ORDERCONF not in names: raise InvalidBrushpack( C_( "brushpack import failure messages", u"No file named “{order_conf_file}”. " u"This is not a brushpack.").format( order_conf_file=_BRUSHPACK_ORDERCONF, )) groups = _parse_order_conf(zf.read(_BRUSHPACK_ORDERCONF)) new_brushes = [] for brushes in groups.itervalues(): for brush in brushes: if brush not in new_brushes: new_brushes.append(brush) logger.info( "%d different brushes found in %r of brushpack", len(new_brushes), _BRUSHPACK_ORDERCONF, ) # Validate file content. The names in order.conf and the # brushes found in the zip must match. This should catch # encoding screwups, everything should be a unicode object. for brush in new_brushes: if brush + '.myb' not in names: raise InvalidBrushpack( C_( "brushpack import failure messages", u"Brush “{brush_name}” is " u"listed in “{order_conf_file}”, " u"but it does not exist in the zipfile.").format( brush_name=brush, order_conf_file=_BRUSHPACK_ORDERCONF, )) for name in names: if name.endswith('.myb'): brush = name[:-4] if brush not in new_brushes: raise InvalidBrushpack( C_( "brushpack import failure messages", u"Brush “{brush_name}” exists in the zipfile, " u"but it is not listed in “{order_conf_file}”." ).format( brush_name=brush, order_conf_file=_BRUSHPACK_ORDERCONF, )) if readme: answer = dialogs.confirm_brushpack_import( basename(path), window, readme, ) if answer == Gtk.ResponseType.REJECT: return set() do_overwrite = False do_ask = True renamed_brushes = {} imported_groups = set() for groupname, brushes in groups.iteritems(): managed_brushes = self.get_group_brushes(groupname) if managed_brushes: answer = dialogs.confirm_rewrite_group( window, translate_group_name(groupname), translate_group_name(DELETED_BRUSH_GROUP)) if answer == dialogs.CANCEL: return set() elif answer == dialogs.OVERWRITE_THIS: self.delete_group(groupname) elif answer == dialogs.DONT_OVERWRITE_THIS: i = 0 old_groupname = groupname while groupname in self.groups: i += 1 groupname = old_groupname + '#%d' % i managed_brushes = self.get_group_brushes(groupname) imported_groups.add(groupname) for brushname in brushes: # extract the brush from the zip assert (brushname + '.myb') in zf.namelist() # Support for utf-8 ZIP filenames that don't have # the utf-8 bit set. brushname_utf8 = brushname.encode('utf-8') try: myb_data = zf.read(brushname + '.myb') except KeyError: myb_data = zf.read(brushname_utf8 + '.myb') try: preview_data = zf.read(brushname + '_prev.png') except KeyError: preview_data = zf.read(brushname_utf8 + '_prev.png') # in case we have imported that brush already in a # previous group, but decided to rename it if brushname in renamed_brushes: brushname = renamed_brushes[brushname] # possibly ask how to import the brush file # (if we didn't already) b = self.get_brush_by_name(brushname) if brushname in new_brushes: new_brushes.remove(brushname) if b: existing_preview_pixbuf = b.preview if do_ask: answer = dialogs.confirm_rewrite_brush( window, brushname, existing_preview_pixbuf, preview_data, ) if answer == dialogs.CANCEL: break elif answer == dialogs.OVERWRITE_ALL: do_overwrite = True do_ask = False elif answer == dialogs.OVERWRITE_THIS: do_overwrite = True do_ask = True elif answer == dialogs.DONT_OVERWRITE_THIS: do_overwrite = False do_ask = True elif answer == dialogs.DONT_OVERWRITE_ANYTHING: do_overwrite = False do_ask = False # find a new name (if requested) brushname_old = brushname i = 0 while not do_overwrite and b: i += 1 brushname = brushname_old + '#%d' % i renamed_brushes[brushname_old] = brushname b = self.get_brush_by_name(brushname) if not b: b = ManagedBrush(self, brushname) # write to disk and reload brush (if overwritten) prefix = b._get_fileprefix(saving=True) with open(prefix + '.myb', 'w') as myb_f: myb_f.write(myb_data) with open(prefix + '_prev.png', 'wb') as preview_f: preview_f.write(preview_data) b.load() # finally, add it to the group if b not in managed_brushes: managed_brushes.append(b) self.brushes_changed(managed_brushes) if DELETED_BRUSH_GROUP in self.groups: # remove deleted brushes that are in some group again self.delete_group(DELETED_BRUSH_GROUP) return imported_groups
def __init__(self, monitor=None): """Initialize :param Monitor monitor: monitor instance (for testing) By default, the central app's `device_monitor` is used to permit parameterless construction. """ super(SettingsEditor, self).__init__() if monitor is None: from gui.application import get_app app = get_app() monitor = app.device_monitor self._monitor = monitor self._devices_store = Gtk.ListStore(object) self._devices_view = Gtk.TreeView(self._devices_store) # TRANSLATORS: Column's data is the device's name col = Gtk.TreeViewColumn( C_( "prefs: devices table: column header", "Device", )) col.set_min_width(200) col.set_expand(True) col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) self._devices_view.append_column(col) cell = Gtk.CellRendererText() cell.set_property("ellipsize", Pango.EllipsizeMode.MIDDLE) col.pack_start(cell, True) col.set_cell_data_func(cell, self._device_name_datafunc) # TRANSLATORS: Column's data is an integer count of the number of axes col = Gtk.TreeViewColumn( C_( "prefs: devices table: column header", "Axes", )) col.set_min_width(30) col.set_resizable(True) col.set_expand(False) col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) self._devices_view.append_column(col) cell = Gtk.CellRendererText() col.pack_start(cell, True) col.set_cell_data_func(cell, self._device_axes_datafunc) # TRANSLATORS: Column shows type labels ("Touchscreen", "Pen" etc.) col = Gtk.TreeViewColumn( C_( "prefs: devices table: column header", "Type", )) col.set_min_width(120) col.set_resizable(True) col.set_expand(False) col.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE) self._devices_view.append_column(col) cell = Gtk.CellRendererText() cell.set_property("ellipsize", Pango.EllipsizeMode.END) col.pack_start(cell, True) col.set_cell_data_func(cell, self._device_type_datafunc) # Usage config value => string store (dropdowns) store = Gtk.ListStore(str, str) for conf_val in AllowedUsage.VALUES: string = AllowedUsage.DISPLAY_STRING[conf_val] store.append([conf_val, string]) self._usage_store = store # TRANSLATORS: Column's data is a dropdown allowing the allowed # TRANSLATORS: tasks for the row's device to be configured. col = Gtk.TreeViewColumn( C_( "prefs: devices table: column header", "Use for...", )) col.set_min_width(100) col.set_resizable(True) col.set_expand(False) self._devices_view.append_column(col) cell = Gtk.CellRendererCombo() cell.set_property("model", self._usage_store) cell.set_property("text-column", self._USAGE_STRING_COL) cell.set_property("mode", Gtk.CellRendererMode.EDITABLE) cell.set_property("editable", True) cell.set_property("has-entry", False) cell.set_property("ellipsize", Pango.EllipsizeMode.END) cell.connect("changed", self._usage_cell_changed_cb) col.pack_start(cell, True) col.set_cell_data_func(cell, self._device_usage_datafunc) # Scroll action config value => string store (dropdowns) store = Gtk.ListStore(str, str) for conf_val in ScrollAction.VALUES: string = ScrollAction.DISPLAY_STRING[conf_val] store.append([conf_val, string]) self._scroll_store = store # TRANSLATORS: Column's data is a dropdown for how the device's # TRANSLATORS: scroll wheel or scroll-gesture events are to be # TRANSLATORS: interpreted normally. col = Gtk.TreeViewColumn( C_( "prefs: devices table: column header", "Scroll...", )) col.set_min_width(100) col.set_resizable(True) col.set_expand(False) self._devices_view.append_column(col) cell = Gtk.CellRendererCombo() cell.set_property("model", self._scroll_store) cell.set_property("text-column", self._USAGE_STRING_COL) cell.set_property("mode", Gtk.CellRendererMode.EDITABLE) cell.set_property("editable", True) cell.set_property("has-entry", False) cell.set_property("ellipsize", Pango.EllipsizeMode.END) cell.connect("changed", self._scroll_cell_changed_cb) col.pack_start(cell, True) col.set_cell_data_func(cell, self._device_scroll_datafunc) # Pretty borders view_scroll = Gtk.ScrolledWindow() view_scroll.set_shadow_type(Gtk.ShadowType.ETCHED_IN) pol = Gtk.PolicyType.AUTOMATIC view_scroll.set_policy(pol, pol) view_scroll.add(self._devices_view) view_scroll.set_hexpand(True) view_scroll.set_vexpand(True) self.attach(view_scroll, 0, 0, 1, 1) self._update_devices_store() self._monitor.devices_updated += self._update_devices_store
# The per-device settings are stored in the prefs in a sub-dict whose # string keys are formed from the device name and enough extra # information to (hopefully) identify the device uniquely. Names are not # unique, and IDs vary according to the order in which you plug devices # in. So for now, our unique strings use a combination of the device's # name, its source as presented by GDK, and the number of axes. _PREFS_ROOT = "input.devices" _PREFS_DEVICE_SUBKEY_FMT = "{name}:{source}:{num_axes}" ## Device type strings _DEVICE_TYPE_STRING = { Gdk.InputSource.CURSOR: C_( "prefs: device's type label", "Cursor/puck", ), Gdk.InputSource.ERASER: C_( "prefs: device's type label", "Eraser", ), Gdk.InputSource.KEYBOARD: C_( "prefs: device's type label", "Keyboard", ), Gdk.InputSource.MOUSE: C_( "prefs: device's type label", "Mouse", ), Gdk.InputSource.PEN: C_( "prefs: device's type label",
def rename_button_clicked_cb(self, button): """Rename the current brush; user is prompted for a new name""" bm = self.app.brushmanager src_brush = bm.selected_brush if not src_brush.name: dialogs.error( self, C_( 'brush settings editor: rename brush: error message', 'No brush selected!', )) return src_name_pp = src_brush.name.replace('_', ' ') dst_name = dialogs.ask_for_name( self, C_( "brush settings editor: rename brush: dialog title", "Rename Brush", ), src_name_pp, ) if not dst_name: return dst_name = dst_name.replace(' ', '_') # ensure we don't overwrite an existing brush by accident dst_deleted = None for group, brushes in bm.groups.iteritems(): for b2 in brushes: if b2.name == dst_name: if group == brushmanager.DELETED_BRUSH_GROUP: dst_deleted = b2 else: msg = C_( 'brush settings editor: ' 'rename brush: error message', 'A brush with this name already exists!', ) dialogs.error(self, msg) return logger.info("Renaming brush %r -> %r", src_brush.name, dst_name) if dst_deleted: deleted_group = brushmanager.DELETED_BRUSH_GROUP deleted_brushes = bm.get_group_brushes(deleted_group) deleted_brushes.remove(dst_deleted) bm.brushes_changed(deleted_brushes) # save src as dst src_name = src_brush.name src_brush.name = dst_name src_brush.save() src_brush.name = src_name # load dst dst_brush = brushmanager.ManagedBrush(bm, dst_name, persistent=True) dst_brush.load() # Replace src with dst, but keep src in the deleted list if it # is a stock brush self._delete_brush(src_brush, replacement=dst_brush) bm.select_brush(dst_brush)