class LogActivity(activity.Activity): def __init__(self, handle): activity.Activity.__init__(self, handle) self._autosearch_timer = None # Paths to watch: ~/.sugar/someuser/logs, /var/log paths = [] paths.append(env.get_profile_path('logs')) paths.append('/var/log') # Additional misc files. ext_files = [] ext_files.append(os.path.expanduser('~/.bash_history')) self.viewer = MultiLogView(paths, ext_files) self.set_canvas(self.viewer) self.viewer.grab_focus() self._build_toolbox() # Get Sugar's clipboard self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) self.show() self._configure_cb(None) Gdk.Screen.get_default().connect('size-changed', self._configure_cb) def _build_toolbox(self): toolbar_box = ToolbarBox() self.max_participants = 1 activity_button = ActivityToolbarButton(self) activity_toolbar = activity_button.page self._toolbar = toolbar_box.toolbar self._toolbar.insert(activity_button, -1) self._secondary_toolbar = Gtk.Toolbar() self._secondary_toolbar_button = ToolbarButton( page=self._secondary_toolbar, icon_name='system-search') self._secondary_toolbar.show() self._toolbar.insert(self._secondary_toolbar_button, -1) self._secondary_toolbar_button.hide() show_list = ToggleToolButton('view-list') show_list.set_active(True) show_list.set_tooltip(_('Show list of files')) show_list.connect('toggled', self._list_toggled_cb) self._toolbar.insert(show_list, -1) show_list.show() copy = CopyButton() copy.connect('clicked', self.__copy_clicked_cb) self._toolbar.insert(copy, -1) wrap_btn = ToggleToolButton("format-wrap") wrap_btn.set_tooltip(_('Word Wrap')) wrap_btn.connect('clicked', self._wrap_cb) self._toolbar.insert(wrap_btn, -1) self.search_entry = iconentry.IconEntry() self.search_entry.set_size_request(Gdk.Screen.width() / 3, -1) self.search_entry.set_icon_from_name( iconentry.ICON_ENTRY_PRIMARY, 'entry-search') self.search_entry.add_clear_button() self.search_entry.connect('activate', self._search_entry_activate_cb) self.search_entry.connect('changed', self._search_entry_changed_cb) self._search_item = Gtk.ToolItem() self._search_item.add(self.search_entry) self._toolbar.insert(self._search_item, -1) self._search_prev = ToolButton('go-previous-paired') self._search_prev.set_tooltip(_('Previous')) self._search_prev.connect('clicked', self._search_prev_cb) self._toolbar.insert(self._search_prev, -1) self._search_next = ToolButton('go-next-paired') self._search_next.set_tooltip(_('Next')) self._search_next.connect('clicked', self._search_next_cb) self._toolbar.insert(self._search_next, -1) self._update_search_buttons() self.collector_palette = CollectorPalette(self) collector_btn = ToolButton('log-export') collector_btn.set_palette(self.collector_palette) collector_btn.connect('clicked', self._logviewer_cb) collector_btn.show() activity_toolbar.insert(collector_btn, -1) self._delete_btn = ToolButton('list-remove') self._delete_btn = ToolButton('list-remove', accelerator='<ctrl>d') self._delete_btn.set_tooltip(_('Delete Log File')) self._delete_btn.connect('clicked', self._delete_log_cb) self._toolbar.insert(self._delete_btn, -1) self._separator = Gtk.SeparatorToolItem() self._separator.set_expand(True) self._separator.set_draw(False) self._toolbar.insert(self._separator, -1) self._stop_btn = StopButton(self) self._toolbar.insert(self._stop_btn, -1) toolbar_box.show_all() self.set_toolbar_box(toolbar_box) def _configure_cb(self, event=None): for control in [self._stop_btn, self._separator, self._delete_btn]: if control in self._toolbar: self._toolbar.remove(control) if Gdk.Screen.width() < Gdk.Screen.height(): self._secondary_toolbar_button.show() self._secondary_toolbar_button.set_expanded(True) self._remove_controls(self._toolbar) self._add_controls(self._secondary_toolbar) else: self._secondary_toolbar_button.set_expanded(False) self._secondary_toolbar_button.hide() self._remove_controls(self._secondary_toolbar) self._add_controls(self._toolbar) for control in [self._delete_btn, self._separator, self._stop_btn]: if control not in self._toolbar: self._toolbar.insert(control, -1) def _remove_controls(self, toolbar): for control in [self._search_item, self._search_prev, self._search_next]: if control in toolbar: toolbar.remove(control) def _add_controls(self, toolbar): for control in [self._search_item, self._search_prev, self._search_next]: if control not in toolbar: toolbar.insert(control, -1) control.show() def _list_toggled_cb(self, widget): if widget.get_active(): self.viewer.list_scroll.show() else: self.viewer.list_scroll.hide() def __copy_clicked_cb(self, button): if self.viewer.active_log: self.viewer.active_log.copy_clipboard(self.clipboard) def _wrap_cb(self, button): if button.get_active(): self.viewer._textview.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) else: self.viewer._textview.set_wrap_mode(Gtk.WrapMode.NONE) def _search_entry_activate_cb(self, entry): if self._autosearch_timer: GLib.source_remove(self._autosearch_timer) self._autosearch_timer = None self.viewer.set_search_text(entry.props.text) self._update_search_buttons() def _search_entry_changed_cb(self, entry): if self._autosearch_timer: GLib.source_remove(self._autosearch_timer) self._autosearch_timer = GLib.timeout_add(_AUTOSEARCH_TIMEOUT, self.__autosearch_cb) def __autosearch_cb(self): self._autosearch_timer = None self.search_entry.activate() return False def _search_prev_cb(self, button): self.viewer.search_next('backward') self._update_search_buttons() def _search_next_cb(self, button): self.viewer.search_next('forward') self._update_search_buttons() def _update_search_buttons(self,): if len(self.viewer.search_text) == 0: self._search_prev.props.sensitive = False self._search_next.props.sensitive = False else: prev_result = self.viewer.get_next_result('backward') next_result = self.viewer.get_next_result('forward') self._search_prev.props.sensitive = prev_result is not None self._search_next.props.sensitive = next_result is not None def _delete_log_cb(self, widget): if self.viewer.active_log: logfile = self.viewer.active_log.logfile try: os.remove(logfile) except OSError as err: notify = NotifyAlert() notify.props.title = _('Error') notify.props.msg = _('%(error)s when deleting %(file)s') % \ {'error': err.strerror, 'file': logfile} notify.connect('response', _notify_response_cb, self) self.add_alert(notify) def _logviewer_cb(self, widget): self.collector_palette.popup(True)
def _setup_toolbars(self): """ Setup the toolbars.. """ project_toolbar = Gtk.Toolbar() custom_slide_toolbar = Gtk.Toolbar() custom_stator_toolbar = Gtk.Toolbar() edit_toolbar = Gtk.Toolbar() # no sharing self.max_participants = 1 toolbox = ToolbarBox() # Activity toolbar activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() project_toolbar_button = ToolbarButton(page=project_toolbar, icon_name='sliderule') project_toolbar.show() toolbox.toolbar.insert(project_toolbar_button, -1) project_toolbar_button.show() custom_slide_toolbar_button = ToolbarButton(page=custom_slide_toolbar, icon_name='custom-slide') custom_slide_toolbar.show() toolbox.toolbar.insert(custom_slide_toolbar_button, -1) custom_slide_toolbar_button.show() custom_stator_toolbar_button = ToolbarButton( page=custom_stator_toolbar, icon_name='custom-stator') custom_stator_toolbar.show() toolbox.toolbar.insert(custom_stator_toolbar_button, -1) custom_stator_toolbar_button.show() edit_toolbar_button = ToolbarButton(label=_('Edit'), page=edit_toolbar, icon_name='toolbar-edit') edit_toolbar_button.show() toolbox.toolbar.insert(edit_toolbar_button, -1) edit_toolbar_button.show() self.set_toolbar_box(toolbox) toolbox.show() toolbar = toolbox.toolbar # Add the buttons to the toolbars self._function_combo = combo_factory(FUNCTIONS, project_toolbar, self._function_combo_cb, default=FC_multiply, tooltip=_('select function')) self.top_button = button_factory('C', project_toolbar, self._dummy_cb, tooltip=_('active slide')) self._slide_combo = combo_factory(SLIDE_TABLE, project_toolbar, self._slide_combo_cb, default=C_slide, tooltip=_('select slide')) self.bottom_button = button_factory('D', project_toolbar, self._dummy_cb, tooltip=_('active stator')) self._stator_combo = combo_factory(STATOR_TABLE, project_toolbar, self._stator_combo_cb, default=D_slide, tooltip=_('select stator')) separator_factory(project_toolbar) self.realign_button = button_factory('realign', project_toolbar, self.realign_cb, tooltip=_('realign slides')) self._offset_function = [] self._calculate_function = [] self._label_function = [] self._domain_min = [] self._domain_max = [] self._step_size = [] self.custom = [] ENTRY = ['C', 'D'] ENTRY_TOOLBAR = [custom_slide_toolbar, custom_stator_toolbar] ENTRY_BUTTON = ['custom-slide', 'custom-stator'] ENTRY_TOOLTIP = [_('create custom slide'), _('create custom stator')] ENTRY_CALLBACK = [self._custom_slide_cb, self._custom_stator_cb] for i in range(2): self._offset_function.append( entry_factory(DEFINITIONS[ENTRY[i]][0], ENTRY_TOOLBAR[i], tooltip=_('position function'), max=10)) self._calculate_function.append( entry_factory(DEFINITIONS[ENTRY[i]][1], ENTRY_TOOLBAR[i], tooltip=_('result function'), max=10)) self._label_function.append( entry_factory(DEFINITIONS[ENTRY[i]][2], ENTRY_TOOLBAR[i], tooltip=_('label function'), max=10)) self._domain_min.append( entry_factory(DEFINITIONS[ENTRY[i]][3], ENTRY_TOOLBAR[i], tooltip=_('domain minimum'), max=4)) self._domain_max.append( entry_factory(DEFINITIONS[ENTRY[i]][4], ENTRY_TOOLBAR[i], tooltip=_('domain maximum'), max=4)) self._step_size.append( entry_factory(DEFINITIONS[ENTRY[i]][5], ENTRY_TOOLBAR[i], tooltip=_('step size'), max=4)) self.custom.append( button_factory(ENTRY_BUTTON[i], ENTRY_TOOLBAR[i], ENTRY_CALLBACK[i], tooltip=ENTRY_TOOLTIP[i])) copy = button_factory('edit-copy', edit_toolbar, self._copy_cb, tooltip=_('Copy'), accelerator='<Ctrl>c') paste = button_factory('edit-paste', edit_toolbar, self._paste_cb, tooltip=_('Paste'), accelerator='<Ctrl>v') separator_factory(toolbox.toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() # workaround to #2050 edit_toolbar_button.set_expanded(True) # start with project toolbar enabled project_toolbar_button.set_expanded(True)
class WebActivity(activity.Activity): def __init__(self, handle): activity.Activity.__init__(self, handle) self._force_close = False if incompatible: return self._incompatible() self._collab = CollabWrapper(self) self._collab.message.connect(self.__message_cb) _logger.debug('Starting the web activity') # TODO PORT # session = WebKit2.get_default_session() # session.set_property('accept-language-auto', True) # session.set_property('ssl-use-system-ca-file', True) # session.set_property('ssl-strict', False) # But of a hack, but webkit doesn't let us change the cookie jar # contents, we we can just pre-seed it cookie_jar = SoupGNOME.CookieJarSqlite(filename=_cookies_db_path, read_only=False) _seed_xs_cookie(cookie_jar) del cookie_jar context = WebKit2.WebContext.get_default() cookie_manager = context.get_cookie_manager() cookie_manager.set_persistent_storage( _cookies_db_path, WebKit2.CookiePersistentStorage.SQLITE) # FIXME # downloadmanager.remove_old_parts() context.connect('download-started', self.__download_requested_cb) self._tabbed_view = TabbedView(self) self._tabbed_view.connect('focus-url-entry', self._on_focus_url_entry) self._tabbed_view.connect('switch-page', self.__switch_page_cb) self._titled_tray = TitledTray(_('Bookmarks')) self._tray = self._titled_tray.tray self.set_tray(self._titled_tray, Gtk.PositionType.BOTTOM) self._tray_links = {} self.model = Model() self.model.add_link_signal.connect(self._add_link_model_cb) self._primary_toolbar = PrimaryToolbar(self._tabbed_view, self) self._edit_toolbar = EditToolbar(self) self._view_toolbar = ViewToolbar(self) self._primary_toolbar.connect('add-link', self.__link_add_button_cb) self._primary_toolbar.connect('remove-link', self.__link_remove_button_cb) self._primary_toolbar.connect('go-home', self._go_home_button_cb) self._primary_toolbar.connect('go-library', self._go_library_button_cb) self._primary_toolbar.connect('set-home', self._set_home_button_cb) self._primary_toolbar.connect('reset-home', self._reset_home_button_cb) self._edit_toolbar_button = ToolbarButton(page=self._edit_toolbar, icon_name='toolbar-edit') self._primary_toolbar.toolbar.insert(self._edit_toolbar_button, 1) view_toolbar_button = ToolbarButton(page=self._view_toolbar, icon_name='toolbar-view') self._primary_toolbar.toolbar.insert(view_toolbar_button, 2) self._primary_toolbar.show_all() self.set_toolbar_box(self._primary_toolbar) self.set_canvas(self._tabbed_view) self._tabbed_view.show() self.connect('key-press-event', self._key_press_cb) if handle.uri: self._tabbed_view.current_browser.load_uri(handle.uri) elif not self._jobject.file_path: # TODO: we need this hack until we extend the activity API for # opening URIs and default docs. self._tabbed_view.load_homepage() # README: this is a workaround to remove old temp file # http://bugs.sugarlabs.org/ticket/3973 self._cleanup_temp_files() self._collab.setup() def __download_requested_cb(self, context, download): if hasattr(self, 'busy'): while self.unbusy() > 0: continue logging.debug('__download_requested_cb %r', download.get_request().get_uri()) downloadmanager.add_download(download, self) return True def fullscreen(self): activity.Activity.fullscreen(self) self._tabbed_view.set_show_tabs(False) def unfullscreen(self): activity.Activity.unfullscreen(self) # Always show tabs here, because they are automatically hidden # if it is like a video fullscreening. We ALWAYS need to make # sure these are visible. Don't make the user lost self._tabbed_view.set_show_tabs(True) def _cleanup_temp_files(self): """Removes temporary files generated by Download Manager that were cancelled by the user or failed for any reason. There is a bug in GLib that makes this to happen: https://bugzilla.gnome.org/show_bug.cgi?id=629301 """ try: uptime_proc = open('/proc/uptime', 'r').read() uptime = int(float(uptime_proc.split()[0])) except EnvironmentError: logging.warning('/proc/uptime could not be read') uptime = None temp_path = os.path.join(self.get_activity_root(), 'instance') now = int(time.time()) cutoff = now - 24 * 60 * 60 # yesterday if uptime is not None: boot_time = now - uptime cutoff = max(cutoff, boot_time) for f in os.listdir(temp_path): if f.startswith('.goutputstream-'): fpath = os.path.join(temp_path, f) mtime = int(os.path.getmtime(fpath)) if mtime < cutoff: logging.warning('Removing old temporary file: %s', fpath) try: os.remove(fpath) except EnvironmentError: logging.error( 'Temporary file could not be ' 'removed: %s', fpath) def _on_focus_url_entry(self, gobject): self._primary_toolbar.entry.grab_focus() def _get_data_from_file_path(self, file_path): fd = open(file_path, 'r') try: data = fd.read() finally: fd.close() return data def _get_save_as(self): if not hasattr(profile, 'get_save_as'): return False return profile.get_save_as() def read_file(self, file_path): if self.metadata['mime_type'] == 'text/plain': data = self._get_data_from_file_path(file_path) self.model.deserialize(data) for link in self.model.data['shared_links']: _logger.debug('read: url=%s title=%s d=%s' % (link['url'], link['title'], link['color'])) self._add_link_totray(link['url'], b64decode(link['thumb']), link['color'], link['title'], link['owner'], -1, link['hash'], link.get('notes')) logging.debug('########## reading %s', data) if 'session_state' in self.model.data: self._tabbed_view.set_session_state( self.model.data['session_state']) else: self._tabbed_view.set_legacy_history( self.model.data['history'], self.model.data['currents']) for number, tab in enumerate(self.model.data['currents']): tab_page = self._tabbed_view.get_nth_page(number) zoom_level = tab.get('zoom_level') if zoom_level is not None: tab_page.browser.set_zoom_level(zoom_level) tab_page.browser.grab_focus() self._tabbed_view.set_current_page(self.model.data['current_tab']) elif self.metadata['mime_type'] == 'text/uri-list': data = self._get_data_from_file_path(file_path) uris = mime.split_uri_list(data) if len(uris) == 1: self._tabbed_view.props.current_browser.load_uri(uris[0]) else: _logger.error('Open uri-list: Does not support' 'list of multiple uris by now.') else: file_uri = 'file://' + file_path self._tabbed_view.props.current_browser.load_uri(file_uri) self._tabbed_view.props.current_browser.grab_focus() def write_file(self, file_path): if not hasattr(self, '_tabbed_view'): _logger.debug('Called write_file before the tabbed_view was made') return if not self.metadata['mime_type']: self.metadata['mime_type'] = 'text/plain' if self.metadata['mime_type'] == 'text/plain': browser = self._tabbed_view.current_browser if not self._jobject.metadata['title_set_by_user'] == '1' and \ not self._get_save_as(): if browser.props.title is None: self.metadata['title'] = _('Untitled') else: self.metadata['title'] = browser.props.title self.model.data['history'] = self._tabbed_view.get_legacy_history() current_tab = self._tabbed_view.get_current_page() self.model.data['current_tab'] = current_tab self.model.data['currents'] = [] for n in range(0, self._tabbed_view.get_n_pages()): tab_page = self._tabbed_view.get_nth_page(n) n_browser = tab_page.browser if n_browser is not None: uri = n_browser.get_uri() history_index = n_browser.get_history_index() info = { 'title': n_browser.props.title, 'url': uri, 'history_index': history_index, 'zoom_level': n_browser.get_zoom_level() } self.model.data['currents'].append(info) self.model.data['session_state'] = \ self._tabbed_view.get_state() f = open(file_path, 'w') try: logging.debug('########## writing %s', self.model.serialize()) f.write(self.model.serialize()) finally: f.close() def __link_add_button_cb(self, button): self._add_link() def __link_remove_button_cb(self, button): browser = self._tabbed_view.props.current_browser uri = browser.get_uri() self.__link_removed_cb(None, sha1(uri).hexdigest()) def _go_home_button_cb(self, button): self._tabbed_view.load_homepage() def _go_library_button_cb(self, button): self._tabbed_view.load_homepage(ignore_settings=True) def _set_home_button_cb(self, button): self._tabbed_view.set_homepage() self._alert(_('The initial page was configured')) def _reset_home_button_cb(self, button): self._tabbed_view.reset_homepage() self._alert(_('The default initial page was configured')) def _alert(self, title, text=None): alert = NotifyAlert(timeout=5) 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) def _key_press_cb(self, widget, event): browser = self._tabbed_view.props.current_browser if event.get_state() & Gdk.ModifierType.CONTROL_MASK: if event.keyval == Gdk.KEY_f: self._edit_toolbar_button.set_expanded(True) self._edit_toolbar.search_entry.grab_focus() return True if event.keyval == Gdk.KEY_l: self._primary_toolbar.entry.grab_focus() return True if event.keyval == Gdk.KEY_equal: # On US keyboards, KEY_equal is KEY_plus without # SHIFT_MASK, so for convenience treat this as the # same as the zoom in accelerator configured in # WebKit2 browser.zoom_in() return True if event.keyval == Gdk.KEY_t: self._tabbed_view.add_tab() return True if event.keyval == Gdk.KEY_w: self._tabbed_view.close_tab() return True # FIXME: copy and paste is supposed to be handled by # Gtk.Entry, but does not work when we catch # key-press-event and return False. if self._primary_toolbar.entry.is_focus(): if event.keyval == Gdk.KEY_c: self._primary_toolbar.entry.copy_clipboard() return True if event.keyval == Gdk.KEY_v: self._primary_toolbar.entry.paste_clipboard() return True return False if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Down, Gdk.KEY_KP_Left, Gdk.KEY_KP_Right): scrolled_window = browser.get_parent() if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Down): adjustment = scrolled_window.get_vadjustment() elif event.keyval in (Gdk.KEY_KP_Left, Gdk.KEY_KP_Right): adjustment = scrolled_window.get_hadjustment() value = adjustment.get_value() step = adjustment.get_step_increment() if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Left): adjustment.set_value(value - step) else: adjustment.set_value(value + step) return True if event.keyval == Gdk.KEY_Escape: browser.stop_loading() return False # allow toolbar entry to handle escape too return False def _add_link(self): ''' take screenshot and add link info to the model ''' browser = self._tabbed_view.props.current_browser ui_uri = browser.get_uri() if self.model.has_link(ui_uri): return buf = b64encode(self._get_screenshot()) timestamp = time.time() args = (ui_uri, browser.props.title, buf, profile.get_nick_name(), profile.get_color().to_string(), timestamp) self.model.add_link(*args, by_me=True) self._collab.post({'type': 'add_link', 'args': args}) def __message_cb(self, collab, buddy, message): type_ = message.get('type') if type_ == 'add_link': self.model.add_link(*message['args']) elif type_ == 'add_link_from_info': self.model.add_link_from_info(message['dict']) elif type_ == 'remove_link': self.remove_link(message['hash']) def get_data(self): return self.model.data def set_data(self, data): for link in data['shared_links']: if link['hash'] not in self.model.get_links_ids(): self.model.add_link_from_info(link) # FIXME: Case where buddy has updated link desciption their_model = Model() their_model.data = data for link in self.model.data['shared_links']: if link['hash'] not in their_model.get_links_ids(): self._collab.post({'type': 'add_link_from_info', 'dict': link}) def _add_link_model_cb(self, model, index, by_me): ''' receive index of new link from the model ''' link = self.model.data['shared_links'][index] widget = self._add_link_totray(link['url'], b64decode(link['thumb']), link['color'], link['title'], link['owner'], index, link['hash'], link.get('notes')) if by_me: animator = Animator(1, widget=self) animator.add( AddLinkAnimation(self, self._tabbed_view.props.current_browser, widget)) animator.start() def _add_link_totray(self, url, buf, color, title, owner, index, hash, notes=None): ''' add a link to the tray ''' item = LinkButton(buf, color, title, owner, hash, notes) item.connect('clicked', self._link_clicked_cb, url) item.connect('remove_link', self.__link_removed_cb) item.notes_changed_signal.connect(self.__link_notes_changed) # use index to add to the tray self._tray_links[hash] = item self._tray.add_item(item, index) item.show() self._view_toolbar.traybutton.props.sensitive = True self._view_toolbar.traybutton.props.active = True self._view_toolbar.update_traybutton_tooltip() return item def __link_removed_cb(self, button, hash): self.remove_link(hash) self._collab.post({'type': 'remove_link', 'hash': hash}) def remove_link(self, hash): ''' remove a link from tray and delete it in the model ''' self._tray_links[hash].hide() self._tray_links[hash].destroy() del self._tray_links[hash] self.model.remove_link(hash) if len(self._tray.get_children()) == 0: self._view_toolbar.traybutton.props.sensitive = False self._view_toolbar.traybutton.props.active = False self._view_toolbar.update_traybutton_tooltip() def __link_notes_changed(self, button, hash, notes): self.model.change_link_notes(hash, notes) def _link_clicked_cb(self, button, url): ''' an item of the link tray has been clicked ''' browser = self._tabbed_view.add_tab() browser.load_uri(url) browser.grab_focus() def _get_screenshot(self): browser = self._tabbed_view.props.current_browser window = browser.get_window() width, height = window.get_width(), window.get_height() thumb_surface = Gdk.Window.create_similar_surface( window, cairo.CONTENT_COLOR, THUMB_WIDTH, THUMB_HEIGHT) cairo_context = cairo.Context(thumb_surface) thumb_scale_w = THUMB_WIDTH * 1.0 / width thumb_scale_h = THUMB_HEIGHT * 1.0 / height cairo_context.scale(thumb_scale_w, thumb_scale_h) Gdk.cairo_set_source_window(cairo_context, window, 0, 0) cairo_context.paint() thumb_str = StringIO.StringIO() thumb_surface.write_to_png(thumb_str) return thumb_str.getvalue() def can_close(self): if self._force_close: return True elif downloadmanager.can_quit(): return True else: alert = Alert() alert.props.title = ngettext('Download in progress', 'Downloads in progress', downloadmanager.num_downloads()) message = ngettext('Stopping now will erase your download', 'Stopping now will erase your downloads', downloadmanager.num_downloads()) alert.props.msg = message cancel_icon = Icon(icon_name='dialog-cancel') cancel_label = ngettext('Continue download', 'Continue downloads', downloadmanager.num_downloads()) alert.add_button(Gtk.ResponseType.CANCEL, cancel_label, cancel_icon) stop_icon = Icon(icon_name='dialog-ok') alert.add_button(Gtk.ResponseType.OK, _('Stop'), stop_icon) stop_icon.show() self.add_alert(alert) alert.connect('response', self.__inprogress_response_cb) alert.show() self.present() return False def __inprogress_response_cb(self, alert, response_id): self.remove_alert(alert) if response_id is Gtk.ResponseType.CANCEL: logging.debug('Keep on') elif response_id == Gtk.ResponseType.OK: logging.debug('Stop downloads and quit') self._force_close = True downloadmanager.remove_all_downloads() self.close() def __switch_page_cb(self, tabbed_view, page, page_num): if not hasattr(self, 'busy'): return browser = page._browser progress = browser.props.estimated_load_progress uri = browser.props.uri if progress < 1.0 and uri: self.busy() else: while self.unbusy() > 0: continue def get_document_path(self, async_cb, async_err_cb): browser = self._tabbed_view.props.current_browser browser.get_source(async_cb, async_err_cb) def get_canvas(self): return self._tabbed_view def _incompatible(self): ''' Display abbreviated activity user interface with alert ''' toolbox = ToolbarBox() stop = StopButton(self) toolbox.toolbar.add(stop) self.set_toolbar_box(toolbox) title = _('Activity not compatible with this system.') msg = _('Please downgrade activity and try again.') alert = Alert(title=title, msg=msg) alert.add_button(0, 'Stop', Icon(icon_name='activity-stop')) self.add_alert(alert) label = Gtk.Label( _('Uh oh, WebKit2 is too old. ' 'Browse-200 and later require WebKit2 API 4.0, ' 'sorry!')) self.set_canvas(label) ''' Workaround: start Terminal activity, then type sugar-erase-bundle org.laptop.WebActivity then in My Settings, choose Software Update, which will offer older Browse. ''' alert.connect('response', self.__incompatible_response_cb) stop.connect('clicked', self.__incompatible_stop_clicked_cb, alert) self.show_all() def __incompatible_stop_clicked_cb(self, button, alert): self.remove_alert(alert) def __incompatible_response_cb(self, alert, response): self.remove_alert(alert) self.close()
class NapierActivity(activity.Activity): ''' Napier's bones: Napier's bones were invented by John Napier (1550-1617), a Scottish mathematician and scientist. They help you to do multiplication. ''' # TODO: Define your own bone. def __init__(self, handle): ''' Initialize the toolbars and the work surface ''' super(NapierActivity, self).__init__(handle) if os.path.exists(os.path.join('~', 'Activities', 'Napier.activity')): self._bone_path = os.path.join('~', 'Activities', 'Napier.activity', 'bones') else: self._bone_path = os.path.join('.', 'bones') self._bones = [] self._bone_images = [ None, None, None, None, None, None, None, None, None, None ] self._blank_image = None self._number = 0 self._number_of_bones = 0 self._setup_toolbars() self._setup_canvas() self._circles = [None, None] self._ovals = [] self._setup_workspace() self._restore() def _setup_canvas(self): ''' Create a canvas ''' self._canvas = Gtk.DrawingArea() self._canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.set_canvas(self._canvas) self._canvas.show() self.show_all() self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("motion-notify-event", self._mouse_move_cb) # self._canvas.connect("key_press_event", self._key_press_cb) def _setup_workspace(self): ''' Add the bones. ''' self._width = Gdk.Screen.width() self._height = int(Gdk.Screen.height() - (GRID_CELL_SIZE * 2)) self._scale = self._height * 1.0 / BONE_HEIGHT self._bone_width = int(BONE_WIDTH * self._scale) self._bone_height = int(BONE_HEIGHT * self._scale) # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._bone_index = Sprite( self._sprites, 0, 0, _load_svg_from_file( os.path.join(self._bone_path, 'bones-index.svg'), self._bone_width, self._bone_height)) self._max_bones = int(self._width / self._bone_width) - 1 self._blank_image = _load_svg_from_file( os.path.join(self._bone_path, 'blank-bone.svg'), self._bone_width, self._bone_height) for bones in range(self._max_bones): self._bones.append( Sprite(self._sprites, bones * self._bone_width, 0, self._blank_image)) circle_image = _load_svg_from_file( os.path.join(self._bone_path, 'circle.svg'), int(self._scale * 45), int(self._scale * 45)) self._circles[0] = Sprite(self._sprites, 0, -100, circle_image) self._circles[1] = Sprite(self._sprites, 0, -100, circle_image) oval_image = _load_svg_from_file( os.path.join(self._bone_path, 'oval.svg'), int(self._scale * 129), int(self._scale * 92)) for bones in range(self._max_bones - 1): self._ovals.append(Sprite(self._sprites, 0, -100, oval_image)) def _setup_toolbars(self): ''' Setup the toolbars. ''' self.max_participants = 1 # no sharing toolbox = ToolbarBox() # Activity toolbar activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() self._bones_toolbar = Gtk.Toolbar() self._bones_toolbar_button = ToolbarButton(label=_('Select a bone'), page=self._bones_toolbar, icon_name='bones') self._bones_toolbar_button.show() toolbox.toolbar.insert(self._bones_toolbar_button, -1) self.set_toolbar_box(toolbox) toolbox.show() self.toolbar = toolbox.toolbar self._new_calc_button = button_factory('erase', self.toolbar, self._new_calc_cb, tooltip=_('Clear')) self._status = label_factory(self.toolbar, '') button_factory('number-0', self._bones_toolbar, self._number_cb, cb_arg=0, tooltip=_('zero')) button_factory('number-1', self._bones_toolbar, self._number_cb, cb_arg=1, tooltip=_('one')) button_factory('number-2', self._bones_toolbar, self._number_cb, cb_arg=2, tooltip=_('two')) button_factory('number-3', self._bones_toolbar, self._number_cb, cb_arg=3, tooltip=_('three')) button_factory('number-4', self._bones_toolbar, self._number_cb, cb_arg=4, tooltip=_('four')) button_factory('number-5', self._bones_toolbar, self._number_cb, cb_arg=5, tooltip=_('five')) button_factory('number-6', self._bones_toolbar, self._number_cb, cb_arg=6, tooltip=_('six')) button_factory('number-7', self._bones_toolbar, self._number_cb, cb_arg=7, tooltip=_('seven')) button_factory('number-8', self._bones_toolbar, self._number_cb, cb_arg=8, tooltip=_('eight')) button_factory('number-9', self._bones_toolbar, self._number_cb, cb_arg=9, tooltip=_('nine')) separator_factory(toolbox.toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() self._bones_toolbar_button.set_expanded(True) def _new_calc_cb(self, button=None): ''' Start a new calculation. ''' for bone in range(self._max_bones): self._bones[bone].set_shape(self._blank_image) self._bones[bone].inval() self._number_of_bones = 0 self._number = 0 self._status.set_label('') return def _number_cb(self, button=None, value=0): ''' Try to add a digit. ''' if self._number_of_bones == self._max_bones: return self._number_of_bones += 1 if self._bone_images[value] is None: self._bone_images[value] = _svg_str_to_pixbuf( _bone_factory(value, scale=self._scale)) self._bones[self._number_of_bones].set_shape(self._bone_images[value]) self._bones[self._number_of_bones].inval() self._number = self._number * 10 + value def _mouse_move_cb(self, win, event): ''' Determine which row we are in and then calculate the product. ''' win.grab_focus() x, y = list(map(int, event.get_coords())) factor = int(y / self._bone_width) # The row determines a factor if self._number == 0 or factor == 0: self._status.set_label('') self._circles[0].move((0, -100)) self._circles[1].move((0, -100)) for number in range(self._max_bones - 1): self._ovals[number].move((0, -100)) else: c0dx = int(4 * self._scale) c0dy = int(12 * self._scale) c1dx = int(44 * self._scale) c1dy = int(47 * self._scale) odx = int(42 * self._scale) ody = int(2 * self._scale) self._circles[0].move( (self._bone_width + c0dx, factor * self._bone_width + c0dy)) self._circles[1].move( (self._number_of_bones * self._bone_width + c1dx, factor * self._bone_width + c1dy)) for number in range(self._number_of_bones - 1): self._ovals[number].move( ((number + 1) * self._bone_width + odx, factor * self._bone_width + ody)) self._status.set_label('{}×{}={}'.format( factor + 1, self._number, (factor + 1) * self._number)) return True def _key_press_cb(self, win, event): ''' TODO: Add bones by typing numbers ''' return True def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _restore(self): ''' Try to restore previous state. ''' if 'number' in self.metadata and self.metadata['number'] != '0': for digit in range(len(self.metadata['number'])): self._number_cb(button=None, value=int(self.metadata['number'][digit])) def write_file(self, file_path): ''' Write the status to the Journal. ''' if not hasattr(self, '_number'): return self.metadata['number'] = str(self._number)
class JukeboxActivity(activity.Activity): __gsignals__ = { 'playlist-finished': (GObject.SignalFlags.RUN_FIRST, None, []), } def __init__(self, handle): activity.Activity.__init__(self, handle) self.player = None self._alert = None self._playlist_jobject = None self.set_title(_('Jukebox Activity')) self.max_participants = 1 self._toolbar_box = ToolbarBox() activity_button = ActivityToolbarButton(self) activity_toolbar = activity_button.page self._toolbar_box.toolbar.insert(activity_button, 0) self.title_entry = activity_toolbar.title self._view_toolbar = ViewToolbar() self._view_toolbar.connect('go-fullscreen', self.__go_fullscreen_cb) self._view_toolbar.connect('toggle-playlist', self.__toggle_playlist_cb) view_toolbar_button = ToolbarButton( page=self._view_toolbar, icon_name='toolbar-view') self._view_toolbar.show() self._toolbar_box.toolbar.insert(view_toolbar_button, -1) view_toolbar_button.show() self._control_toolbar = Gtk.Toolbar() self._control_toolbar_button = ToolbarButton( page=self._control_toolbar, icon_name='media-playback-start') self._control_toolbar.show() self._toolbar_box.toolbar.insert(self._control_toolbar_button, -1) self._control_toolbar_button.hide() self.set_toolbar_box(self._toolbar_box) self._toolbar_box.show_all() self.connect('key_press_event', self.__key_press_event_cb) self.connect('playlist-finished', self.__playlist_finished_cb) # We want to be notified when the activity gets the focus or # loses it. When it is not active, we don't need to keep # reproducing the video self.connect('notify::active', self.__notify_active_cb) self._video_canvas = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) self._playlist_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.playlist_widget = PlayList() self.playlist_widget.connect('play-index', self.__play_index_cb) self.playlist_widget.connect('missing-tracks', self.__missing_tracks_cb) self.playlist_widget.set_size_request( Gdk.Screen.width() * PLAYLIST_WIDTH_PROP, 0) self.playlist_widget.show() self._playlist_box.pack_start(self.playlist_widget, expand=True, fill=True, padding=0) self._playlist_toolbar = Gtk.Toolbar() move_up = ToolButton("go-up") move_up.set_tooltip(_("Move up")) move_up.connect("clicked", self._move_up_cb) self._playlist_toolbar.insert(move_up, 0) move_down = ToolButton("go-down") move_down.set_tooltip(_("Move down")) move_down.connect("clicked", self._move_down_cb) self._playlist_toolbar.insert(move_down, 1) self._playlist_box.pack_end(self._playlist_toolbar, False, False, 0) self._video_canvas.pack_start(self._playlist_box, False, False, 0) # Create the player just once logging.debug('Instantiating GstPlayer') self.player = GstPlayer() self.player.connect('eos', self.__player_eos_cb) self.player.connect('error', self.__player_error_cb) self.player.connect('play', self.__player_play_cb) self.control = Controls(self, self._toolbar_box.toolbar, self._control_toolbar) self._separator = Gtk.SeparatorToolItem() self._separator.props.draw = False self._separator.set_expand(True) self._separator.show() self._toolbar_box.toolbar.insert(self._separator, -1) self._stop = StopButton(self) self._toolbar_box.toolbar.insert(self._stop, -1) self._empty_widget = Gtk.Label(label="") self._empty_widget.show() self.videowidget = VideoWidget() self.set_canvas(self._video_canvas) self._init_view_area() self.show_all() # need hide the playlist by default self._playlist_box.hide() self._configure_cb() self.player.init_view_area(self.videowidget) self._volume_monitor = Gio.VolumeMonitor.get() self._volume_monitor.connect('mount-added', self.__mount_added_cb) self._volume_monitor.connect('mount-removed', self.__mount_removed_cb) if handle.object_id is None: # The activity was launched from scratch. We need to show # the Empty Widget self.playlist_widget.hide() emptypanel.show(self, 'activity-jukebox', _('No media'), _('Choose media files'), self.control.show_picker_cb) self.control.check_if_next_prev() Gdk.Screen.get_default().connect('size-changed', self._configure_cb) def _move_up_cb(self, button): self.playlist_widget.move_up() def _move_down_cb(self, button): self.playlist_widget.move_down() def _configure_cb(self, event=None): self._toolbar_box.toolbar.remove(self._stop) self._toolbar_box.toolbar.remove(self._separator) if Gdk.Screen.width() < Gdk.Screen.height(): self._control_toolbar_button.show() self._control_toolbar_button.set_expanded(True) self.control.update_layout(landscape=False) self._toolbar_box.toolbar.insert(self._separator, -1) else: self._control_toolbar_button.set_expanded(False) self._control_toolbar_button.hide() self.control.update_layout(landscape=True) self._toolbar_box.toolbar.insert(self._stop, -1) def __notify_active_cb(self, widget, event): """Sugar notify us that the activity is becoming active or inactive. When we are inactive, we stop the player if it is reproducing a video. """ logging.debug('JukeboxActivity notify::active signal received') if self.player.player.props.current_uri is not None and \ self.player.playing_video(): if not self.player.is_playing() and self.props.active: self.player.play() if self.player.is_playing() and not self.props.active: self.player.pause() def _init_view_area(self): """ Use a notebook with two pages, one empty an another with the videowidget """ self.view_area = Gtk.Notebook() self.view_area.set_show_tabs(False) self.view_area.append_page(self._empty_widget, None) self.view_area.append_page(self.videowidget, None) self._video_canvas.pack_end(self.view_area, expand=True, fill=True, padding=0) def _switch_canvas(self, show_video): """Show or hide the video visualization in the canvas. When hidden, the canvas is filled with an empty widget to ensure redrawing. """ if show_video: self.view_area.set_current_page(1) else: self.view_area.set_current_page(0) self._video_canvas.queue_draw() def __key_press_event_cb(self, widget, event): keyname = Gdk.keyval_name(event.keyval) logging.info("Keyname Press: %s, time: %s", keyname, event.time) if self.title_entry.has_focus(): return False if keyname == "space": self.control._button_clicked_cb(None) return True def __playlist_finished_cb(self, widget): self._switch_canvas(show_video=False) self._view_toolbar._show_playlist.set_active(True) self.unfullscreen() # Select the first stream to be played when Play button will # be pressed self.playlist_widget.set_current_playing(0) self.control.check_if_next_prev() def songchange(self, direction): current_playing = self.playlist_widget.get_current_playing() if direction == 'prev' and current_playing > 0: self.play_index(current_playing - 1) elif direction == 'next' and \ current_playing < len(self.playlist_widget._items) - 1: self.play_index(current_playing + 1) else: self.emit('playlist-finished') def play_index(self, index): # README: this line is no more necessary because of the # .playing_video() method # self._switch_canvas(show_video=True) self.playlist_widget.set_current_playing(index) path = self.playlist_widget._items[index]['path'] if self.playlist_widget.check_available_media(path): if self.playlist_widget.is_from_journal(path): path = self.playlist_widget.get_path_from_journal(path) self.control.check_if_next_prev() self.player.set_uri(path) self.player.play() else: self.songchange('next') def __play_index_cb(self, widget, index, path): # README: this line is no more necessary because of the # .playing_video() method # self._switch_canvas(show_video=True) self.playlist_widget.set_current_playing(index) if self.playlist_widget.is_from_journal(path): path = self.playlist_widget.get_path_from_journal(path) self.control.check_if_next_prev() self.player.set_uri(path) self.player.play() def __player_eos_cb(self, widget): self.songchange('next') def _show_error_alert(self, title, msg=None): self._alert = ErrorAlert() self._alert.props.title = title if msg is not None: self._alert.props.msg = msg self.add_alert(self._alert) self._alert.connect('response', self._alert_cancel_cb) self._alert.show() def __mount_added_cb(self, volume_monitor, device): logging.debug('Mountpoint added. Checking...') self.remove_alert(self._alert) self.playlist_widget.update() def __mount_removed_cb(self, volume_monitor, device): logging.debug('Mountpoint removed. Checking...') self.remove_alert(self._alert) self.playlist_widget.update() def __missing_tracks_cb(self, widget, tracks): self._show_missing_tracks_alert(tracks) def _show_missing_tracks_alert(self, tracks): self._alert = Alert() title = _('%s tracks not found.') % len(tracks) self._alert.props.title = title icon = Icon(icon_name='dialog-cancel') self._alert.add_button(Gtk.ResponseType.CANCEL, _('Dismiss'), icon) icon.show() icon = Icon(icon_name='dialog-ok') self._alert.add_button(Gtk.ResponseType.APPLY, _('Details'), icon) icon.show() self.add_alert(self._alert) self._alert.connect( 'response', self.__missing_tracks_alert_response_cb, tracks) def __missing_tracks_alert_response_cb(self, alert, response_id, tracks): if response_id == Gtk.ResponseType.APPLY: vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) vbox.props.valign = Gtk.Align.CENTER label = Gtk.Label(label='') label.set_markup(_('<b>Missing tracks</b>')) vbox.pack_start(label, False, False, 15) for track in tracks: label = Gtk.Label(label=track['path']) vbox.add(label) _missing_tracks = Gtk.ScrolledWindow() _missing_tracks.add_with_viewport(vbox) _missing_tracks.show_all() self.view_area.append_page(_missing_tracks, None) self.view_area.set_current_page(2) self.remove_alert(alert) def _alert_cancel_cb(self, alert, response_id): self.remove_alert(alert) def __player_play_cb(self, widget): # Do not show the visualization widget if we are playing just # an audio stream def callback(): if self.player.playing_video(): self._switch_canvas(True) else: self._switch_canvas(False) return False # HACK: we need a timeout here because gstreamer returns # n-video = 0 if we call it immediately GObject.timeout_add(1000, callback) def __player_error_cb(self, widget, message, detail): self.player.stop() self.control.set_disabled() logging.error('ERROR MESSAGE: %s', message) logging.error('ERROR DETAIL: %s', detail) file_path = self.playlist_widget._items[ self.playlist_widget.get_current_playing()]['path'] mimetype = mime.get_for_file(file_path) title = _('Error') msg = _('This "%s" file can\'t be played') % mimetype self._switch_canvas(False) self._show_error_alert(title, msg) def can_close(self): # We need to put the Gst.State in NULL so gstreamer can # cleanup the pipeline self.player.stop() return True def read_file(self, file_path): """Load a file from the datastore on activity start.""" logging.debug('JukeBoxAtivity.read_file: %s', file_path) title = self.metadata['title'] self.playlist_widget.load_file(file_path, title) self._view_toolbar._show_playlist.set_active(True) def write_file(self, file_path): def write_playlist_to_file(file_path): """Open the file at file_path and write the playlist. It is saved in audio/x-mpegurl format. """ list_file = open(file_path, 'w') for uri in self.playlist_widget._items: list_file.write('#EXTINF:%s\n' % uri['title']) list_file.write('%s\n' % uri['path']) list_file.close() if not self.metadata['mime_type']: self.metadata['mime_type'] = 'audio/x-mpegurl' if self.metadata['mime_type'] == 'audio/x-mpegurl': write_playlist_to_file(file_path) else: if self._playlist_jobject is None: self._playlist_jobject = \ self.playlist_widget.create_playlist_jobject() # Add the playlist to the playlist jobject description. # This is only done if the activity was not started from a # playlist or from scratch: description = '' for uri in self.playlist_widget._items: description += '%s\n' % uri['title'] self._playlist_jobject.metadata['description'] = description write_playlist_to_file(self._playlist_jobject.file_path) datastore.write(self._playlist_jobject) def __go_fullscreen_cb(self, toolbar): self.fullscreen() def __toggle_playlist_cb(self, toolbar): if self._view_toolbar._show_playlist.get_active(): self._playlist_box.show_all() else: self._playlist_box.hide() self._video_canvas.queue_draw()
class WebActivity(activity.Activity): def __init__(self, handle): activity.Activity.__init__(self, handle) _logger.debug('Starting the web activity') session = WebKit.get_default_session() session.set_property('accept-language-auto', True) session.set_property('ssl-use-system-ca-file', True) session.set_property('ssl-strict', False) # By default, cookies are not stored persistently, we have to # add a cookie jar so that they get saved to disk. We use one # with a SQlite database: cookie_jar = SoupGNOME.CookieJarSqlite(filename=_cookies_db_path, read_only=False) session.add_feature(cookie_jar) _seed_xs_cookie(cookie_jar) # FIXME # downloadmanager.remove_old_parts() self._force_close = False self._tabbed_view = TabbedView() self._tabbed_view.connect('focus-url-entry', self._on_focus_url_entry) self._tabbed_view.connect('switch-page', self.__switch_page_cb) self._tray = HTray() self.set_tray(self._tray, Gtk.PositionType.BOTTOM) self._primary_toolbar = PrimaryToolbar(self._tabbed_view, self) self._edit_toolbar = EditToolbar(self) self._view_toolbar = ViewToolbar(self) self._primary_toolbar.connect('add-link', self._link_add_button_cb) self._primary_toolbar.connect('go-home', self._go_home_button_cb) self._primary_toolbar.connect('go-library', self._go_library_button_cb) self._primary_toolbar.connect('set-home', self._set_home_button_cb) self._primary_toolbar.connect('reset-home', self._reset_home_button_cb) self._edit_toolbar_button = ToolbarButton( page=self._edit_toolbar, icon_name='toolbar-edit') self._primary_toolbar.toolbar.insert( self._edit_toolbar_button, 1) view_toolbar_button = ToolbarButton( page=self._view_toolbar, icon_name='toolbar-view') self._primary_toolbar.toolbar.insert( view_toolbar_button, 2) self._primary_toolbar.show_all() self.set_toolbar_box(self._primary_toolbar) self.set_canvas(self._tabbed_view) self._tabbed_view.show() self.model = Model() self.model.connect('add_link', self._add_link_model_cb) self.connect('key-press-event', self._key_press_cb) if handle.uri: self._tabbed_view.current_browser.load_uri(handle.uri) elif not self._jobject.file_path: # TODO: we need this hack until we extend the activity API for # opening URIs and default docs. self._tabbed_view.load_homepage() self.messenger = None self.connect('shared', self._shared_cb) # Get the Presence Service self.pservice = presenceservice.get_instance() try: name, path = self.pservice.get_preferred_connection() self.tp_conn_name = name self.tp_conn_path = path self.conn = telepathy.client.Connection(name, path) except TypeError: _logger.debug('Offline') self.initiating = None if self.get_shared_activity() is not None: _logger.debug('shared: %s', self.get_shared()) # We are joining the activity _logger.debug('Joined activity') self.connect('joined', self._joined_cb) if self.get_shared(): # We've already joined self._joined_cb() else: _logger.debug('Created activity') # README: this is a workaround to remove old temp file # http://bugs.sugarlabs.org/ticket/3973 self._cleanup_temp_files() def _cleanup_temp_files(self): """Removes temporary files generated by Download Manager that were cancelled by the user or failed for any reason. There is a bug in GLib that makes this to happen: https://bugzilla.gnome.org/show_bug.cgi?id=629301 """ try: uptime_proc = open('/proc/uptime', 'r').read() uptime = int(float(uptime_proc.split()[0])) except EnvironmentError: logging.warning('/proc/uptime could not be read') uptime = None temp_path = os.path.join(self.get_activity_root(), 'instance') now = int(time.time()) cutoff = now - 24 * 60 * 60 # yesterday if uptime is not None: boot_time = now - uptime cutoff = max(cutoff, boot_time) for f in os.listdir(temp_path): if f.startswith('.goutputstream-'): fpath = os.path.join(temp_path, f) mtime = int(os.path.getmtime(fpath)) if mtime < cutoff: logging.warning('Removing old temporary file: %s', fpath) try: os.remove(fpath) except EnvironmentError: logging.error('Temporary file could not be ' 'removed: %s', fpath) def _on_focus_url_entry(self, gobject): self._primary_toolbar.entry.grab_focus() def _shared_cb(self, activity_): _logger.debug('My activity was shared') self.initiating = True self._setup() _logger.debug('This is my activity: making a tube...') self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(SERVICE, {}) def _setup(self): if self.get_shared_activity() is None: _logger.debug('Failed to share or join activity') return bus_name, conn_path, channel_paths = \ self.get_shared_activity().get_channels() # Work out what our room is called and whether we have Tubes already room = None tubes_chan = None text_chan = None for channel_path in channel_paths: channel = telepathy.client.Channel(bus_name, channel_path) htype, handle = channel.GetHandle() if htype == telepathy.HANDLE_TYPE_ROOM: _logger.debug('Found our room: it has handle#%d "%s"', handle, self.conn.InspectHandles(htype, [handle])[0]) room = handle ctype = channel.GetChannelType() if ctype == telepathy.CHANNEL_TYPE_TUBES: _logger.debug('Found our Tubes channel at %s', channel_path) tubes_chan = channel elif ctype == telepathy.CHANNEL_TYPE_TEXT: _logger.debug('Found our Text channel at %s', channel_path) text_chan = channel if room is None: _logger.debug("Presence service didn't create a room") return if text_chan is None: _logger.debug("Presence service didn't create a text channel") return # Make sure we have a Tubes channel - PS doesn't yet provide one if tubes_chan is None: _logger.debug("Didn't find our Tubes channel, requesting one...") tubes_chan = self.conn.request_channel( telepathy.CHANNEL_TYPE_TUBES, telepathy.HANDLE_TYPE_ROOM, room, True) self.tubes_chan = tubes_chan self.text_chan = text_chan tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal( 'NewTube', self._new_tube_cb) def _list_tubes_reply_cb(self, tubes): for tube_info in tubes: self._new_tube_cb(*tube_info) def _list_tubes_error_cb(self, e): _logger.debug('ListTubes() failed: %s', e) def _joined_cb(self, activity_): if not self.get_shared_activity(): return _logger.debug('Joined an existing shared activity') self.initiating = False self._setup() _logger.debug('This is not my activity: waiting for a tube...') self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes( reply_handler=self._list_tubes_reply_cb, error_handler=self._list_tubes_error_cb) def _new_tube_cb(self, identifier, initiator, type, service, params, state): _logger.debug('New tube: ID=%d initator=%d type=%d service=%s ' 'params=%r state=%d', identifier, initiator, type, service, params, state) if (type == telepathy.TUBE_TYPE_DBUS and service == SERVICE): if state == telepathy.TUBE_STATE_LOCAL_PENDING: self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube( identifier) self.tube_conn = TubeConnection( self.conn, self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES], identifier, group_iface=self.text_chan[ telepathy.CHANNEL_INTERFACE_GROUP]) _logger.debug('Tube created') self.messenger = Messenger(self.tube_conn, self.initiating, self.model) def _get_data_from_file_path(self, file_path): fd = open(file_path, 'r') try: data = fd.read() finally: fd.close() return data def read_file(self, file_path): if self.metadata['mime_type'] == 'text/plain': data = self._get_data_from_file_path(file_path) self.model.deserialize(data) for link in self.model.data['shared_links']: _logger.debug('read: url=%s title=%s d=%s' % (link['url'], link['title'], link['color'])) self._add_link_totray(link['url'], base64.b64decode(link['thumb']), link['color'], link['title'], link['owner'], -1, link['hash'], link.get('notes')) logging.debug('########## reading %s', data) self._tabbed_view.set_history(self.model.data['history']) for number, tab in enumerate(self.model.data['currents']): tab_page = self._tabbed_view.get_nth_page(number) tab_page.browser.set_history_index(tab['history_index']) zoom_level = tab.get('zoom_level') if zoom_level is not None: tab_page.browser.set_zoom_level(zoom_level) tab_page.browser.grab_focus() self._tabbed_view.set_current_page(self.model.data['current_tab']) elif self.metadata['mime_type'] == 'text/uri-list': data = self._get_data_from_file_path(file_path) uris = mime.split_uri_list(data) if len(uris) == 1: self._tabbed_view.props.current_browser.load_uri(uris[0]) else: _logger.error('Open uri-list: Does not support' 'list of multiple uris by now.') else: file_uri = 'file://' + file_path self._tabbed_view.props.current_browser.load_uri(file_uri) self._tabbed_view.props.current_browser.grab_focus() def write_file(self, file_path): if not self.metadata['mime_type']: self.metadata['mime_type'] = 'text/plain' if self.metadata['mime_type'] == 'text/plain': browser = self._tabbed_view.current_browser if not self._jobject.metadata['title_set_by_user'] == '1': if browser.props.title is None: self.metadata['title'] = _('Untitled') else: self.metadata['title'] = browser.props.title self.model.data['history'] = self._tabbed_view.get_history() current_tab = self._tabbed_view.get_current_page() self.model.data['current_tab'] = current_tab self.model.data['currents'] = [] for n in range(0, self._tabbed_view.get_n_pages()): tab_page = self._tabbed_view.get_nth_page(n) n_browser = tab_page.browser if n_browser is not None: uri = n_browser.get_uri() history_index = n_browser.get_history_index() info = {'title': n_browser.props.title, 'url': uri, 'history_index': history_index, 'zoom_level': n_browser.get_zoom_level()} self.model.data['currents'].append(info) f = open(file_path, 'w') try: logging.debug('########## writing %s', self.model.serialize()) f.write(self.model.serialize()) finally: f.close() def _link_add_button_cb(self, button): self._add_link() def _go_home_button_cb(self, button): self._tabbed_view.load_homepage() def _go_library_button_cb(self, button): self._tabbed_view.load_homepage(ignore_gconf=True) def _set_home_button_cb(self, button): self._tabbed_view.set_homepage() self._alert(_('The initial page was configured')) def _reset_home_button_cb(self, button): self._tabbed_view.reset_homepage() self._alert(_('The default initial page was configured')) def _alert(self, title, text=None): alert = NotifyAlert(timeout=5) 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) def _key_press_cb(self, widget, event): key_name = Gdk.keyval_name(event.keyval) browser = self._tabbed_view.props.current_browser if event.get_state() & Gdk.ModifierType.CONTROL_MASK: if key_name == 'd': self._add_link() elif key_name == 'f': _logger.debug('keyboard: Find') self._edit_toolbar_button.set_expanded(True) self._edit_toolbar.search_entry.grab_focus() elif key_name == 'l': _logger.debug('keyboard: Focus url entry') self._primary_toolbar.entry.grab_focus() elif key_name == 'minus': _logger.debug('keyboard: Zoom out') browser.zoom_out() elif key_name in ['plus', 'equal']: _logger.debug('keyboard: Zoom in') browser.zoom_in() elif key_name == '0': _logger.debug('keyboard: Actual size') browser.set_zoom_level(ZOOM_ORIGINAL) elif key_name == 'Left': _logger.debug('keyboard: Go back') browser.go_back() elif key_name == 'Right': _logger.debug('keyboard: Go forward') browser.go_forward() elif key_name == 'r': _logger.debug('keyboard: Reload') browser.reload() elif Gdk.keyval_name(event.keyval) == "t": self._tabbed_view.add_tab() elif key_name == 'w': _logger.debug('keyboard: close tab') self._tabbed_view.close_tab() elif key_name == "Tab": _logger.debug('keyboard: next tab') current_index = self._tabbed_view.get_current_page() if current_index == self._tabbed_view.get_n_pages() - 1: self._tabbed_view.set_current_page(0) else: self._tabbed_view.set_current_page(current_index + 1) elif event.get_state() & Gdk.ModifierType.SHIFT_MASK: if key_name == "ISO_Left_Tab": _logger.debug('keyboard: previous tab') current_index = self._tabbed_view.get_current_page() last_index = self._tabbed_view.get_n_pages() if current_index == 0: self._tabbed_view.set_current_page(last_index - 1) else: self._tabbed_view.set_current_page(current_index - 1) else: return False return True elif key_name in ('KP_Up', 'KP_Down', 'KP_Left', 'KP_Right'): scrolled_window = browser.get_parent() if key_name in ('KP_Up', 'KP_Down'): adjustment = scrolled_window.get_vadjustment() elif key_name in ('KP_Left', 'KP_Right'): adjustment = scrolled_window.get_hadjustment() value = adjustment.get_value() step = adjustment.get_step_increment() if key_name in ('KP_Up', 'KP_Left'): adjustment.set_value(value - step) elif key_name in ('KP_Down', 'KP_Right'): adjustment.set_value(value + step) return True elif key_name == 'Escape': status = browser.get_load_status() loading = WebKit.LoadStatus.PROVISIONAL <= status \ < WebKit.LoadStatus.FINISHED if loading: _logger.debug('keyboard: Stop loading') browser.stop_loading() return False def _add_link(self): ''' take screenshot and add link info to the model ''' browser = self._tabbed_view.props.current_browser ui_uri = browser.get_uri() for link in self.model.data['shared_links']: if link['hash'] == sha1(ui_uri).hexdigest(): _logger.debug('_add_link: link exist already a=%s b=%s', link['hash'], sha1(ui_uri).hexdigest()) return buf = self._get_screenshot() timestamp = time.time() self.model.add_link(ui_uri, browser.props.title, buf, profile.get_nick_name(), profile.get_color().to_string(), timestamp) if self.messenger is not None: self.messenger._add_link(ui_uri, browser.props.title, profile.get_color().to_string(), profile.get_nick_name(), base64.b64encode(buf), timestamp) def _add_link_model_cb(self, model, index): ''' receive index of new link from the model ''' link = self.model.data['shared_links'][index] self._add_link_totray(link['url'], base64.b64decode(link['thumb']), link['color'], link['title'], link['owner'], index, link['hash'], link.get('notes')) def _add_link_totray(self, url, buf, color, title, owner, index, hash, notes=None): ''' add a link to the tray ''' item = LinkButton(buf, color, title, owner, hash, notes) item.connect('clicked', self._link_clicked_cb, url) item.connect('remove_link', self._link_removed_cb) item.notes_changed_signal.connect(self.__link_notes_changed) # use index to add to the tray self._tray.add_item(item, index) item.show() self._view_toolbar.traybutton.props.sensitive = True self._view_toolbar.traybutton.props.active = True self._view_toolbar.update_traybutton_tooltip() def _link_removed_cb(self, button, hash): ''' remove a link from tray and delete it in the model ''' self.model.remove_link(hash) self._tray.remove_item(button) if len(self._tray.get_children()) == 0: self._view_toolbar.traybutton.props.sensitive = False self._view_toolbar.traybutton.props.active = False self._view_toolbar.update_traybutton_tooltip() def __link_notes_changed(self, button, hash, notes): self.model.change_link_notes(hash, notes) def _link_clicked_cb(self, button, url): ''' an item of the link tray has been clicked ''' self._tabbed_view.props.current_browser.load_uri(url) def _get_screenshot(self): browser = self._tabbed_view.props.current_browser window = browser.get_window() width, height = window.get_width(), window.get_height() thumb_width, thumb_height = style.zoom(100), style.zoom(80) thumb_surface = Gdk.Window.create_similar_surface( window, cairo.CONTENT_COLOR, thumb_width, thumb_height) cairo_context = cairo.Context(thumb_surface) thumb_scale_w = thumb_width * 1.0 / width thumb_scale_h = thumb_height * 1.0 / height cairo_context.scale(thumb_scale_w, thumb_scale_h) Gdk.cairo_set_source_window(cairo_context, window, 0, 0) cairo_context.paint() thumb_str = StringIO.StringIO() thumb_surface.write_to_png(thumb_str) return thumb_str.getvalue() def can_close(self): if self._force_close: return True elif downloadmanager.can_quit(): return True else: alert = Alert() alert.props.title = ngettext('Download in progress', 'Downloads in progress', downloadmanager.num_downloads()) message = ngettext('Stopping now will erase your download', 'Stopping now will erase your downloads', downloadmanager.num_downloads()) alert.props.msg = message cancel_icon = Icon(icon_name='dialog-cancel') cancel_label = ngettext('Continue download', 'Continue downloads', downloadmanager.num_downloads()) alert.add_button(Gtk.ResponseType.CANCEL, cancel_label, cancel_icon) stop_icon = Icon(icon_name='dialog-ok') alert.add_button(Gtk.ResponseType.OK, _('Stop'), stop_icon) stop_icon.show() self.add_alert(alert) alert.connect('response', self.__inprogress_response_cb) alert.show() self.present() return False def __inprogress_response_cb(self, alert, response_id): self.remove_alert(alert) if response_id is Gtk.ResponseType.CANCEL: logging.debug('Keep on') elif response_id == Gtk.ResponseType.OK: logging.debug('Stop downloads and quit') self._force_close = True downloadmanager.remove_all_downloads() self.close() def __switch_page_cb(self, tabbed_view, page, page_num): browser = page._browser status = browser.get_load_status() if status in (WebKit.LoadStatus.COMMITTED, WebKit.LoadStatus.FIRST_VISUALLY_NON_EMPTY_LAYOUT): self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.WATCH)) elif status in (WebKit.LoadStatus.PROVISIONAL, WebKit.LoadStatus.FAILED, WebKit.LoadStatus.FINISHED): self.get_window().set_cursor(Gdk.Cursor(Gdk.CursorType.LEFT_PTR)) def get_document_path(self, async_cb, async_err_cb): browser = self._tabbed_view.props.current_browser browser.get_source(async_cb, async_err_cb) def get_canvas(self): return self._tabbed_view
class RulerActivity(activity.Activity): def __init__(self, handle): super(RulerActivity, self).__init__(handle) self.button_dict = {} self.callback_dict = {} self._ready = False font = 'helvetica 12' font_bold = 'helvetica bold 12' # # We need a canvas # self._canvas = MyCanvas() self.set_canvas(self._canvas) self._canvas.show() screen = GdkX11.X11Screen() width = screen.width() height = screen.height() - GRID_CELL_SIZE dpi, self.known_dpi = calc_dpi() self._canvas.set_dpi(dpi) # Create instances of our graphics self._r = show_rulers.ScreenOfRulers(font, font_bold, width, height) self._gcm = show_grids.ScreenGrid_cm(font, font_bold, width, height) self._gmm = show_grids.ScreenGrid_mm(font, font_bold, width, height) self._a90 = show_angles.Angles90(font, font_bold, width, height) self._a360 = show_angles.Angles360(font, font_bold, width, height) self._c = show_checkers.ScreenOfCircles(font, font_bold, width, height) # start with a ruler self._current = self._r self._canvas.add_a_ruler(self._current) # other settings self._grids_mode = "cm" self._angles_mode = "90" # # We need some toolbars # self.max_participants = 1 toolbar_box = ToolbarBox() # Buttons added to the Activity toolbar activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(activity_button, 0) activity_button.show() self.rulers = radio_factory('ruler', toolbar_box.toolbar, self._rulers_cb, tooltip=_('Ruler'), group=None) self.grids = radio_factory('grid-a', toolbar_box.toolbar, self._grids_cb, tooltip=_('Grid'), group=self.rulers) self.angles = radio_factory('angles-90', toolbar_box.toolbar, self._angles_cb, tooltip=_('Angles'), group=self.rulers) self.checker = radio_factory('checker', toolbar_box.toolbar, self._checker_cb, tooltip=_('Checker'), group=self.rulers) self.wrapper = Gtk.ToolItem() self.wrapper2 = Gtk.ToolItem() self.wrapper3 = Gtk.ToolItem() self.custom_unit_entry = Gtk.Entry() self.txt1 = Gtk.Label() self.txt1.set_text(_('1 custom unit equals ')) self.txt2 = Gtk.Label() # TRANS: mm is for Milli Meters self.txt2.set_text(_(' mm.')) self.wrapper.add(self.txt1) self.wrapper2.add(self.custom_unit_entry) self.wrapper3.add(self.txt2) self.wrapper.show_all() self.wrapper2.show_all() self.wrapper3.show_all() separator = Gtk.SeparatorToolItem() separator.props.draw = True separator.set_expand(False) separator.show() toolbar_box.toolbar.insert(separator, -1) custom_units_toolbox = ToolbarBox() custom_units_toolbox.toolbar.insert(self.wrapper, -1) custom_units_toolbox.toolbar.insert(self.wrapper2, -1) custom_units_toolbox.toolbar.insert(self.wrapper3, -1) custom_units_toolbox.show() self.custom_units_button = ToolbarButton(icon_name='view-source', page=custom_units_toolbox) toolbar_box.toolbar.insert(self.custom_units_button, -1) self.custom_unit_entry.connect('changed', self.custom_unit_change_cb) self.custom_units_button.show() if not self.known_dpi: separator = Gtk.SeparatorToolItem() separator.show() toolbar_box.toolbar.insert(separator, -1) dpi = self._canvas.get_dpi() self._dpi_spin_adj = Gtk.Adjustment(dpi, 72, 200, 2, 32, 0) self._dpi_spin = Gtk.SpinButton(self._dpi_spin_adj, 0, 0) self._dpi_spin_id = self._dpi_spin.connect('value-changed', self._dpi_spin_cb) self._dpi_spin.set_numeric(True) self._dpi_spin.show() self.tool_item_dpi = Gtk.ToolItem() self.tool_item_dpi.add(self._dpi_spin) toolbar_box.toolbar.insert(self.tool_item_dpi, -1) self.tool_item_dpi.show() separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(True) separator.show() toolbar_box.toolbar.insert(separator, -1) # The ever-present Stop Button stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>Q' toolbar_box.toolbar.insert(stop_button, -1) stop_button.show() self.set_toolbar_box(toolbar_box) toolbar_box.show() self.show_all() # Restore state if previously saved self._ready = True if 'ruler' in self.metadata and \ self.metadata['ruler'] in self.button_dict: _logger.debug('restoring %s', self.metadata['ruler']) self.button_dict[self.metadata['ruler']].set_active(True) self.callback_dict[self.metadata['ruler']] else: self._rulers_cb() self.rulers.set_active(True) if 'custom_unit' in self.metadata: self.custom_unit_entry.set_text(str(self.metadata['custom_unit'])) else: # set the default self.custom_unit_entry.set_text("25.4") # # Button callbacks # def _rulers_cb(self, button=None): if self._ready: self.custom_units_button.set_sensitive(True) self._current = self._r self._canvas.add_a_ruler(self._current) _logger.debug('selecting ruler') self.metadata['ruler'] = 'ruler' return False def custom_unit_change_cb(self, widget): try: new = float(widget.get_text()) except ValueError: new = MMPERINCH new = abs(new) if new == 0: new = MMPERINCH if widget.get_text != '': widget.set_text(str(new)) self._canvas.add_a_ruler(self._r) self._r.custom_unit_in_mm = new self._r.draw_custom_ruler(self._r.custom_unit_in_mm) self.metadata['custom_unit'] = new def _grids_cb(self, button=None): if self._ready: self.custom_units_button.set_sensitive(False) self.custom_units_button.set_expanded(False) if self._grids_mode == "cm": self._current = self._gcm if hasattr(self, 'grids'): self.grids.set_icon_name("grid-c") self._grids_mode = "mm" else: self._current = self._gmm if hasattr(self, 'grids'): self.grids.set_icon_name("grid-a") self._grids_mode = "cm" self._canvas.add_a_ruler(self._current) _logger.debug('selecting grids') self.metadata['ruler'] = 'grids' return False def _angles_cb(self, button=None): if self._ready: self.custom_units_button.set_sensitive(False) self.custom_units_button.set_expanded(False) if self._angles_mode == "90": self._current = self._a90 if hasattr(self, 'angles'): self.angles.set_icon_name("angles-360") self._angles_mode = "360" else: self._current = self._a360 if hasattr(self, 'angles'): self.angles.set_icon_name("angles-90") self._angles_mode = "90" self._canvas.add_a_ruler(self._current) _logger.debug('selecting angles') self.metadata['ruler'] = 'angles' return False def _checker_cb(self, button=None): if self._ready: self.custom_units_button.set_sensitive(False) self.custom_units_button.set_expanded(False) self._current = self._c self._canvas.add_a_ruler(self._current) _logger.debug('selecting checker') self.metadata['ruler'] = 'checker' return False def _dpi_spin_cb(self, button): self._canvas.set_dpi(self._dpi_spin.get_value_as_int()) self._canvas.add_a_ruler(self._current) return def write_file(self, file_path): ''' Write the dpi to the Journal ''' dpi = self._canvas.get_dpi() _logger.debug("Write dpi: " + str(dpi)) self.metadata['dpi'] = str(dpi)
class AbacusActivity(activity.Activity): def __init__(self, handle): ''' Initiate activity. ''' super(AbacusActivity, self).__init__(handle) self._setting_up = True self.bead_colors = profile.get_color().to_string().split(',') # no sharing self.max_participants = 1 self.sep = [] self.abacus_toolbar = Gtk.Toolbar() custom_toolbar = Gtk.Toolbar() edit_toolbar = Gtk.Toolbar() toolbox = ToolbarBox() activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() edit_toolbar_button = ToolbarButton(label=_('Edit'), page=edit_toolbar, icon_name='toolbar-edit') edit_toolbar_button.show() toolbox.toolbar.insert(edit_toolbar_button, -1) edit_toolbar_button.show() self.abacus_toolbar_button = ToolbarButton( page=self.abacus_toolbar, icon_name='abacus-list') self.abacus_toolbar.show() toolbox.toolbar.insert(self.abacus_toolbar_button, -1) self.abacus_toolbar_button.show() self.custom_toolbar_button = ToolbarButton( page=custom_toolbar, icon_name='view-source') custom_toolbar.show() toolbox.toolbar.insert(self.custom_toolbar_button, -1) self.custom_toolbar_button.show() separator_factory(toolbox.toolbar, False, True) button_factory('edit-delete', toolbox.toolbar, self._reset_cb, tooltip=_('Reset')) separator_factory(toolbox.toolbar, False, True) self._label = label_factory(NAMES['suanpan'], toolbox.toolbar) separator_factory(toolbox.toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = _('<Ctrl>Q') toolbox.toolbar.insert(stop_button, -1) stop_button.show() self.set_toolbar_box(toolbox) toolbox.show() self.abacus_buttons = {} # Traditional self._add_abacus_button('decimal', None) self._add_abacus_button('soroban', self.abacus_buttons['decimal']) self._add_abacus_button('suanpan', self.abacus_buttons['decimal']) self.sep.append(separator_factory(self.abacus_toolbar)) # Bases other than 10 self._add_abacus_button('nepohualtzintzin', self.abacus_buttons['decimal']) self._add_abacus_button('hexadecimal', self.abacus_buttons['decimal']) self._add_abacus_button('binary', self.abacus_buttons['decimal']) self.sep.append(separator_factory(self.abacus_toolbar)) # Fractions self._add_abacus_button('schety', self.abacus_buttons['decimal']) # self._add_abacus_button('fraction', self.abacus_buttons['decimal']) self._add_abacus_button('caacupe', self.abacus_buttons['decimal']) self.sep.append(separator_factory(self.abacus_toolbar)) # Non-traditional self._add_abacus_button('cuisenaire', self.abacus_buttons['decimal']) self.sep.append(separator_factory(self.abacus_toolbar)) # Custom self._add_abacus_button('custom', self.abacus_buttons['decimal']) preferences_button = ToolButton('preferences-system') preferences_button.set_tooltip(_('Custom')) custom_toolbar.insert(preferences_button, -1) preferences_button.palette_invoker.props.toggle_palette = True preferences_button.palette_invoker.props.lock_palette = True preferences_button.props.hide_tooltip_on_click = False preferences_button.show() self._palette = preferences_button.get_palette() button_box = Gtk.VBox() # TRANS: Number of rods on the abacus self._rods_spin = add_spinner_and_label( 15, 1, MAX_RODS, _('Rods:'), self._rods_spin_cb, button_box) # TRANS: Number of beads in the top section of the abacus self._top_spin = add_spinner_and_label( 2, 0, MAX_TOP, _('Top:'), self._top_spin_cb, button_box) # TRANS: Number of beads in the bottom section of the abacus self._bottom_spin = add_spinner_and_label( 5, 0, MAX_BOT, _('Bottom:'), self._bottom_spin_cb, button_box) # TRANS: Scale factor between bottom and top beads self._value_spin = add_spinner_and_label( 5, 1, MAX_BOT + 1, _('Factor:'), self._value_spin_cb, button_box) # TRANS: Scale factor between rods self._base_spin = add_spinner_and_label( 10, 1, (MAX_TOP + 1) * MAX_BOT, _('Base:'), self._base_spin_cb, button_box) hbox = Gtk.HBox() hbox.pack_start(button_box, True, True, style.DEFAULT_SPACING) hbox.show_all() self._palette.set_content(hbox) separator_factory(custom_toolbar, False, False) self.custom_maker = button_factory('new-abacus', custom_toolbar, self._custom_cb, tooltip=_('Custom')) button_factory('edit-copy', edit_toolbar, self._copy_cb, tooltip=_('Copy'), accelerator='<Ctrl>c') button_factory('edit-paste', edit_toolbar, self._paste_cb, tooltip=_('Paste'), accelerator='<Ctrl>v') # Create a canvas canvas = Gtk.DrawingArea() canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.set_canvas(canvas) canvas.show() self.show_all() # Initialize the canvas self.abacus = Abacus(canvas, self) self._setting_up = False # Read the current mode from the Journal if 'rods' in self.metadata: self._rods_spin.set_value(int(self.metadata['rods'])) if 'top' in self.metadata: self._top_spin.set_value(int(self.metadata['top'])) if 'bottom' in self.metadata: self._bottom_spin.set_value(int(self.metadata['bottom'])) if 'factor' in self.metadata: self._value_spin.set_value(int(self.metadata['factor'])) if 'base' in self.metadata: self._base_spin.set_value(int(self.metadata['base'])) if 'abacus' in self.metadata: if self.metadata['abacus'] in self.abacus_buttons: _logger.debug('restoring %s', self.metadata['abacus']) if self.metadata['abacus'] == 'custom': self._custom_cb() self.abacus_buttons[self.metadata['abacus']].set_active(True) else: # Default is Chinese self.abacus_buttons['suanpan'].set_active(True) if 'value' in self.metadata: _logger.debug('restoring value %s', self.metadata['value']) self.abacus.mode.set_value(self.metadata['value']) self.abacus.mode.label(self.abacus.generate_label()) self.abacus.init() # Start with abacus toolbar expanded and suanpan as default self.abacus_toolbar_button.set_expanded(True) def _add_abacus_button(self, name, group): self.abacus_buttons[name] = radio_factory( name, self.abacus_toolbar, self._radio_cb, cb_arg=name, tooltip=NAMES[name], group=group) def _radio_cb(self, button, abacus): self._select_abacus(abacus) def _reset_cb(self, button=None): self.abacus.mode.reset_abacus() self.abacus.mode.label(self.abacus.generate_label()) def _notify_new_abacus(self, prompt): ''' Loading a new abacus can be slooow, so alert the user. ''' # a busy cursor is adequate self.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH)) def _select_abacus(self, abacus): ''' Notify the user of an expected delay and then... ''' if not hasattr(self, 'abacus') or self._setting_up: _logger.debug('setting up') return # Not selected? if not self.abacus_buttons[abacus].get_active(): _logger.debug('%s not active' % abacus) return self._notify_new_abacus(NAMES[abacus]) # Give the cursor/alert time to load GObject.idle_add(self._switch_modes, abacus) def _switch_modes(self, abacus): ''' Display the selected abacus ''' _logger.debug('switching modes to %s', abacus) if abacus == self.abacus.mode.name: _logger.debug('do not switch already in the same mode') self.get_window().set_cursor(None) return # Save current value value = int(float(self.abacus.mode.value())) if abacus == 'custom' and self.abacus.custom is None: self.custom_toolbar_button.set_expanded(True) # self.abacus.mode = self.abacus.custom self.get_window().set_cursor(None) else: _logger.debug('switch_mode: setting abacus to %s' % abacus) self.abacus.select_abacus(abacus) # Load saved value self.abacus.mode.set_value_from_number(value) self.abacus.mode.label(self.abacus.generate_label()) self._label.set_text(NAMES[abacus]) self.get_window().set_cursor(None) def _rods_spin_cb(self, button=None): return def _top_spin_cb(self, button=None): return def _bottom_spin_cb(self, button=None): return def _value_spin_cb(self, button=None): return def _base_spin_cb(self, button=None): return def _custom_cb(self, button=None): ''' Display the custom abacus; hide the others ''' value = float(self.abacus.mode.value(count_beads=False)) self.abacus.mode.hide() if self.abacus.custom is not None: self.abacus.custom.hide() self.abacus.custom = Custom(self.abacus, self.abacus.bead_colors) self.abacus.custom.set_custom_parameters( rods=self._rods_spin.get_value_as_int(), top=self._top_spin.get_value_as_int(), bot=self._bottom_spin.get_value_as_int(), factor=self._value_spin.get_value_as_int(), base=self._base_spin.get_value_as_int()) self.abacus.custom.create() self.abacus.custom.draw_rods_and_beads() self.abacus.custom.show() self._label.set_text(NAMES['custom']) self.abacus.mode = self.abacus.custom self.abacus.mode_dict['custom'][0] = self.abacus.custom self.abacus_toolbar_button.set_expanded(True) self.abacus.mode.set_value_from_number(value) def _copy_cb(self, arg=None): ''' Copy a number to the clipboard from the active abacus. ''' clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) text = self.abacus.generate_label(sum_only=True) if text is not None: clipboard.set_text(text, -1) return def _paste_cb(self, arg=None): ''' Paste a number from the clipboard to the active abacus. ''' clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) text = clipboard.wait_for_text() if text is not None: try: self.abacus.mode.set_value_from_number(float(text)) except ValueError, e: _logger.debug(str(e)) return self.abacus.mode.label(self.abacus.generate_label()) return
class WebActivity(activity.Activity): def __init__(self, handle): activity.Activity.__init__(self, handle) self._collab = CollabWrapper(self) self._collab.message.connect(self.__message_cb) _logger.debug('Starting the web activity') # TODO PORT # session = WebKit2.get_default_session() # session.set_property('accept-language-auto', True) # session.set_property('ssl-use-system-ca-file', True) # session.set_property('ssl-strict', False) # But of a hack, but webkit doesn't let us change the cookie jar # contents, we we can just pre-seed it cookie_jar = SoupGNOME.CookieJarSqlite(filename=_cookies_db_path, read_only=False) _seed_xs_cookie(cookie_jar) del cookie_jar context = WebKit2.WebContext.get_default() cookie_manager = context.get_cookie_manager() cookie_manager.set_persistent_storage( _cookies_db_path, WebKit2.CookiePersistentStorage.SQLITE) # FIXME # downloadmanager.remove_old_parts() context.connect('download-started', self.__download_requested_cb) self._force_close = False self._tabbed_view = TabbedView(self) self._tabbed_view.connect('focus-url-entry', self._on_focus_url_entry) self._tabbed_view.connect('switch-page', self.__switch_page_cb) self._titled_tray = TitledTray(_('Bookmarks')) self._tray = self._titled_tray.tray self.set_tray(self._titled_tray, Gtk.PositionType.BOTTOM) self._tray_links = {} self.model = Model() self.model.add_link_signal.connect(self._add_link_model_cb) self._primary_toolbar = PrimaryToolbar(self._tabbed_view, self) self._edit_toolbar = EditToolbar(self) self._view_toolbar = ViewToolbar(self) self._primary_toolbar.connect('add-link', self.__link_add_button_cb) self._primary_toolbar.connect('remove-link', self.__link_remove_button_cb) self._primary_toolbar.connect('go-home', self._go_home_button_cb) self._primary_toolbar.connect('go-library', self._go_library_button_cb) self._primary_toolbar.connect('set-home', self._set_home_button_cb) self._primary_toolbar.connect('reset-home', self._reset_home_button_cb) self._edit_toolbar_button = ToolbarButton( page=self._edit_toolbar, icon_name='toolbar-edit') self._primary_toolbar.toolbar.insert( self._edit_toolbar_button, 1) view_toolbar_button = ToolbarButton( page=self._view_toolbar, icon_name='toolbar-view') self._primary_toolbar.toolbar.insert( view_toolbar_button, 2) self._primary_toolbar.show_all() self.set_toolbar_box(self._primary_toolbar) self.set_canvas(self._tabbed_view) self._tabbed_view.show() self.connect('key-press-event', self._key_press_cb) if handle.uri: self._tabbed_view.current_browser.load_uri(handle.uri) elif not self._jobject.file_path: # TODO: we need this hack until we extend the activity API for # opening URIs and default docs. self._tabbed_view.load_homepage() # README: this is a workaround to remove old temp file # http://bugs.sugarlabs.org/ticket/3973 self._cleanup_temp_files() self._collab.setup() def __download_requested_cb(self, context, download): if hasattr(self, 'busy'): while self.unbusy() > 0: continue logging.debug('__download_requested_cb %r', download.get_request().get_uri()) downloadmanager.add_download(download, self) return True def unfullscreen(self): activity.Activity.unfullscreen(self) # Always show tabs here, because they are automatically hidden # if it is like a video fullscreening. We ALWAYS need to make # sure these are visible. Don't make the user lost self._tabbed_view.props.show_tabs = True def _cleanup_temp_files(self): """Removes temporary files generated by Download Manager that were cancelled by the user or failed for any reason. There is a bug in GLib that makes this to happen: https://bugzilla.gnome.org/show_bug.cgi?id=629301 """ try: uptime_proc = open('/proc/uptime', 'r').read() uptime = int(float(uptime_proc.split()[0])) except EnvironmentError: logging.warning('/proc/uptime could not be read') uptime = None temp_path = os.path.join(self.get_activity_root(), 'instance') now = int(time.time()) cutoff = now - 24 * 60 * 60 # yesterday if uptime is not None: boot_time = now - uptime cutoff = max(cutoff, boot_time) for f in os.listdir(temp_path): if f.startswith('.goutputstream-'): fpath = os.path.join(temp_path, f) mtime = int(os.path.getmtime(fpath)) if mtime < cutoff: logging.warning('Removing old temporary file: %s', fpath) try: os.remove(fpath) except EnvironmentError: logging.error('Temporary file could not be ' 'removed: %s', fpath) def _on_focus_url_entry(self, gobject): self._primary_toolbar.entry.grab_focus() def _get_data_from_file_path(self, file_path): fd = open(file_path, 'r') try: data = fd.read() finally: fd.close() return data def _get_save_as(self): if not hasattr(profile, 'get_save_as'): return False return profile.get_save_as() def read_file(self, file_path): if self.metadata['mime_type'] == 'text/plain': data = self._get_data_from_file_path(file_path) self.model.deserialize(data) for link in self.model.data['shared_links']: _logger.debug('read: url=%s title=%s d=%s' % (link['url'], link['title'], link['color'])) self._add_link_totray(link['url'], b64decode(link['thumb']), link['color'], link['title'], link['owner'], -1, link['hash'], link.get('notes')) logging.debug('########## reading %s', data) if 'session_state' in self.model.data: self._tabbed_view.set_session_state( self.model.data['session_state']) else: self._tabbed_view.set_legacy_history( self.model.data['history'], self.model.data['currents']) for number, tab in enumerate(self.model.data['currents']): tab_page = self._tabbed_view.get_nth_page(number) zoom_level = tab.get('zoom_level') if zoom_level is not None: tab_page.browser.set_zoom_level(zoom_level) tab_page.browser.grab_focus() self._tabbed_view.set_current_page(self.model.data['current_tab']) elif self.metadata['mime_type'] == 'text/uri-list': data = self._get_data_from_file_path(file_path) uris = mime.split_uri_list(data) if len(uris) == 1: self._tabbed_view.props.current_browser.load_uri(uris[0]) else: _logger.error('Open uri-list: Does not support' 'list of multiple uris by now.') else: file_uri = 'file://' + file_path self._tabbed_view.props.current_browser.load_uri(file_uri) self._tabbed_view.props.current_browser.grab_focus() def write_file(self, file_path): if not hasattr(self, '_tabbed_view'): _logger.error('Called write_file before the tabbed_view was made') return if not self.metadata['mime_type']: self.metadata['mime_type'] = 'text/plain' if self.metadata['mime_type'] == 'text/plain': browser = self._tabbed_view.current_browser if not self._jobject.metadata['title_set_by_user'] == '1' and \ not self._get_save_as(): if browser.props.title is None: self.metadata['title'] = _('Untitled') else: self.metadata['title'] = browser.props.title self.model.data['history'] = self._tabbed_view.get_legacy_history() current_tab = self._tabbed_view.get_current_page() self.model.data['current_tab'] = current_tab self.model.data['currents'] = [] for n in range(0, self._tabbed_view.get_n_pages()): tab_page = self._tabbed_view.get_nth_page(n) n_browser = tab_page.browser if n_browser is not None: uri = n_browser.get_uri() history_index = n_browser.get_history_index() info = {'title': n_browser.props.title, 'url': uri, 'history_index': history_index, 'zoom_level': n_browser.get_zoom_level()} self.model.data['currents'].append(info) self.model.data['session_state'] = \ self._tabbed_view.get_state() f = open(file_path, 'w') try: logging.debug('########## writing %s', self.model.serialize()) f.write(self.model.serialize()) finally: f.close() def __link_add_button_cb(self, button): self._add_link() def __link_remove_button_cb(self, button): browser = self._tabbed_view.props.current_browser uri = browser.get_uri() self.__link_removed_cb(None, sha1(uri).hexdigest()) def _go_home_button_cb(self, button): self._tabbed_view.load_homepage() def _go_library_button_cb(self, button): self._tabbed_view.load_homepage(ignore_gconf=True) def _set_home_button_cb(self, button): self._tabbed_view.set_homepage() self._alert(_('The initial page was configured')) def _reset_home_button_cb(self, button): self._tabbed_view.reset_homepage() self._alert(_('The default initial page was configured')) def _alert(self, title, text=None): alert = NotifyAlert(timeout=5) 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) def _key_press_cb(self, widget, event): browser = self._tabbed_view.props.current_browser if event.get_state() & Gdk.ModifierType.CONTROL_MASK: if event.keyval == Gdk.KEY_f: self._edit_toolbar_button.set_expanded(True) self._edit_toolbar.search_entry.grab_focus() return True if event.keyval == Gdk.KEY_l: self._primary_toolbar.entry.grab_focus() return True if event.keyval == Gdk.KEY_equal: # On US keyboards, KEY_equal is KEY_plus without # SHIFT_MASK, so for convenience treat this as the # same as the zoom in accelerator configured in # WebKit2 browser.zoom_in() return True if event.keyval == Gdk.KEY_t: self._tabbed_view.add_tab() return True if event.keyval == Gdk.KEY_w: self._tabbed_view.close_tab() return True # FIXME: copy and paste is supposed to be handled by # Gtk.Entry, but does not work when we catch # key-press-event and return False. if self._primary_toolbar.entry.is_focus(): if event.keyval == Gdk.KEY_c: self._primary_toolbar.entry.copy_clipboard() return True if event.keyval == Gdk.KEY_v: self._primary_toolbar.entry.paste_clipboard() return True return False if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Down, Gdk.KEY_KP_Left, Gdk.KEY_KP_Right): scrolled_window = browser.get_parent() if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Down): adjustment = scrolled_window.get_vadjustment() elif event.keyval in (Gdk.KEY_KP_Left, Gdk.KEY_KP_Right): adjustment = scrolled_window.get_hadjustment() value = adjustment.get_value() step = adjustment.get_step_increment() if event.keyval in (Gdk.KEY_KP_Up, Gdk.KEY_KP_Left): adjustment.set_value(value - step) else: adjustment.set_value(value + step) return True if event.keyval == Gdk.KEY_Escape: browser.stop_loading() return False # allow toolbar entry to handle escape too return False def _add_link(self): ''' take screenshot and add link info to the model ''' browser = self._tabbed_view.props.current_browser ui_uri = browser.get_uri() if self.model.has_link(ui_uri): return buf = b64encode(self._get_screenshot()) timestamp = time.time() args = (ui_uri, browser.props.title, buf, profile.get_nick_name(), profile.get_color().to_string(), timestamp) self.model.add_link(*args, by_me=True) self._collab.post({'type': 'add_link', 'args': args}) def __message_cb(self, collab, buddy, message): type_ = message.get('type') if type_ == 'add_link': self.model.add_link(*message['args']) elif type_ == 'add_link_from_info': self.model.add_link_from_info(message['dict']) elif type_ == 'remove_link': self.remove_link(message['hash']) def get_data(self): return self.model.data def set_data(self, data): for link in data['shared_links']: if link['hash'] not in self.model.get_links_ids(): self.model.add_link_from_info(link) # FIXME: Case where buddy has updated link desciption their_model = Model() their_model.data = data for link in self.model.data['shared_links']: if link['hash'] not in their_model.get_links_ids(): self._collab.post({'type': 'add_link_from_info', 'dict': link}) def _add_link_model_cb(self, model, index, by_me): ''' receive index of new link from the model ''' link = self.model.data['shared_links'][index] widget = self._add_link_totray( link['url'], b64decode(link['thumb']), link['color'], link['title'], link['owner'], index, link['hash'], link.get('notes')) if by_me: animator = Animator(1, widget=self) animator.add(AddLinkAnimation( self, self._tabbed_view.props.current_browser, widget)) animator.start() def _add_link_totray(self, url, buf, color, title, owner, index, hash, notes=None): ''' add a link to the tray ''' item = LinkButton(buf, color, title, owner, hash, notes) item.connect('clicked', self._link_clicked_cb, url) item.connect('remove_link', self.__link_removed_cb) item.notes_changed_signal.connect(self.__link_notes_changed) # use index to add to the tray self._tray_links[hash] = item self._tray.add_item(item, index) item.show() self._view_toolbar.traybutton.props.sensitive = True self._view_toolbar.traybutton.props.active = True self._view_toolbar.update_traybutton_tooltip() return item def __link_removed_cb(self, button, hash): self.remove_link(hash) self._collab.post({'type': 'remove_link', 'hash': hash}) def remove_link(self, hash): ''' remove a link from tray and delete it in the model ''' self._tray_links[hash].hide() self._tray_links[hash].destroy() del self._tray_links[hash] self.model.remove_link(hash) if len(self._tray.get_children()) == 0: self._view_toolbar.traybutton.props.sensitive = False self._view_toolbar.traybutton.props.active = False self._view_toolbar.update_traybutton_tooltip() def __link_notes_changed(self, button, hash, notes): self.model.change_link_notes(hash, notes) def _link_clicked_cb(self, button, url): ''' an item of the link tray has been clicked ''' browser = self._tabbed_view.add_tab() browser.load_uri(url) browser.grab_focus() def _get_screenshot(self): browser = self._tabbed_view.props.current_browser window = browser.get_window() width, height = window.get_width(), window.get_height() thumb_surface = Gdk.Window.create_similar_surface( window, cairo.CONTENT_COLOR, THUMB_WIDTH, THUMB_HEIGHT) cairo_context = cairo.Context(thumb_surface) thumb_scale_w = THUMB_WIDTH * 1.0 / width thumb_scale_h = THUMB_HEIGHT * 1.0 / height cairo_context.scale(thumb_scale_w, thumb_scale_h) Gdk.cairo_set_source_window(cairo_context, window, 0, 0) cairo_context.paint() thumb_str = StringIO.StringIO() thumb_surface.write_to_png(thumb_str) return thumb_str.getvalue() def can_close(self): if self._force_close: return True elif downloadmanager.can_quit(): return True else: alert = Alert() alert.props.title = ngettext('Download in progress', 'Downloads in progress', downloadmanager.num_downloads()) message = ngettext('Stopping now will erase your download', 'Stopping now will erase your downloads', downloadmanager.num_downloads()) alert.props.msg = message cancel_icon = Icon(icon_name='dialog-cancel') cancel_label = ngettext('Continue download', 'Continue downloads', downloadmanager.num_downloads()) alert.add_button(Gtk.ResponseType.CANCEL, cancel_label, cancel_icon) stop_icon = Icon(icon_name='dialog-ok') alert.add_button(Gtk.ResponseType.OK, _('Stop'), stop_icon) stop_icon.show() self.add_alert(alert) alert.connect('response', self.__inprogress_response_cb) alert.show() self.present() return False def __inprogress_response_cb(self, alert, response_id): self.remove_alert(alert) if response_id is Gtk.ResponseType.CANCEL: logging.debug('Keep on') elif response_id == Gtk.ResponseType.OK: logging.debug('Stop downloads and quit') self._force_close = True downloadmanager.remove_all_downloads() self.close() def __switch_page_cb(self, tabbed_view, page, page_num): if not hasattr(self, 'busy'): return browser = page._browser progress = browser.props.estimated_load_progress uri = browser.props.uri if progress < 1.0 and uri: self.busy() else: while self.unbusy() > 0: continue def get_document_path(self, async_cb, async_err_cb): browser = self._tabbed_view.props.current_browser browser.get_source(async_cb, async_err_cb) def get_canvas(self): return self._tabbed_view
class AbacusActivity(activity.Activity): def __init__(self, handle): ''' Initiate activity. ''' super(AbacusActivity, self).__init__(handle) self._setting_up = True self.bead_colors = profile.get_color().to_string().split(',') # no sharing self.max_participants = 1 self.sep = [] self.abacus_toolbar = Gtk.Toolbar() custom_toolbar = Gtk.Toolbar() edit_toolbar = Gtk.Toolbar() toolbox = ToolbarBox() activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() edit_toolbar_button = ToolbarButton(label=_('Edit'), page=edit_toolbar, icon_name='toolbar-edit') edit_toolbar_button.show() toolbox.toolbar.insert(edit_toolbar_button, -1) edit_toolbar_button.show() self.abacus_toolbar_button = ToolbarButton( page=self.abacus_toolbar, icon_name='abacus-list') self.abacus_toolbar.show() toolbox.toolbar.insert(self.abacus_toolbar_button, -1) self.abacus_toolbar_button.show() self.custom_toolbar_button = ToolbarButton( page=custom_toolbar, icon_name='view-source') custom_toolbar.show() toolbox.toolbar.insert(self.custom_toolbar_button, -1) self.custom_toolbar_button.show() separator_factory(toolbox.toolbar, False, True) button_factory('edit-delete', toolbox.toolbar, self._reset_cb, tooltip=_('Reset')) separator_factory(toolbox.toolbar, False, True) self._label = label_factory(NAMES['suanpan'], toolbox.toolbar) separator_factory(toolbox.toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = _('<Ctrl>Q') toolbox.toolbar.insert(stop_button, -1) stop_button.show() self.set_toolbar_box(toolbox) toolbox.show() self.abacus_buttons = {} # Traditional self._add_abacus_button('decimal', None) self._add_abacus_button('soroban', self.abacus_buttons['decimal']) self._add_abacus_button('suanpan', self.abacus_buttons['decimal']) self.sep.append(separator_factory(self.abacus_toolbar)) # Bases other than 10 self._add_abacus_button('nepohualtzintzin', self.abacus_buttons['decimal']) self._add_abacus_button('hexadecimal', self.abacus_buttons['decimal']) self._add_abacus_button('binary', self.abacus_buttons['decimal']) self.sep.append(separator_factory(self.abacus_toolbar)) # Fractions self._add_abacus_button('schety', self.abacus_buttons['decimal']) # self._add_abacus_button('fraction', self.abacus_buttons['decimal']) self._add_abacus_button('caacupe', self.abacus_buttons['decimal']) self.sep.append(separator_factory(self.abacus_toolbar)) # Non-traditional self._add_abacus_button('cuisenaire', self.abacus_buttons['decimal']) self.sep.append(separator_factory(self.abacus_toolbar)) # Custom self._add_abacus_button('custom', self.abacus_buttons['decimal']) preferences_button = ToolButton('preferences-system') preferences_button.set_tooltip(_('Custom')) custom_toolbar.insert(preferences_button, -1) preferences_button.palette_invoker.props.toggle_palette = True preferences_button.palette_invoker.props.lock_palette = True preferences_button.props.hide_tooltip_on_click = False preferences_button.show() self._palette = preferences_button.get_palette() button_box = Gtk.VBox() # TRANS: Number of rods on the abacus self._rods_spin = add_spinner_and_label( 15, 1, MAX_RODS, _('Rods:'), self._rods_spin_cb, button_box) # TRANS: Number of beads in the top section of the abacus self._top_spin = add_spinner_and_label( 2, 0, MAX_TOP, _('Top:'), self._top_spin_cb, button_box) # TRANS: Number of beads in the bottom section of the abacus self._bottom_spin = add_spinner_and_label( 5, 0, MAX_BOT, _('Bottom:'), self._bottom_spin_cb, button_box) # TRANS: Scale factor between bottom and top beads self._value_spin = add_spinner_and_label( 5, 1, MAX_BOT + 1, _('Factor:'), self._value_spin_cb, button_box) # TRANS: Scale factor between rods self._base_spin = add_spinner_and_label( 10, 1, (MAX_TOP + 1) * MAX_BOT, _('Base:'), self._base_spin_cb, button_box) hbox = Gtk.HBox() hbox.pack_start(button_box, True, True, style.DEFAULT_SPACING) hbox.show_all() self._palette.set_content(hbox) separator_factory(custom_toolbar, False, False) self.custom_maker = button_factory('new-abacus', custom_toolbar, self._custom_cb, tooltip=_('Custom')) button_factory('edit-copy', edit_toolbar, self._copy_cb, tooltip=_('Copy'), accelerator='<Ctrl>c') button_factory('edit-paste', edit_toolbar, self._paste_cb, tooltip=_('Paste'), accelerator='<Ctrl>v') # Create a canvas canvas = Gtk.DrawingArea() canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.set_canvas(canvas) canvas.show() self.show_all() # Initialize the canvas self.abacus = Abacus(canvas, self) self._setting_up = False # Read the current mode from the Journal if 'rods' in self.metadata: self._rods_spin.set_value(int(self.metadata['rods'])) if 'top' in self.metadata: self._top_spin.set_value(int(self.metadata['top'])) if 'bottom' in self.metadata: self._bottom_spin.set_value(int(self.metadata['bottom'])) if 'factor' in self.metadata: self._value_spin.set_value(int(self.metadata['factor'])) if 'base' in self.metadata: self._base_spin.set_value(int(self.metadata['base'])) if 'abacus' in self.metadata: if self.metadata['abacus'] in self.abacus_buttons: _logger.debug('restoring %s', self.metadata['abacus']) if self.metadata['abacus'] == 'custom': self._custom_cb() self.abacus_buttons[self.metadata['abacus']].set_active(True) else: # Default is Chinese self.abacus_buttons['suanpan'].set_active(True) if 'value' in self.metadata: _logger.debug('restoring value %s', self.metadata['value']) self.abacus.mode.set_value(self.metadata['value']) self.abacus.mode.label(self.abacus.generate_label()) self.abacus.init() # Start with abacus toolbar expanded and suanpan as default self.abacus_toolbar_button.set_expanded(True) def _add_abacus_button(self, name, group): self.abacus_buttons[name] = radio_factory( name, self.abacus_toolbar, self._radio_cb, cb_arg=name, tooltip=NAMES[name], group=group) def _radio_cb(self, button, abacus): self._select_abacus(abacus) def _reset_cb(self, button=None): self.abacus.mode.reset_abacus() self.abacus.mode.label(self.abacus.generate_label()) def _notify_new_abacus(self, prompt): ''' Loading a new abacus can be slooow, so alert the user. ''' # a busy cursor is adequate self.get_window().set_cursor(Gdk.Cursor.new(Gdk.CursorType.WATCH)) def _select_abacus(self, abacus): ''' Notify the user of an expected delay and then... ''' if not hasattr(self, 'abacus') or self._setting_up: _logger.debug('setting up') return # Not selected? if not self.abacus_buttons[abacus].get_active(): _logger.debug('%s not active' % abacus) return self._notify_new_abacus(NAMES[abacus]) # Give the cursor/alert time to load GObject.idle_add(self._switch_modes, abacus) def _switch_modes(self, abacus): ''' Display the selected abacus ''' _logger.debug('switching modes to %s', abacus) if abacus == self.abacus.mode.name: _logger.debug('do not switch already in the same mode') self.get_window().set_cursor(None) return # Save current value value = int(float(self.abacus.mode.value())) if abacus == 'custom' and self.abacus.custom is None: self.custom_toolbar_button.set_expanded(True) # self.abacus.mode = self.abacus.custom self.get_window().set_cursor(None) else: _logger.debug('switch_mode: setting abacus to %s' % abacus) self.abacus.select_abacus(abacus) # Load saved value self.abacus.mode.set_value_from_number(value) self.abacus.mode.label(self.abacus.generate_label()) self._label.set_text(NAMES[abacus]) self.get_window().set_cursor(None) def _rods_spin_cb(self, button=None): return def _top_spin_cb(self, button=None): return def _bottom_spin_cb(self, button=None): return def _value_spin_cb(self, button=None): return def _base_spin_cb(self, button=None): return def _custom_cb(self, button=None): ''' Display the custom abacus; hide the others ''' value = float(self.abacus.mode.value(count_beads=False)) self.abacus.mode.hide() if self.abacus.custom is not None: self.abacus.custom.hide() self.abacus.custom = Custom(self.abacus, self.abacus.bead_colors) self.abacus.custom.set_custom_parameters( rods=self._rods_spin.get_value_as_int(), top=self._top_spin.get_value_as_int(), bot=self._bottom_spin.get_value_as_int(), factor=self._value_spin.get_value_as_int(), base=self._base_spin.get_value_as_int()) self.abacus.custom.create() self.abacus.custom.draw_rods_and_beads() self.abacus.custom.show() self._label.set_text(NAMES['custom']) self.abacus.mode = self.abacus.custom self.abacus.mode_dict['custom'][0] = self.abacus.custom self.abacus_toolbar_button.set_expanded(True) self.abacus.mode.set_value_from_number(value) def _copy_cb(self, arg=None): ''' Copy a number to the clipboard from the active abacus. ''' clipBoard = Gtk.Clipboard() text = self.abacus.generate_label(sum_only=True) if text is not None: clipBoard.set_text(text) return def _paste_cb(self, arg=None): ''' Paste a number from the clipboard to the active abacus. ''' clipBoard = Gtk.Clipboard() text = clipBoard.wait_for_text() if text is not None: try: self.abacus.mode.set_value_from_number(float(text)) except ValueError, e: _logger.debug(str(e)) return self.abacus.mode.label(self.abacus.generate_label()) return
class JukeboxActivity(activity.Activity): __gsignals__ = { 'playlist-finished': (GObject.SignalFlags.RUN_FIRST, None, []), } def __init__(self, handle): activity.Activity.__init__(self, handle) self.player = None self._alert = None self._playlist_jobject = None self.set_title(_('Jukebox Activity')) self.max_participants = 1 toolbar_box = ToolbarBox() activity_button = ActivityToolbarButton(self) activity_toolbar = activity_button.page toolbar_box.toolbar.insert(activity_button, 0) self.title_entry = activity_toolbar.title self._view_toolbar = ViewToolbar() self._view_toolbar.connect('go-fullscreen', self.__go_fullscreen_cb) self._view_toolbar.connect('toggle-playlist', self.__toggle_playlist_cb) view_toolbar_button = ToolbarButton(page=self._view_toolbar, icon_name='toolbar-view') self._view_toolbar.show() toolbar_box.toolbar.insert(view_toolbar_button, -1) view_toolbar_button.show() self._control_toolbar = Gtk.Toolbar() self._control_toolbar_button = ToolbarButton( page=self._control_toolbar, icon_name='media-playback-start') self._control_toolbar.show() toolbar_box.toolbar.insert(self._control_toolbar_button, -1) self._control_toolbar_button.hide() self.set_toolbar_box(toolbar_box) toolbar_box.show_all() self.connect('key_press_event', self.__key_press_event_cb) self.connect('playlist-finished', self.__playlist_finished_cb) # We want to be notified when the activity gets the focus or # loses it. When it is not active, we don't need to keep # reproducing the video self.connect('notify::active', self.__notify_active_cb) self._video_canvas = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) self._playlist_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.playlist_widget = PlayList() self.playlist_widget.connect('play-index', self.__play_index_cb) self.playlist_widget.connect('missing-tracks', self.__missing_tracks_cb) self.playlist_widget.set_size_request( Gdk.Screen.width() * PLAYLIST_WIDTH_PROP, 0) self.playlist_widget.show() self._playlist_box.pack_start(self.playlist_widget, expand=True, fill=True, padding=0) self._playlist_toolbar = Gtk.Toolbar() move_up = ToolButton("go-up") move_up.set_tooltip(_("Move up")) move_up.connect("clicked", self._move_up_cb) self._playlist_toolbar.insert(move_up, 0) move_down = ToolButton("go-down") move_down.set_tooltip(_("Move down")) move_down.connect("clicked", self._move_down_cb) self._playlist_toolbar.insert(move_down, 1) self._playlist_box.pack_end(self._playlist_toolbar, False, False, 0) self._video_canvas.pack_start(self._playlist_box, False, False, 0) # Create the player just once logging.debug('Instantiating GstPlayer') self.player = GstPlayer() self.player.connect('eos', self.__player_eos_cb) self.player.connect('error', self.__player_error_cb) self.player.connect('play', self.__player_play_cb) self.control = Controls(self, toolbar_box.toolbar, self._control_toolbar) self._separator = Gtk.SeparatorToolItem() self._separator.props.draw = False self._separator.set_expand(True) self._separator.show() toolbar_box.toolbar.insert(self._separator, -1) self._stop = StopButton(self) toolbar_box.toolbar.insert(self._stop, -1) self._empty_widget = Gtk.Label(label="") self._empty_widget.show() self.videowidget = VideoWidget() self.set_canvas(self._video_canvas) self._init_view_area() self.show_all() # need hide the playlist by default self._playlist_box.hide() self._configure_cb() self.player.init_view_area(self.videowidget) self._volume_monitor = Gio.VolumeMonitor.get() self._volume_monitor.connect('mount-added', self.__mount_added_cb) self._volume_monitor.connect('mount-removed', self.__mount_removed_cb) if handle.object_id is None: # The activity was launched from scratch. We need to show # the Empty Widget self.playlist_widget.hide() emptypanel.show(self, 'activity-jukebox', _('No media'), _('Choose media files'), self.control.show_picker_cb) self.control.check_if_next_prev() Gdk.Screen.get_default().connect('size-changed', self._configure_cb) def _move_up_cb(self, button): self.playlist_widget.move_up() def _move_down_cb(self, button): self.playlist_widget.move_down() def _configure_cb(self, event=None): toolbar = self.get_toolbar_box().toolbar toolbar.remove(self._stop) toolbar.remove(self._separator) if Gdk.Screen.width() < Gdk.Screen.height(): self._control_toolbar_button.show() self._control_toolbar_button.set_expanded(True) self.control.update_layout(landscape=False) toolbar.insert(self._separator, -1) else: self._control_toolbar_button.set_expanded(False) self._control_toolbar_button.hide() self.control.update_layout(landscape=True) toolbar.insert(self._stop, -1) def __notify_active_cb(self, widget, event): """Sugar notify us that the activity is becoming active or inactive. When we are inactive, we stop the player if it is reproducing a video. """ logging.debug('JukeboxActivity notify::active signal received') if self.player.player.props.current_uri is not None and \ self.player.playing_video(): if not self.player.is_playing() and self.props.active: self.player.play() if self.player.is_playing() and not self.props.active: self.player.pause() def _init_view_area(self): """ Use a notebook with two pages, one empty an another with the videowidget """ self.view_area = Gtk.Notebook() self.view_area.set_show_tabs(False) self.view_area.append_page(self._empty_widget, None) self.view_area.append_page(self.videowidget, None) self._video_canvas.pack_end(self.view_area, expand=True, fill=True, padding=0) def _switch_canvas(self, show_video): """Show or hide the video visualization in the canvas. When hidden, the canvas is filled with an empty widget to ensure redrawing. """ if show_video: self.view_area.set_current_page(1) else: self.view_area.set_current_page(0) self._video_canvas.queue_draw() def __key_press_event_cb(self, widget, event): keyname = Gdk.keyval_name(event.keyval) logging.info("Keyname Press: %s, time: %s", keyname, event.time) if self.title_entry.has_focus(): return False if keyname == "space": self.control._button_clicked_cb(None) return True def __playlist_finished_cb(self, widget): self._switch_canvas(show_video=False) self._view_toolbar._show_playlist.set_active(True) self.unfullscreen() # Select the first stream to be played when Play button will # be pressed self.playlist_widget.set_current_playing(0) self.control.check_if_next_prev() def songchange(self, direction): current_playing = self.playlist_widget.get_current_playing() if direction == 'prev' and current_playing > 0: self.play_index(current_playing - 1) elif direction == 'next' and \ current_playing < len(self.playlist_widget._items) - 1: self.play_index(current_playing + 1) else: self.emit('playlist-finished') def play_index(self, index): # README: this line is no more necessary because of the # .playing_video() method # self._switch_canvas(show_video=True) self.playlist_widget.set_current_playing(index) path = self.playlist_widget._items[index]['path'] if self.playlist_widget.check_available_media(path): if self.playlist_widget.is_from_journal(path): path = self.playlist_widget.get_path_from_journal(path) self.control.check_if_next_prev() self.player.set_uri(path) self.player.play() else: self.songchange('next') def __play_index_cb(self, widget, index, path): # README: this line is no more necessary because of the # .playing_video() method # self._switch_canvas(show_video=True) self.playlist_widget.set_current_playing(index) if self.playlist_widget.is_from_journal(path): path = self.playlist_widget.get_path_from_journal(path) self.control.check_if_next_prev() self.player.set_uri(path) self.player.play() def __player_eos_cb(self, widget): self.songchange('next') def _show_error_alert(self, title, msg=None): self._alert = ErrorAlert() self._alert.props.title = title if msg is not None: self._alert.props.msg = msg self.add_alert(self._alert) self._alert.connect('response', self._alert_cancel_cb) self._alert.show() def __mount_added_cb(self, volume_monitor, device): logging.debug('Mountpoint added. Checking...') self.remove_alert(self._alert) self.playlist_widget.update() def __mount_removed_cb(self, volume_monitor, device): logging.debug('Mountpoint removed. Checking...') self.remove_alert(self._alert) self.playlist_widget.update() def __missing_tracks_cb(self, widget, tracks): self._show_missing_tracks_alert(tracks) def _show_missing_tracks_alert(self, tracks): self._alert = Alert() title = _('%s tracks not found.') % len(tracks) self._alert.props.title = title icon = Icon(icon_name='dialog-cancel') self._alert.add_button(Gtk.ResponseType.CANCEL, _('Dismiss'), icon) icon.show() icon = Icon(icon_name='dialog-ok') self._alert.add_button(Gtk.ResponseType.APPLY, _('Details'), icon) icon.show() self.add_alert(self._alert) self._alert.connect('response', self.__missing_tracks_alert_response_cb, tracks) def __missing_tracks_alert_response_cb(self, alert, response_id, tracks): if response_id == Gtk.ResponseType.APPLY: vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) vbox.props.valign = Gtk.Align.CENTER label = Gtk.Label(label='') label.set_markup(_('<b>Missing tracks</b>')) vbox.pack_start(label, False, False, 15) for track in tracks: label = Gtk.Label(label=track['path']) vbox.add(label) _missing_tracks = Gtk.ScrolledWindow() _missing_tracks.add_with_viewport(vbox) _missing_tracks.show_all() self.view_area.append_page(_missing_tracks, None) self.view_area.set_current_page(2) self.remove_alert(alert) def _alert_cancel_cb(self, alert, response_id): self.remove_alert(alert) def __player_play_cb(self, widget): # Do not show the visualization widget if we are playing just # an audio stream def callback(): if self.player.playing_video(): self._switch_canvas(True) else: self._switch_canvas(False) return False # HACK: we need a timeout here because gstreamer returns # n-video = 0 if we call it immediately GObject.timeout_add(1000, callback) def __player_error_cb(self, widget, message, detail): self.player.stop() self.control.set_disabled() logging.error('ERROR MESSAGE: %s', message) logging.error('ERROR DETAIL: %s', detail) file_path = self.playlist_widget._items[ self.playlist_widget.get_current_playing()]['path'] mimetype = mime.get_for_file(file_path) title = _('Error') msg = _('This "%s" file can\'t be played') % mimetype self._switch_canvas(False) self._show_error_alert(title, msg) def can_close(self): # We need to put the Gst.State in NULL so gstreamer can # cleanup the pipeline self.player.stop() return True def read_file(self, file_path): """Load a file from the datastore on activity start.""" logging.debug('JukeBoxAtivity.read_file: %s', file_path) title = self.metadata['title'] self.playlist_widget.load_file(file_path, title) self._view_toolbar._show_playlist.set_active(True) def write_file(self, file_path): def write_playlist_to_file(file_path): """Open the file at file_path and write the playlist. It is saved in audio/x-mpegurl format. """ list_file = open(file_path, 'w') for uri in self.playlist_widget._items: list_file.write('#EXTINF:%s\n' % uri['title']) list_file.write('%s\n' % uri['path']) list_file.close() if not self.metadata['mime_type']: self.metadata['mime_type'] = 'audio/x-mpegurl' if self.metadata['mime_type'] == 'audio/x-mpegurl': write_playlist_to_file(file_path) else: if self._playlist_jobject is None: self._playlist_jobject = \ self.playlist_widget.create_playlist_jobject() # Add the playlist to the playlist jobject description. # This is only done if the activity was not started from a # playlist or from scratch: description = '' for uri in self.playlist_widget._items: description += '%s\n' % uri['title'] self._playlist_jobject.metadata['description'] = description write_playlist_to_file(self._playlist_jobject.file_path) datastore.write(self._playlist_jobject) def __go_fullscreen_cb(self, toolbar): self.fullscreen() def __toggle_playlist_cb(self, toolbar): if self._view_toolbar._show_playlist.get_active(): self._playlist_box.show_all() else: self._playlist_box.hide() self._video_canvas.queue_draw()
class LetterMatch(activity.Activity): ''' Learning the alphabet. Level1: A letter card and six picture cards appear; the user listens to the name of letter and then selects the matching picture. Level2: A picture card and six letter cards appear; the user listens to the name of the picture and then selects the matching letter. Customization toolbar allows loading of new images and sounds. ''' def __init__(self, handle): ''' Initialize the toolbars and the reading board ''' super(LetterMatch, self).__init__(handle) self.datapath = get_path(activity, 'instance') self.image_id = None self.audio_id = None if 'LANG' in os.environ: language = os.environ['LANG'][0:2] elif 'LANGUAGE' in os.environ: language = os.environ['LANGUAGE'][0:2] else: language = 'es' # default to Spanish # FIXME: find some reasonable default situation language = 'es' self.letter = None self.activity_path = activity.get_bundle_path() self._lessons_path = os.path.join(self.activity_path, 'lessons', language) self._images_path = os.path.join(self.activity_path, 'images', language) self._sounds_path = os.path.join(self.activity_path, 'sounds', language) self.data_from_journal = {} if 'data_from_journal' in self.metadata: self.data_from_journal = json.loads( str(self.metadata['data_from_journal'])) self._setup_toolbars() self.canvas = Gtk.DrawingArea() self.canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.canvas.modify_bg(Gtk.StateType.NORMAL, Gdk.color_parse("#000000")) self.canvas.show() self.set_canvas(self.canvas) self.mode = 'letter' self._page = Page(self.canvas, self._lessons_path, self._images_path, self._sounds_path, parent=self) def _setup_toolbars(self): self.max_participants = 1 # no sharing toolbox = ToolbarBox() activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() separator = Gtk.SeparatorToolItem() toolbox.toolbar.insert(separator, -1) self.set_toolbar_box(toolbox) toolbox.show() primary_toolbar = toolbox.toolbar custom_toolbar = ToolbarBox() self.custom_toolbar_button = ToolbarButton(icon_name='view-source', page=custom_toolbar) self.custom_toolbar_button.connect('clicked', self._customization_toolbar_cb) toolbox.toolbar.insert(self.custom_toolbar_button, -1) button = radio_factory('letter', primary_toolbar, self._letter_cb, tooltip=_('listen to the letter names')) radio_factory('picture', primary_toolbar, self._picture_cb, tooltip=_('listen to the letter names'), group=button) self.status = label_factory(primary_toolbar, '', width=300) self.letter_entry = None self.image_button = button_factory('load_image_from_journal', custom_toolbar.toolbar, self._choose_image_from_journal_cb, tooltip=_("Import Image")) self.sound_button = button_factory('load_audio_from_journal', custom_toolbar.toolbar, self._choose_audio_from_journal_cb, tooltip=_("Import Audio")) container = Gtk.ToolItem() self.letter_entry = Gtk.Entry() self.letter_entry.set_max_length(1) self.letter_entry.set_width_chars(3) # because 1 char looks funny self.letter_entry.connect('changed', self._set_letter) self.letter_entry.set_sensitive(False) self.letter_entry.show() container.add(self.letter_entry) container.show_all() custom_toolbar.toolbar.insert(container, -1) self.add_button = button_factory('list-add', custom_toolbar.toolbar, self._copy_to_journal, tooltip=_("Add")) self.add_button.set_sensitive(False) separator_factory(primary_toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() def _set_letter(self, event): ''' Process letter in text entry ''' text = self.letter_entry.get_text().strip() if text and len(text) > 0: if len(text) != 1: text = text[0].upper() text = text.upper() self.letter_entry.set_text(text) self.letter = text if self.letter in self.data_from_journal: self.data_from_journal[self.letter].append( (self.image_id, self.audio_id)) else: self.data_from_journal[self.letter] = \ [(self.image_id, self.audio_id)] self.add_button.set_sensitive(True) else: self.letter = None self.add_button.set_sensitive(False) def _copy_to_journal(self, event): ''' Callback from add button on customization toolbar ''' # Save data to journal and load it into the card database self.metadata['data_from_journal'] = json.dumps(self.data_from_journal) self._page.load_from_journal(self.data_from_journal) # Reinit the preview, et al. after add self.preview_image.hide() self._init_preview() self.image_id = None self.object_id = None self.letter_entry.set_text('') self.letter_entry.set_sensitive(False) self.add_button.set_sensitive(False) def _init_preview(self): ''' Set up customization toolbar, preview image ''' w = int(self._page._card_width) h = int(self._page._card_height) x = int(self._page._grid_x_offset + w + 12) y = int(self._page._grid_y_offset + 40) pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( os.path.join(self._images_path, '../drawing.png'), w, h) self.status.set_text( _('Please choose image and audio objects from the Journal.')) self._page._hide_cards() if not hasattr(self, 'preview_image'): self.preview_image = Sprite(self._page._sprites, 0, 0, pixbuf) else: self.preview_image.set_image(pixbuf) self.preview_image.move((x, y)) self.preview_image.set_layer(100) self._page._canvas.disconnect(self._page.button_press_event_id) self._page._canvas.disconnect(self._page.button_release_event_id) self._page.button_press_event_id = \ self._page._canvas.connect('button-press-event', self._preview_press_cb) self._page.button_release_event_id = \ self._page._canvas.connect('button-release-event', self._dummy_cb) def _customization_toolbar_cb(self, event): ''' Override toolbar button behavior ''' if self.custom_toolbar_button.is_expanded(): self._init_preview() else: if self.mode == 'letter': self._letter_cb() else: self._picture_cb() def _preview_press_cb(self, win, event): ''' Preview image was clicked ''' self._choose_image_from_journal_cb(None) def _dummy_cb(self, win, event): '''Does nothing''' return True def _choose_audio_from_journal_cb(self, event): ''' Create a chooser for audio objects ''' self.add_button.set_sensitive(False) self.letter_entry.set_sensitive(False) self.image_button.set_sensitive(False) self.sound_button.set_sensitive(False) self.audio_id = None chooser = ObjectChooser(what_filter=mime.GENERIC_TYPE_AUDIO) result = chooser.run() if result == Gtk.ResponseType.ACCEPT: jobject = chooser.get_selected_object() self.audio_id = str(jobject._object_id) self.image_button.set_sensitive(True) self.sound_button.set_sensitive(True) if self.image_id and self.audio_id: self.letter_entry.set_sensitive(True) self._page._canvas.disconnect(self._page.button_press_event_id) self._page.button_press_event_id = \ self._page._canvas.connect('button-press-event', self._play_audio_cb) def _play_audio_cb(self, win, event): ''' Preview audio ''' if self.audio_id: play_audio_from_file(datastore.get(self.audio_id).get_file_path()) def _choose_image_from_journal_cb(self, event): ''' Create a chooser for image objects ''' self.add_button.set_sensitive(False) self.letter_entry.set_sensitive(False) self.image_button.set_sensitive(False) self.sound_button.set_sensitive(False) self.image_id = None chooser = ObjectChooser(what_filter=mime.GENERIC_TYPE_IMAGE) result = chooser.run() if result == Gtk.ResponseType.ACCEPT: jobject = chooser.get_selected_object() self.image_id = str(jobject._object_id) x = self._page._grid_x_offset + self._page._card_width + 12 y = self._page._grid_y_offset + 40 w = self._page._card_width h = self._page._card_height pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( jobject.get_file_path(), w, h) self.preview_image.set_image(pixbuf) self.preview_image.move((x, y)) self.preview_image.set_layer(100) self.image_button.set_sensitive(True) self.sound_button.set_sensitive(True) if self.image_id and self.audio_id: self.letter_entry.set_sensitive(True) self._page._canvas.disconnect(self._page.button_press_event_id) self._page.button_press_event_id = \ self._page._canvas.connect('button-press-event', self._play_audio_cb) def _cleanup_preview(self): ''' No longer previewing, so hide image and clean up callbacks ''' if hasattr(self, 'preview_image'): self.preview_image.hide() self._page._canvas.disconnect(self._page.button_press_event_id) self._page._canvas.disconnect(self._page.button_release_event_id) self._page.button_press_event_id = \ self._canvas.connect("button-press-event", self._page._button_press_cb) self._page.button_release_event_id = \ self._canvas.connect("button-release-event", self._page._button_release_cb) def _letter_cb(self, event=None): ''' Click on card to hear the letter name ''' if self.custom_toolbar_button.is_expanded(): self.custom_toolbar_button.set_expanded(False) self._cleanup_preview() self.mode = 'letter' self.status.set_text( _('Click on the picture that matches the letter.')) if hasattr(self, '_page'): self._page.new_page() return def _picture_cb(self, event=None): ''' Click on card to hear the letter name ''' if self.custom_toolbar_button.is_expanded(): self.custom_toolbar_button.set_expanded(False) self._cleanup_preview() self.mode = 'picture' self.status.set_text( _('Click on the letter that matches the picture.')) if hasattr(self, '_page'): self._page.new_page() return def write_file(self, file_path): ''' Write status to the Journal ''' if not hasattr(self, '_page'): return self.metadata['page'] = str(self._page.current_card)
def _setup_toolbars(self): ''' Setup the toolbars. ''' self.max_participants = 2 self.edit_toolbar = Gtk.Toolbar() self.view_toolbar = Gtk.Toolbar() self.adjust_toolbar = Gtk.Toolbar() self.custom_toolbar = Gtk.Toolbar() toolbox = ToolbarBox() activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() edit_toolbar_button = ToolbarButton(label=_("Edit"), page=self.edit_toolbar, icon_name='toolbar-edit') self.edit_toolbar.show() toolbox.toolbar.insert(edit_toolbar_button, -1) edit_toolbar_button.show() view_toolbar_button = ToolbarButton(label=_("View"), page=self.view_toolbar, icon_name='toolbar-view') self.view_toolbar.show() toolbox.toolbar.insert(view_toolbar_button, -1) view_toolbar_button.show() adjust_toolbar_button = ToolbarButton(label=_('Adjust'), page=self.adjust_toolbar, icon_name='preferences-system') self.adjust_toolbar.show() toolbox.toolbar.insert(adjust_toolbar_button, -1) adjust_toolbar_button.show() custom_toolbar_button = ToolbarButton(label=_("Custom"), page=self.custom_toolbar, icon_name='view-source') self.custom_toolbar.show() toolbox.toolbar.insert(custom_toolbar_button, -1) custom_toolbar_button.show() self.set_toolbar_box(toolbox) toolbox.show() self.toolbar = toolbox.toolbar adjust_toolbar_button.set_expanded(True) button_factory('edit-copy', self.edit_toolbar, self._copy_cb, tooltip=_('Copy'), accelerator='<Ctrl>c') button_factory('edit-paste', self.edit_toolbar, self._paste_cb, tooltip=_('Paste'), accelerator='<Ctrl>v') button_factory('view-fullscreen', self.view_toolbar, self.do_fullscreen_cb, tooltip=_('Fullscreen'), accelerator='<Alt>Return') button_factory('media-playback-start', self.view_toolbar, self._play_history_cb, tooltip=_('Play game history')) self.history_button = button_factory('list-numbered', self.view_toolbar, self._show_history_cb, tooltip=_('Show game history')) separator_factory(self.view_toolbar, False, True) label_factory(self.view_toolbar, _('White: ')) self.white_entry = entry_factory('', self.view_toolbar, tooltip=_("White's move")) separator_factory(self.view_toolbar, False, False) label_factory(self.view_toolbar, _('Black: ')) self.black_entry = entry_factory('', self.view_toolbar, tooltip=_("Black's move")) separator_factory(self.view_toolbar, False, True) skin_button1 = radio_factory('white-knight', self.view_toolbar, self.do_default_skin_cb, tooltip=_('Default pieces'), group=None) skin_button2 = radio_factory('white-knight-sugar', self.view_toolbar, self.do_sugar_skin_cb, tooltip=_('Sugar-style pieces'), group=skin_button1) xocolors = XoColor(','.join(self.colors)) icon = Icon(icon_name='white-knight-sugar', xo_color=xocolors) icon.show() skin_button2.set_icon_widget(icon) self.skin_button3 = radio_factory('white-knight-custom', self.view_toolbar, self.do_custom_skin_cb, tooltip=_('Custom pieces'), group=skin_button1) skin_button1.set_active(True) self.play_white_button = radio_factory('white-rook', self.adjust_toolbar, self._play_white_cb, group=None, tooltip=_('Play White')) self.play_black_button = radio_factory('black-rook', self.adjust_toolbar, self._play_black_cb, group=self.play_white_button, tooltip=_('Play Black')) self.play_white_button.set_active(True) separator_factory(self.adjust_toolbar, False, True) self.easy_button = radio_factory('beginner', self.adjust_toolbar, self._easy_cb, group=None, tooltip=_('Beginner')) self.hard_button = radio_factory('expert', self.adjust_toolbar, self._hard_cb, group=self.easy_button, tooltip=_('Expert')) self.easy_button.set_active(True) separator_factory(self.adjust_toolbar, False, True) self.robot_button = radio_factory( 'robot', self.adjust_toolbar, self._robot_cb, group=None, tooltip=_('Play against the computer')) self.human_button = radio_factory('human', self.adjust_toolbar, self._human_cb, group=self.robot_button, tooltip=_('Play against a person')) separator_factory(self.adjust_toolbar, False, False) self.opponent = label_factory(self.adjust_toolbar, '') separator_factory(self.adjust_toolbar, False, True) self.timer_button = ToolButton('timer-0') self.timer_button.set_tooltip(_('Timer')) self.timer_button.connect('clicked', self._timer_button_cb) self.toolbar.insert(self.timer_button, -1) self._setup_timer_palette() self.timer_button.show() self.timer_button.set_sensitive(True) self.robot_button.set_active(True) button_factory('new-game', self.toolbar, self._new_gnuchess_cb, tooltip=_('New game')) button_factory('edit-undo', self.toolbar, self._undo_cb, tooltip=_('Undo')) button_factory('hint', self.toolbar, self._hint_cb, tooltip=_('Hint')) separator_factory(self.toolbar, False, False) self.status = label_factory(self.toolbar, '', width=150) self.status.set_label(_("It is White's move.")) separator_factory(toolbox.toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() for piece in list(PIECES.keys()): for color in ['white', 'black']: button_factory('%s-%s' % (color, piece), self.custom_toolbar, self._reskin_cb, cb_arg='%s_%s' % (color, piece), tooltip=PIECES[piece][color])
class NapierActivity(activity.Activity): ''' Napier's bones: Napier's bones were invented by John Napier (1550-1617), a Scottish mathematician and scientist. They help you to do multiplication. ''' # TODO: Define your own bone. def __init__(self, handle): ''' Initialize the toolbars and the work surface ''' super(NapierActivity, self).__init__(handle) if os.path.exists(os.path.join('~', 'Activities', 'Napier.activity')): self._bone_path = os.path.join('~', 'Activities', 'Napier.activity', 'bones') else: self._bone_path = os.path.join('.', 'bones') self._bones = [] self._bone_images = [None, None, None, None, None, None, None, None, None, None] self._blank_image = None self._number = 0 self._number_of_bones = 0 self._setup_toolbars() self._setup_canvas() self._circles = [None, None] self._ovals = [] self._setup_workspace() self._restore() def _setup_canvas(self): ''' Create a canvas ''' self._canvas = Gtk.DrawingArea() self._canvas.set_size_request(Gdk.Screen.width(), Gdk.Screen.height()) self.set_canvas(self._canvas) self._canvas.show() self.show_all() self._canvas.add_events(Gdk.EventMask.BUTTON_PRESS_MASK) self._canvas.add_events(Gdk.EventMask.BUTTON_RELEASE_MASK) self._canvas.add_events(Gdk.EventMask.POINTER_MOTION_MASK) self._canvas.connect("draw", self.__draw_cb) self._canvas.connect("motion-notify-event", self._mouse_move_cb) # self._canvas.connect("key_press_event", self._key_press_cb) def _setup_workspace(self): ''' Add the bones. ''' self._width = Gdk.Screen.width() self._height = int(Gdk.Screen.height() - (GRID_CELL_SIZE * 2)) self._scale = self._height * 1.0 / BONE_HEIGHT self._bone_width = int(BONE_WIDTH * self._scale) self._bone_height = int(BONE_HEIGHT * self._scale) # Generate the sprites we'll need... self._sprites = Sprites(self._canvas) self._bone_index = Sprite(self._sprites, 0, 0, _load_svg_from_file( os.path.join(self._bone_path, 'bones-index.svg'), self._bone_width, self._bone_height)) self._max_bones = int(self._width / self._bone_width) - 1 self._blank_image = _load_svg_from_file( os.path.join(self._bone_path, 'blank-bone.svg'), self._bone_width, self._bone_height) for bones in range(self._max_bones): self._bones.append(Sprite(self._sprites, bones * self._bone_width, 0, self._blank_image)) circle_image = _load_svg_from_file( os.path.join(self._bone_path, 'circle.svg'), int(self._scale * 45), int(self._scale * 45)) self._circles[0] = Sprite(self._sprites, 0, -100, circle_image) self._circles[1] = Sprite(self._sprites, 0, -100, circle_image) oval_image = _load_svg_from_file( os.path.join(self._bone_path, 'oval.svg'), int(self._scale * 129), int(self._scale * 92)) for bones in range(self._max_bones - 1): self._ovals.append(Sprite(self._sprites, 0, -100, oval_image)) def _setup_toolbars(self): ''' Setup the toolbars. ''' self.max_participants = 1 # no sharing toolbox = ToolbarBox() # Activity toolbar activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() self._bones_toolbar = Gtk.Toolbar() self._bones_toolbar_button = ToolbarButton(label=_('Select a bone'), page=self._bones_toolbar, icon_name='bones') self._bones_toolbar_button.show() toolbox.toolbar.insert(self._bones_toolbar_button, -1) self.set_toolbar_box(toolbox) toolbox.show() self.toolbar = toolbox.toolbar self._new_calc_button = button_factory( 'erase', self.toolbar, self._new_calc_cb, tooltip=_('Clear')) self._status = label_factory(self.toolbar, '') button_factory('number-0', self._bones_toolbar, self._number_cb, cb_arg=0, tooltip=_('zero')) button_factory('number-1', self._bones_toolbar, self._number_cb, cb_arg=1, tooltip=_('one')) button_factory('number-2', self._bones_toolbar, self._number_cb, cb_arg=2, tooltip=_('two')) button_factory('number-3', self._bones_toolbar, self._number_cb, cb_arg=3, tooltip=_('three')) button_factory('number-4', self._bones_toolbar, self._number_cb, cb_arg=4, tooltip=_('four')) button_factory('number-5', self._bones_toolbar, self._number_cb, cb_arg=5, tooltip=_('five')) button_factory('number-6', self._bones_toolbar, self._number_cb, cb_arg=6, tooltip=_('six')) button_factory('number-7', self._bones_toolbar, self._number_cb, cb_arg=7, tooltip=_('seven')) button_factory('number-8', self._bones_toolbar, self._number_cb, cb_arg=8, tooltip=_('eight')) button_factory('number-9', self._bones_toolbar, self._number_cb, cb_arg=9, tooltip=_('nine')) separator_factory(toolbox.toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>q' toolbox.toolbar.insert(stop_button, -1) stop_button.show() self._bones_toolbar_button.set_expanded(True) def _new_calc_cb(self, button=None): ''' Start a new calculation. ''' for bone in range(self._max_bones): self._bones[bone].set_shape(self._blank_image) self._bones[bone].inval() self._number_of_bones = 0 self._number = 0 self._status.set_label('') return def _number_cb(self, button=None, value=0): ''' Try to add a digit. ''' if self._number_of_bones == self._max_bones: return self._number_of_bones += 1 if self._bone_images[value] is None: self._bone_images[value] = _svg_str_to_pixbuf( _bone_factory(value, scale=self._scale)) self._bones[self._number_of_bones].set_shape(self._bone_images[value]) self._bones[self._number_of_bones].inval() self._number = self._number * 10 + value def _mouse_move_cb(self, win, event): ''' Determine which row we are in and then calculate the product. ''' win.grab_focus() x, y = map(int, event.get_coords()) factor = int(y / self._bone_width) # The row determines a factor if self._number == 0 or factor == 0: self._status.set_label('') self._circles[0].move((0, -100)) self._circles[1].move((0, -100)) for number in range(self._max_bones - 1): self._ovals[number].move((0, -100)) else: c0dx = int(4 * self._scale) c0dy = int(12 * self._scale) c1dx = int(44 * self._scale) c1dy = int(47 * self._scale) odx = int(42 * self._scale) ody = int(2 * self._scale) self._circles[0].move((self._bone_width + c0dx, factor * self._bone_width + c0dy)) self._circles[1].move(( self._number_of_bones * self._bone_width + c1dx, factor * self._bone_width + c1dy)) for number in range(self._number_of_bones - 1): self._ovals[number].move(((number + 1) * self._bone_width + odx, factor * self._bone_width + ody)) self._status.set_label('%d × %d = %d' % ( factor + 1, self._number, (factor + 1) * self._number)) return True def _key_press_cb(self, win, event): ''' TODO: Add bones by typing numbers ''' return True def __draw_cb(self, canvas, cr): self._sprites.redraw_sprites(cr=cr) def do_expose_event(self, event): ''' Handle the expose-event by drawing ''' # Restrict Cairo to the exposed area cr = self._canvas.window.cairo_create() cr.rectangle(event.area.x, event.area.y, event.area.width, event.area.height) cr.clip() # Refresh sprite list self._sprites.redraw_sprites(cr=cr) def _destroy_cb(self, win, event): Gtk.main_quit() def _restore(self): ''' Try to restore previous state. ''' if 'number' in self.metadata and self.metadata['number'] != '0': for digit in range(len(self.metadata['number'])): self._number_cb(button=None, value=int(self.metadata['number'][digit])) def write_file(self, file_path): ''' Write the status to the Journal. ''' if not hasattr(self, '_number'): return self.metadata['number'] = str(self._number)
def build_toolbar(self): self.max_participants = 4 toolbar_box = ToolbarBox() activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(activity_button, 0) activity_button.show() create_toolbar = ToolbarButton() create_toolbar.props.page = Gtk.Toolbar() create_toolbar.props.icon_name = 'magicpen' create_toolbar.props.label = _('Create') toolbar_box.toolbar.insert(create_toolbar, -1) self._insert_create_tools(create_toolbar) color = ColorToolButton('color') color.connect('notify::color', self.__color_notify_cb) toolbar_box.toolbar.insert(color, -1) color.show() random = ToggleToolButton('colorRandom') random.set_tooltip(_('Toggle random color')) toolbar_box.toolbar.insert(random, -1) random.set_active(True) random.connect('toggled', self.__random_toggled_cb) random.show() color.random = random random.color = color random.timeout_id = GLib.timeout_add(100, self.__timeout_cb, random) self._insert_stop_play_button(toolbar_box.toolbar) clear_trace = ToolButton('clear-trace') clear_trace.set_tooltip(_('Clear Trace Marks')) clear_trace.set_accelerator(_('<ctrl>x')) clear_trace.connect('clicked', self.clear_trace_cb) clear_trace.set_sensitive(False) toolbar_box.toolbar.insert(clear_trace, -1) clear_trace.show() self.clear_trace = clear_trace self._insert_clear_all_button(toolbar_box.toolbar) load_example = ToolButton('load-sample') load_example.set_tooltip(_('Show sample projects')) load_example.connect('clicked', self._create_store) toolbar_box.toolbar.insert(Gtk.SeparatorToolItem(), -1) toolbar_box.toolbar.insert(load_example, -1) load_example.show() separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_size_request(0, -1) separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) separator.show() stop = StopButton(self) toolbar_box.toolbar.insert(stop, -1) stop.show() separator = Gtk.SeparatorToolItem() activity_button.props.page.insert(separator, -1) separator.show() export_json = ToolButton('save-as-json') export_json.set_tooltip(_('Export tracked objects to journal')) export_json.connect('clicked', self._export_json_cb) activity_button.props.page.insert(export_json, -1) export_json.show() load_project = ToolButton('load-project') load_project.set_tooltip(_('Load project from journal')) load_project.connect('clicked', self._load_project) activity_button.props.page.insert(load_project, -1) load_project.show() self.set_toolbar_box(toolbar_box) toolbar_box.show_all() create_toolbar.set_expanded(True) return toolbar_box
def _setup_toolbars(self): """ Setup the toolbars.. """ project_toolbar = Gtk.Toolbar() custom_slide_toolbar = Gtk.Toolbar() custom_stator_toolbar = Gtk.Toolbar() edit_toolbar = Gtk.Toolbar() # no sharing self.max_participants = 1 toolbox = ToolbarBox() # Activity toolbar activity_button = ActivityToolbarButton(self) toolbox.toolbar.insert(activity_button, 0) activity_button.show() project_toolbar_button = ToolbarButton(page=project_toolbar, icon_name="sliderule") project_toolbar.show() toolbox.toolbar.insert(project_toolbar_button, -1) project_toolbar_button.show() custom_slide_toolbar_button = ToolbarButton(page=custom_slide_toolbar, icon_name="custom-slide") custom_slide_toolbar.show() toolbox.toolbar.insert(custom_slide_toolbar_button, -1) custom_slide_toolbar_button.show() custom_stator_toolbar_button = ToolbarButton(page=custom_stator_toolbar, icon_name="custom-stator") custom_stator_toolbar.show() toolbox.toolbar.insert(custom_stator_toolbar_button, -1) custom_stator_toolbar_button.show() edit_toolbar_button = ToolbarButton(label=_("Edit"), page=edit_toolbar, icon_name="toolbar-edit") edit_toolbar_button.show() toolbox.toolbar.insert(edit_toolbar_button, -1) edit_toolbar_button.show() self.set_toolbar_box(toolbox) toolbox.show() toolbar = toolbox.toolbar # Add the buttons to the toolbars self._function_combo = combo_factory( FUNCTIONS, project_toolbar, self._function_combo_cb, default=FC_multiply, tooltip=_("select function") ) self.top_button = button_factory("C", project_toolbar, self._dummy_cb, tooltip=_("active slide")) self._slide_combo = combo_factory( SLIDE_TABLE, project_toolbar, self._slide_combo_cb, default=C_slide, tooltip=_("select slide") ) self.bottom_button = button_factory("D", project_toolbar, self._dummy_cb, tooltip=_("active stator")) self._stator_combo = combo_factory( STATOR_TABLE, project_toolbar, self._stator_combo_cb, default=D_slide, tooltip=_("select stator") ) separator_factory(project_toolbar) self.realign_button = button_factory("realign", project_toolbar, self.realign_cb, tooltip=_("realign slides")) self._offset_function = [] self._calculate_function = [] self._label_function = [] self._domain_min = [] self._domain_max = [] self._step_size = [] self.custom = [] ENTRY = ["C", "D"] ENTRY_TOOLBAR = [custom_slide_toolbar, custom_stator_toolbar] ENTRY_BUTTON = ["custom-slide", "custom-stator"] ENTRY_TOOLTIP = [_("create custom slide"), _("create custom stator")] ENTRY_CALLBACK = [self._custom_slide_cb, self._custom_stator_cb] for i in range(2): self._offset_function.append( entry_factory(DEFINITIONS[ENTRY[i]][0], ENTRY_TOOLBAR[i], tooltip=_("position function"), max=10) ) self._calculate_function.append( entry_factory(DEFINITIONS[ENTRY[i]][1], ENTRY_TOOLBAR[i], tooltip=_("result function"), max=10) ) self._label_function.append( entry_factory(DEFINITIONS[ENTRY[i]][2], ENTRY_TOOLBAR[i], tooltip=_("label function"), max=10) ) self._domain_min.append( entry_factory(DEFINITIONS[ENTRY[i]][3], ENTRY_TOOLBAR[i], tooltip=_("domain minimum"), max=4) ) self._domain_max.append( entry_factory(DEFINITIONS[ENTRY[i]][4], ENTRY_TOOLBAR[i], tooltip=_("domain maximum"), max=4) ) self._step_size.append( entry_factory(DEFINITIONS[ENTRY[i]][5], ENTRY_TOOLBAR[i], tooltip=_("step size"), max=4) ) self.custom.append( button_factory(ENTRY_BUTTON[i], ENTRY_TOOLBAR[i], ENTRY_CALLBACK[i], tooltip=ENTRY_TOOLTIP[i]) ) copy = button_factory("edit-copy", edit_toolbar, self._copy_cb, tooltip=_("Copy"), accelerator="<Ctrl>c") paste = button_factory("edit-paste", edit_toolbar, self._paste_cb, tooltip=_("Paste"), accelerator="<Ctrl>v") separator_factory(toolbox.toolbar, True, False) stop_button = StopButton(self) stop_button.props.accelerator = "<Ctrl>q" toolbox.toolbar.insert(stop_button, -1) stop_button.show() # workaround to #2050 edit_toolbar_button.set_expanded(True) # start with project toolbar enabled project_toolbar_button.set_expanded(True)
def build_toolbar(self): self.max_participants = 4 toolbar_box = ToolbarBox() activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(activity_button, 0) activity_button.show() create_toolbar = ToolbarButton() create_toolbar.props.page = Gtk.Toolbar() create_toolbar.props.icon_name = 'magicpen' create_toolbar.props.label = _('Create') toolbar_box.toolbar.insert(create_toolbar, -1) self._insert_create_tools(create_toolbar) self._insert_stop_play_button(toolbar_box.toolbar) clear_trace = ToolButton('clear-trace') clear_trace.set_tooltip(_('Clear Trace Marks')) clear_trace.set_accelerator(_('<ctrl>x')) clear_trace.connect('clicked', self.clear_trace_cb) clear_trace.set_sensitive(False) toolbar_box.toolbar.insert(clear_trace, -1) clear_trace.show() self.clear_trace = clear_trace self._insert_clear_all_button(toolbar_box.toolbar) load_example = ToolButton('load-sample') load_example.set_tooltip(_('Show sample projects')) load_example.connect('clicked', self._create_store) toolbar_box.toolbar.insert(Gtk.SeparatorToolItem(), -1) toolbar_box.toolbar.insert(load_example, -1) load_example.show() separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_size_request(0, -1) separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) separator.show() stop = StopButton(self) toolbar_box.toolbar.insert(stop, -1) stop.show() separator = Gtk.SeparatorToolItem() activity_button.props.page.insert(separator, -1) separator.show() export_json = ToolButton('save-as-json') export_json.set_tooltip(_('Export tracked objects to journal')) export_json.connect('clicked', self._export_json_cb) activity_button.props.page.insert(export_json, -1) export_json.show() export_csv = ToolButton('save-as-csv') export_csv.set_tooltip(_('Export tracked objects to journal')) export_csv.connect('clicked', self._export_csv_cb) activity_button.props.page.insert(export_csv, -1) export_csv.show() load_project = ToolButton('load-project') load_project.set_tooltip(_('Load project from journal')) load_project.connect('clicked', self._load_project) activity_button.props.page.insert(load_project, -1) load_project.show() self.set_toolbar_box(toolbar_box) toolbar_box.show_all() create_toolbar.set_expanded(True) return toolbar_box
class RulerActivity(activity.Activity): def __init__(self, handle): super(RulerActivity, self).__init__(handle) self.button_dict = {} self.callback_dict = {} self._ready = False font = 'helvetica 12' font_bold = 'helvetica bold 12' # # We need a canvas # self._canvas = MyCanvas() self.set_canvas(self._canvas) self._canvas.show() screen = Gdk.Screen() width = screen.width() height = screen.height() - GRID_CELL_SIZE dpi, self.known_dpi = calc_dpi() self._canvas.set_dpi(dpi) # Create instances of our graphics self._r = show_rulers.ScreenOfRulers(font, font_bold, width, height) self._gcm = show_grids.ScreenGrid_cm(font, font_bold, width, height) self._gmm = show_grids.ScreenGrid_mm(font, font_bold, width, height) self._a90 = show_angles.Angles90(font, font_bold, width, height) self._a360 = show_angles.Angles360(font, font_bold, width, height) self._c = show_checkers.ScreenOfCircles(font, font_bold, width, height) # start with a ruler self._current = self._r self._canvas.add_a_ruler(self._current) # other settings self._grids_mode = "cm" self._angles_mode = "90" # # We need some toolbars # self.max_participants = 1 toolbar_box = ToolbarBox() # Buttons added to the Activity toolbar activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(activity_button, 0) activity_button.show() self.rulers = radio_factory('ruler', toolbar_box.toolbar, self._rulers_cb, tooltip=_('Ruler'), group=None) self.grids = radio_factory('grid-a', toolbar_box.toolbar, self._grids_cb, tooltip=_('Grid'), group=self.rulers) self.angles = radio_factory('angles-90', toolbar_box.toolbar, self._angles_cb, tooltip=_('Angles'), group=self.rulers) self.checker = radio_factory('checker', toolbar_box.toolbar, self._checker_cb, tooltip=_('Checker'), group=self.rulers) self.wrapper = Gtk.ToolItem() self.wrapper2 = Gtk.ToolItem() self.wrapper3 = Gtk.ToolItem() self.custom_unit_entry = Gtk.Entry() self.txt1 = Gtk.Label() self.txt1.set_text(_('1 custom unit equals ')) self.txt2 = Gtk.Label() # TRANS: mm is for Milli Meters self.txt2.set_text(_(' mm.')) self.wrapper.add(self.txt1) self.wrapper2.add(self.custom_unit_entry) self.wrapper3.add(self.txt2) self.wrapper.show_all() self.wrapper2.show_all() self.wrapper3.show_all() separator = Gtk.SeparatorToolItem() separator.props.draw = True separator.set_expand(False) separator.show() toolbar_box.toolbar.insert(separator, -1) custom_units_toolbox = ToolbarBox() custom_units_toolbox.toolbar.insert(self.wrapper, -1) custom_units_toolbox.toolbar.insert(self.wrapper2, -1) custom_units_toolbox.toolbar.insert(self.wrapper3, -1) custom_units_toolbox.show() self.custom_units_button = ToolbarButton(icon_name='view-source', page=custom_units_toolbox) toolbar_box.toolbar.insert(self.custom_units_button, -1) self.custom_unit_entry.connect('changed', self.custom_unit_change_cb) self.custom_units_button.show() if not self.known_dpi: separator = Gtk.SeparatorToolItem() separator.show() toolbar_box.toolbar.insert(separator, -1) dpi = self._canvas.get_dpi() self._dpi_spin_adj = Gtk.Adjustment(dpi, 72, 200, 2, 32, 0) self._dpi_spin = Gtk.SpinButton(self._dpi_spin_adj, 0, 0) self._dpi_spin_id = self._dpi_spin.connect('value-changed', self._dpi_spin_cb) self._dpi_spin.set_numeric(True) self._dpi_spin.show() self.tool_item_dpi = Gtk.ToolItem() self.tool_item_dpi.add(self._dpi_spin) toolbar_box.toolbar.insert(self.tool_item_dpi, -1) self.tool_item_dpi.show() separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(True) separator.show() toolbar_box.toolbar.insert(separator, -1) # The ever-present Stop Button stop_button = StopButton(self) stop_button.props.accelerator = '<Ctrl>Q' toolbar_box.toolbar.insert(stop_button, -1) stop_button.show() self.set_toolbar_box(toolbar_box) toolbar_box.show() self.show_all() # Restore state if previously saved self._ready = True if 'ruler' in self.metadata and \ self.metadata['ruler'] in self.button_dict: _logger.debug('restoring %s', self.metadata['ruler']) self.button_dict[self.metadata['ruler']].set_active(True) self.callback_dict[self.metadata['ruler']] else: self._rulers_cb() self.rulers.set_active(True) if 'custom_unit' in self.metadata: self.custom_unit_entry.set_text(self.metadata['custom_unit']) else: # set the default self.custom_unit_entry.set_text("25.4") # # Button callbacks # def _rulers_cb(self, button=None): if self._ready: self.custom_units_button.set_sensitive(True) self._current = self._r self._canvas.add_a_ruler(self._current) _logger.debug('selecting ruler') self.metadata['ruler'] = 'ruler' return False def custom_unit_change_cb(self, widget): try: new = float(widget.get_text()) except ValueError: new = MMPERINCH new = abs(new) if new == 0: new = MMPERINCH if widget.get_text != '': widget.set_text(str(new)) self._canvas.add_a_ruler(self._r) self._r.custom_unit_in_mm = new self._r.draw_custom_ruler(self._r.custom_unit_in_mm) self.metadata['custom_unit'] = str(new) def _grids_cb(self, button=None): if self._ready: self.custom_units_button.set_sensitive(False) self.custom_units_button.set_expanded(False) if self._grids_mode == "cm": self._current = self._gcm if hasattr(self, 'grids'): self.grids.set_icon_name("grid-c") self._grids_mode = "mm" else: self._current = self._gmm if hasattr(self, 'grids'): self.grids.set_icon_name("grid-a") self._grids_mode = "cm" self._canvas.add_a_ruler(self._current) _logger.debug('selecting grids') self.metadata['ruler'] = 'grids' return False def _angles_cb(self, button=None): if self._ready: self.custom_units_button.set_sensitive(False) self.custom_units_button.set_expanded(False) if self._angles_mode == "90": self._current = self._a90 if hasattr(self, 'angles'): self.angles.set_icon_name("angles-360") self._angles_mode = "360" else: self._current = self._a360 if hasattr(self, 'angles'): self.angles.set_icon_name("angles-90") self._angles_mode = "90" self._canvas.add_a_ruler(self._current) _logger.debug('selecting angles') self.metadata['ruler'] = 'angles' return False def _checker_cb(self, button=None): if self._ready: self.custom_units_button.set_sensitive(False) self.custom_units_button.set_expanded(False) self._current = self._c self._canvas.add_a_ruler(self._current) _logger.debug('selecting checker') self.metadata['ruler'] = 'checker' return False def _dpi_spin_cb(self, button): self._canvas.set_dpi(self._dpi_spin.get_value_as_int()) self._canvas.add_a_ruler(self._current) return def write_file(self, file_path): ''' Write the dpi to the Journal ''' dpi = self._canvas.get_dpi() _logger.debug("Write dpi: " + str(dpi)) self.metadata['dpi'] = str(dpi)
class LogActivity(activity.Activity): def __init__(self, handle): activity.Activity.__init__(self, handle) self._autosearch_timer = None # Paths to watch: ~/.sugar/someuser/logs, /var/log paths = [] paths.append(env.get_profile_path('logs')) paths.append('/var/log') # Additional misc files. ext_files = [] ext_files.append(os.path.expanduser('~/.bash_history')) self.viewer = MultiLogView(paths, ext_files) self.set_canvas(self.viewer) self.viewer.grab_focus() self._build_toolbox() # Get Sugar's clipboard self.clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) self.show() self._configure_cb(None) Gdk.Screen.get_default().connect('size-changed', self._configure_cb) def _build_toolbox(self): toolbar_box = ToolbarBox() self.max_participants = 1 activity_button = ActivityToolbarButton(self) activity_toolbar = activity_button.page self._toolbar = toolbar_box.toolbar self._toolbar.insert(activity_button, -1) self._secondary_toolbar = Gtk.Toolbar() self._secondary_toolbar_button = ToolbarButton( page=self._secondary_toolbar, icon_name='system-search') self._secondary_toolbar.show() self._toolbar.insert(self._secondary_toolbar_button, -1) self._secondary_toolbar_button.hide() show_list = ToggleToolButton('view-list') show_list.set_active(True) show_list.set_tooltip(_('Show list of files')) show_list.connect('toggled', self._list_toggled_cb) self._toolbar.insert(show_list, -1) show_list.show() copy = CopyButton() copy.connect('clicked', self.__copy_clicked_cb) self._toolbar.insert(copy, -1) wrap_btn = ToggleToolButton("format-wrap") wrap_btn.set_tooltip(_('Word Wrap')) wrap_btn.connect('clicked', self._wrap_cb) self._toolbar.insert(wrap_btn, -1) self.search_entry = iconentry.IconEntry() self.search_entry.set_size_request(Gdk.Screen.width() / 3, -1) self.search_entry.set_icon_from_name( iconentry.ICON_ENTRY_PRIMARY, 'entry-search') self.search_entry.add_clear_button() self.search_entry.connect('activate', self._search_entry_activate_cb) self.search_entry.connect('changed', self._search_entry_changed_cb) self._search_item = Gtk.ToolItem() self._search_item.add(self.search_entry) self._toolbar.insert(self._search_item, -1) self._search_prev = ToolButton('go-previous-paired') self._search_prev.set_tooltip(_('Previous')) self._search_prev.connect('clicked', self._search_prev_cb) self._toolbar.insert(self._search_prev, -1) self._search_next = ToolButton('go-next-paired') self._search_next.set_tooltip(_('Next')) self._search_next.connect('clicked', self._search_next_cb) self._toolbar.insert(self._search_next, -1) self._update_search_buttons() self.collector_palette = CollectorPalette(self) collector_btn = ToolButton('log-export') collector_btn.set_palette(self.collector_palette) collector_btn.connect('clicked', self._logviewer_cb) collector_btn.show() activity_toolbar.insert(collector_btn, -1) self._delete_btn = ToolButton('list-remove') self._delete_btn.set_tooltip(_('Delete Log File')) self._delete_btn.connect('clicked', self._delete_log_cb) self._toolbar.insert(self._delete_btn, -1) self._separator = Gtk.SeparatorToolItem() self._separator.set_expand(True) self._separator.set_draw(False) self._toolbar.insert(self._separator, -1) self._stop_btn = StopButton(self) self._toolbar.insert(self._stop_btn, -1) toolbar_box.show_all() self.set_toolbar_box(toolbar_box) def _configure_cb(self, event=None): for control in [self._stop_btn, self._separator, self._delete_btn]: if control in self._toolbar: self._toolbar.remove(control) if Gdk.Screen.width() < Gdk.Screen.height(): self._secondary_toolbar_button.show() self._secondary_toolbar_button.set_expanded(True) self._remove_controls(self._toolbar) self._add_controls(self._secondary_toolbar) else: self._secondary_toolbar_button.set_expanded(False) self._secondary_toolbar_button.hide() self._remove_controls(self._secondary_toolbar) self._add_controls(self._toolbar) for control in [self._delete_btn, self._separator, self._stop_btn]: if control not in self._toolbar: self._toolbar.insert(control, -1) def _remove_controls(self, toolbar): for control in [self._search_item, self._search_prev, self._search_next]: if control in toolbar: toolbar.remove(control) def _add_controls(self, toolbar): for control in [self._search_item, self._search_prev, self._search_next]: if control not in toolbar: toolbar.insert(control, -1) control.show() def _list_toggled_cb(self, widget): if widget.get_active(): self.viewer.list_scroll.show() else: self.viewer.list_scroll.hide() def __copy_clicked_cb(self, button): if self.viewer.active_log: self.viewer.active_log.copy_clipboard(self.clipboard) def _wrap_cb(self, button): if button.get_active(): self.viewer._textview.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) else: self.viewer._textview.set_wrap_mode(Gtk.WrapMode.NONE) def _search_entry_activate_cb(self, entry): if self._autosearch_timer: GLib.source_remove(self._autosearch_timer) self.viewer.set_search_text(entry.props.text) self._update_search_buttons() def _search_entry_changed_cb(self, entry): if self._autosearch_timer: GLib.source_remove(self._autosearch_timer) self._autosearch_timer = GLib.timeout_add(_AUTOSEARCH_TIMEOUT, self.__autosearch_cb) def __autosearch_cb(self): self._autosearch_timer = None self.search_entry.activate() return False def _search_prev_cb(self, button): self.viewer.search_next('backward') self._update_search_buttons() def _search_next_cb(self, button): self.viewer.search_next('forward') self._update_search_buttons() def _update_search_buttons(self,): if len(self.viewer.search_text) == 0: self._search_prev.props.sensitive = False self._search_next.props.sensitive = False else: prev_result = self.viewer.get_next_result('backward') next_result = self.viewer.get_next_result('forward') self._search_prev.props.sensitive = prev_result is not None self._search_next.props.sensitive = next_result is not None def _delete_log_cb(self, widget): if self.viewer.active_log: logfile = self.viewer.active_log.logfile try: os.remove(logfile) except OSError, err: notify = NotifyAlert() notify.props.title = _('Error') notify.props.msg = _('%(error)s when deleting %(file)s') % \ {'error': err.strerror, 'file': logfile} notify.connect('response', _notify_response_cb, self) self.add_alert(notify)
def build_toolbar(self): self.max_participants = 4 toolbar_box = ToolbarBox() activity_button = ActivityToolbarButton(self) toolbar_box.toolbar.insert(activity_button, 0) activity_button.show() create_toolbar = ToolbarButton() create_toolbar.props.page = Gtk.Toolbar() create_toolbar.props.icon_name = "magicpen" create_toolbar.props.label = _("Create") toolbar_box.toolbar.insert(create_toolbar, -1) self._insert_create_tools(create_toolbar) color = ColorToolButton("Color Button") color.props.icon_name = "color" color.connect("notify::color", self.returnChosenColor) toolbar_box.toolbar.insert(color, -1) color.show() self.randomColor = ToggleToolButton("Random Color") self.randomColor.set_tooltip(_("Toggle random color")) self.randomColor.props.icon_name = "colorRandom" self.randomColor.connect("toggled", self.resetColors) toolbar_box.toolbar.insert(self.randomColor, -1) self.randomColor.set_active(True) self.randomColor.show() self._insert_stop_play_button(toolbar_box.toolbar) clear_trace = ToolButton("clear-trace") clear_trace.set_tooltip(_("Clear Trace Marks")) clear_trace.set_accelerator(_("<ctrl>x")) clear_trace.connect("clicked", self.clear_trace_cb) clear_trace.set_sensitive(False) toolbar_box.toolbar.insert(clear_trace, -1) clear_trace.show() self.clear_trace = clear_trace self._insert_clear_all_button(toolbar_box.toolbar) load_example = ToolButton("load-sample") load_example.set_tooltip(_("Show sample projects")) load_example.connect("clicked", self._create_store) toolbar_box.toolbar.insert(Gtk.SeparatorToolItem(), -1) toolbar_box.toolbar.insert(load_example, -1) load_example.show() separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_size_request(0, -1) separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) separator.show() stop = StopButton(self) toolbar_box.toolbar.insert(stop, -1) stop.show() separator = Gtk.SeparatorToolItem() activity_button.props.page.insert(separator, -1) separator.show() export_json = ToolButton("save-as-json") export_json.set_tooltip(_("Export tracked objects to journal")) export_json.connect("clicked", self._export_json_cb) activity_button.props.page.insert(export_json, -1) export_json.show() load_project = ToolButton("load-project") load_project.set_tooltip(_("Load project from journal")) load_project.connect("clicked", self._load_project) activity_button.props.page.insert(load_project, -1) load_project.show() self.set_toolbar_box(toolbar_box) toolbar_box.show_all() create_toolbar.set_expanded(True) return toolbar_box