Esempio n. 1
0
 def setUp(self):
     self.downloader = SimpleFileDownloader()
     self.downloader.connect("file-url-reachable",
                             self._cb_image_url_reachable)
     self.downloader.connect("file-download-complete",
                             self._cb_image_download_complete)
     self._image_is_reachable = None
     self._image_downloaded_filename = None
     if os.path.exists(self.DOWNLOAD_FILENAME):
         os.unlink(self.DOWNLOAD_FILENAME)
Esempio n. 2
0
    def _download_icon_and_show_when_ready(self, url, pkgname, icon_file_name):
        LOG.debug("did not find the icon locally, must download %s" %
            icon_file_name)

        if url is not None:
            icon_file_path = os.path.join(SOFTWARE_CENTER_ICON_CACHE_DIR,
                icon_file_name)
            image_downloader = SimpleFileDownloader()
            image_downloader.connect('file-download-complete',
                                     self._on_image_download_complete, pkgname)
            image_downloader.download_file(url, icon_file_path)
Esempio n. 3
0
class TestImageDownloader(unittest.TestCase):

    DOWNLOAD_FILENAME = "test_image_download"

    def setUp(self):
        self.downloader = SimpleFileDownloader()
        self.downloader.connect("file-url-reachable",
                                self._cb_image_url_reachable)
        self.downloader.connect("file-download-complete",
                                self._cb_image_download_complete)
        self.downloader.connect("error",
                                self._cb_image_download_error)
        self._image_is_reachable = None
        self._image_downloaded_filename = None
        self._error = False
        if os.path.exists(self.DOWNLOAD_FILENAME):
            os.unlink(self.DOWNLOAD_FILENAME)

    def _cb_image_url_reachable(self, downloader, is_reachable):
        self._image_is_reachable = is_reachable

    def _cb_image_download_complete(self, downloader, filename):
        self._image_downloaded_filename = filename

    def _cb_image_download_error(self, downloader, gerror, exc):
        self._error = True

    def test_download_unreachable(self):
        self.downloader.download_file("http://www.ubuntu.com/really-not-there",
                                      self.DOWNLOAD_FILENAME)
        main_loop = GObject.main_context_default()
        while self._image_is_reachable is None:
            while main_loop.pending():
                main_loop.iteration()
            time.sleep(0.1)
        self.assertNotEqual(self._image_is_reachable, None)
        self.assertFalse(self._image_is_reachable)
        self.assertTrue(not os.path.exists(self.DOWNLOAD_FILENAME))
 
    def test_download_reachable(self):
        self.downloader.download_file("http://www.ubuntu.com",
                                      self.DOWNLOAD_FILENAME)
        main_loop = GObject.main_context_default()
        while (self._image_downloaded_filename is None and
               not self._error):
            while main_loop.pending():
                main_loop.iteration()
            time.sleep(0.1)
        self.assertNotEqual(self._image_is_reachable, None)
        self.assertTrue(self._image_is_reachable)
        self.assertTrue(os.path.exists(self.DOWNLOAD_FILENAME))
Esempio n. 4
0
class TestImageDownloader(unittest.TestCase):

    DOWNLOAD_FILENAME = "test_image_download"

    def setUp(self):
        self.downloader = SimpleFileDownloader()
        self.downloader.connect("file-url-reachable",
                                self._cb_image_url_reachable)
        self.downloader.connect("file-download-complete",
                                self._cb_image_download_complete)
        self.downloader.connect("error", self._cb_image_download_error)
        self._image_is_reachable = None
        self._image_downloaded_filename = None
        self._error = False
        if os.path.exists(self.DOWNLOAD_FILENAME):
            os.unlink(self.DOWNLOAD_FILENAME)

    def _cb_image_url_reachable(self, downloader, is_reachable):
        self._image_is_reachable = is_reachable

    def _cb_image_download_complete(self, downloader, filename):
        self._image_downloaded_filename = filename

    def _cb_image_download_error(self, downloader, gerror, exc):
        self._error = True

    def test_download_unreachable(self):
        self.downloader.download_file("http://www.ubuntu.com/really-not-there",
                                      self.DOWNLOAD_FILENAME)
        main_loop = GObject.main_context_default()
        while self._image_is_reachable is None:
            while main_loop.pending():
                main_loop.iteration()
            time.sleep(0.1)
        self.assertNotEqual(self._image_is_reachable, None)
        self.assertFalse(self._image_is_reachable)
        self.assertTrue(not os.path.exists(self.DOWNLOAD_FILENAME))

    def test_download_reachable(self):
        self.downloader.download_file("http://www.ubuntu.com",
                                      self.DOWNLOAD_FILENAME)
        main_loop = GObject.main_context_default()
        while (self._image_downloaded_filename is None and not self._error):
            while main_loop.pending():
                main_loop.iteration()
            time.sleep(0.1)
        self.assertNotEqual(self._image_is_reachable, None)
        self.assertTrue(self._image_is_reachable)
        self.assertTrue(os.path.exists(self.DOWNLOAD_FILENAME))
 def __init__(self):
     Gtk.OffscreenWindow.__init__(self)
     self.view = WebKit.WebView()
     settings = self.view.get_settings()
     settings.set_property("enable-java-applet", False)
     settings.set_property("enable-plugins", False)
     settings.set_property("enable-scripts", False)
     self.view.set_size_request(-1, ExhibitBanner.MAX_HEIGHT)
     self.add(self.view)
     self.show_all()
     self.loader = SimpleFileDownloader()
     self.loader.connect("file-download-complete",
                         self.on_download_complete)
     self.loader.connect("error", self.on_download_error)
     self.exhibit = None
     self.view.connect("notify::load-status", self._on_load_status)
Esempio n. 6
0
    def _download_icon_and_show_when_ready(self, cache, pkgname, icon_file_name):
        LOG.debug("did not find the icon locally, must download %s" % icon_file_name)

        def on_image_download_complete(downloader, image_file_path):
            pb = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_file_path,
                                                      self.icon_size,
                                                      self.icon_size)
            # replace the icon in the icon_cache now that we've got the real one
            icon_file = os.path.splitext(os.path.basename(image_file_path))[0]
            self.icon_cache[icon_file] = pb
        
        url = get_distro().get_downloadable_icon_url(cache, pkgname, icon_file_name)
        if url is not None:
            icon_file_path = os.path.join(SOFTWARE_CENTER_ICON_CACHE_DIR, icon_file_name)
            image_downloader = SimpleFileDownloader()
            image_downloader.connect('file-download-complete', on_image_download_complete)
            image_downloader.download_file(url, icon_file_path)
Esempio n. 7
0
    def _download_icon_and_show_when_ready(self, url, pkgname, icon_file_name):
        LOG.debug("did not find the icon locally, must download %s" % icon_file_name)

        def on_image_download_complete(downloader, image_file_path, pkgname):
            LOG.debug("download for '%s' complete" % image_file_path)
            pb = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_file_path, self.icon_size, self.icon_size)
            # replace the icon in the icon_cache now that we've got the real
            # one
            icon_file = split_icon_ext(os.path.basename(image_file_path))
            self.icon_cache[icon_file] = pb
            self.emit("needs-refresh", pkgname)

        if url is not None:
            icon_file_path = os.path.join(SOFTWARE_CENTER_ICON_CACHE_DIR, icon_file_name)
            image_downloader = SimpleFileDownloader()
            image_downloader.connect("file-download-complete", on_image_download_complete, pkgname)
            image_downloader.download_file(url, icon_file_path)
 def setUp(self):
     self.downloader = SimpleFileDownloader()
     self.downloader.connect("file-url-reachable",
                             self._cb_image_url_reachable)
     self.downloader.connect("file-download-complete",
                             self._cb_image_download_complete)
     self._image_is_reachable = None
     self._image_downloaded_filename = None
     if os.path.exists(self.DOWNLOAD_FILENAME):
         os.unlink(self.DOWNLOAD_FILENAME)
Esempio n. 9
0
    def __init__(self, distro, icons):
        Gtk.VBox.__init__(self)
        # data
        self.distro = distro
        self.icons = icons
        self.data = ScreenshotData()
        self.data.connect(
            "screenshots-available", self._on_screenshots_available)

        # state tracking
        self.ready = False
        self.screenshot_pixbuf = None
        self.screenshot_available = False
        self._thumbnail_sigs = []
        self._height = 0

        # zoom cursor
        try:
            zoom_pb = self.icons.load_icon(self.ZOOM_ICON, 22, 0)
            # FIXME
            self._zoom_cursor = Gdk.Cursor.new_from_pixbuf(
                                    Gdk.Display.get_default(),
                                    zoom_pb,
                                    0, 0)  # x, y
        except:
            self._zoom_cursor = None

        # convenience class for handling the downloading (or not) of
        # any screenshot
        self.loader = SimpleFileDownloader()
        self.loader.connect(
            'error',
            self._on_screenshot_load_error)
        self.loader.connect(
            'file-url-reachable',
            self._on_screenshot_query_complete)
        self.loader.connect(
            'file-download-complete',
            self._on_screenshot_download_complete)

        self._build_ui()
        # add cleanup handler to avoid signals after we are destroyed
        self.connect("destroy", self._on_destroy)
Esempio n. 10
0
    def __init__(self, id_, url, cancellable, gallery):
        Gtk.Button.__init__(self)
        self.id_ = id_

        def download_complete_cb(loader, path):
            width, height = ThumbnailGallery.THUMBNAIL_SIZE_CONTRAINTS
            pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
                        path,
                        width, height,  # width, height constraints
                        True)  # respect image proportionality
            im = Gtk.Image.new_from_pixbuf(pixbuf)
            self.add(im)
            self.show_all()

        loader = SimpleFileDownloader()
        loader.connect("file-download-complete", download_complete_cb)
        loader.download_file(
            url, use_cache=ScreenshotGallery.USE_CACHING)

        self.connect("draw", self.on_draw)
Esempio n. 11
0
    def __init__(self, distro, icons):
        Gtk.Alignment.__init__(self)
        self.set(0.5, 0.0, 1.0, 1.0)

        # data 
        self.distro = distro
        self.icons = icons

        self.pkgname = None
        self.appname = None
        self.thumb_url = None
        self.large_url = None

        # state tracking
        self.ready = False
        self.screenshot_pixbuf = None
        self.screenshot_available = False
        self.alpha = 0.0

        # zoom cursor
        try:
            zoom_pb = self.icons.load_icon(self.ZOOM_ICON, 22, 0)
            # FIXME
            self._zoom_cursor = Gdk.Cursor.new_from_pixbuf(
                                    Gdk.Display.get_default(),
                                    zoom_pb,
                                    0, 0)   # x, y
        except:
            self._zoom_cursor = None               

        # tip stuff
        self._hide_after = None
        self.tip_alpha = 0.0
        self._tip_fader = 0
        self._tip_layout = self.create_pango_layout("")
        #m = "<small><b>%s</b></small>"
        #~ self._tip_layout.set_markup(m % _("Click for fullsize screenshot"))
        #~ self._tip_layout.set_ellipsize(Pango.EllipsizeMode.END)

        self._tip_xpadding = 4
        self._tip_ypadding = 1

        # cache the tip dimensions
        w, h = self._tip_layout.get_pixel_size()
        self._tip_size = (w+2*self._tip_xpadding, h+2*self._tip_ypadding)

        # convienience class for handling the downloading (or not) of any screenshot
        self.loader = SimpleFileDownloader()
        self.loader.connect('error', self._on_screenshot_load_error)
        self.loader.connect('file-url-reachable', self._on_screenshot_query_complete)
        self.loader.connect('file-download-complete', self._on_screenshot_download_complete)

        self._build_ui()
        return
    def _download_icon_and_show_when_ready(self, url, pkgname, icon_file_name):
        LOG.debug("did not find the icon locally, must download %s" %
                  icon_file_name)

        if url is not None:
            icon_file_path = os.path.join(SOFTWARE_CENTER_ICON_CACHE_DIR,
                                          icon_file_name)
            image_downloader = SimpleFileDownloader()
            image_downloader.connect('file-download-complete',
                                     self._on_image_download_complete, pkgname)
            image_downloader.download_file(url, icon_file_path)
Esempio n. 13
0
 def __init__(self):
     Gtk.OffscreenWindow.__init__(self)
     self.view = WebKit.WebView()
     settings = self.view.get_settings()
     settings.set_property("enable-java-applet", False)
     settings.set_property("enable-plugins", False)
     settings.set_property("enable-scripts", False)
     self.view.set_size_request(-1, ExhibitBanner.MAX_HEIGHT)
     self.add(self.view)
     self.show_all()
     self.loader = SimpleFileDownloader()
     self.loader.connect("file-download-complete",
                         self.on_download_complete)
     self.loader.connect("error",
                         self.on_download_error)
     self.exhibit = None
     self.view.connect("notify::load-status", self._on_load_status)
Esempio n. 14
0
    def __init__(self, id_, url, cancellable, gallery):
        Gtk.Button.__init__(self)
        self.id_ = id_

        def download_complete_cb(loader, path):
            width, height = ThumbnailGallery.THUMBNAIL_SIZE_CONTRAINTS
            pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
                path,
                width,
                height,  # width, height constraints
                True)  # respect image proportionality
            im = Gtk.Image.new_from_pixbuf(pixbuf)
            self.add(im)
            self.show_all()

        loader = SimpleFileDownloader()
        loader.connect("file-download-complete", download_complete_cb)
        loader.download_file(url, use_cache=ScreenshotGallery.USE_CACHING)

        self.connect("draw", self.on_draw)
Esempio n. 15
0
    def _download_icon_and_show_when_ready(self, url, pkgname, icon_file_name):
        LOG.debug("did not find the icon locally, must download %s" %
            icon_file_name)

        def on_image_download_complete(downloader, image_file_path, pkgname):
            LOG.debug("download for '%s' complete" % image_file_path)
            pb = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_file_path,
                                                        self.icon_size,
                                                        self.icon_size)
            # replace the icon in the icon_cache now that we've got the real
            # one
            icon_file = split_icon_ext(os.path.basename(image_file_path))
            self.icon_cache[icon_file] = pb
            self.emit("needs-refresh", pkgname)

        if url is not None:
            icon_file_path = os.path.join(SOFTWARE_CENTER_ICON_CACHE_DIR,
                icon_file_name)
            image_downloader = SimpleFileDownloader()
            image_downloader.connect('file-download-complete',
                on_image_download_complete, pkgname)
            image_downloader.download_file(url, icon_file_path)
Esempio n. 16
0
class ScreenshotGallery(Gtk.VBox):
    """ Widget that displays screenshot availability, download progress,
        and eventually the screenshot itself.
    """

    MAX_SIZE_CONSTRAINTS = 300, 250
    SPINNER_SIZE = 32, 32

    ZOOM_ICON = "stock_zoom-page"
    NOT_AVAILABLE_STRING = _('No screenshot available')

    USE_CACHING = True

    def __init__(self, distro, icons):
        Gtk.VBox.__init__(self)
        # data
        self.distro = distro
        self.icons = icons
        self.data = ScreenshotData()
        self.data.connect("screenshots-available",
                          self._on_screenshots_available)

        # state tracking
        self.ready = False
        self.screenshot_pixbuf = None
        self.screenshot_available = False
        self._thumbnail_sigs = []
        self._height = 0

        # zoom cursor
        try:
            zoom_pb = self.icons.load_icon(self.ZOOM_ICON, 22, 0)
            # FIXME
            self._zoom_cursor = Gdk.Cursor.new_from_pixbuf(
                Gdk.Display.get_default(), zoom_pb, 0, 0)  # x, y
        except:
            self._zoom_cursor = None

        # convenience class for handling the downloading (or not) of
        # any screenshot
        self.loader = SimpleFileDownloader()
        self.loader.connect('error', self._on_screenshot_load_error)
        self.loader.connect('file-url-reachable',
                            self._on_screenshot_query_complete)
        self.loader.connect('file-download-complete',
                            self._on_screenshot_download_complete)

        self._build_ui()
        # add cleanup handler to avoid signals after we are destroyed
        self.connect("destroy", self._on_destroy)

    def _on_destroy(self, widget):
        # we need to disconnect here otherwise gtk segfaults when it
        # tries to set a already destroyed gtk image
        self.loader.disconnect_by_func(self._on_screenshot_download_complete)
        self.loader.disconnect_by_func(self._on_screenshot_load_error)

    # overrides
    def do_get_preferred_width(self):
        if self.data.get_n_screenshots() <= 1:
            pb = self.button.image.get_pixbuf()
            if pb:
                width = pb.get_width() + 20
                return width, width
        return 320, 320

    def do_get_preferred_height(self):
        pb = self.button.image.get_pixbuf()
        if pb:
            height = pb.get_height()
            if self.data.get_n_screenshots() <= 1:
                height += 20
                height = max(self._height, height)
                self._height = height
                return height, height
            else:
                height += 110
                height = max(self._height, height)
                self._height = height
                return height, height
        self._height = max(self._height, 250)
        return self._height, self._height

    # private
    def _build_ui(self):
        self.set_border_width(3)
        # the frame around the screenshot (placeholder)
        self.screenshot = Gtk.VBox()
        self.pack_start(self.screenshot, True, True, 0)

        self.spinner = Gtk.Spinner()
        self.spinner.set_size_request(*self.SPINNER_SIZE)
        self.spinner.set_valign(Gtk.Align.CENTER)
        self.spinner.set_halign(Gtk.Align.CENTER)
        self.screenshot.add(self.spinner)

        # clickable screenshot button
        self.button = ScreenshotButton()
        self.screenshot.pack_start(self.button, True, False, 0)

        # unavailable layout
        self.unavailable = Gtk.Label(label=_(self.NOT_AVAILABLE_STRING))
        self.unavailable.set_alignment(0.5, 0.5)
        # force the label state to INSENSITIVE so we get the nice
        # subtle etched in look
        self.unavailable.set_state(Gtk.StateType.INSENSITIVE)
        self.screenshot.add(self.unavailable)

        self.thumbnails = ThumbnailGallery(self)
        self.thumbnails.set_margin_top(5)
        self.thumbnails.set_halign(Gtk.Align.CENTER)
        self.pack_end(self.thumbnails, False, False, 0)
        self.thumbnails.connect("thumb-selected", self.on_thumbnail_selected)
        self.button.connect("clicked", self.on_clicked)
        self.button.connect('enter-notify-event', self._on_enter)
        self.button.connect('leave-notify-event', self._on_leave)
        self.show_all()

    def _on_enter(self, widget, event):
        if self.get_is_actionable():
            self.get_window().set_cursor(self._zoom_cursor)

    def _on_leave(self, widget, event):
        self.get_window().set_cursor(None)

    def _on_key_press(self, widget, event):
        # react to spacebar, enter, numpad-enter
        if (event.keyval in (Gdk.KEY_space, Gdk.KEY_Return, Gdk.KEY_KP_Enter)
                and self.get_is_actionable()):
            self.set_state(Gtk.StateType.ACTIVE)

    def _on_key_release(self, widget, event):
        # react to spacebar, enter, numpad-enter
        if (event.keyval in (Gdk.KEY_space, Gdk.KEY_Return, Gdk.KEY_KP_Enter)
                and self.get_is_actionable()):
            self.set_state(Gtk.StateType.NORMAL)
            self._show_image_dialog()

    def _show_image_dialog(self):
        """ Displays the large screenshot in a seperate dialog window """

        if self.data and self.screenshot_pixbuf:
            title = _("%s - Screenshot") % self.data.appname
            toplevel = self.get_toplevel()
            d = SimpleShowImageDialog(title, self.screenshot_pixbuf, toplevel)
            d.run()
            d.destroy()

    def _on_screenshots_available(self, screenshots):
        self.thumbnails.set_thumbnails_from_data(screenshots)

    def _on_screenshot_download_complete(self, loader, screenshot_path):
        try:
            self.screenshot_pixbuf = GdkPixbuf.Pixbuf.new_from_file(
                screenshot_path)
        except Exception, e:
            LOG.exception("Pixbuf.new_from_file() failed")
            self.loader.emit('error', GObject.GError, e)
            return False

        #context = self.button.get_style_context()
        tw, th = self.MAX_SIZE_CONSTRAINTS
        pb = self._downsize_pixbuf(self.screenshot_pixbuf, tw, th)
        self.button.image.set_from_pixbuf(pb)
        self.ready = True
        self.display_image()
Esempio n. 17
0
class _HtmlRenderer(Gtk.OffscreenWindow):

    __gsignals__ = {
        "render-finished": (GObject.SignalFlags.RUN_LAST,
                            None,
                            (),
                            )
    }

    def __init__(self):
        Gtk.OffscreenWindow.__init__(self)
        self.view = WebKit.WebView()
        settings = self.view.get_settings()
        settings.set_property("enable-java-applet", False)
        settings.set_property("enable-plugins", False)
        settings.set_property("enable-scripts", False)
        self.view.set_size_request(-1, ExhibitBanner.MAX_HEIGHT)
        self.add(self.view)
        self.show_all()
        self.loader = SimpleFileDownloader()
        self.loader.connect("file-download-complete",
                            self._on_one_download_complete)
        self.loader.connect("error",
                            self._on_download_error)
        self.exhibit = None
        self._downloaded_banner_images = []
        self.view.connect(
            "notify::load-status", self._on_internal_renderer_load_status)

    def set_exhibit(self, exhibit):
        LOG.debug("set_exhibit: '%s'" % exhibit)
        self._downloaded_banner_images = []
        self.exhibit = exhibit
        self._download_next_banner_image()

    def _on_download_error(self, loader, exception, error):
        LOG.warn("download failed: '%s', '%s'" % (exception, error))

    def _on_one_download_complete(self, loader, path):
        LOG.debug("downloading of '%s' finished" % path)
        self._downloaded_banner_images.append(path)
        if len(self._downloaded_banner_images) < len(self.exhibit.banner_urls):
            self._download_next_banner_image()
        self._on_all_banner_images_downloaded()

    def _on_all_banner_images_downloaded(self):
        LOG.debug("downloading of all banner images finished")
        html = self.exhibit.html
        cache_dir = os.path.join(
            softwarecenter.paths.SOFTWARE_CENTER_CACHE_DIR,
            "download-cache")
        for url, local_file in zip(self.exhibit.banner_urls,
                              self._downloaded_banner_images):
            # no need to mangle local urls
            if url.startswith("file"):
                continue
            scheme, netloc, server_path, para, query, frag = urlparse(url)
            image_name = os.path.basename(local_file)
            # replace the server side path with the local image name, this
            # assumes that the image always comes from the same server as
            # the html
            html = html.replace(server_path, image_name)
        self.exhibit.html = html
        LOG.debug("mangled html: '%s'" % html)
        self.view.load_string(html, "text/html", "UTF-8",
                              "file:%s/" % cache_dir)

    def _download_next_banner_image(self):
        LOG.debug("_download_next_banner_image")
        self.loader.download_file(
            self.exhibit.banner_urls[len(self._downloaded_banner_images)],
            use_cache=True,
            simple_quoting_for_webkit=True)

    def _on_internal_renderer_load_status(self, view, prop):
        """Called when the rendering of the html banner is done"""
        if view.get_property("load-status") == WebKit.LoadStatus.FINISHED:
            # this needs to run with a timeout because otherwise the
            # status is emitted before the offscreen image is finished
            GLib.timeout_add(100, lambda: self.emit("render-finished"))
Esempio n. 18
0
class ScreenshotGallery(Gtk.VBox):

    """ Widget that displays screenshot availability, download progress,
        and eventually the screenshot itself.
    """

    MAX_SIZE_CONSTRAINTS = 300, 250
    SPINNER_SIZE = 32, 32

    ZOOM_ICON = "stock_zoom-page"
    NOT_AVAILABLE_STRING = _('No screenshot available')

    USE_CACHING = True

    def __init__(self, distro, icons):
        Gtk.VBox.__init__(self)
        # data
        self.distro = distro
        self.icons = icons
        self.data = ScreenshotData()
        self.data.connect(
            "screenshots-available", self._on_screenshots_available)

        # state tracking
        self.ready = False
        self.screenshot_pixbuf = None
        self.screenshot_available = False
        self._thumbnail_sigs = []
        self._height = 0

        # zoom cursor
        try:
            zoom_pb = self.icons.load_icon(self.ZOOM_ICON, 22, 0)
            # FIXME
            self._zoom_cursor = Gdk.Cursor.new_from_pixbuf(
                                    Gdk.Display.get_default(),
                                    zoom_pb,
                                    0, 0)  # x, y
        except:
            self._zoom_cursor = None

        # convenience class for handling the downloading (or not) of
        # any screenshot
        self.loader = SimpleFileDownloader()
        self.loader.connect(
            'error',
            self._on_screenshot_load_error)
        self.loader.connect(
            'file-url-reachable',
            self._on_screenshot_query_complete)
        self.loader.connect(
            'file-download-complete',
            self._on_screenshot_download_complete)

        self._build_ui()
        # add cleanup handler to avoid signals after we are destroyed
        self.connect("destroy", self._on_destroy)

    def _on_destroy(self, widget):
        # we need to disconnect here otherwise gtk segfaults when it
        # tries to set a already destroyed gtk image
        self.loader.disconnect_by_func(
            self._on_screenshot_download_complete)
        self.loader.disconnect_by_func(
            self._on_screenshot_load_error)

    # overrides
    def do_get_preferred_width(self):
        if self.data.get_n_screenshots() <= 1:
            pb = self.button.image.get_pixbuf()
            if pb:
                width = pb.get_width() + 20
                return width, width
        return 320, 320

    def do_get_preferred_height(self):
        pb = self.button.image.get_pixbuf()
        if pb:
            height = pb.get_height()
            if self.data.get_n_screenshots() <= 1:
                height += 20
                height = max(self._height, height)
                self._height = height
                return height, height
            else:
                height += 110
                height = max(self._height, height)
                self._height = height
                return height, height
        self._height = max(self._height, 250)
        return self._height, self._height

    # private
    def _build_ui(self):
        self.set_border_width(3)
        # the frame around the screenshot (placeholder)
        self.screenshot = Gtk.VBox()
        self.pack_start(self.screenshot, True, True, 0)

        self.spinner = Gtk.Spinner()
        self.spinner.set_size_request(*self.SPINNER_SIZE)
        self.spinner.set_valign(Gtk.Align.CENTER)
        self.spinner.set_halign(Gtk.Align.CENTER)
        self.screenshot.add(self.spinner)

        # clickable screenshot button
        self.button = ScreenshotButton()
        self.screenshot.pack_start(self.button, True, False, 0)

        # unavailable layout
        self.unavailable = Gtk.Label(label=self.NOT_AVAILABLE_STRING)
        self.unavailable.set_alignment(0.5, 0.5)
        # force the label state to INSENSITIVE so we get the nice
        # subtle etched in look
        self.unavailable.set_state(Gtk.StateType.INSENSITIVE)
        self.screenshot.add(self.unavailable)

        self.thumbnails = ThumbnailGallery(self)
        self.thumbnails.set_margin_top(5)
        self.thumbnails.set_halign(Gtk.Align.CENTER)
        self.pack_end(self.thumbnails, False, False, 0)
        self.thumbnails.connect(
            "thumb-selected", self.on_thumbnail_selected)
        self.button.connect("clicked", self.on_clicked)
        self.button.connect('enter-notify-event', self._on_enter)
        self.button.connect('leave-notify-event', self._on_leave)
        self.show_all()

    def _on_enter(self, widget, event):
        if self.get_is_actionable():
            self.get_window().set_cursor(self._zoom_cursor)

    def _on_leave(self, widget, event):
        self.get_window().set_cursor(None)

    def _on_key_press(self, widget, event):
        # react to spacebar, enter, numpad-enter
        if (event.keyval in (Gdk.KEY_space, Gdk.KEY_Return,
            Gdk.KEY_KP_Enter) and self.get_is_actionable()):
            self.set_state(Gtk.StateType.ACTIVE)

    def _on_key_release(self, widget, event):
        # react to spacebar, enter, numpad-enter
        if (event.keyval in (Gdk.KEY_space, Gdk.KEY_Return,
            Gdk.KEY_KP_Enter) and self.get_is_actionable()):
            self.set_state(Gtk.StateType.NORMAL)
            self._show_image_dialog()

    def _show_image_dialog(self):
        """ Displays the large screenshot in a seperate dialog window """

        if self.data and self.screenshot_pixbuf:
            title = _("%s - Screenshot") % self.data.appname
            toplevel = self.get_toplevel()
            d = SimpleShowImageDialog(
                title, self.screenshot_pixbuf, toplevel)
            d.run()
            d.destroy()

    def _on_screenshots_available(self, screenshots):
        self.thumbnails.set_thumbnails_from_data(screenshots)

    def _on_screenshot_download_complete(self, loader, screenshot_path):
        try:
            self.screenshot_pixbuf = GdkPixbuf.Pixbuf.new_from_file(
                screenshot_path)
        except Exception, e:
            LOG.exception("Pixbuf.new_from_file() failed")
            self.loader.emit('error', GObject.GError, e)
            return False

        #context = self.button.get_style_context()
        tw, th = self.MAX_SIZE_CONSTRAINTS
        pb = self._downsize_pixbuf(self.screenshot_pixbuf, tw, th)
        self.button.image.set_from_pixbuf(pb)
        self.ready = True
        self.display_image()
Esempio n. 19
0
class ScreenshotThumbnail(Gtk.Alignment):

    """ Widget that displays screenshot availability, download prrogress,
        and eventually the screenshot itself.
    """

    MAX_SIZE = 300, 300
    IDLE_SIZE = 300, 150
    SPINNER_SIZE = 32, 32

    ZOOM_ICON = "stock_zoom-page"


    def __init__(self, distro, icons):
        Gtk.Alignment.__init__(self)
        self.set(0.5, 0.0, 1.0, 1.0)

        # data 
        self.distro = distro
        self.icons = icons

        self.pkgname = None
        self.appname = None
        self.thumb_url = None
        self.large_url = None

        # state tracking
        self.ready = False
        self.screenshot_pixbuf = None
        self.screenshot_available = False
        self.alpha = 0.0

        # zoom cursor
        try:
            zoom_pb = self.icons.load_icon(self.ZOOM_ICON, 22, 0)
            # FIXME
            self._zoom_cursor = Gdk.Cursor.new_from_pixbuf(
                                    Gdk.Display.get_default(),
                                    zoom_pb,
                                    0, 0)   # x, y
        except:
            self._zoom_cursor = None               

        # tip stuff
        self._hide_after = None
        self.tip_alpha = 0.0
        self._tip_fader = 0
        self._tip_layout = self.create_pango_layout("")
        #m = "<small><b>%s</b></small>"
        #~ self._tip_layout.set_markup(m % _("Click for fullsize screenshot"))
        #~ self._tip_layout.set_ellipsize(Pango.EllipsizeMode.END)

        self._tip_xpadding = 4
        self._tip_ypadding = 1

        # cache the tip dimensions
        w, h = self._tip_layout.get_pixel_size()
        self._tip_size = (w+2*self._tip_xpadding, h+2*self._tip_ypadding)

        # convienience class for handling the downloading (or not) of any screenshot
        self.loader = SimpleFileDownloader()
        self.loader.connect('error', self._on_screenshot_load_error)
        self.loader.connect('file-url-reachable', self._on_screenshot_query_complete)
        self.loader.connect('file-download-complete', self._on_screenshot_download_complete)

        self._build_ui()
        return

    def _build_ui(self):
        self.set_redraw_on_allocate(False)
        # the frame around the screenshot (placeholder)
        self.set_border_width(3)

        # eventbox so we can connect to event signals
        event = Gtk.EventBox()
        event.set_visible_window(False)

        self.spinner_alignment = Gtk.Alignment.new(0.5, 0.5, 1.0, 0.0)

        self.spinner = Gtk.Spinner()
        self.spinner.set_size_request(*self.SPINNER_SIZE)
        self.spinner_alignment.add(self.spinner)

        # the image
        self.image = Gtk.Image()
        self.image.set_redraw_on_allocate(False)
        event.add(self.image)
        self.eventbox = event

        # connect the image to our custom draw func for fading in
        self.image.connect('draw', self._on_image_draw)

        # unavailable layout
        l = Gtk.Label(label=_('No screenshot'))
        # force the label state to INSENSITIVE so we get the nice subtle etched in look
        l.set_state(Gtk.StateType.INSENSITIVE)
        # center children both horizontally and vertically
        self.unavailable = Gtk.Alignment.new(0.5, 0.5, 1.0, 1.0)
        self.unavailable.add(l)

        # set the widget to be reactive to events
        self.set_property("can-focus", True)
        event.set_events(Gdk.EventMask.BUTTON_PRESS_MASK|
                         Gdk.EventMask.BUTTON_RELEASE_MASK|
                         Gdk.EventMask.KEY_RELEASE_MASK|
                         Gdk.EventMask.KEY_PRESS_MASK|
                         Gdk.EventMask.ENTER_NOTIFY_MASK|
                         Gdk.EventMask.LEAVE_NOTIFY_MASK)

        # connect events to signal handlers
        event.connect('enter-notify-event', self._on_enter)
        event.connect('leave-notify-event', self._on_leave)
        event.connect('button-press-event', self._on_press)
        event.connect('button-release-event', self._on_release)

        self.connect('focus-in-event', self._on_focus_in)
#        self.connect('focus-out-event', self._on_focus_out)
        self.connect("key-press-event", self._on_key_press)
        self.connect("key-release-event", self._on_key_release)

    # signal handlers
    def _on_enter(self, widget, event):
        if not self.get_is_actionable(): return

        self.get_window().set_cursor(self._zoom_cursor)
        self.show_tip(hide_after=3000)
        return

    def _on_leave(self, widget, event):
        self.get_window().set_cursor(None)
        self.hide_tip()
        return

    def _on_press(self, widget, event):
        if event.button != 1 or not self.get_is_actionable(): return
        self.set_state(Gtk.StateType.ACTIVE)
        return

    def _on_release(self, widget, event):
        if event.button != 1 or not self.get_is_actionable(): return
        self.set_state(Gtk.StateType.NORMAL)
        self._show_image_dialog()
        return

    def _on_focus_in(self, widget, event):
        self.show_tip(hide_after=3000)
        return

#    def _on_focus_out(self, widget, event):
#        return

    def _on_key_press(self, widget, event):
        # react to spacebar, enter, numpad-enter
        if event.keyval in (Gdk.KEY_space, 
                            Gdk.KEY_Return, 
                            Gdk.KEY_KP_Enter) and self.get_is_actionable():
            self.set_state(Gtk.StateType.ACTIVE)
        return

    def _on_key_release(self, widget, event):
        # react to spacebar, enter, numpad-enter
        if event.keyval in (Gdk.KEY_space,
                            Gdk.KEY_Return, 
                            Gdk.KEY_KP_Enter) and self.get_is_actionable():
            self.set_state(Gtk.StateType.NORMAL)
            self._show_image_dialog()
        return

    def _on_image_draw(self, widget, cr):
        """ If the alpha value is less than 1, we override the normal draw
            for the GtkImage so we can draw with transparencey.
        """
#~ 
        #~ if widget.get_storage_type() != Gtk.ImageType.PIXBUF:
            #~ return
#~ 
        #~ pb = widget.get_pixbuf()
        #~ if not pb: return True
#~ 
        #~ a = widget.get_allocation()
        #~ cr.rectangle(a.x, a.y, a.width, a.height)
        #~ cr.clip()
#~ 
        #~ # draw the pixbuf with the current alpha value
        #~ cr.set_source_pixbuf(pb, a.x, a.y)
        #~ cr.paint_with_alpha(self.alpha)
        #~ 
        #~ if not self.tip_alpha: return True
#~ 
        #~ tw, th = self._tip_size
        #~ if a.width > tw:
            #~ self._tip_layout.set_width(-1)
        #~ else:
            #~ # tip is image width
            #~ tw = a.width
            #~ self._tip_layout.set_width(1024*(tw-2*self._tip_xpadding))
#~ 
        #~ tx, ty = a.x+a.width-tw, a.y+a.height-th
#~ 
        #~ rr = mkit.ShapeRoundedRectangleIrregular()
        #~ rr.layout(cr, tx, ty, tx+tw, ty+th, radii=(6, 0, 0, 0))
#~ 
        #~ cr.set_source_rgba(0,0,0,0.85*self.tip_alpha)
        #~ cr.fill()
#~ 
        #~ cr.move_to(tx+self._tip_xpadding, ty+self._tip_ypadding)
        #~ cr.layout_path(self._tip_layout)
        #~ cr.set_source_rgba(1,1,1,self.tip_alpha)
        #~ cr.fill()

        #~ return True
        return

    def _fade_in(self):
        """ This callback increments the alpha value from zero to 1,
            stopping once 1 is reached or exceeded.
        """

        self.alpha += 0.05
        if self.alpha >= 1.0:
            self.alpha = 1.0
            self.queue_draw()
            return False
        self.queue_draw()
        return True

    def _tip_fade_in(self):
        """ This callback increments the alpha value from zero to 1,
            stopping once 1 is reached or exceeded.
        """

        self.tip_alpha += 0.1
        #ia = self.image.get_allocation()
        tw, th = self._tip_size

        if self.tip_alpha >= 1.0:
            self.tip_alpha = 1.0
            self.image.queue_draw()
#            self.image.queue_draw_area(ia.x+ia.width-tw,
#                                       ia.y+ia.height-th,
#                                       tw, th)
            return False

        self.image.queue_draw()
#        self.image.queue_draw_area(ia.x+ia.width-tw,
#                                   ia.y+ia.height-th,
#                                   tw, th)
        return True

    def _tip_fade_out(self):
        """ This callback increments the alpha value from zero to 1,
            stopping once 1 is reached or exceeded.
        """

        self.tip_alpha -= 0.1
        #ia = self.image.get_allocation()
        tw, th = self._tip_size

        if self.tip_alpha <= 0.0:
            self.tip_alpha = 0.0
#            self.image.queue_draw_area(ia.x+ia.width-tw,
#                                       ia.y+ia.height-th,
#                                       tw, th)
            self.image.queue_draw()
            return False

        self.image.queue_draw()
#        self.image.queue_draw_area(ia.x+ia.width-tw,
#                                   ia.y+ia.height-th,
#                                   tw, th)
        return True

    def _show_image_dialog(self):
        """ Displays the large screenshot in a seperate dialog window """

        if self.screenshot_pixbuf:
            title = _("%s - Screenshot") % self.appname
            toplevel = self.get_toplevel()
            d = SimpleShowImageDialog(title, self.screenshot_pixbuf, toplevel)
            d.run()
            d.destroy()
        return

    def _on_screenshot_load_error(self, loader, err_type, err_message):
        self.set_screenshot_available(False)
        self.ready = True
        return

    def _on_screenshot_query_complete(self, loader, reachable):
        self.set_screenshot_available(reachable)
        if not reachable: self.ready = True
        return

    def _downsize_pixbuf(self, pb, target_w, target_h):
        w = pb.get_width()
        h = pb.get_height()

        if w > h:
            sf = float(target_w) / w
        else:
            sf = float(target_h) / h

        sw = int(w*sf)
        sh = int(h*sf)

        return pb.scale_simple(sw, sh, GdkPixbuf.InterpType.BILINEAR)

    def _on_screenshot_download_complete(self, loader, screenshot_path):

        def setter_cb(path):
            try:
                self.screenshot_pixbuf = GdkPixbuf.Pixbuf.new_from_file(path)
            except Exception, e:
                LOG.exception("Pixbuf.new_from_file() failed")
                self.loader.emit('error', GObject.GError, e)
                return False

            # remove the spinner
            if self.spinner_alignment.get_parent():
                self.spinner.stop()
                self.spinner.hide()
                self.remove(self.spinner_alignment)

            pb = self._downsize_pixbuf(self.screenshot_pixbuf, *self.MAX_SIZE)

            if not self.eventbox.get_parent():
                self.add(self.eventbox)
                if self.get_property("visible"):
                    self.show_all()

            self.image.set_size_request(-1, -1)
            self.image.set_from_pixbuf(pb)

            # queue parent redraw if height of new pb is less than idle height
            if pb.get_height() < self.IDLE_SIZE[1]:
                if self.get_parent():
                    self.get_parent().queue_draw()

            # start the fade in
            GObject.timeout_add(50, self._fade_in)
            self.ready = True
            return False

        GObject.timeout_add(500, setter_cb, screenshot_path)
        return
class _HtmlRenderer(Gtk.OffscreenWindow):

    __gsignals__ = {
        "render-finished": (
            GObject.SignalFlags.RUN_LAST,
            None,
            (),
        )
    }

    def __init__(self):
        Gtk.OffscreenWindow.__init__(self)
        self.view = WebKit.WebView()
        settings = self.view.get_settings()
        settings.set_property("enable-java-applet", False)
        settings.set_property("enable-plugins", False)
        settings.set_property("enable-scripts", False)
        self.view.set_size_request(-1, ExhibitBanner.MAX_HEIGHT)
        self.add(self.view)
        self.show_all()
        self.loader = SimpleFileDownloader()
        self.loader.connect("file-download-complete",
                            self.on_download_complete)
        self.loader.connect("error", self.on_download_error)
        self.exhibit = None
        self.view.connect("notify::load-status", self._on_load_status)

    def _on_load_status(self, view, prop):
        if view.get_property("load-status") == WebKit.LoadStatus.FINISHED:
            # this needs to run with a timeout because otherwise the
            # status is emited before the offscreen image is finihsed
            GObject.timeout_add(100, lambda: self.emit("render-finished"))

    def on_download_error(self, loader, exception, error):
        LOG.warn("download failed: '%s', '%s'" % (exception, error))

    def on_download_complete(self, loader, path):
        image_name = os.path.basename(path)
        cache_dir = os.path.dirname(path)
        if hasattr(self.exhibit, "html") and self.exhibit.html:
            html = self.exhibit.html
        else:
            html = EXHIBIT_HTML % {
                'banner_url': self.exhibit.banner_url,
                'title': self.exhibit.title_translated,
                'subtitle': "",
            }
        # replace the server side path with the local image name, this
        # assumes that the image always comes from the same server as
        # the html
        scheme, netloc, server_path, para, query, frag = urlparse(
            self.exhibit.banner_url)
        html = html.replace(server_path, image_name)
        self.view.load_string(html, "text/html", "UTF-8",
                              "file:%s/" % cache_dir)

    def set_exhibit(self, exhibit):
        self.exhibit = exhibit
        self.loader.download_file(exhibit.banner_url,
                                  use_cache=True,
                                  simple_quoting_for_webkit=True)
Esempio n. 21
0
class _HtmlRenderer(Gtk.OffscreenWindow):

    __gsignals__ = {
        "render-finished": (GObject.SignalFlags.RUN_LAST,
                            None,
                            (),
                           )
        }

    def __init__(self):
        Gtk.OffscreenWindow.__init__(self)
        self.view = WebKit.WebView()
        settings = self.view.get_settings()
        settings.set_property("enable-java-applet", False)
        settings.set_property("enable-plugins", False)
        settings.set_property("enable-scripts", False)
        self.view.set_size_request(-1, ExhibitBanner.MAX_HEIGHT)
        self.add(self.view)
        self.show_all()
        self.loader = SimpleFileDownloader()
        self.loader.connect("file-download-complete",
                            self.on_download_complete)
        self.loader.connect("error",
                            self.on_download_error)
        self.exhibit = None
        self.view.connect("notify::load-status", self._on_load_status)

    def _on_load_status(self, view, prop):
        if view.get_property("load-status") == WebKit.LoadStatus.FINISHED:
            # this needs to run with a timeout because otherwise the
            # status is emited before the offscreen image is finihsed
            GObject.timeout_add(100, lambda: self.emit("render-finished"))

    def on_download_error(self, loader, exception, error):
        LOG.warn("download failed: '%s', '%s'" % (exception, error))

    def on_download_complete(self, loader, path):
        image_name = os.path.basename(path)
        cache_dir = os.path.dirname(path)
        if hasattr(self.exhibit, "html") and self.exhibit.html:
            html = self.exhibit.html
        else:
            html = EXHIBIT_HTML % {
                'banner_url': self.exhibit.banner_url,
                'title': self.exhibit.title_translated,
                'subtitle': "",
            }
        # replace the server side path with the local image name, this
        # assumes that the image always comes from the same server as
        # the html
        scheme, netloc, server_path, para, query, frag = urlparse(
            self.exhibit.banner_url)
        html = html.replace(server_path, image_name)
        self.view.load_string(html, "text/html", "UTF-8",
                              "file:%s/" % cache_dir)

    def set_exhibit(self, exhibit):
        self.exhibit = exhibit
        self.loader.download_file(exhibit.banner_url,
                                  use_cache=True,
                                  simple_quoting_for_webkit=True)