Skip to content

Instantly share code, notes, and snippets.

@JasonLG1979
Last active December 4, 2017 17:01
Show Gist options
  • Save JasonLG1979/c23206f80b1d7750e84f3f8912d7c812 to your computer and use it in GitHub Desktop.
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.
#
# 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