diff options
Diffstat (limited to 'src/view/home/activitiesring.py')
-rw-r--r-- | src/view/home/activitiesring.py | 263 |
1 files changed, 206 insertions, 57 deletions
diff --git a/src/view/home/activitiesring.py b/src/view/home/activitiesring.py index dccb066..5d66ec3 100644 --- a/src/view/home/activitiesring.py +++ b/src/view/home/activitiesring.py @@ -42,23 +42,29 @@ from session import get_session_manager _logger = logging.getLogger('ActivitiesRing') -class ActivitiesRing(hippo.CanvasBox, hippo.CanvasItem): +_ICON_DND_TARGET = ('activity-icon', gtk.TARGET_SAME_WIDGET, 0) + +class ActivitiesRing(hippo.Canvas): __gtype_name__ = 'SugarActivitiesRing' - def __init__(self): - hippo.CanvasBox.__init__(self, - background_color=style.COLOR_WHITE.get_int()) + def __init__(self, **kwargs): + gobject.GObject.__init__(self, **kwargs) + + self._box = hippo.CanvasBox() + self._box.props.background_color = style.COLOR_WHITE.get_int() + self.set_root(self._box) shell_model = shellmodel.get_instance() shell_model.connect('notify::state', self._shell_state_changed_cb) self._my_icon = _MyIcon(style.XLARGE_ICON_SIZE) - self.append(self._my_icon, hippo.PACK_FIXED) + self._box.append(self._my_icon, hippo.PACK_FIXED) self._current_activity = CurrentActivityIcon() - self.append(self._current_activity, hippo.PACK_FIXED) + self._box.append(self._current_activity, hippo.PACK_FIXED) - self.set_layout(RingLayout()) + self._layout = RingLayout() + self._box.set_layout(self._layout) registry = activity.get_registry() registry.get_activities_async(reply_handler=self._get_activities_cb) @@ -66,16 +72,27 @@ class ActivitiesRing(hippo.CanvasBox, hippo.CanvasItem): registry.connect('activity-removed', self.__activity_removed_cb) registry.connect('activity-changed', self.__activity_changed_cb) - def _compare_activities(self, icon_a, icon_b): - if hasattr(icon_a, 'installation_time') and \ - hasattr(icon_b, 'installation_time'): - return icon_b.installation_time - icon_a.installation_time - else: - return 0 + # DND stuff + self._pressed_button = None + self._press_start_x = None + self._press_start_y = None + self._last_clicked_icon = None + + self.drag_source_set(0, [], 0) + self.add_events(gtk.gdk.BUTTON_PRESS_MASK | + gtk.gdk.POINTER_MOTION_HINT_MASK) + self.connect('motion-notify-event', self.__motion_notify_event_cb) + self.connect('button-press-event', self.__button_press_event_cb) + self.connect('drag-begin', self.__drag_begin_cb) + + self.drag_dest_set(0, [], 0) + self.connect('drag-motion', self.__drag_motion_cb) + self.connect('drag-drop', self.__drag_drop_cb) + self.connect('drag-data-received', self.__drag_data_received_cb) def _add_activity(self, activity_info): icon = ActivityIcon(activity_info) - self.insert_sorted(icon, 0, self._compare_activities) + self._layout.append(icon) def _get_activities_cb(self, activity_list): for info in activity_list: @@ -88,7 +105,7 @@ class ActivitiesRing(hippo.CanvasBox, hippo.CanvasItem): self._add_activity(activity_info) def _find_activity_icon(self, bundle_id, version): - for icon in self.get_children(): + for icon in self._box.get_children(): if isinstance(icon, ActivityIcon) and \ icon.bundle_id == bundle_id and icon.version == version: return icon @@ -98,7 +115,7 @@ class ActivitiesRing(hippo.CanvasBox, hippo.CanvasItem): icon = self._find_activity_icon(activity_info.bundle_id, activity_info.version) if icon is not None: - self.remove(icon) + self._layout.remove(icon) def __activity_changed_cb(self, activity_registry, activity_info): if activity_info.bundle_id == "org.laptop.JournalActivity": @@ -106,7 +123,7 @@ class ActivitiesRing(hippo.CanvasBox, hippo.CanvasItem): icon = self._find_activity_icon(activity_info.bundle_id, activity_info.version) if icon is not None and not activity_info.favorite: - self.remove(icon) + self._box.remove(icon) elif icon is None and activity_info.favorite: self._add_activity(activity_info) @@ -115,23 +132,105 @@ class ActivitiesRing(hippo.CanvasBox, hippo.CanvasItem): if model.props.state == ShellModel.STATE_SHUTDOWN: pass - def do_allocate(self, width, height, origin_changed): - hippo.CanvasBox.do_allocate(self, width, height, origin_changed) + def do_size_allocate(self, allocation): + hippo.Canvas.do_size_allocate(self, allocation) + + width = allocation.width + height = allocation.height [my_icon_width, my_icon_height] = self._my_icon.get_allocation() x = (width - my_icon_width) / 2 y = (height - my_icon_height - style.GRID_CELL_SIZE) / 2 - self.set_position(self._my_icon, x, y) + self._box.set_position(self._my_icon, x, y) [icon_width, icon_height] = self._current_activity.get_allocation() x = (width - icon_width) / 2 y = (height + my_icon_height + style.DEFAULT_PADDING \ - style.GRID_CELL_SIZE) / 2 - self.set_position(self._current_activity, x, y) + self._box.set_position(self._current_activity, x, y) def enable_xo_palette(self): self._my_icon.enable_palette() + # TODO: Dnd methods. This should be merged somehow inside hippo-canvas. + def __button_press_event_cb(self, widget, event): + if event.button == 1 and event.type == gtk.gdk.BUTTON_PRESS: + self._last_clicked_icon = self._get_icon_at_coords(event.x, event.y) + if self._last_clicked_icon is not None: + self._pressed_button = event.button + self._press_start_x = event.x + self._press_start_y = event.y + + return False + + def _get_icon_at_coords(self, x, y): + for icon in self._box.get_children(): + icon_x, icon_y = icon.get_context().translate_to_widget(icon) + icon_width, icon_height = icon.get_allocation() + + if (x >= icon_x ) and (x <= icon_x + icon_width) and \ + (y >= icon_y ) and (y <= icon_y + icon_height) and \ + isinstance(icon, ActivityIcon): + return icon + return None + + def __motion_notify_event_cb(self, widget, event): + if not self._pressed_button: + return False + + # if the mouse button is not pressed, no drag should occurr + if not event.state & gtk.gdk.BUTTON1_MASK: + self._pressed_button = None + return False + + if event.is_hint: + x, y, state_ = event.window.get_pointer() + else: + x = event.x + y = event.y + + if widget.drag_check_threshold(int(self._press_start_x), + int(self._press_start_y), + int(x), + int(y)): + context_ = widget.drag_begin([_ICON_DND_TARGET], + gtk.gdk.ACTION_MOVE, + 1, + event) + return False + + def __drag_begin_cb(self, widget, context): + icon_file_name = self._last_clicked_icon.props.file_name + # TODO: we should get the pixbuf from the widget, so it has colors, etc + pixbuf = gtk.gdk.pixbuf_new_from_file(icon_file_name) + context.set_icon_pixbuf(pixbuf, 0, 0) + + def __drag_motion_cb(self, widget, context, x, y, time): + if self._last_clicked_icon is not None: + context.drag_status(context.suggested_action, time) + return True + else: + return False + + def __drag_drop_cb(self, widget, context, x, y, time): + if self._last_clicked_icon is not None: + self.drag_get_data(context, _ICON_DND_TARGET[0]) + + self._layout.move_icon(self._last_clicked_icon, x, y) + + self._pressed_button = None + self._press_start_x = None + self._press_start_y = None + self._last_clicked_icon = None + + return True + else: + return False + + def __drag_data_received_cb(self, widget, context, x, y, selection_data, + info, time): + context.drop_finish(success=True, time=time) + class ActivityIcon(CanvasIcon): def __init__(self, activity_info): CanvasIcon.__init__(self, cache=True, file_name=activity_info.icon) @@ -198,11 +297,18 @@ class CurrentActivityIcon(CanvasIcon, hippo.CanvasItem): def __active_activity_changed_cb(self, home_model, home_activity): self._update(home_activity) +_MINIMUM_RADIUS = style.XLARGE_ICON_SIZE / 2 + style.DEFAULT_SPACING + \ + style.STANDARD_ICON_SIZE * 2 +_MAXIMUM_RADIUS = (gtk.gdk.screen_height() - style.GRID_CELL_SIZE) / 2 - \ + style.STANDARD_ICON_SIZE - style.DEFAULT_SPACING + class RingLayout(gobject.GObject, hippo.CanvasLayout): __gtype_name__ = 'SugarRingLayout' + def __init__(self): gobject.GObject.__init__(self) self._box = None + self._fixed_positions = {} def do_set_box(self, box): self._box = box @@ -214,15 +320,8 @@ class RingLayout(gobject.GObject, hippo.CanvasLayout): return 0, gtk.gdk.screen_width() def _calculate_radius_and_icon_size(self, children_count): - minimum_radius = style.XLARGE_ICON_SIZE / 2 + style.DEFAULT_SPACING + \ - style.STANDARD_ICON_SIZE * 2 - maximum_radius = (gtk.gdk.screen_height() - style.GRID_CELL_SIZE) \ - / 2 - style.STANDARD_ICON_SIZE - style.DEFAULT_SPACING angle = 2 * math.pi / children_count - _logger.debug('minimum_radius %r maximum_radius %r angle %r' % \ - (minimum_radius, maximum_radius, angle)) - # what's the radius required without downscaling? distance = style.STANDARD_ICON_SIZE + style.DEFAULT_SPACING icon_size = style.STANDARD_ICON_SIZE @@ -232,60 +331,110 @@ class RingLayout(gobject.GObject, hippo.CanvasLayout): else: radius = math.sqrt(distance ** 2 / (math.sin(angle) ** 2 + (math.cos(angle) - 1) ** 2)) - - _logger.debug('radius 1 %r' % radius) - if radius < minimum_radius: + if radius < _MINIMUM_RADIUS: # we can upscale, if we want icon_size += style.STANDARD_ICON_SIZE * \ - (0.5 * (minimum_radius - radius)/minimum_radius) - radius = minimum_radius - elif radius > maximum_radius: - radius = maximum_radius + (0.5 * (_MINIMUM_RADIUS - radius) / _MINIMUM_RADIUS) + radius = _MINIMUM_RADIUS + elif radius > _MAXIMUM_RADIUS: + radius = _MAXIMUM_RADIUS # need to downscale. what's the icon size required? distance = math.sqrt((radius * math.sin(angle)) ** 2 + \ (radius * (math.cos(angle) - 1)) ** 2) icon_size = distance - style.DEFAULT_SPACING - - _logger.debug('radius 2 %r icon_size %r' % (radius, icon_size)) return radius, icon_size def _calculate_position(self, radius, icon_size, index, children_count): width, height = self._box.get_allocation() - angle = index * (2 * math.pi / children_count) - math.pi/2 + angle = index * (2 * math.pi / children_count) - math.pi / 2 x = radius * math.cos(angle) + (width - icon_size) / 2 y = radius * math.sin(angle) + (height - icon_size - style.GRID_CELL_SIZE) / 2 return x, y + def _get_children_in_ring(self): + children = self._box.get_layout_children() + width, height = self._box.get_allocation() + children_in_ring = [] + for child in children: + if child.item in self._fixed_positions: + x, y = self._fixed_positions[child.item] + distance_to_center = math.hypot(x - width / 2, y - height / 2) + # TODO at what distance should we consider a child inside the ring? + else: + children_in_ring.append(child) + + return children_in_ring + + def _update_icon_sizes(self): + children_in_ring = self._get_children_in_ring() + if children_in_ring: + radius_, icon_size = \ + self._calculate_radius_and_icon_size(len(children_in_ring)) + + for n in range(len(children_in_ring)): + child = children_in_ring[n] + child.item.props.size = icon_size + + for child in self._box.get_layout_children(): + if child not in children_in_ring: + child.item.props.size = style.STANDARD_ICON_SIZE + + def _compare_activities(self, icon_a, icon_b): + if hasattr(icon_a, 'installation_time') and \ + hasattr(icon_b, 'installation_time'): + return icon_b.installation_time - icon_a.installation_time + else: + return 0 + + def append(self, child): + self._box.insert_sorted(child, 0, self._compare_activities) + self._update_icon_sizes() + + def remove(self, child): + self._box.remove(child) + self._update_icon_sizes() + + def move_icon(self, child, x, y): + if child not in self._box.get_children(): + raise ValueError('Child not in box.') + self._fixed_positions[child] = (x, y) + self._box.emit_request_changed() + def do_allocate(self, x, y, width, height, req_width, req_height, origin_changed): - _logger.debug('RingLayout.do_allocate: %r %r %r %r %r %r %r' % (x, y, - width, height, req_width, req_height, origin_changed)) + children_in_ring = self._get_children_in_ring() + if children_in_ring: + radius, icon_size = \ + self._calculate_radius_and_icon_size(len(children_in_ring)) - children = self._box.get_layout_children() - if not children: - return + for n in range(len(children_in_ring)): + child = children_in_ring[n] - radius, icon_size = self._calculate_radius_and_icon_size(len(children)) + x, y = self._calculate_position(radius, icon_size, n, + len(children_in_ring)) - for n in range(len(children)): - child = children[n] - # TODO: We get here a glib warning and I don't know why. - child.item.props.size = icon_size + # We need to always get requests to not confuse hippo + min_w_, child_width = child.get_width_request() + min_h_, child_height = child.get_height_request(child_width) - x, y = self._calculate_position(radius, icon_size, n, len(children)) + child.allocate(int(x), int(y), child_width, child_height, + origin_changed) + + for child in self._box.get_layout_children(): + if child in children_in_ring: + continue # We need to always get requests to not confuse hippo - min_w, child_width = child.get_width_request() - min_h, child_height = child.get_height_request(child_width) - - child.allocate(int(x), - int(y), - child_width, - child_height, - origin_changed) + min_w_, child_width = child.get_width_request() + min_h_, child_height = child.get_height_request(child_width) + + x, y = self._fixed_positions[child.item] + + child.allocate(int(x), int(y), child_width, child_height, + origin_changed) class _MyIcon(MyIcon): def __init__(self, scale): |