class TagImageField(gtk.HBox): def __init__(self, all_button=True): gtk.HBox.__init__(self, homogeneous=False, spacing=5) self.parent_row = None self.all_func = None self.update_func = None # Prevents the update function from being called, make # sure you do that manually after the batch update self.batch_update = False self.pixbuf = None self.info = CoverImage(None, None, None, None) self.default_type = 3 self.mime_info = { 'image/jpeg': { # Title for display 'title': _('JPEG image'), # Type and options for GDK Pixbuf saving 'type': 'jpeg', 'options': { 'quality': '90' } }, 'image/png': { 'title': _('PNG image'), 'type': 'png', 'options': {} }, 'image/': { 'title': _('Image'), # Store unknown images as JPEG 'type': 'jpeg', 'options': { 'quality': '90' } }, # TODO: Handle linked images '-->': { 'title': _('Linked image') } } builder = gtk.Builder() builder.add_from_file( xdg.get_data_path('ui', 'trackproperties_dialog_cover_row.ui')) builder.connect_signals(self) cover_row = builder.get_object('cover_row') cover_row.reparent(self) button = builder.get_object('button') button.drag_dest_set(gtk.DEST_DEFAULT_ALL, [], gtk.gdk.ACTION_COPY) button.drag_dest_add_uri_targets() self.image = builder.get_object('image') self.info_label = builder.get_object('info_label') self.type_model = builder.get_object('type_model') self.type_selection = builder.get_object('type_selection') self.type_selection.set_sensitive(False) self.description_entry = builder.get_object('description_entry') self.description_entry.set_sensitive(False) self.all_button = None if all_button: self.all_button = AllButton(self) self.pack_start(self.all_button, expand=False, fill=False) def grab_focus(self): """ Gives focus to the internal widget """ self.image.grab_focus() def register_parent_row(self, parent_row): self.parent_row = parent_row def register_update_func(self, func): self.update_func = func def register_all_func(self, function): self.all_func = function def set_value(self, val, all_vals=None, doupdate=True): if doupdate: if val: loader = gtk.gdk.PixbufLoader() try: loader.write(val.data) loader.close() except glib.GError: pass else: self.batch_update = True self.set_pixbuf(loader.get_pixbuf(), val.mime) # some file types do not support multiple cover types if val.type is not None: self.type_selection.set_active(val.type) self.type_selection.set_sensitive(True) else: self.type_selection.set_active(-1) self.type_selection.set_sensitive(False) if val.desc is not None: self.description_entry.set_text(val.desc) self.description_entry.set_sensitive(True) else: self.description_entry.set_text('') self.description_entry.set_sensitive(False) self.batch_update = False else: self.batch_update = True self.set_pixbuf(None) self.type_selection.set_active(-1) self.type_selection.set_sensitive(False) self.description_entry.set_text('') self.description_entry.set_sensitive(False) self.batch_update = False self.call_update_func() if not None in (all_vals, self.all_button): self.all_button.set_active(all(val == v for v in all_vals)) def get_value(self): if not self.pixbuf: return None mime = self.mime_info[self.info.mime] # Retrieve proper image data writer = io.BytesIO() self.pixbuf.save_to_callback(writer.write, mime['type'], mime['options']) # Move to the beginning of the buffer to allow read operations writer.seek(0) return self.info._replace(data=writer.read()) def call_update_func(self): """ Wrapper around the update function """ if not self.update_func or self.batch_update: return self.update_func(self, self.parent_row.tag, self.parent_row.multi_id, self.get_value) def set_pixbuf(self, pixbuf, mime=None): """ Updates the displayed cover image and info values """ self.pixbuf = pixbuf if pixbuf is None: self.image.set_from_stock(gtk.STOCK_ADD, gtk.ICON_SIZE_DIALOG) self.info_label.set_markup('') else: self.image.set_from_pixbuf( pixbuf.scale_simple(100, 100, gtk.gdk.INTERP_BILINEAR)) width, height = pixbuf.get_width(), pixbuf.get_height() if mime is None: # TRANSLATORS: do not translate 'width' and 'height' markup = _('{width}x{height} pixels').format(width=width, height=height) else: # TRANSLATORS: do not translate 'format', 'width', and 'height' markup = _('{format} ({width}x{height} pixels)').format( format=self.mime_info.get( mime, self.mime_info['image/'])['title'], width=width, height=height) self.info_label.set_markup(markup) self.info = self.info._replace(mime=mime) def on_button_clicked(self, button): """ Allows setting the cover image using a file selection dialog """ dialog = dialogs.FileOperationDialog( title=_('Select image to set as cover'), parent=self.get_toplevel(), buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK)) dialog.set_select_multiple(False) filefilter = gtk.FileFilter() # Not using gtk.FileFilter.add_pixbuf_formats since # not all image formats are supported in tags filefilter.set_name(_('Supported image formats')) filefilter.add_pattern('*.[jJ][pP][gG]') filefilter.add_pattern('*.[jJ][pP][eE][gG]') filefilter.add_pattern('*.[pP][nN][gG]') dialog.add_filter(filefilter) if dialog.run() == gtk.RESPONSE_OK: filename = dialog.get_filename() try: pixbuf = gtk.gdk.pixbuf_new_from_file(filename) info = gtk.gdk.pixbuf_get_file_info(filename)[0] except TypeError: pass else: self.batch_update = True self.set_pixbuf(pixbuf, info['mime_types'][0]) self.type_selection.set_active(self.default_type) self.type_selection.set_sensitive(True) self.description_entry.set_text( os.path.basename(filename).rsplit('.', 1)[0]) self.description_entry.set_sensitive(True) self.batch_update = False self.call_update_func() dialog.destroy() def on_button_drag_data_received(self, widget, context, x, y, selection, info, time): """ Allows setting the cover image via drag and drop """ if selection.target == 'text/uri-list': filename = gio.File(selection.get_uris()[0]).get_path() try: pixbuf = gtk.gdk.pixbuf_new_from_file(filename) info = gtk.gdk.pixbuf_get_file_info(filename)[0] except TypeError: pass else: self.batch_update = True self.set_pixbuf(pixbuf, info['mime_types'][0]) self.type_selection.set_active(self.default_type) self.description_entry.set_sensitive(True) self.description_entry.set_text( os.path.basename(filename).rsplit('.', 1)[0]) self.description_entry.set_sensitive(True) self.batch_update = False self.call_update_func() def on_type_selection_changed(self, combobox): """ Notifies about changes in the cover type """ self.info = self.info._replace( type=self.type_model[combobox.get_active()][0]) self.call_update_func() def on_description_entry_changed(self, entry): """ Notifies about changes in the cover description """ self.info = self.info._replace(desc=entry.get_text()) self.call_update_func()
class TagImageField(Gtk.HBox): def __init__(self, all_button=True): Gtk.HBox.__init__(self, homogeneous=False, spacing=5) self.parent_row = None self.all_func = None self.update_func = None # Prevents the update function from being called, make # sure you do that manually after the batch update self.batch_update = False self.pixbuf = None self.info = CoverImage(None, None, None, None) self.default_type = 3 self.mime_info = { 'image/jpeg': { # Title for display 'title': _('JPEG image'), # Type and options for GDK Pixbuf saving 'type': 'jpeg', 'options': {'quality': '90'} }, 'image/png': { 'title': _('PNG image'), 'type': 'png', 'options': {} }, 'image/': { 'title': _('Image'), # Store unknown images as JPEG 'type': 'jpeg', 'options': {'quality': '90'} }, # TODO: Handle linked images '-->': { 'title': _('Linked image') } } builder = Gtk.Builder() builder.add_from_file(xdg.get_data_path('ui', 'trackproperties_dialog_cover_row.ui')) builder.connect_signals(self) cover_row = builder.get_object('cover_row') cover_row.reparent(self) button = builder.get_object('button') button.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) button.drag_dest_add_uri_targets() self.image = builder.get_object('image') self.info_label = builder.get_object('info_label') self.type_model = builder.get_object('type_model') self.type_selection = builder.get_object('type_selection') self.type_selection.set_sensitive(False) self.type_selection.connect('scroll-event', dummy_scroll_handler) self.description_entry = builder.get_object('description_entry') self.description_entry.set_sensitive(False) self.all_button = None if all_button: self.all_button = AllButton(self) self.pack_start(self.all_button, False, False, 0) def grab_focus(self): """ Gives focus to the internal widget """ self.image.grab_focus() def register_parent_row(self, parent_row): self.parent_row = parent_row def register_update_func(self, func): self.update_func = func def register_all_func(self, function): self.all_func = function def set_value(self, val, all_vals=None, doupdate=True): if doupdate: if val: loader = GdkPixbuf.PixbufLoader() try: loader.write(val.data) loader.close() except GLib.GError: pass else: self.batch_update = True self.set_pixbuf(loader.get_pixbuf(), val.mime) # some file types do not support multiple cover types if val.type is not None: self.type_selection.set_active(val.type) self.type_selection.set_sensitive(True) else: self.type_selection.set_active(-1) self.type_selection.set_sensitive(False) if val.desc is not None: self.description_entry.set_text(val.desc) self.description_entry.set_sensitive(True) else: self.description_entry.set_text('') self.description_entry.set_sensitive(False) self.batch_update = False else: self.batch_update = True self.set_pixbuf(None) self.type_selection.set_active(-1) self.type_selection.set_sensitive(False) self.description_entry.set_text('') self.description_entry.set_sensitive(False) self.batch_update = False self.call_update_func() if not None in (all_vals, self.all_button): self.all_button.set_active(all(val == v for v in all_vals)) def get_value(self): if not self.pixbuf: return None mime = self.mime_info[self.info.mime] # Retrieve proper image data writer = io.BytesIO() self.pixbuf.save_to_callback(writer.write, mime['type'], mime['options']) # Move to the beginning of the buffer to allow read operations writer.seek(0) return self.info._replace(data=writer.read()) def call_update_func(self): """ Wrapper around the update function """ if not self.update_func or self.batch_update: return self.update_func(self, self.parent_row.tag, self.parent_row.multi_id, self.get_value) def set_pixbuf(self, pixbuf, mime=None): """ Updates the displayed cover image and info values """ self.pixbuf = pixbuf if pixbuf is None: self.image.set_from_icon_name('list-add', Gtk.IconSize.DIALOG) self.info_label.set_markup('') else: self.image.set_from_pixbuf(pixbuf.scale_simple( 100, 100, GdkPixbuf.InterpType.BILINEAR)) width, height = pixbuf.get_width(), pixbuf.get_height() if mime is None: # TRANSLATORS: do not translate 'width' and 'height' markup = _('{width}x{height} pixels').format(width=width, height=height) else: # TRANSLATORS: do not translate 'format', 'width', and 'height' markup = _('{format} ({width}x{height} pixels)').format( format=self.mime_info.get(mime, self.mime_info['image/'])['title'], width=width, height=height ) self.info_label.set_markup(markup) self.info = self.info._replace(mime=mime) def on_button_clicked(self, button): """ Allows setting the cover image using a file selection dialog """ dialog = dialogs.FileOperationDialog( title=_('Select image to set as cover'), parent=self.get_toplevel(), buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK) ) dialog.set_select_multiple(False) filefilter = Gtk.FileFilter() # Not using Gtk.FileFilter.add_pixbuf_formats since # not all image formats are supported in tags filefilter.set_name(_('Supported image formats')) filefilter.add_pattern('*.[jJ][pP][gG]') filefilter.add_pattern('*.[jJ][pP][eE][gG]') filefilter.add_pattern('*.[pP][nN][gG]') dialog.add_filter(filefilter) if dialog.run() == Gtk.ResponseType.OK: filename = dialog.get_filename() try: pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) info = GdkPixbuf.Pixbuf.get_file_info(filename)[0] except TypeError: pass else: self.batch_update = True self.set_pixbuf(pixbuf, info['mime_types'][0]) self.type_selection.set_active(self.default_type) self.type_selection.set_sensitive(True) self.description_entry.set_text(os.path.basename(filename).rsplit('.', 1)[0]) self.description_entry.set_sensitive(True) self.batch_update = False self.call_update_func() dialog.destroy() def on_button_drag_data_received(self, widget, context, x, y, selection, info, time): """ Allows setting the cover image via drag and drop """ if selection.target.name() == 'text/uri-list': filename = Gio.File.new_for_uri(selection.get_uris()[0]).get_path() try: pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) info = GdkPixbuf.Pixbuf.get_file_info(filename)[0] except TypeError: pass else: self.batch_update = True self.set_pixbuf(pixbuf, info['mime_types'][0]) self.type_selection.set_active(self.default_type) self.description_entry.set_sensitive(True) self.description_entry.set_text(os.path.basename(filename).rsplit('.', 1)[0]) self.description_entry.set_sensitive(True) self.batch_update = False self.call_update_func() def on_type_selection_changed(self, combobox): """ Notifies about changes in the cover type """ self.info = self.info._replace(type=self.type_model[combobox.get_active()][0]) self.call_update_func() def on_description_entry_changed(self, entry): """ Notifies about changes in the cover description """ self.info = self.info._replace(desc=entry.get_text()) self.call_update_func()
class TagImageField(Gtk.Box): __gtype_name__ = 'TagImageField' button, image, type_model, description_entry, type_selection, \ info_label = GtkTemplate.Child.widgets(6) def __init__(self, all_button=True): Gtk.Box.__init__(self) self.init_template() self.parent_row = None self.all_func = None self.update_func = None # Prevents the update function from being called, make # sure you do that manually after the batch update self.batch_update = False self.pixbuf = None self.info = CoverImage(None, None, None, None) self.default_type = 3 self.mime_info = { 'image/jpeg': { # Title for display 'title': _('JPEG image'), # Type and options for GDK Pixbuf saving 'type': 'jpeg', 'options': { 'quality': '90' } }, 'image/png': { 'title': _('PNG image'), 'type': 'png', 'options': {} }, 'image/': { 'title': _('Image'), # Store unknown images as JPEG 'type': 'jpeg', 'options': { 'quality': '90' } }, # TODO: Handle linked images '-->': { 'title': _('Linked image') } } self.button.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.COPY) self.button.drag_dest_add_uri_targets() self.type_selection.connect('scroll-event', dummy_scroll_handler) self.all_button = None if all_button: self.all_button = AllButton(self) self.pack_start(self.all_button, False, False, 0) def grab_focus(self): """ Gives focus to the internal widget """ self.image.grab_focus() def register_parent_row(self, parent_row): self.parent_row = parent_row def register_update_func(self, func): self.update_func = func def register_all_func(self, function): self.all_func = function def set_value(self, val, all_vals=None, doupdate=True): if doupdate: if val: loader = GdkPixbuf.PixbufLoader() try: loader.write(val.data) loader.close() except GLib.GError: pass else: self.batch_update = True self.set_pixbuf(loader.get_pixbuf(), val.mime) # some file types do not support multiple cover types if val.type is not None: self.type_selection.set_active(val.type) self.type_selection.set_sensitive(True) else: self.type_selection.set_active(-1) self.type_selection.set_sensitive(False) if val.desc is not None: self.description_entry.set_text(val.desc) self.description_entry.set_sensitive(True) else: self.description_entry.set_text('') self.description_entry.set_sensitive(False) self.batch_update = False else: self.batch_update = True self.set_pixbuf(None) self.type_selection.set_active(-1) self.type_selection.set_sensitive(False) self.description_entry.set_text('') self.description_entry.set_sensitive(False) self.batch_update = False self.call_update_func() if not None in (all_vals, self.all_button): self.all_button.set_active(all(val == v for v in all_vals)) def get_value(self): if not self.pixbuf: return None mime = self.mime_info[self.info.mime] # Retrieve proper image data writer = io.BytesIO() def gdk_pixbuf_save_func(buf, count, user_data): if writer.write(buf) == count: return True return False # workaround for some undocumented changes in GdkPixbuf API # see https://bugzilla.gnome.org/show_bug.cgi?id=670372 # see https://github.com/mypaint/mypaint/issues/236 try: save_to_callback_function = self.pixbuf.save_to_callbackv except AttributeError: save_to_callback_function = self.pixbuf.save_to_callback save_to_callback_function(gdk_pixbuf_save_func, None, mime['type'], \ mime['options'].keys(), mime['options'].values()) # Move to the beginning of the buffer to allow read operations writer.seek(0) return self.info._replace(data=writer.read()) def call_update_func(self): """ Wrapper around the update function """ if not self.update_func or self.batch_update: return self.update_func(self, self.parent_row.tag, self.parent_row.multi_id, self.get_value) def set_pixbuf(self, pixbuf, mime=None): """ Updates the displayed cover image and info values """ self.pixbuf = pixbuf if pixbuf is None: self.image.set_from_icon_name('list-add', Gtk.IconSize.DIALOG) self.info_label.set_markup('') else: self.image.set_from_pixbuf( pixbuf.scale_simple(100, 100, GdkPixbuf.InterpType.BILINEAR)) width, height = pixbuf.get_width(), pixbuf.get_height() if mime is None: # TRANSLATORS: do not translate 'width' and 'height' markup = _('{width}x{height} pixels').format(width=width, height=height) else: # TRANSLATORS: do not translate 'format', 'width', and 'height' markup = _('{format} ({width}x{height} pixels)').format( format=self.mime_info.get( mime, self.mime_info['image/'])['title'], width=width, height=height) self.info_label.set_markup(markup) self.info = self.info._replace(mime=mime) def _on_button_clicked(self, button): """ Allows setting the cover image using a file selection dialog """ dialog = dialogs.FileOperationDialog( title=_('Select image to set as cover'), parent=self.get_toplevel(), buttons=(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL, Gtk.STOCK_OK, Gtk.ResponseType.OK)) dialog.set_select_multiple(False) filefilter = Gtk.FileFilter() # Not using Gtk.FileFilter.add_pixbuf_formats since # not all image formats are supported in tags filefilter.set_name(_('Supported image formats')) filefilter.add_pattern('*.[jJ][pP][gG]') filefilter.add_pattern('*.[jJ][pP][eE][gG]') filefilter.add_pattern('*.[pP][nN][gG]') dialog.add_filter(filefilter) if dialog.run() == Gtk.ResponseType.OK: filename = dialog.get_filename() try: pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) info = GdkPixbuf.Pixbuf.get_file_info(filename)[0] except TypeError: pass else: self.batch_update = True self.set_pixbuf(pixbuf, info.get_mime_types()[0]) self.type_selection.set_active(self.default_type) self.type_selection.set_sensitive(True) self.description_entry.set_text( os.path.basename(filename).rsplit('.', 1)[0]) self.description_entry.set_sensitive(True) self.batch_update = False self.call_update_func() dialog.destroy() def _on_button_drag_data_received(self, widget, context, x, y, selection, info, time): """ Allows setting the cover image via drag and drop """ if selection.target.name() == 'text/uri-list': filename = Gio.File.new_for_uri(selection.get_uris()[0]).get_path() try: pixbuf = GdkPixbuf.Pixbuf.new_from_file(filename) info = GdkPixbuf.Pixbuf.get_file_info(filename)[0] except TypeError: pass else: self.batch_update = True self.set_pixbuf(pixbuf, info['mime_types'][0]) self.type_selection.set_active(self.default_type) self.description_entry.set_sensitive(True) self.description_entry.set_text( os.path.basename(filename).rsplit('.', 1)[0]) self.description_entry.set_sensitive(True) self.batch_update = False self.call_update_func() def _on_type_selection_changed(self, combobox): """ Notifies about changes in the cover type """ self.info = self.info._replace( type=self.type_model[combobox.get_active()][0]) self.call_update_func() def _on_description_entry_changed(self, entry): """ Notifies about changes in the cover description """ self.info = self.info._replace(desc=entry.get_text()) self.call_update_func()