Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/GetIABooksActivity.py
diff options
context:
space:
mode:
Diffstat (limited to 'GetIABooksActivity.py')
-rw-r--r--GetIABooksActivity.py1256
1 files changed, 1256 insertions, 0 deletions
diff --git a/GetIABooksActivity.py b/GetIABooksActivity.py
new file mode 100644
index 0000000..a1488d0
--- /dev/null
+++ b/GetIABooksActivity.py
@@ -0,0 +1,1256 @@
+#! /usr/bin/env python
+
+# Copyright (C) 2009 James D. Simmons
+# Copyright (C) 2009 Sayamindu Dasgupta <sayamindu@laptop.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+import os
+import logging
+import time
+
+from gi.repository import Gtk
+from gi.repository import Gdk
+from gi.repository import GdkPixbuf
+from gi.repository import GObject
+from gi.repository import Pango
+
+from sugar3.graphics.toolbarbox import ToolbarBox
+from sugar3.activity.widgets import StopButton
+from sugar3.graphics import style
+from sugar3.graphics.toolbutton import ToolButton
+from sugar3.graphics.toggletoolbutton import ToggleToolButton
+from sugar3.graphics.toolcombobox import ToolComboBox
+from sugar3.graphics.combobox import ComboBox
+from sugar3.graphics import iconentry
+from sugar3 import profile
+from sugar3.activity import activity
+from sugar3.activity.widgets import ToolbarButton
+from sugar3.bundle.activitybundle import ActivityBundle
+from sugar3.datastore import datastore
+from sugar3.graphics.alert import NotifyAlert
+from sugar3.graphics.alert import Alert
+from sugar3.graphics.icon import Icon
+from gettext import gettext as _
+
+import dbus
+import ConfigParser
+import base64
+import urllib2
+import socket
+
+
+from listview import ListView
+import opds
+import languagenames
+import devicemanager
+
+_MIMETYPES = {'PDF': u'application/pdf', 'PDF BW': u'application/pdf-bw',
+ 'EPUB': u'application/epub+zip', 'DJVU': u'image/x.djvu', 'MP3': u'audio/mpeg', 'OGG': u'audio/ogg'}
+_SOURCES = {}
+_SOURCES_CONFIG = {}
+
+READ_STREAM_SERVICE = 'read-activity-http'
+
+# directory exists if powerd is running. create a file here,
+# named after our pid, to inhibit suspend.
+POWERD_INHIBIT_DIR = '/var/run/powerd-inhibit-suspend'
+
+
+class GetIABooksActivity(activity.Activity):
+
+ def __init__(self, handle):
+ "The entry point to the Activity"
+ activity.Activity.__init__(self, handle, False)
+ self.max_participants = 1
+
+ self.selected_book = None
+ self.queryresults = None
+ self._getter = None
+ self.show_images = True
+ self.languages = {}
+ self._lang_code_handler = languagenames.LanguageNames()
+ self.catalogs_configuration = {}
+ self.catalog_history = []
+
+ if os.path.exists('/etc/get-books.cfg'):
+ self._read_configuration('/etc/get-books.cfg')
+ else:
+ self._read_configuration()
+
+ toolbar_box = ToolbarBox()
+ activity_button = ToolButton()
+ color = profile.get_color()
+ bundle = ActivityBundle(activity.get_bundle_path())
+ icon = Icon(file=bundle.get_icon(), xo_color=color)
+ activity_button.set_icon_widget(icon)
+ activity_button.show()
+
+ toolbar_box.toolbar.insert(activity_button, 0)
+ self._add_search_controls(toolbar_box.toolbar)
+
+ separator = Gtk.SeparatorToolItem()
+ separator.props.draw = False
+ separator.set_expand(True)
+ toolbar_box.toolbar.insert(separator, -1)
+
+ toolbar_box.toolbar.insert(StopButton(self), -1)
+
+ self.set_toolbar_box(toolbar_box)
+ toolbar_box.show_all()
+ self._books_toolbar = toolbar_box.toolbar
+
+ self._create_controls()
+
+ if not self.powerd_running():
+ try:
+ bus = dbus.SystemBus()
+ proxy = bus.get_object('org.freedesktop.ohm',
+ '/org/freedesktop/ohm/Keystore')
+ self.ohm_keystore = dbus.Interface(proxy,
+ 'org.freedesktop.ohm.Keystore')
+ except dbus.DBusException, e:
+ logging.warning("Error setting OHM inhibit: %s", e)
+ self.ohm_keystore = None
+
+ def powerd_running(self):
+ self.using_powerd = os.access(POWERD_INHIBIT_DIR, os.W_OK)
+ logging.error("using_powerd: %d", self.using_powerd)
+ return self.using_powerd
+
+ def _inhibit_suspend(self):
+ if self.using_powerd:
+ fd = open(POWERD_INHIBIT_DIR + "/%u" % os.getpid(), 'w')
+ logging.error("inhibit_suspend file is %s", (POWERD_INHIBIT_DIR \
+ + "/%u" % os.getpid()))
+ fd.close()
+ return True
+
+ if self.ohm_keystore is not None:
+ try:
+ self.ohm_keystore.SetKey('suspend.inhibit', 1)
+ return self.ohm_keystore.GetKey('suspend.inhibit')
+ except dbus.exceptions.DBusException:
+ logging.debug("failed to inhibit suspend")
+ return False
+ else:
+ return False
+
+ def _allow_suspend(self):
+ if self.using_powerd:
+ if os.path.exists(POWERD_INHIBIT_DIR + "/%u" % os.getpid()):
+ os.unlink(POWERD_INHIBIT_DIR + "/%u" % os.getpid())
+ logging.error("allow_suspend unlinking %s", (POWERD_INHIBIT_DIR \
+ + "/%u" % os.getpid()))
+ return True
+
+ if self.ohm_keystore is not None:
+ try:
+ self.ohm_keystore.SetKey('suspend.inhibit', 0)
+ return self.ohm_keystore.GetKey('suspend.inhibit')
+ except dbus.exceptions.DBusException:
+ logging.error("failed to allow suspend")
+ return False
+ else:
+ return False
+
+ def _read_configuration(self, file_name='get-books.cfg'):
+ logging.error('Reading configuration from file %s', file_name)
+ config = ConfigParser.ConfigParser()
+ config.readfp(open(file_name))
+ if config.has_option('GetBooks', 'show_images'):
+ self.show_images = config.getboolean('GetBooks', 'show_images')
+ self.languages = {}
+ if config.has_option('GetBooks', 'languages'):
+ languages_param = config.get('GetBooks', 'languages')
+ for language in languages_param.split(','):
+ lang_code = language.strip()
+ if len(lang_code) > 0:
+ self.languages[lang_code] = \
+ self._lang_code_handler.get_full_language_name(lang_code)
+
+ for section in config.sections():
+ if section != 'GetBooks' and not section.startswith('Catalogs'):
+ name = config.get(section, 'name')
+ _SOURCES[section] = name
+ repo_config = {}
+ repo_config['query_uri'] = config.get(section, 'query_uri')
+ repo_config['opds_cover'] = config.get(section, 'opds_cover')
+ if config.has_option(section, 'summary_field'):
+ repo_config['summary_field'] = \
+ config.get(section, 'summary_field')
+ else:
+ repo_config['summary_field'] = None
+ if config.has_option(section, 'blacklist'):
+ blacklist = config.get(section, 'blacklist')
+ repo_config['blacklist'] = blacklist.split(',')
+ # TODO strip?
+ else:
+ repo_config['blacklist'] = []
+
+ _SOURCES_CONFIG[section] = repo_config
+
+ logging.error('_SOURCES %s', _SOURCES)
+ logging.error('_SOURCES_CONFIG %s', _SOURCES_CONFIG)
+
+ for section in config.sections():
+ if section.startswith('Catalogs'):
+ catalog_source = section.split('_')[1]
+ if not catalog_source in _SOURCES_CONFIG:
+ logging.error('There are not a source for the catalog ' +
+ 'section %s', section)
+ break
+ source_config = _SOURCES_CONFIG[catalog_source]
+ opds_cover = source_config['opds_cover']
+ for catalog in config.options(section):
+ catalog_config = {}
+ catalog_config['query_uri'] = config.get(section, catalog)
+ catalog_config['opds_cover'] = opds_cover
+ catalog_config['source'] = catalog_source
+ catalog_config['name'] = catalog
+ catalog_config['summary_field'] = \
+ source_config['summary_field']
+ self.catalogs_configuration[catalog] = catalog_config
+
+ self.source = _SOURCES_CONFIG.keys()[0]
+
+ self.filter_catalogs_by_source()
+
+ logging.error('languages %s', self.languages)
+ logging.error('catalogs %s', self.catalogs)
+
+ def _add_search_controls(self, toolbar):
+ book_search_item = Gtk.ToolItem()
+ toolbar.search_entry = iconentry.IconEntry()
+ toolbar.search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY,
+ 'system-search')
+ toolbar.search_entry.add_clear_button()
+ toolbar.search_entry.connect('activate',
+ self.__search_entry_activate_cb)
+ width = int(Gdk.Screen.width() / 4)
+ toolbar.search_entry.set_size_request(width, -1)
+ book_search_item.add(toolbar.search_entry)
+ toolbar.search_entry.show()
+ toolbar.insert(book_search_item, -1)
+ book_search_item.show()
+
+ toolbar.source_combo = ComboBox()
+ toolbar.source_combo.props.sensitive = True
+ toolbar.source_changed_cb_id = \
+ toolbar.source_combo.connect('changed', self.__source_changed_cb)
+ combotool = ToolComboBox(toolbar.source_combo)
+ combotool.set_size_request(width, -1)
+ toolbar.insert(combotool, -1)
+ combotool.show()
+
+ self.bt_catalogs = ToggleToolButton('books')
+ self.bt_catalogs.set_tooltip(_('Catalogs'))
+ toolbar.insert(self.bt_catalogs, -1)
+ self.bt_catalogs.connect('toggled', self.__toggle_cats_cb)
+ if len(self.catalogs) > 0:
+ self.bt_catalogs.show()
+
+ if len(self.languages) > 0:
+ toolbar.config_toolbarbutton = ToolbarButton()
+ toolbar.config_toolbarbutton.props.icon_name = 'preferences-system'
+ toolbar.config_toolbarbox = Gtk.Toolbar()
+ toolbar.config_toolbarbutton.props.page = toolbar.config_toolbarbox
+ toolbar.language_combo = ComboBox()
+ toolbar.language_combo.props.sensitive = True
+ combotool = ToolComboBox(toolbar.language_combo)
+ toolbar.language_combo.append_item('all', _('Any language'))
+ for key in self.languages.keys():
+ toolbar.language_combo.append_item(key, self.languages[key])
+ toolbar.language_combo.set_active(0)
+ toolbar.config_toolbarbutton.props.page.insert(combotool, -1)
+ toolbar.insert(toolbar.config_toolbarbutton, -1)
+ toolbar.config_toolbarbutton.show()
+ combotool.show()
+ toolbar.language_changed_cb_id = \
+ toolbar.language_combo.connect('changed',
+ self.__language_changed_cb)
+
+ self.bt_catalogs = ToggleToolButton('books')
+ self.bt_catalogs.set_tooltip(_('Catalogs'))
+ toolbar.insert(self.bt_catalogs, -1)
+ self.bt_catalogs.connect('toggled', self.__toggle_cats_cb)
+ if len(self.catalogs) > 0:
+ self.bt_catalogs.show()
+
+ self._device_manager = devicemanager.DeviceManager()
+ self._refresh_sources(toolbar)
+ self._device_manager.connect('device-changed',
+ self.__device_changed_cb)
+
+ toolbar.search_entry.grab_focus()
+ return toolbar
+
+ def __bt_catalogs_clicked_cb(self, button):
+ palette = button.get_palette()
+ palette.popup(immediate=True, state=palette.SECONDARY)
+
+ def __switch_catalog_cb(self, catalog_name):
+ catalog_config = self.catalogs[catalog_name.decode('utf-8')]
+ self.__activate_catalog_cb(None, catalog_config)
+
+ def __activate_catalog_cb(self, menu, catalog_config):
+ query_language = self.get_query_language()
+
+ self.enable_button(False)
+ self.clear_downloaded_bytes()
+ self.book_selected = False
+ self.listview.clear()
+ logging.error('SOURCE %s', catalog_config['source'])
+ self._books_toolbar.search_entry.props.text = ''
+ self.source = catalog_config['source']
+ position = _SOURCES_CONFIG[self.source]['position']
+ self._books_toolbar.source_combo.set_active(position)
+
+ if self.queryresults is not None:
+ self.queryresults.cancel()
+ self.queryresults = None
+ #arch = open("/home/olpc/Activities/BibliotecaCeibal.activity/prueba","w")
+ #arch.write(query_language)
+ #arch.close()
+ query_language = 'all'
+ self.queryresults = opds.RemoteQueryResult(catalog_config,
+ '', query_language)
+ self.show_message(_('Performing lookup, please wait...'))
+ # README: I think we should create some global variables for
+ # each cursor that we are using to avoid the creation of them
+ # every time that we want to change it
+ self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.WATCH))
+
+ self.queryresults.connect('updated', self.__query_updated_cb)
+
+ def update_format_combo(self, links):
+ self.format_combo.handler_block(self.__format_changed_cb_id)
+ self.format_combo.remove_all()
+ for key in _MIMETYPES.keys():
+ if _MIMETYPES[key] in links.keys():
+ self.format_combo.append_item(_MIMETYPES[key], key)
+ self.format_combo.set_active(0)
+ self.format_combo.handler_unblock(self.__format_changed_cb_id)
+
+ def get_search_terms(self):
+ return self._books_toolbar.search_entry.props.text
+
+ def __device_changed_cb(self, mgr):
+ logging.debug('Device was added/removed')
+ self._refresh_sources(self._books_toolbar)
+
+ def _refresh_sources(self, toolbar):
+ toolbar.source_combo.handler_block(toolbar.source_changed_cb_id)
+
+ #TODO: Do not blindly clear this
+ toolbar.source_combo.remove_all()
+
+ position = 0
+ for key in _SOURCES.keys():
+ toolbar.source_combo.append_item(_SOURCES[key], key,
+ icon_name='internet-icon')
+ _SOURCES_CONFIG[key]['position'] = position
+ position = position + 1
+
+ # Add menu for local books
+ if len(_SOURCES) > 0:
+ toolbar.source_combo.append_separator()
+ toolbar.source_combo.append_item('local_books', _('My books'),
+ icon_name='activity-journal')
+
+ devices = self._device_manager.get_devices()
+
+ first_device = True
+ for device_name in devices:
+ device = devices[device_name]
+ logging.debug('device %s', device)
+ if device['removable']:
+ mount_point = device['mount_path']
+ label = device['label']
+ if label == '' or label is None:
+ capacity = device['size']
+ label = (_('%.2f GB Volume') % (capacity / (1024.0 ** 3)))
+ logging.debug('Adding device %s', (label))
+ if first_device:
+ toolbar.source_combo.append_separator()
+ first_device = False
+ toolbar.source_combo.append_item(mount_point, label)
+
+ toolbar.source_combo.set_active(0)
+ toolbar.source_combo.handler_unblock(toolbar.source_changed_cb_id)
+
+ def __format_changed_cb(self, combo):
+ self.show_book_data(False)
+
+ def __language_changed_cb(self, combo):
+ self.find_books(self.get_search_terms())
+
+ def __search_entry_activate_cb(self, entry):
+ self.find_books(self.get_search_terms())
+
+ def __get_book_cb(self, button):
+ self.get_book()
+
+ def enable_button(self, state):
+ self._download.props.sensitive = state
+ self.format_combo.props.sensitive = state
+
+ def move_up_catalog(self, treeview):
+ len_cat = len(self.catalog_history)
+ if len_cat == 1:
+ return
+ else:
+ # move a level up the tree
+ self.catalog_listview.handler_block(self._catalog_changed_id)
+ self.catalog_history.pop()
+ len_cat -= 1
+ if(len_cat == 1):
+ title = self.catalog_history[0]['title']
+ self.bt_move_up_catalog.set_label(title)
+ self.bt_move_up_catalog.hide_image()
+ else:
+ title = self.catalog_history[len_cat - 1]['title']
+ self.bt_move_up_catalog.set_label(title)
+ self.bt_move_up_catalog.show_image()
+ self.catalogs = self.catalog_history[len_cat - 1]['catalogs']
+ if len(self.catalogs) > 0:
+ self.path_iter = {}
+ self.categories = []
+ for key in self.catalogs.keys():
+ self.categories.append({'text': key, 'dentro': []})
+ self.treemodel.clear()
+ for p in self.categories:
+ self.path_iter[p['text']] = \
+ self.treemodel.append([p['text']])
+ self.catalog_listview.handler_unblock(self._catalog_changed_id)
+
+ def move_down_catalog(self, treeview):
+ treestore, coldex = \
+ self.catalog_listview.get_selection().get_selected()
+ len_cat = len(self.catalog_history)
+ if len_cat > 0 and self.catalog_history[len_cat - 1]['catalogs'] == []:
+ self.catalog_history.pop()
+ len_cat = len(self.catalog_history)
+
+ # README: when the Activity starts by default there is nothing
+ # selected and this signal is called, so we have to avoid this
+ # 'append' because it fails
+ if coldex is not None:
+ self.catalog_history.append(
+ {'title': treestore.get_value(coldex, 0), 'catalogs': []})
+ self.__switch_catalog_cb(treestore.get_value(coldex, 0))
+
+ def _sort_logfile(self, treemodel, itera, iterb):
+ a = treemodel.get_value(itera, 0)
+ b = treemodel.get_value(iterb, 0)
+ if a == None or b == None:
+ return 0
+ a = a.lower()
+ b = b.lower()
+ if a > b:
+ return 1
+ if a < b:
+ return -1
+ return 0
+
+ def __toggle_cats_cb(self, button):
+ if button.get_active():
+ self.tree_scroller.show_all()
+ self.separa.show()
+ else:
+ self.tree_scroller.hide()
+ self.separa.hide()
+
+ def _create_controls(self):
+ self._download_content_length = 0
+ self._download_content_type = None
+ self.progressbox = Gtk.Box(spacing=20,
+ orientation=Gtk.Orientation.HORIZONTAL)
+ self.progressbar = Gtk.ProgressBar()
+ self.progressbar.set_fraction(0.0)
+ self.progressbox.pack_start(self.progressbar, expand=True, fill=True,
+ padding=0)
+ self.cancel_btn = Gtk.Button(stock=Gtk.STOCK_CANCEL)
+ self.cancel_btn.connect('clicked', self.__cancel_btn_clicked_cb)
+ self.progressbox.pack_start(self.cancel_btn, expand=False,
+ fill=False, padding=0)
+
+ self.msg_label = Gtk.Label()
+
+ self.list_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+
+ # Catalogs treeview
+ self.catalog_listview = Gtk.TreeView()
+ self.catalog_listview.headers_clickble = True
+ self.catalog_listview.hover_expand = True
+ self.catalog_listview.rules_hint = True
+ self._catalog_changed_id = self.catalog_listview.connect(
+ 'cursor-changed', self.move_down_catalog)
+ self.catalog_listview.set_enable_search(False)
+
+ self.treemodel = Gtk.ListStore(str)
+ self.treemodel.set_sort_column_id(0, Gtk.SortType.ASCENDING)
+ self.catalog_listview.set_model(self.treemodel)
+
+ renderer = Gtk.CellRendererText()
+ renderer.set_property('wrap-mode', Pango.WrapMode.WORD)
+ self.treecol = Gtk.TreeViewColumn(_('Catalogs'), renderer, text=0)
+ self.treecol.set_property('clickable', True)
+ self.treecol.connect('clicked', self.move_up_catalog)
+ self.catalog_listview.append_column(self.treecol)
+ self.bt_move_up_catalog = ButtonWithImage(_('Catalogs'))
+ self.bt_move_up_catalog.hide_image()
+ self.treecol.set_widget(self.bt_move_up_catalog)
+
+ self.load_source_catalogs()
+
+ self.tree_scroller = Gtk.ScrolledWindow(hadjustment=None,
+ vadjustment=None)
+ self.tree_scroller.set_policy(Gtk.PolicyType.NEVER,
+ Gtk.PolicyType.AUTOMATIC)
+ self.tree_scroller.add(self.catalog_listview)
+ self.list_box.pack_start(self.tree_scroller, expand=False,
+ fill=False, padding=0)
+ self.separa = Gtk.VSeparator()
+ self.list_box.pack_start(self.separa, expand=False,
+ fill=False, padding=0)
+
+ # books listview
+ self.listview = ListView(self._lang_code_handler)
+ self.listview.connect('selection-changed', self.selection_cb)
+ self.listview.set_enable_search(False)
+
+ self.list_scroller = Gtk.ScrolledWindow(hadjustment=None,
+ vadjustment=None)
+ self.list_scroller.set_policy(Gtk.PolicyType.AUTOMATIC,
+ Gtk.PolicyType.AUTOMATIC)
+ vadjustment = self.list_scroller.get_vadjustment()
+ vadjustment.connect('value-changed',
+ self.__vadjustment_value_changed_cb)
+ self.list_scroller.add(self.listview)
+ self.list_box.pack_start(self.list_scroller, expand=True,
+ fill=True, padding=0)
+
+ self.scrolled = Gtk.ScrolledWindow()
+ self.scrolled.set_policy(Gtk.PolicyType.NEVER,
+ Gtk.PolicyType.AUTOMATIC)
+ self.scrolled.props.shadow_type = Gtk.ShadowType.NONE
+ self.textview = Gtk.TextView()
+ self.textview.set_editable(False)
+ self.textview.set_cursor_visible(False)
+ self.textview.set_wrap_mode(Gtk.WrapMode.WORD)
+ self.textview.set_justification(Gtk.Justification.LEFT)
+ self.textview.set_left_margin(20)
+ self.textview.set_right_margin(20)
+ self.scrolled.add(self.textview)
+ self.list_box.show_all()
+ self.separa.hide()
+ self.tree_scroller.hide()
+
+ vbox_download = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+
+ hbox_format = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ format_label = Gtk.Label(label=_('Format:'))
+ self.format_combo = ComboBox()
+ for key in _MIMETYPES.keys():
+ self.format_combo.append_item(_MIMETYPES[key], key)
+ self.format_combo.set_active(0)
+ self.format_combo.props.sensitive = False
+ self.__format_changed_cb_id = \
+ self.format_combo.connect('changed', self.__format_changed_cb)
+
+ hbox_format.pack_start(format_label, False, False, 10)
+ hbox_format.pack_start(self.format_combo, False, False, 10)
+ vbox_download.pack_start(hbox_format, False, False, 10)
+
+ self._download = Gtk.Button(_('Get Book'))
+ self._download.props.sensitive = False
+ self._download.connect('clicked', self.__get_book_cb)
+ vbox_download.pack_start(self._download, False, False, 10)
+
+ bottom_hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+
+ if self.show_images:
+ self.__image_downloader = None
+ self.image = Gtk.Image()
+ self.add_default_image()
+ bottom_hbox.pack_start(self.image, False, False, 10)
+ bottom_hbox.pack_start(self.scrolled, True, True, 10)
+ bottom_hbox.pack_start(vbox_download, False, False, 10)
+ bottom_hbox.show_all()
+
+ vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ vbox.pack_start(self.msg_label, False, False, 10)
+ vbox.pack_start(self.progressbox, False, False, 10)
+ vbox.pack_start(self.list_box, True, True, 0)
+ vbox.pack_start(bottom_hbox, False, False, 10)
+ self.set_canvas(vbox)
+ self.listview.show()
+ vbox.show()
+ self.list_scroller.show()
+ self.progressbox.hide()
+ self.show_message(
+ _('Enter words from the Author or Title to begin search.'))
+
+ self._books_toolbar.search_entry.grab_focus()
+ if len(self.catalogs) > 0:
+ self.bt_catalogs.set_active(True)
+
+ def filter_catalogs_by_source(self):
+ self.catalogs = {}
+ for catalog_key in self.catalogs_configuration:
+ catalog = self.catalogs_configuration[catalog_key]
+ if catalog['source'] == self.source:
+ self.catalogs[catalog_key] = catalog
+
+ def load_source_catalogs(self):
+ self.filter_catalogs_by_source()
+
+ if len(self.catalogs) > 0:
+ self.categories = []
+ self.path_iter = {}
+ self.catalog_history = []
+ self.catalog_history.append({'title': _('Catalogs'),
+ 'catalogs': self.catalogs})
+ for key in self.catalogs.keys():
+ self.categories.append({'text': key, 'dentro': []})
+ self.treemodel.clear()
+
+ for p in self.categories:
+ self.path_iter[p['text']] = self.treemodel.append([p['text']])
+
+ def can_close(self):
+ self._lang_code_handler.close()
+ if self.queryresults is not None:
+ self.queryresults.cancel()
+ self.queryresults = None
+ return True
+
+ def selection_cb(self, widget):
+ # Testing...
+ selected_book = self.listview.get_selected_book()
+ if self.source == 'local_books':
+ if selected_book:
+ self.download_url = ''
+ self.selected_book = selected_book
+ self._download.hide()
+ self.show_book_data()
+ self._object_id = selected_book.get_object_id()
+ self._show_journal_alert(_('Selected book'),
+ self.selected_title)
+ else:
+ self.clear_downloaded_bytes()
+ if selected_book:
+ self.update_format_combo(selected_book.get_download_links())
+ self.selected_book = selected_book
+ self._download.show()
+ self.show_book_data()
+
+ def show_message(self, text):
+ self.msg_label.set_text(text)
+ self.msg_label.show()
+
+ def hide_message(self):
+ self.msg_label.hide()
+
+ def show_book_data(self, load_image=True):
+ self.selected_title = self.selected_book.get_title()
+ book_data = _('Title:\t\t') + self.selected_title + '\n'
+ self.selected_author = self.selected_book.get_author()
+ book_data += _('Author:\t\t') + self.selected_author + '\n'
+ self.selected_publisher = self.selected_book.get_publisher()
+ self.selected_summary = self.selected_book.get_summary()
+ if (self.selected_summary is not 'Unknown'):
+ book_data += _('Summary:\t') + self.selected_summary + '\n'
+ self.selected_language_code = self.selected_book.get_language()
+ if self.selected_language_code != '':
+ try:
+ self.selected_language = \
+ self._lang_code_handler.get_full_language_name(
+ self.selected_book.get_language())
+ except:
+ self.selected_language = self.selected_book.get_language()
+ book_data += _('Language:\t') + self.selected_language + '\n'
+ book_data += _('Publisher:\t') + self.selected_publisher + '\n'
+ if self.source != 'local_books':
+ try:
+ self.download_url = self.selected_book.get_download_links()[\
+ self.format_combo.props.value]
+ except:
+ pass
+ textbuffer = self.textview.get_buffer()
+ textbuffer.set_text('\n' + book_data)
+ self.enable_button(True)
+
+ # Cover Image
+ self.exist_cover_image = False
+ if self.show_images and load_image:
+ if self.source == 'local_books':
+ cover_image_buffer = self.get_journal_entry_cover_image(
+ self.selected_book.get_object_id())
+ if (cover_image_buffer):
+ self.add_image_buffer(
+ self.get_pixbuf_from_buffer(cover_image_buffer))
+ else:
+ self.add_default_image()
+ else:
+ url_image = self.selected_book.get_image_url()
+ self.add_default_image()
+ if url_image:
+ self.download_image(url_image.values()[0])
+
+ def get_pixbuf_from_buffer(self, image_buffer):
+ """Buffer To Pixbuf"""
+ pixbuf_loader = GdkPixbuf.PixbufLoader()
+ pixbuf_loader.write(image_buffer)
+ pixbuf_loader.close()
+ pixbuf = pixbuf_loader.get_pixbuf()
+ return pixbuf
+
+ def get_journal_entry_cover_image(self, object_id):
+ ds_object = datastore.get(object_id)
+ if 'cover_image' in ds_object.metadata:
+ cover_data = ds_object.metadata['cover_image']
+ return base64.b64decode(cover_data)
+ elif 'preview' in ds_object.metadata:
+ return ds_object.metadata['preview']
+ else:
+ return ""
+
+ def download_image(self, url):
+ self._inhibit_suspend()
+ if self.__image_downloader is not None:
+ self.__image_downloader.stop_download()
+ self.__image_downloader = opds.ImageDownloader(self, url)
+ self.__image_downloader.connect('updated', self.__image_updated_cb)
+
+ def __image_updated_cb(self, downloader, file_name):
+ if file_name is not None:
+ self.add_image(file_name)
+ self.exist_cover_image = True
+ os.remove(file_name)
+ else:
+ self.add_default_image()
+ self.__image_downloader = None
+ self._allow_suspend()
+
+ def add_default_image(self):
+ if self.format_combo.props.value == 'audio/mpeg':
+ file_path = os.path.join(activity.get_bundle_path(), 'musica.svg')
+ self.add_image(file_path)
+ elif self.format_combo.props.value == 'audio/ogg':
+ file_path = os.path.join(activity.get_bundle_path(), 'application-ogg.png')
+ self.add_image(file_path)
+ else:
+ file_path = os.path.join(activity.get_bundle_path(),'generic_cover.png')
+ self.add_image(file_path)
+
+ def add_image(self, file_path):
+ pixbuf = GdkPixbuf.Pixbuf.new_from_file(file_path)
+ self.add_image_buffer(pixbuf)
+
+ def add_image_buffer(self, pixbuf):
+ image_height = int(Gdk.Screen.height() / 4)
+ image_width = image_height / 3 * 2
+ width, height = pixbuf.get_width(), pixbuf.get_height()
+ scale = 1
+ if (width > image_width) or (height > image_height):
+ scale_x = image_width / float(width)
+ scale_y = image_height / float(height)
+ scale = min(scale_x, scale_y)
+
+ pixbuf2 = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB,
+ pixbuf.get_has_alpha(), pixbuf.get_bits_per_sample(),
+ image_width, image_height)
+
+ pixbuf2.fill(style.COLOR_PANEL_GREY.get_int())
+
+ margin_x = int((image_width - (width * scale)) / 2)
+ margin_y = int((image_height - (height * scale)) / 2)
+
+ pixbuf.scale(pixbuf2, margin_x, margin_y,
+ image_width - (margin_x * 2), image_height - (margin_y * 2),
+ margin_x, margin_y, scale, scale,
+ GdkPixbuf.InterpType.BILINEAR)
+
+ self.image.set_from_pixbuf(pixbuf2)
+
+ def get_query_language(self):
+ query_language = None
+ if len(self.languages) > 0:
+ query_language = self._books_toolbar.language_combo.props.value
+ return query_language
+
+ def find_books(self, search_text=''):
+ self._inhibit_suspend()
+ self.source = self._books_toolbar.source_combo.props.value
+
+ query_language = self.get_query_language()
+
+ self.enable_button(False)
+ self.clear_downloaded_bytes()
+ self.book_selected = False
+ self.listview.clear()
+
+ if self.queryresults is not None:
+ self.queryresults.cancel()
+ self.queryresults = None
+
+ if self.source == 'local_books':
+ self.listview.populate_with_books(
+ self.get_entrys_info(search_text))
+ else:
+ aux = 0 #Arreglado que si apretas ENTER aparezcan TODOS los libros de Ceibal
+ if self.source == 'Ceibal' and search_text == '':
+ aux = 1
+ #if self.source == 'local_books' and search_text == '':
+ # aux = 1
+ if search_text == '' and aux == 0:
+ return
+ if len(search_text) < 3 and aux == 0:
+ self.show_message(_('You must enter at least 3 letters.'))
+ self._books_toolbar.search_entry.grab_focus()
+ return
+ if self.source == 'Internet Archive':
+ self.queryresults = \
+ opds.InternetArchiveQueryResult(search_text,
+ query_language, self)
+ elif self.source in _SOURCES_CONFIG:
+ #if self.source in _SOURCES_CONFIG:
+ repo_configuration = _SOURCES_CONFIG[self.source]
+ self.queryresults = opds.RemoteQueryResult(repo_configuration,
+ search_text, query_language)
+ else:
+ self.queryresults = opds.LocalVolumeQueryResult(self.source,
+ search_text, query_language)
+
+ self.show_message(_('Performing lookup, please wait...'))
+ self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.WATCH))
+ self.queryresults.connect('updated', self.__query_updated_cb)
+
+ def __query_updated_cb(self, query, midway):
+ self.listview.populate(self.queryresults)
+ if 'bozo_exception' in self.queryresults._feedobj:
+ # something went wrong and we have to inform about this
+ bozo_exception = self.queryresults._feedobj.bozo_exception
+ if isinstance(bozo_exception, urllib2.URLError):
+ if isinstance(bozo_exception.reason, socket.gaierror):
+ if bozo_exception.reason.errno == -2:
+ self.show_message(_('Could not reach the server. '
+ 'Maybe you are not connected to the network'))
+ self.window.set_cursor(None)
+ return
+ self.show_message(_('There was an error downloading the list.'))
+ elif (len(self.queryresults.get_catalog_list()) > 0):
+ self.show_message(_('New catalog list %s was found') \
+ % self.queryresults._configuration["name"])
+ self.catalogs_updated(query, midway)
+ elif len(self.queryresults) == 0:
+ self.show_message(_('Sorry, no books could be found.'))
+ if not midway and len(self.queryresults) > 0:
+ self.hide_message()
+ query_language = self.get_query_language()
+ if query_language != 'all' and query_language != 'en':
+ # the bookserver send english books if there are not books in
+ # the requested language
+ only_english = True
+ for book in self.queryresults.get_book_list():
+ if book.get_language() == query_language:
+ only_english = False
+ break
+ if only_english:
+ self.show_message(
+ _('Sorry, we only found english books.'))
+ self.get_window().set_cursor(None)
+ self._allow_suspend()
+
+ def catalogs_updated(self, query, midway):
+ self.catalogs = {}
+ i = 0
+ for catalog_item in self.queryresults.get_catalog_list():
+ logging.debug('Add catalog %s', catalog_item.get_title())
+ catalog_config = {}
+ download_link = ''
+ download_links = catalog_item.get_download_links()
+ for link in download_links.keys():
+ download_link = download_links[link]
+ break
+ catalog_config['query_uri'] = download_link
+ catalog_config['opds_cover'] = \
+ catalog_item._configuration['opds_cover']
+ catalog_config['source'] = catalog_item._configuration['source']
+ source_config = _SOURCES_CONFIG[catalog_config['source']]
+ catalog_config['name'] = catalog_item.get_title()
+ catalog_config['summary_field'] = \
+ catalog_item._configuration['summary_field']
+ catalog_config['index'] = i
+ if catalog_item.get_title() in source_config['blacklist']:
+ logging.debug('Catalog "%s" is in blacklist',
+ catalog_item.get_title())
+ else:
+ self.catalogs[catalog_item.get_title().strip()] = \
+ catalog_config
+ i = i + 1
+ if len(self.catalogs) > 0:
+ len_cat = len(self.catalog_history)
+ self.catalog_history[len_cat - 1]['catalogs'] = self.catalogs
+ self.path_iter = {}
+ self.categories = []
+ for key in self.catalogs.keys():
+ self.categories.append({'text': key, 'dentro': []})
+ self.treemodel.clear()
+ for p in self.categories:
+ self.path_iter[p['text']] = \
+ self.treemodel.append([p['text']])
+
+ title = self.catalog_history[len_cat - 1]['title']
+ self.bt_move_up_catalog.set_label(title)
+ self.bt_move_up_catalog.show_image()
+
+ else:
+ self.catalog_history.pop()
+
+ def __source_changed_cb(self, widget):
+ search_terms = self.get_search_terms()
+ if search_terms == '':
+ self.find_books(None)
+ else:
+ self.find_books(search_terms)
+ # enable/disable catalogs button if configuration is available
+ self.source = self._books_toolbar.source_combo.props.value
+
+ # Get catalogs for this source
+ self.load_source_catalogs()
+
+ if len(self.catalogs) > 0:
+ self.bt_catalogs.show()
+ self.bt_catalogs.set_active(True)
+ else:
+ self.bt_catalogs.set_active(False)
+ self.bt_catalogs.hide()
+
+ def __vadjustment_value_changed_cb(self, vadjustment):
+
+ if not self.queryresults.is_ready():
+ return
+ try:
+ # Use various tricks to update resultset as user scrolls down
+ if ((vadjustment.props.upper - vadjustment.props.lower) > 1000 \
+ and (vadjustment.props.upper - vadjustment.props.value - \
+ vadjustment.props.page_size) / (vadjustment.props.upper - \
+ vadjustment.props.lower) < 0.3) or ((vadjustment.props.upper \
+ - vadjustment.props.value
+ - vadjustment.props.page_size) < 200):
+ if self.queryresults.has_next():
+ self.show_message(_('Tiene mas resultados, espere por favor...'))
+ self.queryresults.update_with_next()
+ finally:
+ return
+
+ def __cancel_btn_clicked_cb(self, btn):
+ if self._getter is not None:
+ try:
+ self._getter.cancel()
+ except:
+ logging.debug('Got an exception while trying' + \
+ 'to cancel download')
+ self.progressbox.hide()
+ self.listview.props.sensitive = True
+ self._books_toolbar.search_entry.set_sensitive(True)
+ logging.debug('Download was canceled by the user.')
+ self._allow_suspend()
+
+ def get_book(self):
+ self.enable_button(False)
+ self.progressbox.show_all()
+ GObject.idle_add(self.download_book, self.download_url)
+
+ def download_book(self, url):
+ self._inhibit_suspend()
+ logging.error('DOWNLOAD BOOK %s', url)
+ self.listview.props.sensitive = False
+ self._books_toolbar.search_entry.set_sensitive(False)
+ path = os.path.join(self.get_activity_root(), 'instance',
+ 'tmp%i' % time.time())
+ self._getter = opds.ReadURLDownloader(url)
+ self._getter.connect("finished", self._get_book_result_cb)
+ self._getter.connect("progress", self._get_book_progress_cb)
+ self._getter.connect("error", self._get_book_error_cb)
+ logging.debug("Starting download from %s to %s", url, path)
+ try:
+ self._getter.start(path)
+ except:
+ self.show_error_alert(_('Error'), _('Connection timed out for ') +
+ self.selected_title)
+
+ self._download_content_length = self._getter.get_content_length()
+ self._download_content_type = self._getter.get_content_type()
+
+ def _get_book_result_cb(self, getter, tempfile, suggested_name):
+ self.listview.props.sensitive = True
+ self._books_toolbar.search_entry.set_sensitive(True)
+ if self._download_content_type.startswith('text/html'):
+ # got an error page instead
+ self._get_book_error_cb(getter, 'HTTP Error')
+ return
+ self.process_downloaded_book(tempfile, suggested_name)
+
+ def _get_book_progress_cb(self, getter, bytes_downloaded):
+ if self._download_content_length > 0:
+ logging.debug("Downloaded %u of %u bytes...",
+ bytes_downloaded, self._download_content_length)
+ else:
+ logging.debug("Downloaded %u bytes...",
+ bytes_downloaded)
+ total = self._download_content_length
+ self.set_downloaded_bytes(bytes_downloaded, total)
+ while Gtk.events_pending():
+ Gtk.main_iteration()
+
+ def _get_book_error_cb(self, getter, err):
+ self.listview.props.sensitive = True
+ self.enable_button(True)
+ self.progressbox.hide()
+ logging.debug("Error getting document: %s", err)
+ self._download_content_length = 0
+ self._download_content_type = None
+ self._getter = None
+ if self.source == 'Internet Archive' and \
+ getter._url.endswith('_text.pdf'):
+ # in the IA server there are files ending with _text.pdf
+ # smaller and better than the .pdf files, but not for all
+ # the books. We try to download them and if is not present
+ # download the .pdf file
+ self.download_url = self.download_url.replace('_text.pdf', '.pdf')
+ self.get_book()
+ else:
+ self._allow_suspend()
+ self._show_error_alert(_('Error: Could not download %s. ' +
+ 'The path in the catalog seems to be incorrect') %
+ self.selected_title)
+
+ def set_downloaded_bytes(self, downloaded_bytes, total):
+ fraction = float(downloaded_bytes) / float(total)
+ self.progressbar.set_fraction(fraction)
+
+ def clear_downloaded_bytes(self):
+ self.progressbar.set_fraction(0.0)
+
+ def process_downloaded_book(self, tempfile, suggested_name):
+ logging.debug("Got document %s (%s)", tempfile, suggested_name)
+ self.create_journal_entry(tempfile)
+ self._getter = None
+ self._allow_suspend()
+
+ def create_journal_entry(self, tempfile):
+ journal_entry = datastore.create()
+ journal_title = self.selected_title
+ if self.selected_author != '':
+ journal_title = journal_title + ', by ' + self.selected_author
+ journal_entry.metadata['title'] = journal_title
+ journal_entry.metadata['title_set_by_user'] = '1'
+ journal_entry.metadata['keep'] = '0'
+ journal_entry.metadata['mime_type'] = \
+ self.format_combo.props.value
+ # Fix fake mime type for black&white pdfs
+ if journal_entry.metadata['mime_type'] == _MIMETYPES['PDF BW']:
+ journal_entry.metadata['mime_type'] = _MIMETYPES['PDF']
+
+ journal_entry.metadata['buddies'] = ''
+ journal_entry.metadata['icon-color'] = profile.get_color().to_string()
+ textbuffer = self.textview.get_buffer()
+ journal_entry.metadata['description'] = \
+ textbuffer.get_text(textbuffer.get_start_iter(),
+ textbuffer.get_end_iter(), True)
+ if self.exist_cover_image:
+ image_buffer = self._get_preview_image_buffer()
+ journal_entry.metadata['preview'] = dbus.ByteArray(image_buffer)
+ image_buffer = self._get_cover_image_buffer()
+ journal_entry.metadata['cover_image'] = \
+ dbus.ByteArray(base64.b64encode(image_buffer))
+ else:
+ journal_entry.metadata['cover_image'] = ""
+
+ journal_entry.metadata['tags'] = self.source
+ journal_entry.metadata['source'] = self.source
+ journal_entry.metadata['author'] = self.selected_author
+ journal_entry.metadata['publisher'] = self.selected_publisher
+ journal_entry.metadata['summary'] = self.selected_summary
+ journal_entry.metadata['language'] = self.selected_language_code
+
+ journal_entry.file_path = tempfile
+ datastore.write(journal_entry)
+ os.remove(tempfile)
+ self.progressbox.hide()
+ self._object_id = journal_entry.object_id
+ self._show_journal_alert(_('Download completed'), self.selected_title)
+
+ def _show_journal_alert(self, title, msg):
+ _stop_alert = Alert()
+ _stop_alert.props.title = title
+ _stop_alert.props.msg = msg
+ open_icon = Icon(icon_name='zoom-activity')
+ _stop_alert.add_button(Gtk.ResponseType.APPLY,
+ _('Show in Journal'), open_icon)
+ open_icon.show()
+ ok_icon = Icon(icon_name='dialog-ok')
+ _stop_alert.add_button(Gtk.ResponseType.OK, _('Ok'), ok_icon)
+ ok_icon.show()
+ # Remove other alerts
+ for alert in self._alerts:
+ self.remove_alert(alert)
+
+ self.add_alert(_stop_alert)
+ _stop_alert.connect('response', self.__stop_response_cb)
+ _stop_alert.show()
+
+ def __stop_response_cb(self, alert, response_id):
+ if response_id is Gtk.ResponseType.APPLY:
+ activity.show_object_in_journal(self._object_id)
+ self.remove_alert(alert)
+
+ def _get_preview_image_buffer(self):
+ preview_width, preview_height = style.zoom(300), style.zoom(225)
+
+ pixbuf = self.image.get_pixbuf()
+ width, height = pixbuf.get_width(), pixbuf.get_height()
+
+ scale = 1
+ if (width > preview_width) or (height > preview_height):
+ scale_x = preview_width / float(width)
+ scale_y = preview_height / float(height)
+ scale = min(scale_x, scale_y)
+
+ pixbuf2 = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB,
+ pixbuf.get_has_alpha(), pixbuf.get_bits_per_sample(),
+ preview_width, preview_height)
+ pixbuf2.fill(style.COLOR_WHITE.get_int())
+
+ margin_x = int((preview_width - (width * scale)) / 2)
+ margin_y = int((preview_height - (height * scale)) / 2)
+
+ pixbuf.scale(pixbuf2, margin_x, margin_y,
+ preview_width - (margin_x * 2),
+ preview_height - (margin_y * 2),
+ margin_x, margin_y, scale, scale,
+ GdkPixbuf.InterpType.BILINEAR)
+
+ succes, data = pixbuf2.save_to_bufferv('png', [], [])
+ return data
+
+ def _get_cover_image_buffer(self):
+ pixbuf = self.image.get_pixbuf()
+ succes, data = pixbuf.save_to_bufferv('png', [], [])
+ return data
+
+ def _show_error_alert(self, title, text=None):
+ alert = NotifyAlert(timeout=20)
+ alert.props.title = title
+ alert.props.msg = text
+ self.add_alert(alert)
+ alert.connect('response', self._alert_cancel_cb)
+ alert.show()
+
+ def _alert_cancel_cb(self, alert, response_id):
+ self.remove_alert(alert)
+ self.textview.grab_focus()
+
+ def get_entrys_info(self, query):
+ books = []
+ for key in _MIMETYPES.keys():
+ books.extend(self.get_entry_info_format(query, _MIMETYPES[key]))
+ return books
+
+ def get_entry_info_format(self, query, mime):
+ books = []
+ if query is not None and len(query) > 0:
+ ds_objects, num_objects = datastore.find(
+ {'mime_type': '%s' % mime,
+ 'query': '*%s*' % query})
+ else:
+ ds_objects, num_objects = datastore.find(
+ {'mime_type': '%s' % mime})
+
+ logging.error('Local search %d books found %s format', num_objects,
+ mime)
+ for i in range(0, num_objects):
+ entry = {}
+ entry['title'] = ds_objects[i].metadata['title']
+ entry['mime'] = ds_objects[i].metadata['mime_type']
+ entry['object_id'] = ds_objects[i].object_id
+
+ if 'author' in ds_objects[i].metadata:
+ entry['author'] = ds_objects[i].metadata['author']
+ else:
+ entry['author'] = ''
+
+ if 'publisher' in ds_objects[i].metadata:
+ entry['dcterms_publisher'] = \
+ ds_objects[i].metadata['publisher']
+ else:
+ entry['dcterms_publisher'] = ''
+
+ if 'language' in ds_objects[i].metadata:
+ entry['dcterms_language'] = \
+ ds_objects[i].metadata['language']
+ else:
+ entry['dcterms_language'] = ''
+
+ if 'source' in ds_objects[i].metadata:
+ entry['source'] = \
+ ds_objects[i].metadata['source']
+ else:
+ entry['source'] = ''
+
+ if entry['source'] in _SOURCES_CONFIG:
+ repo_configuration = _SOURCES_CONFIG[entry['source']]
+ summary_field = repo_configuration['summary_field']
+ if 'summary' in ds_objects[i].metadata:
+ entry[summary_field] = ds_objects[i].metadata['summary']
+ else:
+ entry[summary_field] = ''
+ else:
+ repo_configuration = None
+ books.append(opds.Book(repo_configuration, entry, ''))
+ return books
+
+ def close(self, skip_save=False):
+ "Override the close method so we don't try to create a Journal entry."
+ activity.Activity.close(self, True)
+
+ def save(self):
+ pass
+
+
+class ButtonWithImage(Gtk.Button):
+
+ def __init__(self, label_text):
+ GObject.GObject.__init__(self,)
+ self.icon_move_up = Icon(icon_name='go-up')
+ # self.remove(self.get_children()[0])
+ self.hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
+ self.add(self.hbox)
+ self.hbox.add(self.icon_move_up)
+ self.label = Gtk.Label(label=label_text)
+ self.hbox.add(self.label)
+ self.show_all()
+
+ def hide_image(self):
+ self.icon_move_up.hide()
+
+ def show_image(self):
+ self.icon_move_up.show()
+
+ def set_label(self, text):
+ self.label.set_text(text)