Merge branch 'master' of git+ssh://dev.laptop.org/git/sugar

This commit is contained in:
Simon McVittie 2007-08-07 13:43:27 +01:00
commit df3069a931
6 changed files with 304 additions and 31 deletions

View File

@ -221,7 +221,10 @@ class IntroWindow(gtk.Window):
section = 'Server' section = 'Server'
if not cp.has_section(section): if not cp.has_section(section):
cp.add_section(section) cp.add_section(section)
cp.set(section, 'Server', 'olpc.collabora.co.uk') if env.is_emulator():
cp.set(section, 'Server', 'olpc.collabora.co.uk')
else:
cp.set(section, 'Server', '')
cp.set(section, 'Registered', 'False') cp.set(section, 'Registered', 'False')
config_path = os.path.join(env.get_profile_path(), 'config') config_path = os.path.join(env.get_profile_path(), 'config')

View File

@ -58,6 +58,7 @@ class HomeActivity(gobject.GObject):
self._window = None self._window = None
self._xid = None self._xid = None
self._pid = None
self._service = None self._service = None
self._activity_id = activity_id self._activity_id = activity_id
self._bundle = bundle self._bundle = bundle
@ -81,6 +82,7 @@ class HomeActivity(gobject.GObject):
self._window = window self._window = window
self._xid = window.get_xid() self._xid = window.get_xid()
self._pid = window.get_pid()
def get_service(self): def get_service(self):
"""Get the activity service """Get the activity service
@ -168,6 +170,10 @@ class HomeActivity(gobject.GObject):
""" """
return self._launch_time return self._launch_time
def get_pid(self):
"""Returns the activity's PID"""
return self._pid
def equals(self, activity): def equals(self, activity):
if self._activity_id and activity.get_activity_id(): if self._activity_id and activity.get_activity_id():
return self._activity_id == activity.get_activity_id() return self._activity_id == activity.get_activity_id()

View File

@ -65,10 +65,15 @@ class ActivityHost:
pass pass
def present(self): def present(self):
self._window.activate(gtk.get_current_event_time()) # wnck.Window.activate() expects a timestamp, but we don't
# always have one, and libwnck will complain if we pass "0",
# and matchbox doesn't look at the timestamp anyway. So we
# just always pass "1".
self._window.activate(1)
def close(self): def close(self):
self._window.close(gtk.get_current_event_time()) # The "1" is a fake timestamp as with present()
self._window.close(1)
def show_dialog(self, dialog): def show_dialog(self, dialog):
dialog.show() dialog.show()

View File

@ -14,12 +14,16 @@
# along with this program; if not, write to the Free Software # along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
import hippo
import math
import gobject
import colorsys import colorsys
from gettext import gettext as _
import math
import hippo
import gobject
import gtk
from sugar.graphics.canvasicon import CanvasIcon from sugar.graphics.canvasicon import CanvasIcon
from sugar.graphics.palette import Palette
from sugar.graphics import style from sugar.graphics import style
from sugar.graphics import xocolor from sugar.graphics import xocolor
from sugar import profile from sugar import profile
@ -45,6 +49,13 @@ def html_to_rgb(html_color):
class ActivityIcon(CanvasIcon): class ActivityIcon(CanvasIcon):
_INTERVAL = 250 _INTERVAL = 250
__gsignals__ = {
'resume': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ([])),
'stop': (gobject.SIGNAL_RUN_FIRST,
gobject.TYPE_NONE, ([]))
}
def __init__(self, activity): def __init__(self, activity):
icon_name = activity.get_icon_name() icon_name = activity.get_icon_name()
self._orig_color = activity.get_icon_color() self._orig_color = activity.get_icon_color()
@ -61,15 +72,38 @@ class ActivityIcon(CanvasIcon):
self._activity = activity self._activity = activity
self._pulse_id = 0 self._pulse_id = 0
palette = Palette(_('Starting...'))
self.set_palette(palette)
activity.connect('notify::launching', self._launching_changed_cb) activity.connect('notify::launching', self._launching_changed_cb)
if activity.props.launching: if activity.props.launching:
self._start_pulsing() self._start_pulsing()
else:
self._setup_palette()
def _setup_palette(self):
palette = self.get_palette()
palette.set_primary_text(self._activity.get_title())
resume_menu_item = gtk.MenuItem(_('Resume'))
resume_menu_item.connect('activate', self._resume_activate_cb)
palette.append_menu_item(resume_menu_item)
resume_menu_item.show()
# FIXME: kludge
if self._activity.get_type() != "org.laptop.JournalActivity":
stop_menu_item = gtk.MenuItem(_('Stop'))
stop_menu_item.connect('activate', self._stop_activate_cb)
palette.append_menu_item(stop_menu_item)
stop_menu_item.show()
def _launching_changed_cb(self, activity, pspec): def _launching_changed_cb(self, activity, pspec):
if activity.props.launching: if activity.props.launching:
self._start_pulsing() self._start_pulsing()
else: else:
self._stop_pulsing() self._stop_pulsing()
self._setup_palette()
def __del__(self): def __del__(self):
self._cleanup() self._cleanup()
@ -129,7 +163,16 @@ class ActivityIcon(CanvasIcon):
self._cleanup() self._cleanup()
self._level = 100.0 self._level = 100.0
self.props.xo_color = self._orig_color self.props.xo_color = self._orig_color
self.emit_paint_needed(0, 0, -1, -1)
# Force the donut to redraw now that we know how much memory
# the activity is using.
self.emit_request_changed()
def _resume_activate_cb(self, menuitem):
self.emit('resume')
def _stop_activate_cb(self, menuitem):
self.emit('stop')
def get_activity(self): def get_activity(self):
return self._activity return self._activity
@ -141,12 +184,15 @@ class ActivitiesDonut(hippo.CanvasBox, hippo.CanvasItem):
self._activities = [] self._activities = []
self._shell = shell self._shell = shell
self._angles = []
self._model = shell.get_model().get_home() self._model = shell.get_model().get_home()
self._model.connect('activity-added', self._activity_added_cb) self._model.connect('activity-added', self._activity_added_cb)
self._model.connect('activity-removed', self._activity_removed_cb) self._model.connect('activity-removed', self._activity_removed_cb)
self._model.connect('active-activity-changed', self._activity_changed_cb) self._model.connect('active-activity-changed', self._activity_changed_cb)
self.connect('button-release-event', self._button_release_event_cb)
def _get_icon_from_activity(self, activity): def _get_icon_from_activity(self, activity):
for icon in self._activities: for icon in self._activities:
if icon.get_activity().equals(activity): if icon.get_activity().equals(activity):
@ -170,24 +216,154 @@ class ActivitiesDonut(hippo.CanvasBox, hippo.CanvasItem):
def _add_activity(self, activity): def _add_activity(self, activity):
icon = ActivityIcon(activity) icon = ActivityIcon(activity)
icon.connect('activated', self._activity_icon_clicked_cb) icon.connect('resume', self._activity_icon_resumed_cb)
icon.connect('stop', self._activity_icon_stop_cb)
self.append(icon, hippo.PACK_FIXED) self.append(icon, hippo.PACK_FIXED)
self._activities.append(icon) self._activities.append(icon)
self.emit_paint_needed(0, 0, -1, -1) self.emit_paint_needed(0, 0, -1, -1)
def _activity_icon_clicked_cb(self, icon): def _activity_icon_resumed_cb(self, icon):
activity = icon.get_activity() activity = icon.get_activity()
activity_host = self._shell.get_activity(activity.get_activity_id()) activity_host = self._shell.get_activity(activity.get_activity_id())
if activity_host: if activity_host:
activity_host.present() activity_host.present()
else:
logging.error("Could not find ActivityHost for activity %s" %
activity.get_activity_id())
def _activity_icon_stop_cb(self, icon):
activity = icon.get_activity()
activity_host = self._shell.get_activity(activity.get_activity_id())
if activity_host:
activity_host.close()
else:
logging.error("Could not find ActivityHost for activity %s" %
activity.get_activity_id())
def _get_activity(self, x, y):
# Compute the distance from the center.
[width, height] = self.get_allocation()
x -= width / 2
y -= height / 2
r = math.hypot(x, y)
# Ignore the click if it's not inside the donut
if r < self._get_inner_radius() or r > self._get_radius():
return None
# Now figure out where in the donut the click was.
angle = math.atan2(-y, -x) + math.pi
# Unfortunately, _get_angles() doesn't count from 0 to 2pi, it
# counts from roughly pi/2 to roughly 5pi/2. So we have to
# compare its return values against both angle and angle+2pi
high_angle = angle + 2 * math.pi
for index, activity in enumerate(self._model):
[angle_start, angle_end] = self._get_angles(index)
if angle_start < angle and angle_end > angle:
return activity
elif angle_start < high_angle and angle_end > high_angle:
return activity
return None
def _button_release_event_cb(self, item, event):
activity = self._get_activity(event.x, event.y)
if activity is None:
return False
activity_host = self._shell.get_activity(activity.get_activity_id())
if activity_host:
activity_host.present()
return True
MAX_ACTIVITIES = 10
MIN_ACTIVITY_WEDGE_SIZE = 1.0 / MAX_ACTIVITIES
def _get_activity_sizes(self):
# First get the size of each process that hosts an activity,
# and the number of activities it hosts.
process_size = {}
num_activities = {}
total_activity_size = 0
for activity in self._model:
pid = activity.get_pid()
if not pid:
# Still starting up, hasn't opened a window yet
continue
if process_size.has_key(pid):
num_activities[pid] += 1
continue
try:
statm = open('/proc/%s/statm' % pid)
# We use "RSS" (the second field in /proc/PID/statm)
# for the activity size because that's what ps and top
# use for calculating "%MEM". We multiply by 4 to
# convert from pages to kb.
process_size[pid] = int(statm.readline().split()[1]) * 4
total_activity_size += process_size[pid]
num_activities[pid] = 1
statm.close()
except IOError:
logging.warn('ActivitiesDonut: could not read /proc/%s/statm' %
pid)
except (IndexError, ValueError):
logging.warn('ActivitiesDonut: /proc/%s/statm was not in ' +
'expected format' % pid)
# Next, see how much free memory is left.
try:
meminfo = open('/proc/meminfo')
meminfo.readline()
free_memory = int(meminfo.readline()[9:-3])
meminfo.close()
except IOError:
logging.warn('ActivitiesDonut: could not read /proc/meminfo')
except (IndexError, ValueError):
logging.warn('ActivitiesDonut: /proc/meminfo was not in ' +
'expected format')
# Each activity starts with MIN_ACTIVITY_WEDGE_SIZE. The
# remaining space in the donut is allocated proportionately
# among the activities-of-known-size and the free space
used_space = ActivitiesDonut.MIN_ACTIVITY_WEDGE_SIZE * len(self._model)
remaining_space = max(0.0, 1.0 - used_space)
total_memory = total_activity_size + free_memory
activity_sizes = []
for activity in self._model:
percent = ActivitiesDonut.MIN_ACTIVITY_WEDGE_SIZE
pid = activity.get_pid()
if process_size.has_key(pid):
size = process_size[pid] / num_activities[pid]
percent += remaining_space * size / total_memory
activity_sizes.append(percent)
return activity_sizes
def _compute_angles(self):
percentages = self._get_activity_sizes()
self._angles = []
if len(percentages) == 0:
return
# The first wedge (Journal) should be centered at 6 o'clock
size = percentages[0] * 2 * math.pi
angle = (math.pi - size) / 2
self._angles.append(angle)
for size in percentages:
self._angles.append(self._angles[-1] + size * 2 * math.pi)
def _get_angles(self, index): def _get_angles(self, index):
angle = 2 * math.pi / 8 return [self._angles[index],
bottom_align = (math.pi - angle) / 2 self._angles[(index + 1) % len(self._angles)]]
return [index * angle + bottom_align,
(index + 1) * angle + bottom_align]
def _get_radius(self): def _get_radius(self):
[width, height] = self.get_allocation() [width, height] = self.get_allocation()
@ -251,9 +427,8 @@ class ActivitiesDonut(hippo.CanvasBox, hippo.CanvasItem):
radius = (self._get_inner_radius() + self._get_radius()) / 2 radius = (self._get_inner_radius() + self._get_radius()) / 2
i = 0 self._compute_angles()
for h_activity in self._model: for i, icon in enumerate(self._activities):
icon = self._get_icon_from_activity(h_activity)
[angle_start, angle_end] = self._get_angles(i) [angle_start, angle_end] = self._get_angles(i)
angle = angle_start + (angle_end - angle_start) / 2 angle = angle_start + (angle_end - angle_start) / 2
@ -262,5 +437,3 @@ class ActivitiesDonut(hippo.CanvasBox, hippo.CanvasItem):
x = int(radius * math.cos(angle)) - icon_width / 2 x = int(radius * math.cos(angle)) - icon_width / 2
y = int(radius * math.sin(angle)) - icon_height / 2 y = int(radius * math.sin(angle)) - icon_height / 2
self.set_position(icon, x + width / 2, y + height / 2) self.set_position(icon, x + width / 2, y + height / 2)
i += 1

View File

@ -200,6 +200,9 @@ def delete(object_id):
def find(query, sorting=None, limit=None, offset=None, reply_handler=None, def find(query, sorting=None, limit=None, offset=None, reply_handler=None,
error_handler=None): error_handler=None):
query = query.copy()
if sorting: if sorting:
query['order_by'] = sorting query['order_by'] = sorting
if limit: if limit:

View File

@ -16,32 +16,115 @@
# Boston, MA 02111-1307, USA. # Boston, MA 02111-1307, USA.
import os import os
import gobject
import gtk import gtk
import re
from sugar.graphics.style import Color
class Icon(gtk.Image): class Icon(gtk.Image):
def __init__(self, name, size=gtk.ICON_SIZE_LARGE_TOOLBAR): __gtype_name__ = 'SugarIcon'
gtk.Image.__init__(self)
__gproperties__ = {
'xo-color' : (object, None, None,
gobject.PARAM_WRITABLE),
'fill-color' : (object, None, None,
gobject.PARAM_READWRITE),
'stroke-color' : (object, None, None,
gobject.PARAM_READWRITE)
}
def __init__(self, name, size=gtk.ICON_SIZE_LARGE_TOOLBAR, **kwargs):
self._fill_color = None
self._stroke_color = None
self._icon_name = name
self._size = size
self._theme = gtk.icon_theme_get_default()
gobject.GObject.__init__(self, **kwargs)
# If we have a non-styled-icon
if not self._fill_color and not self._stroke_color:
self._update_normal_icon()
def _get_pixbuf(self, data, width, height):
loader = gtk.gdk.PixbufLoader('svg')
loader.set_size(width, height)
loader.write(data, len(data))
loader.close()
return loader.get_pixbuf()
def _read_icon_data(self, icon_name):
filename = self._get_real_name(icon_name)
icon_file = open(filename, 'r')
data = icon_file.read()
icon_file.close()
return data
def _update_normal_icon(self):
icon_theme = gtk.icon_theme_get_for_screen(self.get_screen()) icon_theme = gtk.icon_theme_get_for_screen(self.get_screen())
icon_set = gtk.IconSet() icon_set = gtk.IconSet()
normal_name = name if icon_theme.has_icon(self._icon_name):
if icon_theme.has_icon(normal_name):
source = gtk.IconSource() source = gtk.IconSource()
source.set_icon_name(normal_name) source.set_icon_name(self._icon_name)
icon_set.add_source(source)
elif os.path.exists(normal_name):
source = gtk.IconSource()
source.set_filename(normal_name)
icon_set.add_source(source) icon_set.add_source(source)
inactive_name = name + '-inactive' inactive_name = self._icon_name + '-inactive'
if icon_theme.has_icon(inactive_name): if icon_theme.has_icon(inactive_name):
source = gtk.IconSource() source = gtk.IconSource()
source.set_icon_name(inactive_name) source.set_icon_name(inactive_name)
source.set_state(gtk.STATE_INSENSITIVE) source.set_state(gtk.STATE_INSENSITIVE)
icon_set.add_source(source) icon_set.add_source(source)
self.set_from_icon_set(icon_set, size) self.set_from_icon_set(icon_set, self._size)
def _update_icon(self):
if not self._fill_color and not self._stroke_color:
self._update_normal_icon()
return
if not self._data:
data = self._read_icon_data(self._icon_name)
else:
data = self._data
if self._fill_color:
entity = '<!ENTITY fill_color "%s">' % self._fill_color
data = re.sub('<!ENTITY fill_color .*>', entity, data)
if self._stroke_color:
entity = '<!ENTITY stroke_color "%s">' % self._stroke_color
data = re.sub('<!ENTITY stroke_color .*>', entity, data)
self._data = data
# Redraw pixbuf
[w, h] = gtk.icon_size_lookup(self._size)
pixbuf = self._get_pixbuf(self._data, w, h)
self.set_from_pixbuf(pixbuf)
def _get_real_name(self, name):
info = self._theme.lookup_icon(name, self._size, 0)
if not info:
raise ValueError("Icon '" + name + "' not found.")
fname = info.get_filename()
del info
return fname
def do_set_property(self, pspec, value):
if pspec.name == 'xo-color':
self.props.fill_color = value.get_fill_color()
self.props.stroke_color = value.get_stroke_color()
elif pspec.name == 'fill-color':
self._fill_color = value
self._update_icon()
elif pspec.name == 'stroke-color':
self._stroke_color = value
self._update_icon()
def do_get_property(self, pspec):
if pspec.name == 'fill-color':
return self._fill_color
elif pspec.name == 'stroke-color':
return self._stroke_color