Esempio n. 1
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.application = kwargs['application']

        self._night_light_handler_id = 0
        self._night_light_proxy = None

        self.builder = Gtk.Builder()
        self.builder.add_from_resource(
            '/info/febvre/Komikku/ui/main_window.ui')
        self.builder.add_from_resource('/info/febvre/Komikku/ui/menu/main.xml')

        self.logging_manager = self.application.get_logger()
        self.downloader = Downloader(self)
        self.updater = Updater(self, Settings.get_default().update_at_startup)

        self.overlay = self.builder.get_object('overlay')
        self.stack = self.builder.get_object('stack')
        self.title_stack = self.builder.get_object('title_stack')
        self.left_button_image = self.builder.get_object('left_button_image')
        self.menu_button = self.builder.get_object('menu_button')
        self.menu_button_image = self.builder.get_object('menu_button_image')

        self.activity_indicator = ActivityIndicator()
        self.overlay.add_overlay(self.activity_indicator)
        self.overlay.set_overlay_pass_through(self.activity_indicator, True)
        self.activity_indicator.show_all()

        self.assemble_window()
Esempio n. 2
0
    def __init__(self, pager, chapter, index):
        Gtk.Overlay.__init__(self)

        self.pager = pager
        self.reader = pager.reader
        self.window = pager.window

        self.chapter = chapter
        self.index = index
        self.path = None

        self.status = None  # rendering, rendered, offlimit, cleaned
        self.error = None  # connection error, server error or corrupt file error
        self.loadable = False  # loadable from disk or downloadable from server (chapter pages are known)

        self.set_size()

        self.scrolledwindow = Gtk.ScrolledWindow()
        if self.reader.reading_direction == 'vertical':
            self.scrolledwindow.get_vadjustment().connect(
                'changed', self.adjust_scroll)
        else:
            self.scrolledwindow.get_hadjustment().connect(
                'changed', self.adjust_scroll)

        self.add(self.scrolledwindow)

        viewport = Gtk.Viewport()
        self.image = Gtk.Image()
        self.imagebuf = None
        viewport.add(self.image)
        self.scrolledwindow.add(viewport)

        # Activity indicator
        self.activity_indicator = ActivityIndicator()
        self.add_overlay(self.activity_indicator)
        self.set_overlay_pass_through(self.activity_indicator,
                                      True)  # Allows scrolling in zoom mode

        self.show_all()
Esempio n. 3
0
    def __init__(self, window):
        self.window = window
        self.builder = Gtk.Builder()
        self.builder.add_from_resource('/info/febvre/Komikku/ui/add_dialog.ui')

        self.dialog = self.builder.get_object('dialog')
        self.dialog.get_children()[0].set_border_width(0)

        # Header bar
        self.builder.get_object('back_button').connect('clicked', self.on_back_button_clicked)
        self.custom_title_stack = self.builder.get_object('custom_title_stack')

        # Make title centered
        self.builder.get_object('custom_title_servers_page_label').set_margin_end(38)

        self.overlay = self.builder.get_object('overlay')
        self.stack = self.builder.get_object('stack')

        self.activity_indicator = ActivityIndicator()
        self.overlay.add_overlay(self.activity_indicator)
        self.overlay.set_overlay_pass_through(self.activity_indicator, True)
        self.activity_indicator.show_all()

        # Servers page
        listbox = self.builder.get_object('servers_page_listbox')
        listbox.get_style_context().add_class('list-bordered')
        listbox.connect('row-activated', self.on_server_clicked)

        servers_settings = Settings.get_default().servers_settings
        servers_languages = Settings.get_default().servers_languages

        for server_data in get_servers_list():
            if servers_languages and server_data['lang'] not in servers_languages:
                continue

            server_settings = servers_settings.get(get_server_main_id_by_id(server_data['id']))
            if server_settings is not None and (not server_settings['enabled'] or server_settings['langs'].get(server_data['lang']) is False):
                continue

            row = Gtk.ListBoxRow()
            row.get_style_context().add_class('add-dialog-server-listboxrow')
            row.server_data = server_data
            box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
            row.add(box)

            # Server logo
            pixbuf = Pixbuf.new_from_resource_at_scale(get_server_logo_resource_path_by_id(server_data['id']), 24, 24, True)
            logo = Gtk.Image()
            logo.set_from_pixbuf(pixbuf)
            box.pack_start(logo, False, True, 0)

            # Server title
            label = Gtk.Label(xalign=0)
            label.set_text(server_data['name'])
            box.pack_start(label, True, True, 0)

            # Server language
            label = Gtk.Label()
            label.set_text(LANGUAGES[server_data['lang']])
            label.get_style_context().add_class('add-dialog-server-language-label')
            box.pack_start(label, False, True, 0)

            listbox.add(row)

        listbox.show_all()

        # Search page
        self.custom_title_search_page_searchentry = self.builder.get_object('custom_title_search_page_searchentry')
        self.custom_title_search_page_searchentry.connect('activate', self.search)

        self.search_page_listbox = self.builder.get_object('search_page_listbox')
        self.search_page_listbox.get_style_context().add_class('list-bordered')
        self.search_page_listbox.connect('row-activated', self.on_manga_clicked)

        # Manga page
        grid = self.builder.get_object('manga_page_grid')
        grid.set_margin_top(6)
        grid.set_margin_end(6)
        grid.set_margin_bottom(6)
        grid.set_margin_start(6)
        self.custom_title_manga_page_label = self.builder.get_object('custom_title_manga_page_label')
        self.add_button = self.builder.get_object('add_button')
        self.add_button.connect('clicked', self.on_add_button_clicked)
        self.read_button = self.builder.get_object('read_button')
        self.read_button.connect('clicked', self.on_read_button_clicked)

        self.show_page('servers')
Esempio n. 4
0
class AddDialog:
    page = None
    search_lock = False

    server = None
    manga_slug = None
    manga_data = None
    manga = None

    def __init__(self, window):
        self.window = window
        self.builder = Gtk.Builder()
        self.builder.add_from_resource('/info/febvre/Komikku/ui/add_dialog.ui')

        self.dialog = self.builder.get_object('dialog')
        self.dialog.get_children()[0].set_border_width(0)

        # Header bar
        self.builder.get_object('back_button').connect('clicked', self.on_back_button_clicked)
        self.custom_title_stack = self.builder.get_object('custom_title_stack')

        # Make title centered
        self.builder.get_object('custom_title_servers_page_label').set_margin_end(38)

        self.overlay = self.builder.get_object('overlay')
        self.stack = self.builder.get_object('stack')

        self.activity_indicator = ActivityIndicator()
        self.overlay.add_overlay(self.activity_indicator)
        self.overlay.set_overlay_pass_through(self.activity_indicator, True)
        self.activity_indicator.show_all()

        # Servers page
        listbox = self.builder.get_object('servers_page_listbox')
        listbox.get_style_context().add_class('list-bordered')
        listbox.connect('row-activated', self.on_server_clicked)

        servers_settings = Settings.get_default().servers_settings
        servers_languages = Settings.get_default().servers_languages

        for server_data in get_servers_list():
            if servers_languages and server_data['lang'] not in servers_languages:
                continue

            server_settings = servers_settings.get(get_server_main_id_by_id(server_data['id']))
            if server_settings is not None and (not server_settings['enabled'] or server_settings['langs'].get(server_data['lang']) is False):
                continue

            row = Gtk.ListBoxRow()
            row.get_style_context().add_class('add-dialog-server-listboxrow')
            row.server_data = server_data
            box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
            row.add(box)

            # Server logo
            pixbuf = Pixbuf.new_from_resource_at_scale(get_server_logo_resource_path_by_id(server_data['id']), 24, 24, True)
            logo = Gtk.Image()
            logo.set_from_pixbuf(pixbuf)
            box.pack_start(logo, False, True, 0)

            # Server title
            label = Gtk.Label(xalign=0)
            label.set_text(server_data['name'])
            box.pack_start(label, True, True, 0)

            # Server language
            label = Gtk.Label()
            label.set_text(LANGUAGES[server_data['lang']])
            label.get_style_context().add_class('add-dialog-server-language-label')
            box.pack_start(label, False, True, 0)

            listbox.add(row)

        listbox.show_all()

        # Search page
        self.custom_title_search_page_searchentry = self.builder.get_object('custom_title_search_page_searchentry')
        self.custom_title_search_page_searchentry.connect('activate', self.search)

        self.search_page_listbox = self.builder.get_object('search_page_listbox')
        self.search_page_listbox.get_style_context().add_class('list-bordered')
        self.search_page_listbox.connect('row-activated', self.on_manga_clicked)

        # Manga page
        grid = self.builder.get_object('manga_page_grid')
        grid.set_margin_top(6)
        grid.set_margin_end(6)
        grid.set_margin_bottom(6)
        grid.set_margin_start(6)
        self.custom_title_manga_page_label = self.builder.get_object('custom_title_manga_page_label')
        self.add_button = self.builder.get_object('add_button')
        self.add_button.connect('clicked', self.on_add_button_clicked)
        self.read_button = self.builder.get_object('read_button')
        self.read_button.connect('clicked', self.on_read_button_clicked)

        self.show_page('servers')

    def clear_search(self):
        self.custom_title_search_page_searchentry.set_text('')
        self.clear_results()

    def clear_results(self):
        for child in self.search_page_listbox.get_children():
            self.search_page_listbox.remove(child)

    def hide_notification(self):
        self.builder.get_object('notification_revealer').set_reveal_child(False)

    def on_add_button_clicked(self, button):
        def run():
            manga = Manga.new(self.manga_data, self.server)
            GLib.idle_add(complete, manga)

        def complete(manga):
            self.manga = manga

            self.show_notification(_('{0} manga added').format(self.manga.name))

            self.window.library.on_manga_added(self.manga)

            self.add_button.set_sensitive(True)
            self.add_button.hide()
            self.read_button.show()
            self.activity_indicator.stop()

            return False

        self.activity_indicator.start()
        self.add_button.set_sensitive(False)

        thread = threading.Thread(target=run)
        thread.daemon = True
        thread.start()

    def on_back_button_clicked(self, button):
        if self.page == 'servers':
            self.dialog.close()

        elif self.page == 'search':
            self.activity_indicator.stop()
            self.search_lock = False
            self.server = None
            self.show_page('servers')

        elif self.page == 'manga':
            self.activity_indicator.stop()
            self.manga_slug = None
            self.show_page('search')

    def on_manga_clicked(self, listbox, row):
        if row.manga_data is None:
            return

        self.show_manga(row.manga_data)

    def on_read_button_clicked(self, button):
        self.window.card.init(self.manga, transition=False)
        self.dialog.close()

    def on_server_clicked(self, listbox, row):
        self.server = getattr(row.server_data['module'], row.server_data['class_name'])()
        self.show_page('search')

    def open(self, action, param):
        self.dialog.set_modal(True)
        self.dialog.set_transient_for(self.window)
        self.dialog.present()

    def search(self, entry=None):
        if self.search_lock:
            return

        term = self.custom_title_search_page_searchentry.get_text().strip()

        # Find manga by Id
        if term.startswith('id:'):
            slug = term[3:]

            if not slug:
                return

            self.show_manga(dict(slug=slug))
            return

        if not term and getattr(self.server, 'get_most_populars', None) is None:
            # An empty term is allowed only if server has 'get_most_populars' method
            return

        def run(server):
            most_populars = not term

            try:
                if most_populars:
                    # We offer most popular mangas as starting search results
                    result = server.get_most_populars()
                else:
                    result = server.search(term)

                if result:
                    GLib.idle_add(complete, result, server, most_populars)
                else:
                    GLib.idle_add(error, result, server)
            except Exception as e:
                user_error_message = log_error_traceback(e)
                GLib.idle_add(error, None, server, user_error_message)

        def complete(result, server, most_populars):
            if server != self.server:
                return False

            self.activity_indicator.stop()

            if most_populars:
                row = Gtk.ListBoxRow()
                row.get_style_context().add_class('add-dialog-search-section-listboxrow')
                row.manga_data = None
                box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
                row.add(box)
                label = Gtk.Label(xalign=0, margin=6)
                label.set_text(_('MOST POPULARS'))
                box.pack_start(label, True, True, 0)

                self.search_page_listbox.add(row)

            for item in result:
                row = Gtk.ListBoxRow()
                row.manga_data = item
                box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6)
                row.add(box)
                label = Gtk.Label(xalign=0, margin=6)
                label.set_ellipsize(Pango.EllipsizeMode.END)
                label.set_text(item['name'])
                box.pack_start(label, True, True, 0)

                self.search_page_listbox.add(row)

            self.search_page_listbox.show_all()

            self.search_lock = False

            return False

        def error(result, server, message=None):
            if server != self.server:
                return

            self.activity_indicator.stop()
            self.search_lock = False

            if message:
                self.show_notification(message)
            elif result is None:
                self.show_notification(_('Oops, search failed. Please try again.'), 2)
            elif len(result) == 0:
                self.show_notification(_('No results'))

        self.search_lock = True
        self.clear_results()
        self.activity_indicator.start()

        thread = threading.Thread(target=run, args=(self.server, ))
        thread.daemon = True
        thread.start()

    def show_manga(self, manga_data):
        def run(server, manga_slug):
            try:
                current_manga_data = server.get_manga_data(manga_data)

                if current_manga_data is not None:
                    GLib.idle_add(complete, current_manga_data, server)
                else:
                    GLib.idle_add(error, server, manga_slug)
            except Exception as e:
                user_error_message = log_error_traceback(e)
                GLib.idle_add(error, server, manga_slug, user_error_message)

        def complete(manga_data, server):
            if server != self.server or manga_data['slug'] != self.manga_slug:
                return False

            self.manga_data = manga_data

            # Populate manga card
            try:
                cover_data = self.server.get_manga_cover_image(self.manga_data.get('cover'))
            except Exception as e:
                cover_data = None
                user_error_message = log_error_traceback(e)
                if user_error_message:
                    self.show_notification(user_error_message)

            if cover_data is None:
                pixbuf = Pixbuf.new_from_resource_at_scale('/info/febvre/Komikku/images/missing_file.png', 174, -1, True)
            else:
                cover_stream = Gio.MemoryInputStream.new_from_data(cover_data, None)
                if get_buffer_mime_type(cover_data) != 'image/gif':
                    pixbuf = Pixbuf.new_from_stream_at_scale(cover_stream, 174, -1, True, None)
                else:
                    pixbuf = scale_pixbuf_animation(PixbufAnimation.new_from_stream(cover_stream), 174, -1, True, True)

            if isinstance(pixbuf, PixbufAnimation):
                self.builder.get_object('cover_image').set_from_animation(pixbuf)
            else:
                self.builder.get_object('cover_image').set_from_pixbuf(pixbuf)

            authors = html_escape(', '.join(self.manga_data['authors'])) if self.manga_data['authors'] else '-'
            self.builder.get_object('authors_value_label').set_markup('<span size="small">{0}</span>'.format(authors))

            genres = html_escape(', '.join(self.manga_data['genres'])) if self.manga_data['genres'] else '-'
            self.builder.get_object('genres_value_label').set_markup('<span size="small">{0}</span>'.format(genres))

            status = _(Manga.STATUSES[self.manga_data['status']]) if self.manga_data['status'] else '-'
            self.builder.get_object('status_value_label').set_markup('<span size="small">{0}</span>'.format(status))

            scanlators = html_escape(', '.join(self.manga_data['scanlators'])) if self.manga_data['scanlators'] else '-'
            self.builder.get_object('scanlators_value_label').set_markup('<span size="small">{0}</span>'.format(scanlators))

            self.builder.get_object('server_value_label').set_markup(
                '<span size="small"><a href="{0}">{1} [{2}]</a>\n{3} chapters</span>'.format(
                    self.server.get_manga_url(self.manga_data['slug'], self.manga_data.get('url')),
                    html_escape(self.server.name),
                    self.server.lang.upper(),
                    len(self.manga_data['chapters'])
                )
            )

            self.builder.get_object('synopsis_value_label').set_text(self.manga_data['synopsis'] or '-')

            self.activity_indicator.stop()
            self.show_page('manga')

            return False

        def error(server, manga_slug, message=None):
            if server != self.server or manga_slug != self.manga_slug:
                return False

            self.activity_indicator.stop()

            self.show_notification(message or _("Oops, failed to retrieve manga's information."), 2)

            return False

        self.manga_slug = manga_data['slug']
        self.activity_indicator.start()

        thread = threading.Thread(target=run, args=(self.server, self.manga_slug, ))
        thread.daemon = True
        thread.start()

    def show_notification(self, message, interval=5):
        if not message:
            return

        self.builder.get_object('notification_label').set_text(message)
        self.builder.get_object('notification_revealer').set_reveal_child(True)

        revealer_timer = threading.Timer(interval, GLib.idle_add, args=[self.hide_notification])
        revealer_timer.start()

    def show_page(self, name):
        if name == 'search':
            if self.page == 'servers':
                self.custom_title_search_page_searchentry.set_placeholder_text(_('Search in {0}…').format(self.server.name))
                self.clear_search()
                self.search()
            else:
                self.custom_title_search_page_searchentry.grab_focus_without_selecting()
        elif name == 'manga':
            self.custom_title_manga_page_label.set_text(self.manga_data['name'])

            # Check if selected manga is already in library
            db_conn = create_db_connection()
            row = db_conn.execute(
                'SELECT * FROM mangas WHERE slug = ? AND server_id = ?',
                (self.manga_data['slug'], self.manga_data['server_id'])
            ).fetchone()
            db_conn.close()

            if row:
                self.manga = Manga.get(row['id'], self.server)

                self.read_button.show()
                self.add_button.hide()
            else:
                self.add_button.show()
                self.read_button.hide()

        self.custom_title_stack.set_visible_child_name(name)
        self.stack.set_visible_child_name(name)
        self.page = name
Esempio n. 5
0
class MainWindow(Gtk.ApplicationWindow):
    mobile_width = False
    page = None

    is_maximized = False
    is_fullscreen = False
    _prev_size = None

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.application = kwargs['application']

        self._night_light_handler_id = 0
        self._night_light_proxy = None

        self.builder = Gtk.Builder()
        self.builder.add_from_resource(
            '/info/febvre/Komikku/ui/main_window.ui')
        self.builder.add_from_resource('/info/febvre/Komikku/ui/menu/main.xml')

        self.logging_manager = self.application.get_logger()
        self.downloader = Downloader(self)
        self.updater = Updater(self, Settings.get_default().update_at_startup)

        self.overlay = self.builder.get_object('overlay')
        self.stack = self.builder.get_object('stack')
        self.title_stack = self.builder.get_object('title_stack')
        self.left_button_image = self.builder.get_object('left_button_image')
        self.menu_button = self.builder.get_object('menu_button')
        self.menu_button_image = self.builder.get_object('menu_button_image')

        self.activity_indicator = ActivityIndicator()
        self.overlay.add_overlay(self.activity_indicator)
        self.overlay.set_overlay_pass_through(self.activity_indicator, True)
        self.activity_indicator.show_all()

        self.assemble_window()

    def add_accelerators(self):
        self.application.set_accels_for_action('app.settings', ['<Control>p'])
        self.application.set_accels_for_action('app.add', ['<Control>plus'])
        self.application.set_accels_for_action('app.fullscreen', ['F11'])

    def add_actions(self):
        add_action = Gio.SimpleAction.new('add', None)
        add_action.connect('activate', self.on_left_button_clicked)

        settings_action = Gio.SimpleAction.new('settings', None)
        settings_action.connect('activate', self.on_settings_menu_clicked)

        about_action = Gio.SimpleAction.new('about', None)
        about_action.connect('activate', self.on_about_menu_clicked)

        shortcuts_action = Gio.SimpleAction.new('shortcuts', None)
        shortcuts_action.connect('activate', self.on_shortcuts_menu_clicked)

        fullscreen_action = Gio.SimpleAction.new('fullscreen', None)
        fullscreen_action.connect('activate', self.toggle_fullscreen)

        self.application.add_action(add_action)
        self.application.add_action(settings_action)
        self.application.add_action(about_action)
        self.application.add_action(shortcuts_action)
        self.application.add_action(fullscreen_action)

        self.library.add_actions()
        self.card.add_actions()
        self.reader.add_actions()

    def assemble_window(self):
        # Default size
        window_size = Settings.get_default().window_size
        self.set_default_size(window_size[0], window_size[1])

        # Min size
        geom = Gdk.Geometry()
        geom.min_width = 360
        geom.min_height = 288
        self.set_geometry_hints(None, geom, Gdk.WindowHints.MIN_SIZE)

        # Titlebar
        self.titlebar = self.builder.get_object('titlebar')
        self.headerbar = self.builder.get_object('headerbar')

        self.left_button = self.builder.get_object('left_button')
        self.left_button.connect('clicked', self.on_left_button_clicked, None)

        self.builder.get_object('fullscreen_button').connect(
            'clicked', self.toggle_fullscreen, None)

        self.set_titlebar(self.titlebar)

        # Fisrt start grid
        self.first_start_grid = self.builder.get_object('first_start_grid')
        pix = Pixbuf.new_from_resource_at_scale(
            '/info/febvre/Komikku/images/logo.png', 256, 256, True)
        self.builder.get_object('app_logo').set_from_pixbuf(pix)

        # Init pages
        self.library = Library(self)
        self.card = Card(self)
        self.reader = Reader(self)

        # Window
        self.connect('check-resize', self.on_resize)
        self.connect('delete-event', self.on_application_quit)
        self.connect('key-press-event', self.on_key_press_event)
        self.connect('window-state-event', self.on_window_state_event)

        # Custom CSS
        screen = Gdk.Screen.get_default()

        css_provider = Gtk.CssProvider()
        css_provider_resource = Gio.File.new_for_uri(
            'resource:///info/febvre/Komikku/css/style.css')
        css_provider.load_from_file(css_provider_resource)

        context = Gtk.StyleContext()
        context.add_provider_for_screen(screen, css_provider,
                                        Gtk.STYLE_PROVIDER_PRIORITY_USER)
        if Gio.Application.get_default().development_mode is True:
            self.get_style_context().add_class('devel')

        # Theme (light or dark)
        self.init_theme()

        self.library.show()

    def change_layout(self):
        pass

    def confirm(self, title, message, callback):
        def on_response(dialog, response_id):
            if response_id == Gtk.ResponseType.YES:
                callback()

            dialog.destroy()

        dialog = Handy.Dialog.new(self)
        dialog.get_style_context().add_class('solid-csd')
        dialog.connect('response', on_response)
        dialog.set_title(title)
        dialog.add_buttons('Yes', Gtk.ResponseType.YES, 'Cancel',
                           Gtk.ResponseType.CANCEL)
        dialog.set_default_response(Gtk.ResponseType.YES)

        label = Gtk.Label()
        label.set_text(message)
        label.set_line_wrap(True)
        label.set_vexpand(True)
        label.set_property('margin', 16)
        label.set_valign(Gtk.Align.CENTER)
        label.set_halign(Gtk.Align.CENTER)
        label.set_justify(Gtk.Justification.CENTER)
        dialog.get_content_area().add(label)

        dialog.show_all()

    def hide_notification(self):
        self.builder.get_object('notification_revealer').set_reveal_child(
            False)

    def init_theme(self):
        if Settings.get_default().night_light and not self._night_light_proxy:
            # Watch night light changes
            self._night_light_proxy = Gio.DBusProxy.new_sync(
                Gio.bus_get_sync(Gio.BusType.SESSION,
                                 None), Gio.DBusProxyFlags.NONE, None,
                'org.gnome.SettingsDaemon.Color',
                '/org/gnome/SettingsDaemon/Color',
                'org.gnome.SettingsDaemon.Color', None)

            def property_changed(proxy, changed_properties,
                                 invalidated_properties):
                properties = changed_properties.unpack()
                if 'NightLightActive' in properties.keys():
                    Gtk.Settings.get_default().set_property(
                        'gtk-application-prefer-dark-theme',
                        properties['NightLightActive'])

            self._night_light_handler_id = self._night_light_proxy.connect(
                'g-properties-changed', property_changed)

            Gtk.Settings.get_default().set_property(
                'gtk-application-prefer-dark-theme',
                self._night_light_proxy.get_cached_property(
                    'NightLightActive'))
        else:
            if self._night_light_proxy and self._night_light_handler_id > 0:
                self._night_light_proxy.disconnect(
                    self._night_light_handler_id)
                self._night_light_proxy = None
                self._night_light_handler_id = 0

            Gtk.Settings.get_default().set_property(
                'gtk-application-prefer-dark-theme',
                Settings.get_default().dark_theme)

    def on_about_menu_clicked(self, action, param):
        from komikku.application import CREDITS

        builder = Gtk.Builder()
        builder.add_from_resource('/info/febvre/Komikku/about_dialog.ui')

        about_dialog = builder.get_object('about_dialog')
        about_dialog.set_authors([
            *CREDITS['developers'],
            '',
            _('Contributors: Code, Patches, Debugging:'),
            '',
            *CREDITS['contributors'],
            '',
        ])
        about_dialog.set_translator_credits('\n'.join(CREDITS['translators']))
        about_dialog.set_modal(True)
        about_dialog.set_transient_for(self)
        if about_dialog.run() in (Gtk.ResponseType.CANCEL,
                                  Gtk.ResponseType.DELETE_EVENT):
            about_dialog.hide()

    def on_application_quit(self, window, event):
        def before_quit():
            self.save_window_size()
            backup_db()

        if self.downloader.running or self.updater.running:

            def confirm_callback():
                self.downloader.stop()
                self.updater.stop()

                while self.downloader.running or self.updater.running:
                    time.sleep(0.1)
                    continue

                before_quit()
                self.application.quit()

            message = [
                _('Are you sure you want to quit?'),
            ]
            if self.downloader.running:
                message.append(
                    _('Some chapters are currently being downloaded.'))
            if self.updater.running:
                message.append(_('Some mangas are currently being updated.'))

            self.confirm(_('Quit?'), '\n'.join(message), confirm_callback)

            return True

        before_quit()
        return False

    def on_key_press_event(self, widget, event):
        """
        Go back navigation with <Escape> key:
        - Library <- Manga <- Reader
        - Exit selection mode (Library and Manga chapters)
        """

        if event.keyval != Gdk.KEY_Escape:
            # Propagate the event further
            return False

        if self.page == 'library' and not self.library.selection_mode:
            return True

        self.on_left_button_clicked(None, None)

        return True

    def on_left_button_clicked(self, action, param):
        if self.page == 'library':
            if self.library.selection_mode:
                self.library.leave_selection_mode()
            else:
                AddDialog(self).open(action, param)
        elif self.page == 'card':
            if self.card.selection_mode:
                self.card.leave_selection_mode()
            else:
                self.library.show(invalidate_sort=True)
        elif self.page == 'reader':
            self.set_unfullscreen()

            # Refresh to update all previously chapters consulted (last page read may have changed)
            # and update info like disk usage
            self.card.refresh(self.reader.chapters_consulted)
            self.card.show()

    def on_resize(self, window):
        size = self.get_size()
        if self._prev_size and self._prev_size.width == size.width and self._prev_size.height == size.height:
            return

        self._prev_size = size

        self.library.on_resize()
        if self.page == 'reader':
            self.reader.on_resize()

        if size.width < 700:
            if self.mobile_width is True:
                return

            self.mobile_width = True
            self.change_layout()
        else:
            if self.mobile_width is True:
                self.mobile_width = False
                self.change_layout()

    def on_settings_menu_clicked(self, action, param):
        SettingsDialog(self).open(action, param)

    def on_shortcuts_menu_clicked(self, action, param):
        builder = Gtk.Builder()
        builder.add_from_resource(
            '/info/febvre/Komikku/ui/shortcuts_overview.ui')

        shortcuts_overview = builder.get_object('shortcuts_overview')
        shortcuts_overview.set_modal(True)
        shortcuts_overview.set_transient_for(self)
        shortcuts_overview.present()

    def on_window_state_event(self, widget, event):
        self.is_maximized = (event.new_window_state
                             & Gdk.WindowState.MAXIMIZED) != 0
        self.is_fullscreen = (event.new_window_state
                              & Gdk.WindowState.FULLSCREEN) != 0

    def save_window_size(self):
        if not self.is_maximized and not self.is_fullscreen:
            size = self.get_size()
            Settings.get_default().window_size = [size.width, size.height]

    def set_fullscreen(self):
        if not self.is_fullscreen:
            self.reader.controls.on_fullscreen()
            self.fullscreen()

    def set_unfullscreen(self):
        if self.is_fullscreen:
            self.reader.controls.on_unfullscreen()
            self.unfullscreen()

    def show_notification(self, message, interval=5):
        self.builder.get_object('notification_label').set_text(message)
        self.builder.get_object('notification_revealer').set_reveal_child(True)

        revealer_timer = Timer(interval,
                               GLib.idle_add,
                               args=[self.hide_notification])
        revealer_timer.start()

    def show_page(self, name, transition=True):
        if not transition:
            # Save defined transition type
            transition_type = self.stack.get_transition_type()
            # Set transition type to NONE
            self.stack.set_transition_type(Gtk.StackTransitionType.NONE)
            self.title_stack.set_transition_type(Gtk.StackTransitionType.NONE)

        self.stack.set_visible_child_name(name)
        self.title_stack.set_visible_child_name(name)

        if not transition:
            # Restore transition type
            self.stack.set_transition_type(transition_type)
            self.title_stack.set_transition_type(transition_type)

        self.page = name

    def toggle_fullscreen(self, *args):
        if self.is_fullscreen:
            self.set_unfullscreen()
        else:
            self.set_fullscreen()
Esempio n. 6
0
class Page(Gtk.Overlay):
    __gsignals__ = {
        'rendered': (GObject.SIGNAL_RUN_FIRST, None, ()),
    }

    def __init__(self, pager, chapter, index):
        Gtk.Overlay.__init__(self)

        self.pager = pager
        self.reader = pager.reader
        self.window = pager.window

        self.chapter = chapter
        self.index = index
        self.path = None

        self.status = None  # rendering, rendered, offlimit, cleaned
        self.error = None  # connection error, server error or corrupt file error
        self.loadable = False  # loadable from disk or downloadable from server (chapter pages are known)

        self.set_size()

        self.scrolledwindow = Gtk.ScrolledWindow()
        if self.reader.reading_direction == 'vertical':
            self.scrolledwindow.get_vadjustment().connect(
                'changed', self.adjust_scroll)
        else:
            self.scrolledwindow.get_hadjustment().connect(
                'changed', self.adjust_scroll)

        self.add(self.scrolledwindow)

        viewport = Gtk.Viewport()
        self.image = Gtk.Image()
        self.imagebuf = None
        viewport.add(self.image)
        self.scrolledwindow.add(viewport)

        # Activity indicator
        self.activity_indicator = ActivityIndicator()
        self.add_overlay(self.activity_indicator)
        self.set_overlay_pass_through(self.activity_indicator,
                                      True)  # Allows scrolling in zoom mode

        self.show_all()

    @property
    def animated(self):
        return self.imagebuf.animated

    @property
    def loaded(self):
        return self.pixbuf is not None

    def adjust_scroll(self, hadj):
        """ Update page horizontal scrollbar value according to reading direction """
        if self.reader.pager.zoom['active']:
            return

        hadj.set_value(hadj.get_upper() if self.reader.reading_direction ==
                       'right-to-left' else 0)

    def clean(self):
        self.status = 'cleaned'
        self.loadable = False
        self.imagebuf = None
        self.image.clear()

    def on_button_retry_clicked(self, button):
        button.destroy()
        self.render()

    def render(self):
        def run():
            # First, we ensure that chapter's list of pages is known
            if self.chapter.pages is None:
                try:
                    if not self.chapter.update_full():
                        on_error('server')
                        GLib.idle_add(complete)
                        return
                except Exception as e:
                    user_error_message = log_error_traceback(e)
                    on_error('connection', user_error_message)
                    GLib.idle_add(complete)
                    return

            # If page's index is out of pages numbers, page belongs to previous or next chapter.
            if self.index < 0 or self.index > len(self.chapter.pages) - 1:
                if self.index < 0:
                    # Page is the last page of previous chapter
                    self.chapter = self.reader.manga.get_next_chapter(
                        self.chapter, -1)
                elif self.index > len(self.chapter.pages) - 1:
                    # Page is the first page of next chapter
                    self.chapter = self.reader.manga.get_next_chapter(
                        self.chapter, 1)

                if self.chapter is not None:
                    # Chapter has changed
                    # Again, we ensure that chapter's list of pages is known
                    if self.chapter.pages is None:
                        try:
                            if not self.chapter.update_full():
                                on_error('server')
                                GLib.idle_add(complete)
                                return
                        except Exception as e:
                            user_error_message = log_error_traceback(e)
                            on_error('connection', user_error_message)
                            GLib.idle_add(complete)
                            return

                    if self.index < 0:
                        # Page is the last page of chapter
                        self.index = len(self.chapter.pages) - 1
                    else:
                        # Page is the first page of chapter
                        self.index = 0

                    self.loadable = True
                else:
                    # Page does not exist, it's out of limit
                    # ie before first page of first chapter or after last page of last chapter
                    self.status = 'offlimit'
                    GLib.idle_add(complete)
                    return
            else:
                self.loadable = True

            page_path = self.chapter.get_page_path(self.index)
            if page_path is None:
                try:
                    page_path = self.chapter.get_page(self.index)
                    if page_path:
                        self.path = page_path
                    else:
                        on_error('server')
                except Exception as e:
                    user_error_message = log_error_traceback(e)
                    on_error('connection', user_error_message)
            else:
                self.path = page_path

            GLib.idle_add(complete)

        def complete():
            if self.status == 'cleaned' or self.get_parent() is None:
                # Page has been removed from pager
                # rare case that occurs during a quick navigation
                return False

            if self.status != 'offlimit':
                self.set_image()
                self.status = 'rendered'

            self.activity_indicator.stop()

            self.emit('rendered')

            return False

        def on_error(kind, message=None):
            assert kind in (
                'connection',
                'server',
            ), 'Invalid error kind'

            if message is not None:
                self.window.show_notification(message, 2)

            self.error = kind

            self.show_retry_button()

        if self.status is not None and self.error is None:
            return

        self.imagebuf = None
        self.status = 'rendering'

        self.activity_indicator.start()

        thread = threading.Thread(target=run)
        thread.daemon = True
        thread.start()

    def rescale(self):
        if self.status == 'rendered':
            self.set_image()

    def resize(self):
        self.set_size()

        if self.status == 'rendered':
            self.set_image()

    def set_image(self):
        if self.imagebuf is None:
            if self.path is None:
                self.imagebuf = Imagebuf.new_from_resource(
                    '/info/febvre/Komikku/images/missing_file.png')
            else:
                self.imagebuf = Imagebuf.new_from_file(self.path)
                if self.imagebuf is None:
                    GLib.unlink(self.path)

                    self.show_retry_button()
                    self.window.show_notification(_('Failed to load image'), 2)

                    self.error = 'corrupt_file'
                    self.imagebuf = Imagebuf.new_from_resource(
                        '/info/febvre/Komikku/images/missing_file.png')

        # Crop image borders
        imagebuf = self.imagebuf.crop_borders(
        ) if self.reader.manga.borders_crop == 1 else self.imagebuf

        # Adjust image
        if self.reader.scaling != 'original':
            adapt_to_width_height = imagebuf.height / (imagebuf.width /
                                                       self.reader.size.width)
            adapt_to_height_width = imagebuf.width / (imagebuf.height /
                                                      self.reader.size.height)

            if self.reader.scaling == 'width' or (
                    self.reader.scaling == 'screen'
                    and adapt_to_width_height <= self.reader.size.height):
                # Adapt image to width
                pixbuf = imagebuf.get_scaled_pixbuf(self.reader.size.width,
                                                    adapt_to_width_height,
                                                    False)
            elif self.reader.scaling == 'height' or (
                    self.reader.scaling == 'screen'
                    and adapt_to_height_width <= self.reader.size.width):
                # Adapt image to height
                pixbuf = imagebuf.get_scaled_pixbuf(adapt_to_height_width,
                                                    self.reader.size.height,
                                                    False)
        else:
            pixbuf = imagebuf.get_pixbuf()

        if isinstance(pixbuf, PixbufAnimation):
            self.image.set_from_animation(pixbuf)
        else:
            self.image.set_from_pixbuf(pixbuf)

    def set_size(self):
        self.set_size_request(self.reader.size.width, self.reader.size.height)

    def show_retry_button(self):
        button = Gtk.Button.new()
        button.set_image(
            Gtk.Image.new_from_icon_name('view-refresh-symbolic',
                                         Gtk.IconSize.LARGE_TOOLBAR))
        button.set_image_position(Gtk.PositionType.TOP)
        button.set_always_show_image(True)
        button.set_label(_('Retry'))
        button.set_valign(Gtk.Align.CENTER)
        button.set_halign(Gtk.Align.CENTER)
        button.connect('clicked', self.on_button_retry_clicked)

        self.add_overlay(button)
        button.show()