Last active
December 4, 2017 17:01
-
-
Save JasonLG1979/c23206f80b1d7750e84f3f8912d7c812 to your computer and use it in GitHub Desktop.
Gtk.Widget.get_scale_factor is broken and always returns 1. This will make sure your Gtk.Image scales automatically based on scale factor and will fallback gracefully on error.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# Copyright (C) 2017 Jason Gray <[email protected]> | |
# | |
# This program is free software: you can redistribute it and/or modify it | |
# under the terms of the GNU General Public License version 3, as published | |
# by the Free Software Foundation. | |
# | |
# This program is distributed in the hope that it will be useful, but | |
# WITHOUT ANY WARRANTY; without even the implied warranties of | |
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR | |
# PURPOSE. See the GNU General Public License for more details. | |
# | |
# You should have received a copy of the GNU General Public License along | |
# with this program. If not, see <http://www.gnu.org/licenses/>. | |
# END LICENSE | |
import gi | |
gi.require_version('Gtk', '3.0') | |
from gi.repository import GLib, GObject, Gio, Gtk, Gdk, GdkPixbuf | |
SCALE_FACTOR_OF_ONE = 96 | |
class SmartImage(Gtk.Image): | |
__gtype_name__ = 'SmartImage' | |
def __init__( | |
self, | |
fallback_icon=None, | |
pre_scale_pixel_size=None, | |
**kwargs, | |
): | |
super().__init__( | |
use_fallback=True, | |
visible=True, | |
**kwargs | |
) | |
self._fallback_icon = fallback_icon | |
self._pre_scale_pixel_size = pre_scale_pixel_size | |
if not self.props.icon_name: | |
self.props.icon_name = self.props.fallback_icon | |
self._cached_pixbuf = None | |
self._screen = Gdk.Screen.get_default() | |
self._screen.connect('notify::resolution', self._rescale_self) | |
self.props.pixel_size = self._get_max_size() | |
@GObject.Property( | |
type=str, | |
default='image-missing', | |
nick='fallback-icon prop', | |
blurb='the fallback icon to use incase of error.', | |
flags=GObject.ParamFlags.READWRITE, | |
) | |
def fallback_icon(self): | |
return self._fallback_icon or 'image-missing' | |
@fallback_icon.setter | |
def fallback_icon(self, icon_name): | |
self._fallback_icon = icon_name | |
@GObject.Property( | |
type=int, | |
default=4, | |
nick='pre-scale-pixel-size prop', | |
blurb='the desired pre scale pixel size.', | |
flags=GObject.ParamFlags.READWRITE, | |
) | |
def pre_scale_pixel_size(self): | |
return self._pre_scale_pixel_size or 4 | |
@pre_scale_pixel_size.setter | |
def pre_scale_pixel_size(self, size): | |
self._pre_scale_pixel_size = size | |
def _get_max_size(self): | |
scale_factor = self._screen.get_resolution() / SCALE_FACTOR_OF_ONE | |
return int(scale_factor * self.props.pre_scale_pixel_size) | |
def set_pixbuf_from_uri(self, uri): | |
def on_read_async(source, result, data): | |
try: | |
GdkPixbuf.Pixbuf.new_from_stream_async( | |
source.read_finish(result), | |
None, | |
on_from_stream_async, | |
None, | |
) | |
except GLib.Error: | |
self._fallback() | |
def on_from_stream_async(source, result, data): | |
try: | |
self._cached_pixbuf = GdkPixbuf.Pixbuf.new_from_stream_finish(result) | |
self._rescale_self() | |
except GLib.Error: | |
self._fallback() | |
if uri: | |
Gio.File.new_for_uri(uri).read_async(GLib.PRIORITY_LOW, None, on_read_async, None) | |
else: | |
self._fallback() | |
def _rescale_self(self, *ignore): | |
max_size = self._get_max_size() | |
self.props.pixel_size = max_size | |
if not self._cached_pixbuf: | |
return | |
h = self._cached_pixbuf.props.height | |
w = self._cached_pixbuf.props.width | |
new_h = h * max_size // w | |
if new_h <= max_size: | |
new_w = max_size | |
else: | |
new_w = w * max_size // h | |
new_h = max_size | |
pixbuf = self._cached_pixbuf.scale_simple(new_w, new_h, GdkPixbuf.InterpType.BILINEAR) | |
self.set_from_pixbuf(pixbuf) | |
def _fallback(self): | |
self._cached_pixbuf = None | |
self.set_from_icon_name(self.props.fallback_icon, Gtk.IconSize.INVALID) | |
class SmartImageTest(Gtk.Window): | |
def __init__(self): | |
Gtk.Window.__init__(self, title='Test') | |
self.box = Gtk.Box() | |
self.add(self.box) | |
self.button1 = Gtk.Button.new() | |
self.smart_image = SmartImage( | |
fallback_icon='audio-x-generic-symbolic', | |
pre_scale_pixel_size=48, | |
icon_size=Gtk.IconSize.INVALID, | |
) | |
self.smart_image.set_pixbuf_from_uri('https://avatars3.githubusercontent.com/u/6667703?s=460&v=4') | |
self.button1.set_image(self.smart_image) | |
self.box.add(self.button1) | |
stream = Gio.File.new_for_uri('https://avatars3.githubusercontent.com/u/6667703?s=460&v=4').read(None) | |
self.pixbuf = GdkPixbuf.Pixbuf.new_from_stream_at_scale(stream, 48, 48, False, None) | |
self.dumb_image = Gtk.Image.new_from_pixbuf(self.pixbuf) | |
self.button2 = Gtk.Button.new() | |
self.button2.set_image(self.dumb_image) | |
self.box.add(self.button2) | |
self.button3 = Gtk.Button.new_from_icon_name('audio-x-generic-symbolic', Gtk.IconSize.DIALOG) | |
self.box.add(self.button3) | |
self.smart_image2 = SmartImage( | |
fallback_icon='audio-x-generic-symbolic', | |
pre_scale_pixel_size=48, | |
icon_size=Gtk.IconSize.INVALID, | |
) | |
self.button4 = Gtk.Button.new() | |
self.button4.set_image(self.smart_image2) | |
self.box.add(self.button4) | |
self.set_default_size(-1, -1) | |
self.set_resizable(False) | |
self.set_border_width(36) | |
if __name__ == '__main__': | |
win = SmartImageTest() | |
win.connect('delete-event', Gtk.main_quit) | |
win.show_all() | |
Gtk.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment