diff --git a/NEWS b/NEWS index f53d9884..0b6e8f04 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,6 @@ +* #4941: Add menu entry with dialog to show About this XO (erikos) +* #5089: show frame shortly when adding object to clipboard (erikos) +* #5097: Fix pasting text between activities through the clipboard. (tomeu) * #4660: Use improved PS API which streamlines ShareActivity process (smcv) 0.70.1 diff --git a/lib/sugar/activity/activity.py b/lib/sugar/activity/activity.py index 19d67fea..2b227af0 100644 --- a/lib/sugar/activity/activity.py +++ b/lib/sugar/activity/activity.py @@ -504,12 +504,16 @@ class Activity(Window, gtk.Container): self.save() elif pspec.name == 'max-participants': self._max_participants = value + else: + Window.do_set_property(self, pspec, value) def do_get_property(self, pspec): if pspec.name == 'active': return self._active elif pspec.name == 'max-participants': return self._max_participants + else: + return Window.do_get_property(self, pspec) def get_id(self): """Returns the activity id of the current instance of your activity. diff --git a/lib/sugar/graphics/palette.py b/lib/sugar/graphics/palette.py index 9f77feea..50925244 100644 --- a/lib/sugar/graphics/palette.py +++ b/lib/sugar/graphics/palette.py @@ -474,7 +474,7 @@ class Palette(gtk.Window): if self._group_id: group = palettegroup.get_group(self._group_id) if group and group.is_up(): - self._set_state(group.get_state()) + self._set_state(self.PRIMARY) immediate = True group.popdown() @@ -686,12 +686,19 @@ class WidgetInvoker(Invoker): widget.connect('leave-notify-event', self._leave_notify_event_cb) def get_rect(self): - x, y = self._widget.window.get_origin() allocation = self._widget.get_allocation() + if self._widget.window is not None: + x, y = self._widget.window.get_origin() + else: + logging.warning( + "Trying to position palette with invoker that's not realized.") + x = 0 + y = 0 if self._widget.flags() & gtk.NO_WINDOW: - x += allocation.x - y += allocation.y + x += allocation.x + y += allocation.y + width = allocation.width height = allocation.height diff --git a/lib/sugar/graphics/window.py b/lib/sugar/graphics/window.py index 3b3394a9..31894006 100644 --- a/lib/sugar/graphics/window.py +++ b/lib/sugar/graphics/window.py @@ -15,12 +15,73 @@ # Free Software Foundation, Inc., 59 Temple Place - Suite 330, # Boston, MA 02111-1307, USA. +import gobject import gtk +import logging + +from sugar.graphics.icon import Icon + +class UnfullscreenButton(gtk.Window): -class Window(gtk.Window): def __init__(self): gtk.Window.__init__(self) + self.set_decorated(False) + self.set_resizable(False) + self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG) + + self.set_border_width(0) + + self.props.accept_focus = False + + #Setup estimate of width, height + w, h = gtk.icon_size_lookup(gtk.ICON_SIZE_LARGE_TOOLBAR) + self._width = w + self._height = h + + self.connect('size-request', self._size_request_cb) + + screen = self.get_screen() + screen.connect('size-changed', self._screen_size_changed_cb) + + self._button = gtk.Button() + self._button.set_relief(gtk.RELIEF_NONE) + + self._icon = Icon(icon_name='view-return', + icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR) + self._icon.show() + self._button.add(self._icon) + + self._button.show() + self.add(self._button) + + def connect_button_press(self, cb): + self._button.connect('button-press-event', cb) + + def _reposition(self): + x = gtk.gdk.screen_width() - self._width + self.move(x, 0) + + def _size_request_cb(self, widget, req): + self._width = req.width + self._height = req.height + self._reposition() + + def _screen_size_changed_cb(self, screen): + self._reposition() + +class Window(gtk.Window): + + __gproperties__ = { + 'enable-fullscreen-mode': (bool, None, None, True, + gobject.PARAM_READWRITE), + } + + def __init__(self, **args): + self._enable_fullscreen_mode = True + + gtk.Window.__init__(self, **args) + self.connect('realize', self.__window_realize_cb) self.connect('window-state-event', self.__window_state_event_cb) self.connect('key-press-event', self.__key_press_cb) @@ -42,6 +103,24 @@ class Window(gtk.Window): self.add(self._vbox) self._vbox.show() + self._is_fullscreen = False + self._unfullscreen_button = UnfullscreenButton() + self._unfullscreen_button.set_transient_for(self) + self._unfullscreen_button.connect_button_press( + self.__unfullscreen_button_pressed) + + def do_get_property(self, prop): + if prop.name == 'enable-fullscreen-mode': + return self._enable_fullscreen_mode + else: + return gtk.Window.do_get_property(self, prop) + + def do_set_property(self, prop, val): + if prop.name == 'enable-fullscreen-mode': + self._enable_fullscreen_mode = val + else: + gtk.Window.do_set_property(self, prop, val) + def set_canvas(self, canvas): if self.canvas: self._event_box.remove(self.canvas) @@ -102,20 +181,40 @@ class Window(gtk.Window): window.window.set_group(group.window) def __window_state_event_cb(self, window, event): + if not (event.changed_mask & gtk.gdk.WINDOW_STATE_FULLSCREEN): + return False + if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN: if self.toolbox is not None: self.toolbox.hide() if self.tray is not None: self.tray.hide() - elif event.new_window_state == 0: + + self._is_fullscreen = True + if self.props.enable_fullscreen_mode: + self._unfullscreen_button.show() + + else: if self.toolbox is not None: self.toolbox.show() if self.tray is not None: self.tray.show() + self._is_fullscreen = False + if self.props.enable_fullscreen_mode: + self._unfullscreen_button.hide() + def __key_press_cb(self, widget, event): + key = gtk.gdk.keyval_name(event.keyval) if event.state & gtk.gdk.MOD1_MASK: - if gtk.gdk.keyval_name(event.keyval) == 'space': + if key == 'space': self.tray.props.visible = not self.tray.props.visible return True + elif key == 'Escape' and self._is_fullscreen and \ + self.props.enable_fullscreen_mode: + self.unfullscreen() + return True return False + + def __unfullscreen_button_pressed(self, widget, event): + self.unfullscreen() diff --git a/shell/view/Shell.py b/shell/view/Shell.py index 05e058e1..b9d259f0 100644 --- a/shell/view/Shell.py +++ b/shell/view/Shell.py @@ -198,7 +198,7 @@ class Shell(gobject.GObject): new_level = model.props.zoom_level if new_level == ShellModel.ZOOM_HOME: - self._frame.show(Frame.MODE_HOME) + self._frame.show(Frame.MODE_NON_INTERACTIVE) if self._zoom_level == ShellModel.ZOOM_HOME: self._frame.hide() diff --git a/shell/view/clipboardicon.py b/shell/view/clipboardicon.py index 56ae669a..4b36395b 100644 --- a/shell/view/clipboardicon.py +++ b/shell/view/clipboardicon.py @@ -44,12 +44,14 @@ class ClipboardIcon(RadioToolButton): self._preview = None self._activity = None self.owns_clipboard = False + self.props.sensitive = False + self.props.active = False self._icon = Icon() self._icon.props.xo_color = profile.get_color() self.set_icon_widget(self._icon) self._icon.show() - + cb_service = clipboardservice.get_instance() cb_service.connect('object-state-changed', self._object_state_changed_cb) obj = cb_service.get_object(self._object_id) @@ -80,6 +82,10 @@ class ClipboardIcon(RadioToolButton): def _put_in_clipboard(self): logging.debug('ClipboardIcon._put_in_clipboard') + + if self._percent < 100: + raise ValueError('Object is not complete, cannot be put into the clipboard.') + targets = self._get_targets() if targets: clipboard = gtk.Clipboard() @@ -125,14 +131,19 @@ class ClipboardIcon(RadioToolButton): self.child.drag_source_set_icon_name(self._icon.props.icon_name) self._name = name - self._percent = percent self._preview = preview self._activity = activity self.palette.set_state(name, percent, preview, activity, self._is_bundle(obj['FORMATS'])) - if self.props.active: - self._put_in_clipboard() + old_percent = self._percent + self._percent = percent + if self._percent == 100: + self.props.sensitive = True + + # Clipboard object became complete. Make it the active one. + if old_percent < 100 and self._percent == 100: + self.props.active = True def _notify_active_cb(self, widget, pspec): if self.props.active: diff --git a/shell/view/frame/clipboardbox.py b/shell/view/frame/clipboardbox.py index 1195c022..cf2f9f55 100644 --- a/shell/view/frame/clipboardbox.py +++ b/shell/view/frame/clipboardbox.py @@ -62,7 +62,6 @@ class ClipboardBox(hippo.CanvasBox): hippo.CanvasBox.__init__(self) self._icons = {} self._context_map = _ContextMap() - self._selected_icon = None self._tray = VTray() self.append(hippo.CanvasWidget(widget=self._tray), hippo.PACK_EXPAND) @@ -109,17 +108,10 @@ class ClipboardBox(hippo.CanvasBox): icon = ClipboardIcon(object_id, name, group) self._tray.add_item(icon, 0) icon.show() - - self._set_icon_selected(icon) self._icons[object_id] = icon logging.debug('ClipboardBox: ' + object_id + ' was added.') - def _set_icon_selected(self, icon): - logging.debug('_set_icon_selected') - icon.props.active = True - self._selected_icon = icon - def _object_deleted_cb(self, cb_service, object_id): icon = self._icons[object_id] self._tray.remove_item(icon) diff --git a/shell/view/frame/clipboardpanelwindow.py b/shell/view/frame/clipboardpanelwindow.py index 6a018448..b492e78b 100644 --- a/shell/view/frame/clipboardpanelwindow.py +++ b/shell/view/frame/clipboardpanelwindow.py @@ -59,29 +59,35 @@ class ClipboardPanelWindow(FrameWindow): targets = clipboard.wait_for_targets() for target in targets: if target not in ('TIMESTAMP', 'TARGETS', 'MULTIPLE', 'SAVE_TARGETS'): + logging.debug('Asking for target %s.' % target) selection = clipboard.wait_for_contents(target) - if selection: - self._add_selection(key, selection) + if not selection: + logging.warning('no data for selection target %s.' % target) + continue + self._add_selection(key, selection) cb_service.set_object_percent(key, percent=100) def _add_selection(self, key, selection): - if selection.data: - logging.debug('adding type ' + selection.type + '.') - - cb_service = clipboardservice.get_instance() - if selection.type == 'text/uri-list': - uris = selection.data.split('\n') - if len(uris) > 1: - raise NotImplementedError('Multiple uris in text/uri-list still not supported.') + if not selection.data: + logging.warning('no data for selection target %s.' % selection.type) + return + + logging.debug('adding type ' + selection.type + '.') + + cb_service = clipboardservice.get_instance() + if selection.type == 'text/uri-list': + uris = selection.data.split('\n') + if len(uris) > 1: + raise NotImplementedError('Multiple uris in text/uri-list still not supported.') - cb_service.add_object_format(key, - selection.type, - uris[0], - on_disk=True) - else: - cb_service.add_object_format(key, - selection.type, - selection.data, - on_disk=False) + cb_service.add_object_format(key, + selection.type, + uris[0], + on_disk=True) + else: + cb_service.add_object_format(key, + selection.type, + selection.data, + on_disk=False) diff --git a/shell/view/frame/frame.py b/shell/view/frame/frame.py index c5072628..e8f8fa4d 100644 --- a/shell/view/frame/frame.py +++ b/shell/view/frame/frame.py @@ -85,7 +85,7 @@ class _KeyListener(object): class Frame(object): MODE_MOUSE = 0 MODE_KEYBOARD = 1 - MODE_HOME = 2 + MODE_NON_INTERACTIVE = 2 def __init__(self, shell): self.mode = None @@ -239,7 +239,7 @@ class Frame(object): def _clipboard_object_added_cb(self, cb_service, object_id, name): if not self.visible: - self.show() + self.show(self.MODE_NON_INTERACTIVE) gobject.timeout_add(2000, lambda: self.hide()) def _enter_notify_cb(self, window, event): diff --git a/shell/view/home/HomeBox.py b/shell/view/home/HomeBox.py index f5683668..8764887c 100644 --- a/shell/view/home/HomeBox.py +++ b/shell/view/home/HomeBox.py @@ -15,9 +15,10 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA import os +import logging import signal -import math from gettext import gettext as _ +import re import gobject import gtk @@ -26,9 +27,7 @@ import dbus from hardware import hardwaremanager from sugar.graphics import style -from sugar.graphics.xocolor import XoColor -from sugar.graphics.palette import Palette, CanvasInvoker -from sugar.graphics.icon import CanvasIcon +from sugar.graphics.palette import Palette from sugar.profile import get_profile from sugar import env @@ -38,6 +37,8 @@ from view.home.MyIcon import MyIcon from model.shellmodel import ShellModel from hardware import schoolserver +_logger = logging.getLogger('HomeBox') + class HomeBox(hippo.CanvasBox, hippo.CanvasItem): __gtype_name__ = 'SugarHomeBox' @@ -170,7 +171,12 @@ class _MyIcon(MyIcon): item.connect('activate', self._register_activate_cb) palette.menu.append(item) item.show() - + + item = gtk.MenuItem(_('About this XO')) + item.connect('activate', self._about_activate_cb) + palette.menu.append(item) + item.show() + self.set_palette(palette) def _reboot_activate_cb(self, menuitem): @@ -206,6 +212,65 @@ class _MyIcon(MyIcon): if self._profile.is_registered(): self.get_palette().menu.remove(menuitem) + def _about_activate_cb(self, menuitem): + dialog = gtk.Dialog(_('About this XO'), + self.palette, + gtk.DIALOG_MODAL | + gtk.DIALOG_DESTROY_WITH_PARENT, + (gtk.STOCK_OK, gtk.RESPONSE_OK)) + + not_available = _('Not available') + build = self._read_file('/boot/olpc_build') + if build is None: + build = not_available + label_build = gtk.Label('Build: %s' % build) + label_build.set_alignment(0, 0.5) + label_build.show() + dialog.vbox.pack_start(label_build) + + firmware = self._read_file('/ofw/openprom/model') + if firmware is None: + firmware = not_available + else: + firmware = re.split(" +", firmware) + if len(firmware) == 3: + firmware = firmware[1] + label_firmware = gtk.Label('Firmware: %s' % firmware) + label_firmware.set_alignment(0, 0.5) + label_firmware.show() + dialog.vbox.pack_start(label_firmware) + + serial = self._read_file('/ofw/serial-number') + if serial is None: + serial = not_available + label_serial = gtk.Label('Serial Number: %s' % serial) + label_serial.set_alignment(0, 0.5) + label_serial.show() + dialog.vbox.pack_start(label_serial) + + dialog.set_default_response(gtk.RESPONSE_OK) + dialog.connect('response', self._response_cb) + dialog.show() + + def _read_file(self, path): + if os.access(path, os.R_OK) == 0: + _logger.error('read_file() No such file or directory: %s', path) + return None + + fd = open(path, 'r') + value = fd.read() + fd.close() + if value: + value = value.strip('\n') + return value + else: + _logger.error('read_file() No information in file or directory: %s', path) + return None + + def _response_cb(self, widget, response_id): + if response_id == gtk.RESPONSE_OK: + widget.destroy() + def _close_emulator(self): if os.environ.has_key('SUGAR_EMULATOR_PID'): pid = int(os.environ['SUGAR_EMULATOR_PID']) diff --git a/shell/view/home/activitiesdonut.py b/shell/view/home/activitiesdonut.py index 51a222a3..ebdf8bec 100755 --- a/shell/view/home/activitiesdonut.py +++ b/shell/view/home/activitiesdonut.py @@ -101,7 +101,7 @@ class ActivityIcon(CanvasIcon): palette.set_primary_text(self._activity.get_title()) - resume_menu_item = MenuItem(_('Resume'), 'zoom-activity') + resume_menu_item = MenuItem(_('Resume'), 'activity-start') resume_menu_item.connect('activate', self._resume_activate_cb) palette.menu.append(resume_menu_item) resume_menu_item.show()