def on_server_activated(self, widget, gparam, server_main_id): if isinstance(widget, Handy.ExpanderRow): Settings.get_default().toggle_server(server_main_id, widget.get_enable_expansion()) else: Settings.get_default().toggle_server(server_main_id, widget.get_active())
def on_background_color_changed(row, param): index = row.get_selected_index() if index == 0: Settings.get_default().background_color = 'white' elif index == 1: Settings.get_default().background_color = 'black'
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_reading_direction_changed(row, param): index = row.get_selected_index() if index == 0: Settings.get_default().reading_direction = 'right-to-left' elif index == 1: Settings.get_default().reading_direction = 'left-to-right' elif index == 2: Settings.get_default().reading_direction = 'vertical'
def on_scaling_changed(row, param): index = row.get_selected_index() if index == 0: Settings.get_default().scaling = 'screen' elif index == 1: Settings.get_default().scaling = 'width' elif index == 2: Settings.get_default().scaling = 'height' elif index == 3: Settings.get_default().scaling = 'original'
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 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 complete(manga, recent_chapters_ids, nb_deleted_chapters): nb_recent_chapters = len(recent_chapters_ids) if nb_recent_chapters > 0: self.window.show_notification( n_('{0}\n{1} new chapter has been found', '{0}\n{1} new chapters have been found', nb_recent_chapters).format( manga.name, nb_recent_chapters ) ) # Auto download new chapters if Settings.get_default().new_chapters_auto_download: for chapter_id in recent_chapters_ids: self.window.downloader.add(chapter_id) self.window.downloader.start() self.emit('manga-updated', manga, nb_recent_chapters, nb_deleted_chapters) return False
def show(self): def on_menu_popover_closed(menu_button): self.pager.grab_focus() self.builder.get_object('fullscreen_button').show() self.window.menu_button.set_menu_model( self.builder.get_object('menu-reader')) self.window.menu_button_image.set_from_icon_name( 'view-more-symbolic', Gtk.IconSize.MENU) # Watch when menu is closed to be able to restore focus to pager self.window.menu_button.get_popover().connect('closed', on_menu_popover_closed) self.page_number_label.hide() self.controls.hide() if Settings.get_default().fullscreen: self.window.set_fullscreen() self.window.show_page('reader')
def on_desktop_notifications_changed(switch_button, gparam): if switch_button.get_active(): Settings.get_default().desktop_notifications = True else: Settings.get_default().desktop_notifications = False
def on_borders_crop_changed(switch_button, gparam): Settings.get_default().borders_crop = switch_button.get_active()
def on_update_at_startup_changed(switch_button, gparam): if switch_button.get_active(): Settings.get_default().update_at_startup = True else: Settings.get_default().update_at_startup = False
def on_servers_language_activated(switch_button, gparam, code): if switch_button.get_active(): Settings.get_default().add_servers_language(code) else: Settings.get_default().remove_servers_language(code)
def on_new_chapters_auto_download_changed(switch_button, gparam): if switch_button.get_active(): Settings.get_default().new_chapters_auto_download = True else: Settings.get_default().new_chapters_auto_download = False
def background_color(self): return self.manga.background_color or Settings.get_default( ).background_color
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 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 on_fullscreen_changed(switch_button, gparam): Settings.get_default().fullscreen = switch_button.get_active()
def on_long_strip_detection_changed(switch_button, gparam): Settings.get_default().long_strip_detection = switch_button.get_active()
def borders_crop(self): if self.manga.borders_crop in (0, 1): return bool(self.manga.borders_crop) return Settings.get_default().borders_crop
def on_night_light_changed(self, switch_button, gparam): Settings.get_default().night_light = switch_button.get_active() self.parent.init_theme()
def reading_direction(self): return self.manga.reading_direction or Settings.get_default( ).reading_direction
def scaling(self): return self.manga.scaling or Settings.get_default().scaling
def on_server_language_activated(switch_button, gparam, server_main_id, lang): Settings.get_default().toggle_server_lang(server_main_id, lang, switch_button.get_active())
def on_theme_changed(self, switch_button, gparam): Settings.get_default().dark_theme = switch_button.get_active() self.parent.init_theme()
def __init__(self, window): GObject.GObject.__init__(self) self.window = window if Settings.get_default().downloader_state: self.start()
def open(self): def on_get_password(attributes, password, name, login_entry, password_entry): if not attributes or not password: return login_entry.set_text(attributes['login']) password_entry.set_text(password) self.set_title(_('Servers Settings')) self.set_transient_for(self.parent) settings = Settings.get_default().servers_settings languages = Settings.get_default().servers_languages servers_data = {} for server_data in get_servers_list(order_by=('name', 'lang')): main_id = get_server_main_id_by_id(server_data['id']) if main_id not in servers_data: servers_data[main_id] = dict( main_id=main_id, name=server_data['name'], module=server_data['module'], langs=[], ) if not languages or server_data['lang'] in languages: servers_data[main_id]['langs'].append(server_data['lang']) for server_main_id, server_data in servers_data.items(): if not server_data['langs']: continue server_class = getattr(server_data['module'], server_data['main_id'].capitalize()) has_login = getattr(server_class, 'has_login') server_settings = settings.get(server_main_id) server_enabled = server_settings is None or server_settings['enabled'] is True if len(server_data['langs']) > 1 or has_login: vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) vbox.set_border_width(12) expander_row = Handy.ExpanderRow() expander_row.set_title(server_data['name']) expander_row.set_enable_expansion(server_enabled) expander_row.connect('notify::enable-expansion', self.on_server_activated, server_main_id) expander_row.add(vbox) self.group.add(expander_row) if len(server_data['langs']) > 1: for lang in server_data['langs']: lang_enabled = server_settings is None or server_settings['langs'].get(lang, True) hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) label = Gtk.Label(LANGUAGES[lang], xalign=0) label.get_style_context().add_class('dim-label') hbox.pack_start(label, True, True, 0) switch = Gtk.Switch.new() switch.set_active(lang_enabled) switch.connect('notify::active', self.on_server_language_activated, server_main_id, lang) hbox.pack_start(switch, False, False, 0) vbox.add(hbox) if has_login: frame = Gtk.Frame() vbox.add(frame) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) box.set_margin_top(6) box.set_margin_right(6) box.set_margin_bottom(6) box.set_margin_left(6) frame.add(box) label = Gtk.Label(_('User Account')) label.set_valign(Gtk.Align.CENTER) box.pack_start(label, True, True, 0) login_entry = Gtk.Entry() login_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY, 'avatar-default-symbolic') box.pack_start(login_entry, True, True, 0) password_entry = Gtk.Entry() password_entry.set_input_purpose(Gtk.InputPurpose.PASSWORD) password_entry.set_visibility(False) password_entry.set_icon_from_icon_name(Gtk.EntryIconPosition.PRIMARY, 'dialog-password-symbolic') box.pack_start(password_entry, True, True, 0) btn = Gtk.Button(_('Test')) btn.connect('clicked', self.test_account_login, server_main_id, server_class, login_entry, password_entry) btn.set_always_show_image(True) box.pack_start(btn, False, False, 0) helper = SecretAccountHelper() helper.get(server_main_id, on_get_password, login_entry, password_entry) else: action_row = Handy.ActionRow() action_row.set_title(server_data['name']) switch = Gtk.Switch.new() switch.set_active(server_enabled) switch.set_valign(Gtk.Align.CENTER) switch.connect('notify::active', self.on_server_activated, server_main_id) action_row.add(switch) self.group.add(action_row) self.group.show_all() self.present()
def start(self): def run(exclude_errors=False): db_conn = create_db_connection() if exclude_errors: rows = db_conn.execute( 'SELECT * FROM downloads WHERE status != "error" ORDER BY date ASC' ).fetchall() else: rows = db_conn.execute( 'SELECT * FROM downloads ORDER BY date ASC').fetchall() db_conn.close() interrupted = False for row in rows: if self.stop_flag: break download = Download.get(row['id']) if download is None: # Download has been removed in the meantime continue chapter = download.chapter download.update(dict(status='downloading')) GLib.idle_add(notify_download_started, download) try: if chapter.update_full() and len(chapter.pages) > 0: error_counter = 0 success_counter = 0 for index, _page in enumerate(chapter.pages): if self.stop_flag: interrupted = True break if chapter.get_page_path(index) is None: path = chapter.get_page(index) if path is not None: success_counter += 1 download.update( dict(percent=(index + 1) * 100 / len(chapter.pages))) else: error_counter += 1 download.update(dict(errors=error_counter)) GLib.idle_add(notify_download_progress, download, success_counter, error_counter) if index < len(chapter.pages ) - 1 and not self.stop_flag: time.sleep(DOWNLOAD_DELAY) else: success_counter += 1 if interrupted: download.update(dict(status='pending')) else: if error_counter == 0: # All pages were successfully downloaded chapter.update(dict(downloaded=1)) download.delete() GLib.idle_add(notify_download_success, chapter) else: # At least one page failed to be downloaded download.update(dict(status='error')) GLib.idle_add(notify_download_error, download) else: # Possible causes: # - Empty chapter # - Outdated chapter info # - Server has undergone changes (API, HTML) and plugin code is outdated download.update(dict(status='error')) GLib.idle_add(notify_download_error, download) except Exception as e: # Possible causes: # - No Internet connection # - Connexion timeout, read timeout # - Server down download.update(dict(status='error')) user_error_message = log_error_traceback(e) GLib.idle_add(notify_download_error, download, user_error_message) if not rows or self.stop_flag: self.running = False GLib.idle_add(self.emit, 'ended') else: # Continue, new downloads may have been added in the meantime run(exclude_errors=True) def notify_download_success(chapter): if notification is not None: notification.update( _('Download completed'), _('[{0}] Chapter {1}').format(chapter.manga.name, chapter.title)) notification.show() self.emit('download-changed', None, chapter) return False def notify_download_error(download, message=None): if message: self.window.show_notification(message) self.emit('download-changed', download, None) return False def notify_download_started(download): self.emit('download-changed', download, None) return False def notify_download_progress(download, success_counter, error_counter): if notification is not None: summary = _('{0}/{1} pages downloaded').format( success_counter, len(download.chapter.pages)) if error_counter > 0: summary = '{0} ({1})'.format(summary, _('error')) notification.update( summary, _('[{0}] Chapter {1}').format(download.chapter.manga.name, download.chapter.title)) notification.show() self.emit('download-changed', download, None) return False if self.running: return Settings.get_default().downloader_state = True self.running = True self.stop_flag = False if Settings.get_default().desktop_notifications: # Create notification notification = Notify.Notification.new('') notification.set_timeout(Notify.EXPIRES_DEFAULT) else: notification = None GLib.idle_add(self.emit, 'started') thread = threading.Thread(target=run) thread.daemon = True thread.start()
def set_config_values(self): settings = Settings.get_default() # # General # # Dark theme self.theme_switch.set_active(settings.dark_theme) self.theme_switch.connect('notify::active', self.on_theme_changed) # Night light self.night_light_switch.set_active(settings.night_light) self.night_light_switch.connect('notify::active', self.on_night_light_changed) # Desktop notifications self.desktop_notifications_switch.set_active(settings.desktop_notifications) self.desktop_notifications_switch.connect('notify::active', self.on_desktop_notifications_changed) # # Library # # Update manga at startup self.update_at_startup_switch.set_active(settings.update_at_startup) self.update_at_startup_switch.connect('notify::active', self.on_update_at_startup_changed) # Auto download new chapters self.new_chapters_auto_download_switch.set_active(settings.new_chapters_auto_download) self.new_chapters_auto_download_switch.connect('notify::active', self.on_new_chapters_auto_download_changed) # Servers languages servers_languages = settings.servers_languages vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) vbox.set_border_width(12) for code, language in LANGUAGES.items(): hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) label = Gtk.Label(language, xalign=0) hbox.pack_start(label, True, True, 0) switch = Gtk.Switch.new() switch.set_active(code in servers_languages) switch.connect('notify::active', self.on_servers_language_activated, code) hbox.pack_start(switch, False, False, 0) vbox.pack_start(hbox, True, True, 0) self.servers_languages_expander_row.add(vbox) vbox.show_all() # Servers settings self.servers_settings_button.connect('clicked', self.open_servers_settings) # Long strip detection self.long_strip_detection_switch.set_active(settings.long_strip_detection) self.long_strip_detection_switch.connect('notify::active', self.on_long_strip_detection_changed) # # Reader # # Reading direction liststore = Gio.ListStore.new(Handy.ValueObject) liststore.insert(0, Handy.ValueObject.new(_('Right to Left ←'))) liststore.insert(1, Handy.ValueObject.new(_('Left to Right →'))) liststore.insert(2, Handy.ValueObject.new(_('Vertical ↓'))) self.reading_direction_row.bind_name_model(liststore, Handy.ValueObject.dup_string) self.reading_direction_row.set_selected_index(settings.reading_direction_value) self.reading_direction_row.connect('notify::selected-index', self.on_reading_direction_changed) # Image scaling liststore = Gio.ListStore.new(Handy.ValueObject) liststore.insert(0, Handy.ValueObject.new(_('Adapt to Screen'))) liststore.insert(1, Handy.ValueObject.new(_('Adapt to Width'))) liststore.insert(2, Handy.ValueObject.new(_('Adapt to Height'))) liststore.insert(3, Handy.ValueObject.new(_('Original Size'))) self.scaling_row.bind_name_model(liststore, Handy.ValueObject.dup_string) self.scaling_row.set_selected_index(settings.scaling_value) self.scaling_row.connect('notify::selected-index', self.on_scaling_changed) # Background color liststore = Gio.ListStore.new(Handy.ValueObject) liststore.insert(0, Handy.ValueObject.new(_('White'))) liststore.insert(1, Handy.ValueObject.new(_('Black'))) self.background_color_row.bind_name_model(liststore, Handy.ValueObject.dup_string) self.background_color_row.set_selected_index(settings.background_color_value) self.background_color_row.connect('notify::selected-index', self.on_background_color_changed) # Borders crop self.borders_crop_switch.set_active(settings.borders_crop) self.borders_crop_switch.connect('notify::active', self.on_borders_crop_changed) # Full screen self.fullscreen_switch.set_active(settings.fullscreen) self.fullscreen_switch.connect('notify::active', self.on_fullscreen_changed)
def stop(self, save_state=False): if self.running: self.stop_flag = True if save_state: Settings.get_default().downloader_state = False