コード例 #1
0
    def setup(self, src, dst, type=None):
        """Setup the extractor with archive <src> and destination dir <dst>.
        Return a threading.Condition related to the is_ready() method, or
        None if the format of <src> isn't supported.
        """
        self._src = src
        self._dst = dst
        self._files = []
        self._extracted = set()
        self._archive = archive_tools.get_recursive_archive_handler(src,
                                                                    dst,
                                                                    type=type)
        if self._archive is None:
            msg = _('Non-supported archive format: %s') % os.path.basename(src)
            log.warning(msg)
            raise ArchiveException(msg)

        self._contents_listed = False
        self._extract_started = False
        self._condition = threading.Condition()
        self._list_thread = WorkerThread(self._list_contents, name='list')
        self._list_thread.append_order(self._archive)
        self._setupped = True

        return self._condition
コード例 #2
0
 def extract(self):
     """Start extracting the files in the file list one by one using a
     new thread. Every time a new file is extracted a notify() will be
     signalled on the Condition that was returned by setup().
     """
     with self._condition:
         if not self._contents_listed:
             return
         if not self._extract_started:
             if self._archive.support_concurrent_extractions \
                and not self._archive.is_solid():
                 max_threads = prefs['max extract threads']
             else:
                 max_threads = 1
             if self._archive.is_solid():
                 fn = self._extract_all_files
             else:
                 fn = self._extract_file
             self._extract_thread = WorkerThread(fn,
                                                 name='extract',
                                                 max_threads=max_threads,
                                                 unique_orders=True)
             self._extract_started = True
         else:
             self._extract_thread.clear_orders()
         if self._archive.is_solid():
             # Sort files so we don't queue the same batch multiple times.
             self._extract_thread.append_order(sorted(self._files))
         else:
             self._extract_thread.extend_orders(self._files)
コード例 #3
0
    def __init__(self, window):

        #: Reference to main window
        self._window = window

        #: Caching thread
        self._thread = WorkerThread(self._cache_pixbuf, name='image',
                                    sort_orders=True)

        #: Archive path, if currently opened file is archive
        self._base_path = None
        #: List of image file names, either from extraction or directory
        self._image_files = None
        #: Index of current page
        self._current_image_index = None
        #: Set of images reading for decoding (i.e. already extracted)
        self._available_images = set()
        #: List of pixbufs we want to cache
        self._wanted_pixbufs = []
        #: Pixbuf map from page > Pixbuf
        self._raw_pixbufs = {}
        #: How many pages to keep in cache
        self._cache_pages = prefs['max pages to cache']

        self._window.filehandler.file_available += self._file_available
コード例 #4
0
ファイル: archive_extractor.py プロジェクト: drdrpyan/mcomix
 def extract(self):
     """Start extracting the files in the file list one by one using a
     new thread. Every time a new file is extracted a notify() will be
     signalled on the Condition that was returned by setup().
     """
     with self._condition:
         if not self._contents_listed:
             return
         if not self._extract_started:
             if self._archive.support_concurrent_extractions \
                and not self._archive.is_solid():
                 max_threads = prefs['max extract threads']
             else:
                 max_threads = 1
             if self._archive.is_solid():
                 fn = self._extract_all_files
             else:
                 fn = self._extract_file
             self._extract_thread = WorkerThread(fn,
                                                 name='extract',
                                                 max_threads=max_threads,
                                                 unique_orders=True)
             self._extract_started = True
         else:
             self._extract_thread.clear_orders()
         if self._archive.is_solid():
             # Sort files so we don't queue the same batch multiple times.
             self._extract_thread.append_order(sorted(self._files))
         else:
             self._extract_thread.extend_orders(self._files)
コード例 #5
0
ファイル: thumbnail_view.py プロジェクト: drdrpyan/mcomix
    def __init__(self, model):
        """ Constructs a new ThumbnailView.
        @param model: L{gtk.TreeModel} instance. The model needs a pixbuf
                      and a boolean column for internal calculations.
        """

        #: Model index of the thumbnail status field (gobject.BOOLEAN)
        self.status_column = -1
        #: Model index of the pixbuf field
        self.pixbuf_column = -1

        #: Ignore updates when this flag is True.
        self._updates_stopped = True
        #: Worker thread
        self._thread = WorkerThread(self._pixbuf_worker,
                                    name='thumbview',
                                    unique_orders=True,
                                    max_threads=prefs["max threads"])
コード例 #6
0
    def __init__(self, uid_column, pixbuf_column, status_column):
        """ Constructs a new ThumbnailView.
        @param uid_column: index of unique identifer column.
        @param pixbuf_column: index of pixbuf column.
        @param status_column: index of status boolean column
                              (True if pixbuf is not temporary filler)
        """

        #: Keep track of already generated thumbnails.
        self._uid_column = uid_column
        self._pixbuf_column = pixbuf_column
        self._status_column = status_column

        #: Ignore updates when this flag is True.
        self._updates_stopped = True
        #: Worker thread
        self._thread = WorkerThread(self._pixbuf_worker,
                                    name='thumbview',
                                    unique_orders=True,
                                    max_threads=prefs["max threads"])
コード例 #7
0
ファイル: thumbnail_view.py プロジェクト: drdrpyan/mcomix
    def __init__(self, model):
        """ Constructs a new ThumbnailView.
        @param model: L{gtk.TreeModel} instance. The model needs a pixbuf
                      and a boolean column for internal calculations.
        """

        #: Model index of the thumbnail status field (gobject.BOOLEAN)
        self.status_column = -1
        #: Model index of the pixbuf field
        self.pixbuf_column = -1

        #: Ignore updates when this flag is True.
        self._updates_stopped = True
        #: Worker thread
        self._thread = WorkerThread(self._pixbuf_worker,
                                    name='thumbview',
                                    unique_orders=True,
                                    max_threads=prefs["max threads"])
コード例 #8
0
ファイル: thumbnail_view.py プロジェクト: HoverHell/mcomix
    def __init__(self, uid_column, pixbuf_column, status_column):
        """ Constructs a new ThumbnailView.
        @param uid_column: index of unique identifer column.
        @param pixbuf_column: index of pixbuf column.
        @param status_column: index of status boolean column
                              (True if pixbuf is not temporary filler)
        """

        #: Keep track of already generated thumbnails.
        self._uid_column = uid_column
        self._pixbuf_column = pixbuf_column
        self._status_column = status_column

        #: Ignore updates when this flag is True.
        self._updates_stopped = True
        #: Worker thread
        self._thread = WorkerThread(self._pixbuf_worker,
                                    name='thumbview',
                                    unique_orders=True,
                                    max_threads=prefs["max threads"])
コード例 #9
0
ファイル: archive_extractor.py プロジェクト: drdrpyan/mcomix
    def setup(self, src, dst, type=None):
        """Setup the extractor with archive <src> and destination dir <dst>.
        Return a threading.Condition related to the is_ready() method, or
        None if the format of <src> isn't supported.
        """
        self._src = src
        self._dst = dst
        self._type = type or archive_tools.archive_mime_type(src)
        self._files = []
        self._extracted = set()
        self._archive = archive_tools.get_recursive_archive_handler(src, dst, type=self._type)
        if self._archive is None:
            msg = _('Non-supported archive format: %s') % os.path.basename(src)
            log.warning(msg)
            raise ArchiveException(msg)

        self._contents_listed = False
        self._extract_started = False
        self._condition = threading.Condition()
        self._list_thread = WorkerThread(self._list_contents, name='list')
        self._list_thread.append_order(self._archive)
        self._setupped = True

        return self._condition
コード例 #10
0
ファイル: pageselect.py プロジェクト: drdrpyan/mcomix
    def __init__(self, window):
        self._window = window
        self._page_selector_dialog = gtk.Dialog.__init__(self, "Go to page...", window,
                                     gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
        self.add_buttons(_('_Go'), gtk.RESPONSE_OK,
                         _('_Cancel'), gtk.RESPONSE_CANCEL,)
        self.set_default_response(gtk.RESPONSE_OK)
        self.set_has_separator(False)
        self.connect('response', self._response)
        self.set_resizable(True)

        self._number_of_pages = self._window.imagehandler.get_number_of_pages()

        self._selector_adjustment = gtk.Adjustment(value=self._window.imagehandler.get_current_page(),
                              lower=1,upper=self._number_of_pages,
                              step_incr=1, page_incr=1 )

        self._selector_adjustment.connect( 'value-changed', self._cb_value_changed )

        self._page_selector = gtk.VScale(self._selector_adjustment)
        self._page_selector.set_draw_value(False)
        self._page_selector.set_digits( 0 )

        self._page_spinner = gtk.SpinButton(self._selector_adjustment)
        self._page_spinner.connect( 'changed', self._page_text_changed )
        self._page_spinner.set_activates_default(True)
        self._page_spinner.set_numeric(True)
        self._pages_label = gtk.Label(_(' of %s') % self._number_of_pages)
        self._pages_label.set_alignment(0, 0.5)

        self._image_preview = gtk.Image()
        self._image_preview.set_size_request(
            prefs['thumbnail size'], prefs['thumbnail size'])

        self.connect('configure-event', self._size_changed_cb)
        self.set_size_request(prefs['pageselector width'],
                prefs['pageselector height'])

        # Group preview image and page selector next to each other
        preview_box = gtk.HBox()
        preview_box.set_border_width(5)
        preview_box.set_spacing(5)
        preview_box.pack_start(self._image_preview, True)
        preview_box.pack_end(self._page_selector, False)
        # Below them, group selection spinner and current page label
        selection_box = gtk.HBox()
        selection_box.set_border_width(5)
        selection_box.pack_start(self._page_spinner, True)
        selection_box.pack_end(self._pages_label, False)

        self.get_content_area().pack_start(preview_box, True)
        self.get_content_area().pack_end(selection_box, False)
        self.show_all()

        # Set focus on the input box.
        self._page_spinner.select_region(0, -1)
        self._page_spinner.grab_focus()

        # Currently displayed thumbnail page.
        self._thumbnail_page = 0
        self._thread = WorkerThread(self._generate_thumbnail, name='preview')
        self._update_thumbnail(int(self._selector_adjustment.value))
        self._window.imagehandler.page_available += self._page_available
コード例 #11
0
class ImageHandler(object):

    """The FileHandler keeps track of images, pages, caches and reads files.

    When the Filehandler's methods refer to pages, they are indexed from 1,
    i.e. the first page is page 1 etc.

    Other modules should *never* read directly from the files pointed to by
    paths given by the FileHandler's methods. The files are not even
    guaranteed to exist at all times since the extraction of archives is
    threaded.
    """

    def __init__(self, window):

        #: Reference to main window
        self._window = window

        #: Caching thread
        self._thread = WorkerThread(self._cache_pixbuf, name='image',
                                    sort_orders=True)

        #: Archive path, if currently opened file is archive
        self._base_path = None
        #: List of image file names, either from extraction or directory
        self._image_files = None
        #: Index of current page
        self._current_image_index = None
        #: Set of images reading for decoding (i.e. already extracted)
        self._available_images = set()
        #: List of pixbufs we want to cache
        self._wanted_pixbufs = []
        #: Pixbuf map from page > Pixbuf
        self._raw_pixbufs = {}
        #: How many pages to keep in cache
        self._cache_pages = prefs['max pages to cache']

        self._window.filehandler.file_available += self._file_available

    def _get_pixbuf(self, index):
        """Return the pixbuf indexed by <index> from cache.
        Pixbufs not found in cache are fetched from disk first.
        """
        pixbuf = image_tools.MISSING_IMAGE_ICON

        if index not in self._raw_pixbufs:
            self._wait_on_page(index + 1)

            try:
                pixbuf = image_tools.load_pixbuf(self._image_files[index])
                self._raw_pixbufs[index] = pixbuf
                tools.garbage_collect()
            except Exception as e:
                self._raw_pixbufs[index] = image_tools.MISSING_IMAGE_ICON
                log.error('Could not load pixbuf for page %u: %r', index + 1, e)
        else:
            try:
                pixbuf = self._raw_pixbufs[index]
            except Exception:
                pass

        return pixbuf

    def get_pixbufs(self, number_of_bufs):
        """Returns number_of_bufs pixbufs for the image(s) that should be
        currently displayed. This method might fetch images from disk, so make
        sure that number_of_bufs is as small as possible.
        """
        result = []
        for i in range(number_of_bufs):
            result.append(self._get_pixbuf(self._current_image_index + i))
        return result

    def get_pixbuf_auto_background(self, number_of_bufs): # XXX limited to at most 2 pages
        """ Returns an automatically calculated background color
        for the current page(s). """

        pixbufs = self.get_pixbufs(number_of_bufs)

        if len(pixbufs) == 1:
            auto_bg = image_tools.get_most_common_edge_colour(pixbufs[0])
        elif len(pixbufs) == 2:
            left, right = pixbufs
            if self._window.is_manga_mode:
                left, right = right, left

            auto_bg = image_tools.get_most_common_edge_colour((left, right))
        else:
            assert False, 'Unexpected pixbuf count'

        return auto_bg

    def do_cacheing(self):
        """Make sure that the correct pixbufs are stored in cache. These
        are (in the current implementation) the current image(s), and
        if cacheing is enabled, also the one or two pixbufs before and
        after the current page. All other pixbufs are deleted and garbage
        collected directly in order to save memory.
        """
        if not self._window.filehandler.file_loaded:
            return

        # Flush caching orders.
        self._thread.clear_orders()
        # Get list of wanted pixbufs.
        wanted_pixbufs = self._ask_for_pages(self.get_current_page())
        if -1 != self._cache_pages:
            # We're not caching everything, remove old pixbufs.
            for index in set(self._raw_pixbufs) - set(wanted_pixbufs):
                del self._raw_pixbufs[index]
        log.debug('Caching page(s) %s', ' '.join([str(index + 1) for index in wanted_pixbufs]))
        self._wanted_pixbufs = wanted_pixbufs
        # Start caching available images not already in cache.
        wanted_pixbufs = [index for index in wanted_pixbufs
                          if index in self._available_images and not index in self._raw_pixbufs]
        orders = [(priority, index) for priority, index in enumerate(wanted_pixbufs)]
        if len(orders) > 0:
            self._thread.extend_orders(orders)

    def _cache_pixbuf(self, wanted):
        priority, index = wanted
        log.debug('Caching page %u', index + 1)
        self._get_pixbuf(index)

    def set_page(self, page_num):
        """Set up filehandler to the page <page_num>.
        """
        assert 0 < page_num <= self.get_number_of_pages()
        self._current_image_index = page_num - 1
        self.do_cacheing()

    def get_virtual_double_page(self, page=None):
        """Return True if the current state warrants use of virtual
        double page mode (i.e. if double page mode is on, the corresponding
        preference is set, and one of the two images that should normally
        be displayed has a width that exceeds its height), or if currently
        on the first page.
        """
        if page == None:
            page = self.get_current_page()

        if (page == 1 and
            prefs['virtual double page for fitting images'] & constants.SHOW_DOUBLE_AS_ONE_TITLE and
            self._window.filehandler.archive_type is not None):
            return True

        if (not prefs['default double page'] or
            not prefs['virtual double page for fitting images'] & constants.SHOW_DOUBLE_AS_ONE_WIDE or
            page == self.get_number_of_pages()):
            return False

        for page in (page, page + 1):
            if not self.page_is_available(page):
                return False
            pixbuf = self._get_pixbuf(page - 1)
            width, height = pixbuf.get_width(), pixbuf.get_height()
            if prefs['auto rotate from exif']:
                rotation = image_tools.get_implied_rotation(pixbuf)
                assert rotation in (0, 90, 180, 270)
                if rotation in (90, 270):
                    width, height = height, width
            if width > height:
                return True

        return False

    def get_real_path(self):
        """Return the "real" path to the currently viewed file, i.e. the
        full path to the archive or the full path to the currently
        viewed image.
        """
        if self._window.filehandler.archive_type is not None:
            return self._window.filehandler.get_path_to_base()
        return self.get_path_to_page()

    def cleanup(self):
        """Run clean-up tasks. Should be called prior to exit."""

        self.first_wanted = 0
        self.last_wanted = 1

        self._thread.stop()
        self._base_path = None
        self._image_files = []
        self._current_image_index = None
        self._available_images.clear()
        self._raw_pixbufs.clear()
        self._cache_pages = prefs['max pages to cache']

    def page_is_available(self, page=None):
        """ Returns True if <page> is available and calls to get_pixbufs
        would not block. If <page> is None, the current page(s) are assumed. """

        if page is None:
            current_page = self.get_current_page()
            if not current_page:
                # Current 'book' has no page.
                return False
            index_list = [ current_page - 1 ]
            if self._window.displayed_double() and current_page < len(self._image_files):
                index_list.append(current_page)
        else:
            index_list = [ page - 1 ]

        for index in index_list:
            if not index in self._available_images:
                return False

        return True

    @callback.Callback
    def page_available(self, page):
        """ Called whenever a new page becomes available, i.e. the corresponding
        file has been extracted. """
        log.debug('Page %u is available', page)
        index = page - 1
        assert index not in self._available_images
        self._available_images.add(index)
        # Check if we need to cache it.
        priority = None
        if index in self._wanted_pixbufs:
            # In the list of wanted pixbufs.
            priority = self._wanted_pixbufs.index(index)
        elif -1 == self._cache_pages:
            # We're caching everything.
            priority = self.get_number_of_pages()
        if priority is not None:
            self._thread.append_order((priority, index))

    def _file_available(self, filepaths):
        """ Called by the filehandler when a new file becomes available. """
        # Find the page that corresponds to <filepath>
        if not self._image_files:
            return

        available = sorted(filepaths)
        for i, imgpath in enumerate(self._image_files):
            if tools.bin_search(available, imgpath) >= 0:
                self.page_available(i + 1)

    def get_number_of_pages(self):
        """Return the number of pages in the current archive/directory."""
        if self._image_files is not None:
            return len(self._image_files)
        else:
            return 0

    def get_current_page(self):
        """Return the current page number (starting from 1), or 0 if no file is loaded."""
        if self._current_image_index is not None:
            return self._current_image_index + 1
        else:
            return 0

    def get_path_to_page(self, page=None):
        """Return the full path to the image file for <page>, or the current
        page if <page> is None.
        """
        if page is None:
            index = self._current_image_index
        else:
            index = page - 1

        if isinstance(index, int) and 0 <= index < len(self._image_files):
            return self._image_files[index]
        else:
            return None

    def get_page_filename(self, page=None, double=False):
        """Return the filename of the <page>, or the filename of the
        currently viewed page if <page> is None. If <double> is True, return
        a tuple (p, p') where p is the filename of <page> (or the current
        page) and p' is the filename of the page after.
        """
        if page is None:
            page = self.get_current_page()

        first_path = self.get_path_to_page(page)
        if first_path == None:
            return ('','') if double else ''

        if double:
            second_path = self.get_path_to_page(page + 1)

            if second_path != None:
                first = os.path.basename(first_path)
                second = os.path.basename(second_path)
            else:
                return ('','') if double else ''

            return first, second

        return os.path.basename(first_path)

    def get_page_filesize(self, page=None, double=False):
        """Return the filesize of the <page>, or the filesize of the
        currently viewed page if <page> is None. If <double> is True, return
        a tuple (s, s') where s is the filesize of <page> (or the current
        page) and s' is the filesize of the page after.
        """
        if not self.page_is_available():
            return ('-1','-1') if double else '-1'

        if page is None:
            page = self.get_current_page()

        first_path = self.get_path_to_page(page)
        if first_path is None:
            return ('-1','-1') if double else '-1'

        if double:
            second_path = self.get_path_to_page(page + 1)
            if second_path != None:
                try:
                    first = tools.format_byte_size(os.stat(first_path).st_size)
                except OSError:
                    first = ''
                try:
                    second = tools.format_byte_size(os.stat(second_path).st_size)
                except OSError:
                    second = ''
            else:
                return ('-1','-1') if double else '-1'
            return first, second

        try:
            size = tools.format_byte_size(os.stat(first_path).st_size)
        except OSError:
            size = ''

        return size

    def get_pretty_current_filename(self):
        """Return a string with the name of the currently viewed file that is
        suitable for printing.
        """
        if self._window.filehandler.archive_type is not None:
            name = os.path.basename(self._base_path)
        elif self._image_files:
            img_file = os.path.abspath(self._image_files[self._current_image_index])
            name = os.path.join(
                os.path.basename(os.path.dirname(img_file)),
                os.path.basename(img_file)
            )
        else:
            name = u''

        return i18n.to_unicode(name)

    def get_size(self, page=None):
        """Return a tuple (width, height) with the size of <page>. If <page>
        is None, return the size of the current page.
        """
        self._wait_on_page(page)

        page_path = self.get_path_to_page(page)
        if page_path is None:
            return (0, 0)

        format, width, height = image_tools.get_image_info(page_path)
        return (width, height)

    def get_mime_name(self, page=None):
        """Return a string with the name of the mime type of <page>. If
        <page> is None, return the mime type name of the current page.
        """
        self._wait_on_page(page)

        page_path = self.get_path_to_page(page)
        if page_path is None:
            return None

        format, width, height = image_tools.get_image_info(page_path)
        return format

    def get_thumbnail(self, page=None, width=128, height=128, create=False,
                      nowait=False):
        """Return a thumbnail pixbuf of <page> that fit in a box with
        dimensions <width>x<height>. Return a thumbnail for the current
        page if <page> is None.

        If <create> is True, and <width>x<height> <= 128x128, the
        thumbnail is also stored on disk.

        If <nowait> is True, don't wait for <page> to be available.
        """
        if not self._wait_on_page(page, check_only=nowait):
            # Page is not available!
            return None
        path = self.get_path_to_page(page)

        if path == None:
            return None

        try:
            thumbnailer = thumbnail_tools.Thumbnailer(store_on_disk=create,
                                                      size=(width, height))
            return thumbnailer.thumbnail(path)
        except Exception:
            log.debug("Failed to create thumbnail for image `%s':\n%s",
                      path, traceback.format_exc())
            return image_tools.MISSING_IMAGE_ICON

    def _wait_on_page(self, page, check_only=False):
        """Block the running (main) thread until the file corresponding to
        image <page> has been fully extracted.

        If <check_only> is True, only check (and return status), don't wait.
        """
        if page is None:
            index = self._current_image_index
        else:
            index = page - 1
        if index in self._available_images:
            # Already extracted!
            return True
        if check_only:
            # Asked for check only...
            return False

        log.debug('Waiting for page %u', page)
        path = self.get_path_to_page(page)
        self._window.filehandler._wait_on_file(path)
        return True

    def _ask_for_pages(self, page):
        """Ask for pages around <page> to be given priority extraction.
        """
        files = []
        if prefs['default double page']:
            page_width = 2
        else:
            page_width = 1
        if 0 == self._cache_pages:
            # Only ask for current page.
            num_pages = page_width
        elif -1 == self._cache_pages:
            # Ask for 10 pages.
            num_pages = min(10, self.get_number_of_pages())
        else:
            num_pages = self._cache_pages

        page_list = [page - 1 - page_width + n for n in range(num_pages)]

        # Current and next page first, followed by previous page.
        previous_page = page_list[0:page_width]
        del page_list[0:page_width]
        page_list[2*page_width:2*page_width] = previous_page
        page_list = [index for index in page_list
                     if index >= 0 and index < len(self._image_files)]

        log.debug('Ask for priority extraction around page %u: %s',
                  page, ' '.join([str(n + 1) for n in page_list]))

        for index in page_list:
            if index not in self._available_images:
                files.append(self._image_files[index])

        if len(files) > 0:
            self._window.filehandler._ask_for_files(files)

        return page_list
コード例 #12
0
class ThumbnailViewBase(object):
    """ This class provides shared functionality for Gtk.TreeView and
    Gtk.IconView. Instantiating this class directly is *impossible*,
    as it depends on methods provided by the view classes. """
    def __init__(self, uid_column, pixbuf_column, status_column):
        """ Constructs a new ThumbnailView.
        @param uid_column: index of unique identifer column.
        @param pixbuf_column: index of pixbuf column.
        @param status_column: index of status boolean column
                              (True if pixbuf is not temporary filler)
        """

        #: Keep track of already generated thumbnails.
        self._uid_column = uid_column
        self._pixbuf_column = pixbuf_column
        self._status_column = status_column

        #: Ignore updates when this flag is True.
        self._updates_stopped = True
        #: Worker thread
        self._thread = WorkerThread(self._pixbuf_worker,
                                    name='thumbview',
                                    unique_orders=True,
                                    max_threads=prefs["max threads"])

    def generate_thumbnail(self, uid):
        """ This function must return the thumbnail for C{uid}. """
        raise NotImplementedError()

    def get_visible_range(self):
        """ See L{Gtk.IconView.get_visible_range}. """
        raise NotImplementedError()

    def stop_update(self):
        """ Stops generation of pixbufs. """
        self._updates_stopped = True
        self._thread.stop()

    def draw_thumbnails_on_screen(self, *args):
        """ Prepares valid thumbnails for currently displayed icons.
        This method is supposed to be called from the expose-event
        callback function. """

        visible = self.get_visible_range()
        if not visible:
            # No valid paths available
            return

        pixbufs_needed = []
        start = visible[0][0]
        end = visible[1][0]
        # Read ahead/back and start caching a few more icons. Currently invisible
        # icons are always cached only after the visible icons have been completed.
        additional = (end - start) // 2
        required = tuple(range(start, end + additional + 1)) + \
            tuple(range(max(0, start - additional), start))
        model = self.get_model()
        # Filter invalid paths.
        required = [path for path in required if 0 <= path < len(model)]
        with self._thread:
            # Flush current pixmap generation orders.
            self._thread.clear_orders()
            for path in required:
                iter = model.get_iter(path)
                uid, generated = model.get(iter, self._uid_column,
                                           self._status_column)
                # Do not queue again if thumbnail was already created.
                if not generated:
                    pixbufs_needed.append((uid, iter))
            if len(pixbufs_needed) > 0:
                self._updates_stopped = False
                self._thread.extend_orders(pixbufs_needed)

    def _pixbuf_worker(self, order):
        """ Run by a worker thread to generate the thumbnail for a path."""
        uid, iter = order
        pixbuf = self.generate_thumbnail(uid)
        if pixbuf is not None:
            GLib.idle_add(self._pixbuf_finished, iter, pixbuf)

    def _pixbuf_finished(self, iter, pixbuf):
        """ Executed when a pixbuf was created, to actually insert the pixbuf
        into the view store. C{pixbuf_info} is a tuple containing
        (index, pixbuf). """

        if self._updates_stopped:
            return 0

        model = self.get_model()
        model.set(iter, self._status_column, True, self._pixbuf_column, pixbuf)

        # Remove this idle handler.
        return 0
コード例 #13
0
ファイル: thumbnail_view.py プロジェクト: HoverHell/mcomix
class ThumbnailViewBase(object):
    """ This class provides shared functionality for gtk.TreeView and
    gtk.IconView. Instantiating this class directly is *impossible*,
    as it depends on methods provided by the view classes. """

    def __init__(self, uid_column, pixbuf_column, status_column):
        """ Constructs a new ThumbnailView.
        @param uid_column: index of unique identifer column.
        @param pixbuf_column: index of pixbuf column.
        @param status_column: index of status boolean column
                              (True if pixbuf is not temporary filler)
        """

        #: Keep track of already generated thumbnails.
        self._uid_column = uid_column
        self._pixbuf_column = pixbuf_column
        self._status_column = status_column

        #: Ignore updates when this flag is True.
        self._updates_stopped = True
        #: Worker thread
        self._thread = WorkerThread(self._pixbuf_worker,
                                    name='thumbview',
                                    unique_orders=True,
                                    max_threads=prefs["max threads"])

    def generate_thumbnail(self, uid):
        """ This function must return the thumbnail for C{uid}. """
        raise NotImplementedError()

    def get_visible_range(self):
        """ See L{gtk.IconView.get_visible_range}. """
        raise NotImplementedError()

    def stop_update(self):
        """ Stops generation of pixbufs. """
        self._updates_stopped = True
        self._thread.stop()

    def draw_thumbnails_on_screen(self, *args):
        """ Prepares valid thumbnails for currently displayed icons.
        This method is supposed to be called from the expose-event
        callback function. """

        visible = self.get_visible_range()
        if not visible:
            # No valid paths available
            return

        pixbufs_needed = []
        start = visible[0][0]
        end = visible[1][0]
        # Read ahead/back and start caching a few more icons. Currently invisible
        # icons are always cached only after the visible icons have been completed.
        additional = (end - start) // 2
        required = range(start, end + additional + 1) + \
                   range(max(0, start - additional), start)
        model = self.get_model()
        # Filter invalid paths.
        required = [path for path in required if 0 <= path < len(model)]
        with self._thread:
            # Flush current pixmap generation orders.
            self._thread.clear_orders()
            for path in required:
                iter = model.get_iter(path)
                uid, generated = model.get(iter,
                                           self._uid_column,
                                           self._status_column)
                # Do not queue again if thumbnail was already created.
                if not generated:
                    pixbufs_needed.append((uid, iter))
            if len(pixbufs_needed) > 0:
                self._updates_stopped = False
                self._thread.extend_orders(pixbufs_needed)

    def _pixbuf_worker(self, order):
        """ Run by a worker thread to generate the thumbnail for a path."""
        uid, iter = order
        pixbuf = self.generate_thumbnail(uid)
        if pixbuf is not None:
            gobject.idle_add(self._pixbuf_finished, iter, pixbuf)

    def _pixbuf_finished(self, iter, pixbuf):
        """ Executed when a pixbuf was created, to actually insert the pixbuf
        into the view store. C{pixbuf_info} is a tuple containing
        (index, pixbuf). """

        if self._updates_stopped:
            return 0

        model = self.get_model()
        model.set(iter, self._status_column, True, self._pixbuf_column, pixbuf)

        # Remove this idle handler.
        return 0
コード例 #14
0
ファイル: archive_extractor.py プロジェクト: drdrpyan/mcomix
class Extractor:

    """Extractor is a threaded class for extracting different archive formats.

    The Extractor can be loaded with paths to archives and a path to a
    destination directory. Once an archive has been set and its contents
    listed, it is possible to filter out the files to be extracted and set the
    order in which they should be extracted.  The extraction can then be
    started in a new thread in which files are extracted one by one, and a
    signal is sent on a condition after each extraction, so that it is possible
    for other threads to wait on specific files to be ready.

    Note: Support for gzip/bzip2 compressed tar archives is limited, see
    set_files() for more info.
    """

    def __init__(self):
        self._setupped = False

    def setup(self, src, dst, type=None):
        """Setup the extractor with archive <src> and destination dir <dst>.
        Return a threading.Condition related to the is_ready() method, or
        None if the format of <src> isn't supported.
        """
        self._src = src
        self._dst = dst
        self._type = type or archive_tools.archive_mime_type(src)
        self._files = []
        self._extracted = set()
        self._archive = archive_tools.get_recursive_archive_handler(src, dst, type=self._type)
        if self._archive is None:
            msg = _('Non-supported archive format: %s') % os.path.basename(src)
            log.warning(msg)
            raise ArchiveException(msg)

        self._contents_listed = False
        self._extract_started = False
        self._condition = threading.Condition()
        self._list_thread = WorkerThread(self._list_contents, name='list')
        self._list_thread.append_order(self._archive)
        self._setupped = True

        return self._condition

    def get_files(self):
        """Return a list of names of all the files the extractor is currently
        set for extracting. After a call to setup() this is by default all
        files found in the archive. The paths in the list are relative to
        the archive root and are not absolute for the files once extracted.
        """
        with self._condition:
            if not self._contents_listed:
                return
            return self._files[:]

    def get_directory(self):
        """Returns the root extraction directory of this extractor."""
        return self._dst

    def set_files(self, files):
        """Set the files that the extractor should extract from the archive in
        the order of extraction. Normally one would get the list of all files
        in the archive using get_files(), then filter and/or permute this
        list before sending it back using set_files().

        Note: Random access on gzip or bzip2 compressed tar archives is
        no good idea. These formats are supported *only* for backwards
        compability. They are fine formats for some purposes, but should
        not be used for scanned comic books. So, we cheat and ignore the
        ordering applied with this method on such archives.
        """
        with self._condition:
            if not self._contents_listed:
                return
            self._files = [f for f in files if f not in self._extracted]
            if self._extract_started:
                self.extract()

    def is_ready(self, name):
        """Return True if the file <name> in the extractor's file list
        (as set by set_files()) is fully extracted.
        """
        with self._condition:
            return name in self._extracted

    def get_mime_type(self):
        """Return the mime type name of the extractor's current archive."""
        return self._type

    def stop(self):
        """Signal the extractor to stop extracting and kill the extracting
        thread. Blocks until the extracting thread has terminated.
        """
        if self._setupped:
            self._list_thread.stop()
            if self._extract_started:
                self._extract_thread.stop()
                self._extract_started = False
            self.setupped = False

    def extract(self):
        """Start extracting the files in the file list one by one using a
        new thread. Every time a new file is extracted a notify() will be
        signalled on the Condition that was returned by setup().
        """
        with self._condition:
            if not self._contents_listed:
                return
            if not self._extract_started:
                if self._archive.support_concurrent_extractions \
                   and not self._archive.is_solid():
                    max_threads = prefs['max extract threads']
                else:
                    max_threads = 1
                if self._archive.is_solid():
                    fn = self._extract_all_files
                else:
                    fn = self._extract_file
                self._extract_thread = WorkerThread(fn,
                                                    name='extract',
                                                    max_threads=max_threads,
                                                    unique_orders=True)
                self._extract_started = True
            else:
                self._extract_thread.clear_orders()
            if self._archive.is_solid():
                # Sort files so we don't queue the same batch multiple times.
                self._extract_thread.append_order(sorted(self._files))
            else:
                self._extract_thread.extend_orders(self._files)

    @callback.Callback
    def contents_listed(self, extractor, files):
        """ Called after the contents of the archive has been listed. """
        pass

    @callback.Callback
    def file_extracted(self, extractor, filename):
        """ Called whenever a new file is extracted and ready. """
        pass

    def close(self):
        """Close any open file objects, need only be called manually if the
        extract() method isn't called.
        """
        self.stop()
        if self._archive:
            self._archive.close()

    def _extraction_finished(self, name):
        with self._condition:
            self._files.remove(name)
            self._extracted.add(name)
            self._condition.notifyAll()
        self.file_extracted(self, name)

    def _extract_all_files(self, files):

        # With multiple extractions for each pass, some of the files might have
        # already been extracted.
        with self._condition:
            files = list(set(files) - self._extracted)
            files.sort()

        try:
            log.debug(u'Extracting from "%s" to "%s": "%s"', self._src, self._dst, '", "'.join(files))
            for f in self._archive.iter_extract(files, self._dst):
                if self._extract_thread.must_stop():
                    return
                self._extraction_finished(f)

        except Exception, ex:
            # Better to ignore any failed extractions (e.g. from a corrupt
            # archive) than to crash here and leave the main thread in a
            # possible infinite block. Damaged or missing files *should* be
            # handled gracefully by the main program anyway.
            log.error(_('! Extraction error: %s'), ex)
            log.debug('Traceback:\n%s', traceback.format_exc())
コード例 #15
0
class Extractor(object):
    """Extractor is a threaded class for extracting different archive formats.

    The Extractor can be loaded with paths to archives and a path to a
    destination directory. Once an archive has been set and its contents
    listed, it is possible to filter out the files to be extracted and set the
    order in which they should be extracted.  The extraction can then be
    started in a new thread in which files are extracted one by one, and a
    signal is sent on a condition after each extraction, so that it is possible
    for other threads to wait on specific files to be ready.

    Note: Support for gzip/bzip2 compressed tar archives is limited, see
    set_files() for more info.
    """
    def __init__(self):
        self._setupped = False

    def setup(self, src, dst, type=None):
        """Setup the extractor with archive <src> and destination dir <dst>.
        Return a threading.Condition related to the is_ready() method, or
        None if the format of <src> isn't supported.
        """
        self._src = src
        self._dst = dst
        self._files = []
        self._extracted = set()
        self._archive = archive_tools.get_recursive_archive_handler(src,
                                                                    dst,
                                                                    type=type)
        if self._archive is None:
            msg = _('Non-supported archive format: %s') % os.path.basename(src)
            log.warning(msg)
            raise ArchiveException(msg)

        self._contents_listed = False
        self._extract_started = False
        self._condition = threading.Condition()
        self._list_thread = WorkerThread(self._list_contents, name='list')
        self._list_thread.append_order(self._archive)
        self._setupped = True

        return self._condition

    def get_files(self):
        """Return a list of names of all the files the extractor is currently
        set for extracting. After a call to setup() this is by default all
        files found in the archive. The paths in the list are relative to
        the archive root and are not absolute for the files once extracted.
        """
        with self._condition:
            if not self._contents_listed:
                return
            return self._files[:]

    def get_directory(self):
        """Returns the root extraction directory of this extractor."""
        return self._dst

    def set_files(self, files):
        """Set the files that the extractor should extract from the archive in
        the order of extraction. Normally one would get the list of all files
        in the archive using get_files(), then filter and/or permute this
        list before sending it back using set_files().

        Note: Random access on gzip or bzip2 compressed tar archives is
        no good idea. These formats are supported *only* for backwards
        compability. They are fine formats for some purposes, but should
        not be used for scanned comic books. So, we cheat and ignore the
        ordering applied with this method on such archives.
        """
        with self._condition:
            if not self._contents_listed:
                return
            self._files = [f for f in files if f not in self._extracted]
            if not self._files:
                # Nothing to do!
                return
            if self._extract_started:
                self.extract()

    def is_ready(self, name):
        """Return True if the file <name> in the extractor's file list
        (as set by set_files()) is fully extracted.
        """
        with self._condition:
            return name in self._extracted

    def stop(self):
        """Signal the extractor to stop extracting and kill the extracting
        thread. Blocks until the extracting thread has terminated.
        """
        if self._setupped:
            self._list_thread.stop()
            if self._extract_started:
                self._extract_thread.stop()
                self._extract_started = False
            self.setupped = False

    def extract(self):
        """Start extracting the files in the file list one by one using a
        new thread. Every time a new file is extracted a notify() will be
        signalled on the Condition that was returned by setup().
        """
        with self._condition:
            if not self._contents_listed:
                return
            if not self._extract_started:
                if self._archive.support_concurrent_extractions \
                   and not self._archive.is_solid():
                    max_threads = prefs['max extract threads']
                else:
                    max_threads = 1
                if self._archive.is_solid():
                    fn = self._extract_all_files
                else:
                    fn = self._extract_file
                self._extract_thread = WorkerThread(fn,
                                                    name='extract',
                                                    max_threads=max_threads,
                                                    unique_orders=True)
                self._extract_started = True
            else:
                self._extract_thread.clear_orders()
            if self._archive.is_solid():
                # Sort files so we don't queue the same batch multiple times.
                self._extract_thread.append_order(sorted(self._files))
            else:
                self._extract_thread.extend_orders(self._files)

    @callback.Callback
    def contents_listed(self, extractor, files):
        """ Called after the contents of the archive has been listed. """
        pass

    @callback.Callback
    def file_extracted(self, extractor, filename):
        """ Called whenever a new file is extracted and ready. """
        pass

    def close(self):
        """Close any open file objects, need only be called manually if the
        extract() method isn't called.
        """
        self.stop()
        if self._archive:
            self._archive.close()

    def _extraction_finished(self, name):
        with self._condition:
            self._files.remove(name)
            self._extracted.add(name)
            self._condition.notifyAll()
        self.file_extracted(self, name)

    def _extract_all_files(self, files):

        # With multiple extractions for each pass, some of the files might have
        # already been extracted.
        with self._condition:
            files = list(set(files) - self._extracted)
            files.sort()

        try:
            log.debug(u'Extracting from "%s" to "%s": "%s"', self._src,
                      self._dst, '", "'.join(files))
            for f in self._archive.iter_extract(files, self._dst):
                if self._extract_thread.must_stop():
                    return
                self._extraction_finished(f)

        except Exception as ex:
            # Better to ignore any failed extractions (e.g. from a corrupt
            # archive) than to crash here and leave the main thread in a
            # possible infinite block. Damaged or missing files *should* be
            # handled gracefully by the main program anyway.
            log.error(_('! Extraction error: %s'), ex)
            log.debug('Traceback:\n%s', traceback.format_exc())

    def _extract_file(self, name):
        """Extract the file named <name> to the destination directory,
        mark the file as "ready", then signal a notify() on the Condition
        returned by setup().
        """

        try:
            log.debug(u'Extracting from "%s" to "%s": "%s"', self._src,
                      self._dst, name)
            self._archive.extract(name, self._dst)

        except Exception as ex:
            # Better to ignore any failed extractions (e.g. from a corrupt
            # archive) than to crash here and leave the main thread in a
            # possible infinite block. Damaged or missing files *should* be
            # handled gracefully by the main program anyway.
            log.error(_('! Extraction error: %s'), ex)
            log.debug('Traceback:\n%s', traceback.format_exc())

        if self._extract_thread.must_stop():
            return
        self._extraction_finished(name)

    def _list_contents(self, archive):
        files = []
        for f in archive.iter_contents():
            if self._list_thread.must_stop():
                return
            files.append(f)
        with self._condition:
            self._files = files
            self._contents_listed = True
        self.contents_listed(self, files)
コード例 #16
0
ファイル: pageselect.py プロジェクト: drdrpyan/mcomix
class Pageselector(gtk.Dialog):

    """The Pageselector takes care of the popup page selector
    """

    def __init__(self, window):
        self._window = window
        self._page_selector_dialog = gtk.Dialog.__init__(self, "Go to page...", window,
                                     gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
        self.add_buttons(_('_Go'), gtk.RESPONSE_OK,
                         _('_Cancel'), gtk.RESPONSE_CANCEL,)
        self.set_default_response(gtk.RESPONSE_OK)
        self.set_has_separator(False)
        self.connect('response', self._response)
        self.set_resizable(True)

        self._number_of_pages = self._window.imagehandler.get_number_of_pages()

        self._selector_adjustment = gtk.Adjustment(value=self._window.imagehandler.get_current_page(),
                              lower=1,upper=self._number_of_pages,
                              step_incr=1, page_incr=1 )

        self._selector_adjustment.connect( 'value-changed', self._cb_value_changed )

        self._page_selector = gtk.VScale(self._selector_adjustment)
        self._page_selector.set_draw_value(False)
        self._page_selector.set_digits( 0 )

        self._page_spinner = gtk.SpinButton(self._selector_adjustment)
        self._page_spinner.connect( 'changed', self._page_text_changed )
        self._page_spinner.set_activates_default(True)
        self._page_spinner.set_numeric(True)
        self._pages_label = gtk.Label(_(' of %s') % self._number_of_pages)
        self._pages_label.set_alignment(0, 0.5)

        self._image_preview = gtk.Image()
        self._image_preview.set_size_request(
            prefs['thumbnail size'], prefs['thumbnail size'])

        self.connect('configure-event', self._size_changed_cb)
        self.set_size_request(prefs['pageselector width'],
                prefs['pageselector height'])

        # Group preview image and page selector next to each other
        preview_box = gtk.HBox()
        preview_box.set_border_width(5)
        preview_box.set_spacing(5)
        preview_box.pack_start(self._image_preview, True)
        preview_box.pack_end(self._page_selector, False)
        # Below them, group selection spinner and current page label
        selection_box = gtk.HBox()
        selection_box.set_border_width(5)
        selection_box.pack_start(self._page_spinner, True)
        selection_box.pack_end(self._pages_label, False)

        self.get_content_area().pack_start(preview_box, True)
        self.get_content_area().pack_end(selection_box, False)
        self.show_all()

        # Set focus on the input box.
        self._page_spinner.select_region(0, -1)
        self._page_spinner.grab_focus()

        # Currently displayed thumbnail page.
        self._thumbnail_page = 0
        self._thread = WorkerThread(self._generate_thumbnail, name='preview')
        self._update_thumbnail(int(self._selector_adjustment.value))
        self._window.imagehandler.page_available += self._page_available

    def _cb_value_changed(self, *args):
        """ Called whenever the spinbox value changes. Updates the preview thumbnail. """
        page = int(self._selector_adjustment.value)
        if page != self._thumbnail_page:
            self._update_thumbnail(page)

    def _size_changed_cb(self, *args):
        # Window cannot be scaled down unless the size request is reset
        self.set_size_request(-1, -1)
        # Store dialog size
        prefs['pageselector width'] = self.get_allocation().width
        prefs['pageselector height'] = self.get_allocation().height

        self._update_thumbnail(int(self._selector_adjustment.value))

    def _page_text_changed(self, control, *args):
        """ Called when the page selector has been changed. Used to instantly update
            the preview thumbnail when entering page numbers by hand. """
        if control.get_text().isdigit():
            page = int(control.get_text())
            if page > 0 and page <= self._number_of_pages:
                control.set_value(page)

    def _response(self, widget, event, *args):
        if event == gtk.RESPONSE_OK:
            self._window.set_page(int(self._selector_adjustment.value))

        self._window.imagehandler.page_available -= self._page_available
        self._thread.stop()
        self.destroy()

    def _update_thumbnail(self, page):
        """ Trigger a thumbnail update. """
        width = self._image_preview.get_allocation().width
        height = self._image_preview.get_allocation().height
        self._thumbnail_page = page
        self._thread.clear_orders()
        self._thread.append_order((page, width, height))

    def _generate_thumbnail(self, params):
        """ Generate the preview thumbnail for the page selector.
        A transparent image will be used if the page is not yet available. """
        page, width, height = params

        pixbuf = self._window.imagehandler.get_thumbnail(page,
            width=width, height=height, nowait=True)
        self._thumbnail_finished(page, pixbuf)

    @callback.Callback
    def _thumbnail_finished(self, page, pixbuf):
        # Don't bother if we changed page in the meantime.
        if page == self._thumbnail_page:
            self._image_preview.set_from_pixbuf(pixbuf)

    def _page_available(self, page):
        if page == int(self._selector_adjustment.value):
            self._update_thumbnail(page)
コード例 #17
0
ファイル: thumbnail_view.py プロジェクト: drdrpyan/mcomix
class ThumbnailViewBase(object):
    """ This class provides shared functionality for gtk.TreeView and
    gtk.IconView. Instantiating this class directly is *impossible*,
    as it depends on methods provided by the view classes. """
    def __init__(self, model):
        """ Constructs a new ThumbnailView.
        @param model: L{gtk.TreeModel} instance. The model needs a pixbuf
                      and a boolean column for internal calculations.
        """

        #: Model index of the thumbnail status field (gobject.BOOLEAN)
        self.status_column = -1
        #: Model index of the pixbuf field
        self.pixbuf_column = -1

        #: Ignore updates when this flag is True.
        self._updates_stopped = True
        #: Worker thread
        self._thread = WorkerThread(self._pixbuf_worker,
                                    name='thumbview',
                                    unique_orders=True,
                                    max_threads=prefs["max threads"])

    def generate_thumbnail(self, file_path, model, path):
        """ This function must return the thumbnail for C{file_path}.
        C{model} and {path} point at the relevant model line. """
        raise NotImplementedError()

    def get_file_path_from_model(self, model, iter):
        """ This function should retrieve a file path from C{model},
        the current row being specified by C{iter}. """
        raise NotImplementedError()

    def get_visible_range(self):
        """ See L{gtk.IconView.get_visible_range}. """
        raise NotImplementedError()

    def stop_update(self):
        """ Stops generation of pixbufs. """
        self._updates_stopped = True
        self._thread.stop()

    def draw_thumbnails_on_screen(self, *args):
        """ Prepares valid thumbnails for currently displayed icons.
        This method is supposed to be called from the expose-event
        callback function. """

        visible = self.get_visible_range()
        if not visible:
            # No valid paths available
            return

        # Flush current pixmap generation orders.
        self._thread.clear_orders()

        pixbufs_needed = []
        start = visible[0][0]
        end = visible[1][0]
        # Read ahead/back and start caching a few more icons. Currently invisible
        # icons are always cached only after the visible icons have been completed.
        additional = (end - start) // 2
        required = range(start, end + additional + 1) + \
                   range(max(0, start - additional), start)
        model = self.get_model()
        for path in required:
            try:
                iter = model.get_iter(path)
            except ValueError:
                iter = None

            # Do not queue again if cover was already created
            if (iter is not None
                    and not model.get_value(iter, self.status_column)):

                file_path = self.get_file_path_from_model(model, iter)
                pixbufs_needed.append((file_path, path))

        if len(pixbufs_needed) > 0:
            self._updates_stopped = False
            self._thread.extend_orders(pixbufs_needed)

    def _pixbuf_worker(self, order):
        """ Run by a worker thread to generate the thumbnail for a path."""
        file_path, path = order
        pixbuf = self.generate_thumbnail(file_path, path)
        if pixbuf is not None:
            gobject.idle_add(self._pixbuf_finished, path, pixbuf)

    def _pixbuf_finished(self, path, pixbuf):
        """ Executed when a pixbuf was created, to actually insert the pixbuf
        into the view store. C{pixbuf_info} is a tuple containing
        (index, pixbuf). """

        if self._updates_stopped:
            return 0

        model = self.get_model()
        iter = model.get_iter(path)
        model.set(iter, self.pixbuf_column, pixbuf)
        # Mark as generated
        model.set_value(iter, self.status_column, True)

        # Remove this idle handler.
        return 0
コード例 #18
0
class Pageselector(Gtk.Dialog):
    """The Pageselector takes care of the popup page selector
    """
    def __init__(self, window):
        self._window = window
        super(Pageselector, self).__init__(
            "Go to page...", window,
            Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT)
        self.add_buttons(
            _('_Go'),
            Gtk.ResponseType.OK,
            _('_Cancel'),
            Gtk.ResponseType.CANCEL,
        )
        self.set_default_response(Gtk.ResponseType.OK)
        self.connect('response', self._response)
        self.set_resizable(True)

        self._number_of_pages = self._window.imagehandler.get_number_of_pages()

        self._selector_adjustment = Gtk.Adjustment(
            value=self._window.imagehandler.get_current_page(),
            lower=1,
            upper=self._number_of_pages,
            step_incr=1,
            page_incr=1)

        self._page_selector = Gtk.VScale.new(self._selector_adjustment)
        self._page_selector.set_draw_value(False)
        self._page_selector.set_digits(0)

        self._page_spinner = Gtk.SpinButton.new(self._selector_adjustment, 0.0,
                                                0)
        self._page_spinner.connect('changed', self._page_text_changed)
        self._page_spinner.set_activates_default(True)
        self._page_spinner.set_numeric(True)
        self._pages_label = Gtk.Label(label=_(' of %s') %
                                      self._number_of_pages)
        self._pages_label.set_alignment(0, 0.5)

        self._image_preview = Gtk.Image()
        self._image_preview.set_size_request(prefs['thumbnail size'],
                                             prefs['thumbnail size'])

        self.connect('configure-event', self._size_changed_cb)
        self.set_size_request(prefs['pageselector width'],
                              prefs['pageselector height'])

        # Group preview image and page selector next to each other
        preview_box = Gtk.HBox()
        preview_box.set_border_width(5)
        preview_box.set_spacing(5)
        preview_box.pack_start(self._image_preview, True, True, 0)
        preview_box.pack_end(self._page_selector, False, True, 0)
        # Below them, group selection spinner and current page label
        selection_box = Gtk.HBox()
        selection_box.set_border_width(5)
        selection_box.pack_start(self._page_spinner, True, True, 0)
        selection_box.pack_end(self._pages_label, False, True, 0)

        self.get_content_area().pack_start(preview_box, True, True, 0)
        self.get_content_area().pack_end(selection_box, False, True, 0)
        self.show_all()

        self._selector_adjustment.connect('value-changed',
                                          self._cb_value_changed)

        # Set focus on the input box.
        self._page_spinner.select_region(0, -1)
        self._page_spinner.grab_focus()

        # Currently displayed thumbnail page.
        self._thumbnail_page = 0
        self._thread = WorkerThread(self._generate_thumbnail, name='preview')
        self._update_thumbnail(int(self._selector_adjustment.props.value))
        self._window.imagehandler.page_available += self._page_available

    def _cb_value_changed(self, *args):
        """ Called whenever the spinbox value changes. Updates the preview thumbnail. """
        page = int(self._selector_adjustment.props.value)
        if page != self._thumbnail_page:
            self._update_thumbnail(page)

    def _size_changed_cb(self, *args):
        # Window cannot be scaled down unless the size request is reset
        self.set_size_request(-1, -1)
        # Store dialog size
        prefs['pageselector width'] = self.get_allocation().width
        prefs['pageselector height'] = self.get_allocation().height

        self._update_thumbnail(int(self._selector_adjustment.props.value))

    def _page_text_changed(self, control, *args):
        """ Called when the page selector has been changed. Used to instantly update
            the preview thumbnail when entering page numbers by hand. """
        if control.get_text().isdigit():
            page = int(control.get_text())
            if page > 0 and page <= self._number_of_pages:
                control.set_value(page)

    def _response(self, widget, event, *args):
        if event == Gtk.ResponseType.OK:
            self._window.set_page(int(self._selector_adjustment.props.value))

        self._window.imagehandler.page_available -= self._page_available
        self._thread.stop()
        self.destroy()

    def _update_thumbnail(self, page):
        """ Trigger a thumbnail update. """
        width = self._image_preview.get_allocation().width
        height = self._image_preview.get_allocation().height
        self._thumbnail_page = page
        self._thread.clear_orders()
        self._thread.append_order((page, width, height))

    def _generate_thumbnail(self, params):
        """ Generate the preview thumbnail for the page selector.
        A transparent image will be used if the page is not yet available. """
        page, width, height = params

        pixbuf = self._window.imagehandler.get_thumbnail(page,
                                                         width=width,
                                                         height=height,
                                                         nowait=True)
        self._thumbnail_finished(page, pixbuf)

    @callback.Callback
    def _thumbnail_finished(self, page, pixbuf):
        # Don't bother if we changed page in the meantime.
        if page == self._thumbnail_page:
            self._image_preview.set_from_pixbuf(pixbuf)

    def _page_available(self, page):
        if page == int(self._selector_adjustment.props.value):
            self._update_thumbnail(page)
コード例 #19
0
    def __init__(self, window):
        self._window = window
        super(Pageselector, self).__init__(
            "Go to page...", window,
            Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT)
        self.add_buttons(
            _('_Go'),
            Gtk.ResponseType.OK,
            _('_Cancel'),
            Gtk.ResponseType.CANCEL,
        )
        self.set_default_response(Gtk.ResponseType.OK)
        self.connect('response', self._response)
        self.set_resizable(True)

        self._number_of_pages = self._window.imagehandler.get_number_of_pages()

        self._selector_adjustment = Gtk.Adjustment(
            value=self._window.imagehandler.get_current_page(),
            lower=1,
            upper=self._number_of_pages,
            step_incr=1,
            page_incr=1)

        self._page_selector = Gtk.VScale.new(self._selector_adjustment)
        self._page_selector.set_draw_value(False)
        self._page_selector.set_digits(0)

        self._page_spinner = Gtk.SpinButton.new(self._selector_adjustment, 0.0,
                                                0)
        self._page_spinner.connect('changed', self._page_text_changed)
        self._page_spinner.set_activates_default(True)
        self._page_spinner.set_numeric(True)
        self._pages_label = Gtk.Label(label=_(' of %s') %
                                      self._number_of_pages)
        self._pages_label.set_alignment(0, 0.5)

        self._image_preview = Gtk.Image()
        self._image_preview.set_size_request(prefs['thumbnail size'],
                                             prefs['thumbnail size'])

        self.connect('configure-event', self._size_changed_cb)
        self.set_size_request(prefs['pageselector width'],
                              prefs['pageselector height'])

        # Group preview image and page selector next to each other
        preview_box = Gtk.HBox()
        preview_box.set_border_width(5)
        preview_box.set_spacing(5)
        preview_box.pack_start(self._image_preview, True, True, 0)
        preview_box.pack_end(self._page_selector, False, True, 0)
        # Below them, group selection spinner and current page label
        selection_box = Gtk.HBox()
        selection_box.set_border_width(5)
        selection_box.pack_start(self._page_spinner, True, True, 0)
        selection_box.pack_end(self._pages_label, False, True, 0)

        self.get_content_area().pack_start(preview_box, True, True, 0)
        self.get_content_area().pack_end(selection_box, False, True, 0)
        self.show_all()

        self._selector_adjustment.connect('value-changed',
                                          self._cb_value_changed)

        # Set focus on the input box.
        self._page_spinner.select_region(0, -1)
        self._page_spinner.grab_focus()

        # Currently displayed thumbnail page.
        self._thumbnail_page = 0
        self._thread = WorkerThread(self._generate_thumbnail, name='preview')
        self._update_thumbnail(int(self._selector_adjustment.props.value))
        self._window.imagehandler.page_available += self._page_available
コード例 #20
0
ファイル: thumbnail_view.py プロジェクト: drdrpyan/mcomix
class ThumbnailViewBase(object):
    """ This class provides shared functionality for gtk.TreeView and
    gtk.IconView. Instantiating this class directly is *impossible*,
    as it depends on methods provided by the view classes. """

    def __init__(self, model):
        """ Constructs a new ThumbnailView.
        @param model: L{gtk.TreeModel} instance. The model needs a pixbuf
                      and a boolean column for internal calculations.
        """

        #: Model index of the thumbnail status field (gobject.BOOLEAN)
        self.status_column = -1
        #: Model index of the pixbuf field
        self.pixbuf_column = -1

        #: Ignore updates when this flag is True.
        self._updates_stopped = True
        #: Worker thread
        self._thread = WorkerThread(self._pixbuf_worker,
                                    name='thumbview',
                                    unique_orders=True,
                                    max_threads=prefs["max threads"])

    def generate_thumbnail(self, file_path, model, path):
        """ This function must return the thumbnail for C{file_path}.
        C{model} and {path} point at the relevant model line. """
        raise NotImplementedError()

    def get_file_path_from_model(self, model, iter):
        """ This function should retrieve a file path from C{model},
        the current row being specified by C{iter}. """
        raise NotImplementedError()

    def get_visible_range(self):
        """ See L{gtk.IconView.get_visible_range}. """
        raise NotImplementedError()

    def stop_update(self):
        """ Stops generation of pixbufs. """
        self._updates_stopped = True
        self._thread.stop()

    def draw_thumbnails_on_screen(self, *args):
        """ Prepares valid thumbnails for currently displayed icons.
        This method is supposed to be called from the expose-event
        callback function. """

        visible = self.get_visible_range()
        if not visible:
            # No valid paths available
            return

        # Flush current pixmap generation orders.
        self._thread.clear_orders()

        pixbufs_needed = []
        start = visible[0][0]
        end = visible[1][0]
        # Read ahead/back and start caching a few more icons. Currently invisible
        # icons are always cached only after the visible icons have been completed.
        additional = (end - start) // 2
        required = range(start, end + additional + 1) + \
                   range(max(0, start - additional), start)
        model = self.get_model()
        for path in required:
            try:
                iter = model.get_iter(path)
            except ValueError:
                iter = None

            # Do not queue again if cover was already created
            if (iter is not None and
                not model.get_value(iter, self.status_column)):

                file_path = self.get_file_path_from_model(model, iter)
                pixbufs_needed.append((file_path, path))

        if len(pixbufs_needed) > 0:
            self._updates_stopped = False
            self._thread.extend_orders(pixbufs_needed)

    def _pixbuf_worker(self, order):
        """ Run by a worker thread to generate the thumbnail for a path."""
        file_path, path = order
        pixbuf = self.generate_thumbnail(file_path, path)
        if pixbuf is not None:
            gobject.idle_add(self._pixbuf_finished, path, pixbuf)

    def _pixbuf_finished(self, path, pixbuf):
        """ Executed when a pixbuf was created, to actually insert the pixbuf
        into the view store. C{pixbuf_info} is a tuple containing
        (index, pixbuf). """

        if self._updates_stopped:
            return 0

        model = self.get_model()
        iter = model.get_iter(path)
        model.set(iter, self.pixbuf_column, pixbuf)
        # Mark as generated
        model.set_value(iter, self.status_column, True)

        # Remove this idle handler.
        return 0