sugar-toolkit-gtk3/shell/shell.py
2006-06-30 11:30:32 -04:00

546 lines
16 KiB
Python
Executable File

import dbus
import dbus.service
import dbus.glib
import pygtk
pygtk.require('2.0')
import gtk
import pango
import gobject
import sugar.util
from sugar.chat.ChatWindow import ChatWindow
from sugar.chat.ActivityChat import ActivityChat
from sugar.chat.MeshChat import MeshChat
from sugar.LogWriter import LogWriter
from Owner import ShellOwner
from StartPage import StartPage
from WindowManager import WindowManager
from PresenceWindow import PresenceWindow
class ActivityHostSignalHelper(gobject.GObject):
__gsignals__ = {
'shared': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ([]))
}
def __init__(self, parent):
gobject.GObject.__init__(self)
self._parent = parent
def emit_shared(self):
self.emit('shared')
class ActivityHost(dbus.service.Object):
def __init__(self, activity_container, activity_name, default_type, activity_id = None):
self.peer_service = None
self.activity_name = activity_name
self.ellipsize_tab = False
self._shared = False
self._signal_helper = ActivityHostSignalHelper(self)
self.activity_container = activity_container
if activity_id is None:
self.activity_id = sugar.util.unique_id()
else:
self.activity_id = activity_id
self._default_type = default_type
self.dbus_object_name = "/com/redhat/Sugar/Shell/Activities/%s" % self.activity_id
dbus.service.Object.__init__(self, activity_container.service, self.dbus_object_name)
self.socket = gtk.Socket()
self.socket.set_data("sugar-activity", self)
self.socket.show()
hbox = gtk.HBox(False, 4);
self.tab_activity_image = gtk.Image()
self.tab_activity_image.set_from_stock(gtk.STOCK_CONVERT, gtk.ICON_SIZE_MENU)
hbox.pack_start(self.tab_activity_image)
#self.tab_activity_image.show()
self.label_hbox = gtk.HBox(False, 4);
self.label_hbox.connect("style-set", self.__tab_label_style_set_cb)
hbox.pack_start(self.label_hbox)
self.tab_label = gtk.Label(self.activity_name)
self.tab_label.set_single_line_mode(True)
self.tab_label.set_alignment(0.0, 0.5)
self.tab_label.set_padding(0, 0)
self.tab_label.show()
close_image = gtk.Image()
close_image.set_from_stock (gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
close_image.show()
self.tab_close_button = gtk.Button()
rcstyle = gtk.RcStyle();
rcstyle.xthickness = rcstyle.ythickness = 0;
self.tab_close_button.modify_style (rcstyle);
self.tab_close_button.add(close_image)
self.tab_close_button.set_relief(gtk.RELIEF_NONE)
self.tab_close_button.set_focus_on_click(False)
self.tab_close_button.connect("clicked", self.tab_close_button_clicked)
self.label_hbox.pack_start(self.tab_label)
self.label_hbox.pack_start(self.tab_close_button, False, False, 0)
self.label_hbox.show()
hbox.show()
self._create_chat()
notebook = self.activity_container.notebook
index = notebook.append_page(self.socket, hbox)
notebook.set_current_page(index)
def _create_chat(self):
self._activity_chat = ActivityChat(self)
def got_focus(self):
if self.peer_service != None:
self.peer_service.got_focus()
def lost_focus(self):
self.peer_service.lost_focus()
def get_chat(self):
return self._activity_chat
def get_default_type(self):
return self._default_type
def __close_button_clicked_reply_cb(self):
pass
def __close_button_clicked_error_cb(self, error):
pass
def publish(self):
self._activity_chat.publish()
self.peer_service.publish()
def tab_close_button_clicked(self, button):
self.peer_service.close_from_user(reply_handler = self.__close_button_clicked_reply_cb, \
error_handler = self.__close_button_clicked_error_cb)
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
in_signature="", \
out_signature="t")
def get_host_xembed_id(self):
window_id = self.socket.get_id()
#print "window_id = %d"%window_id
return window_id
def connect(self, signal, func):
self._signal_helper.connect(signal, func)
def get_shared(self):
"""Return True if this activity is shared, False if
it has not been shared yet."""
return self._shared
def _shared_signal(self):
self._shared = True
self._signal_helper.emit_shared()
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
in_signature="ss", \
out_signature="")
def set_peer_service_name(self, peer_service_name, peer_object_name):
#print "peer_service_name = %s, peer_object_name = %s"%(peer_service_name, peer_object_name)
self.__peer_service_name = peer_service_name
self.__peer_object_name = peer_object_name
self.peer_service = dbus.Interface(self.activity_container.bus.get_object( \
self.__peer_service_name, self.__peer_object_name), \
"com.redhat.Sugar.Activity")
self.activity_container.bus.add_signal_receiver(self._shared_signal,
signal_name="ActivityShared",
dbus_interface="com.redhat.Sugar.Activity",
named_service=self.__peer_service_name,
path=self.__peer_object_name)
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
in_signature="b", \
out_signature="")
def set_ellipsize_tab(self, ellipsize):
self.ellipsize_tab = True
self.update_tab_size()
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
in_signature="b", \
out_signature="")
def set_can_close(self, can_close):
if can_close:
self.tab_close_button.show()
else:
self.tab_close_button.hide()
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
in_signature="b", \
out_signature="")
def set_tab_show_icon(self, show_icon):
if show_icon:
self.tab_activity_image.show()
else:
self.tab_activity_image.hide()
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
in_signature="b", \
out_signature="")
def set_has_changes(self, has_changes):
if has_changes:
attrs = pango.AttrList()
attrs.insert(pango.AttrForeground(50000, 0, 0, 0, -1))
attrs.insert(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1))
self.tab_label.set_attributes(attrs)
else:
self.tab_label.set_attributes(pango.AttrList())
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
in_signature="s", \
out_signature="")
def set_tab_text(self, text):
self.tab_label.set_text(text)
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
in_signature="ayibiiii", \
out_signature="")
def set_tab_icon(self, data, colorspace, has_alpha, bits_per_sample, width, height, rowstride):
#print "width=%d, height=%d"%(width, height)
#print " data = ", data
pixstr = ""
for c in data:
# Work around for a bug in dbus < 0.61 where integers
# are not correctly marshalled
if c < 0:
c += 256
pixstr += chr(c)
pixbuf = gtk.gdk.pixbuf_new_from_data(pixstr, colorspace, has_alpha, bits_per_sample, width, height, rowstride)
#print pixbuf
self.tab_activity_image.set_from_pixbuf(pixbuf)
@dbus.service.method("com.redhat.Sugar.Shell.ActivityHost", \
in_signature="", \
out_signature="")
def shutdown(self):
#print "shutdown"
for owner, activity in self.activity_container.activities[:]:
if activity == self:
self.activity_container.activities.remove((owner, activity))
for i in range(self.activity_container.notebook.get_n_pages()):
child = self.activity_container.notebook.get_nth_page(i)
if child == self.socket:
#print "found child"
self.activity_container.notebook.remove_page(i)
break
del self
def get_host_activity_id(self):
"""Real function that the shell should use for getting the
activity's ID."""
return self.activity_id
def get_id(self):
"""Interface-type function to match activity.Activity's
get_id() function."""
return self.activity_id
def default_type(self):
"""Interface-type function to match activity.Activity's
default_type() function."""
return self._default_type
def get_object_path(self):
return self.dbus_object_name
def update_tab_size(self):
if self.ellipsize_tab:
self.tab_label.set_ellipsize(pango.ELLIPSIZE_END)
context = self.label_hbox.get_pango_context()
font_desc = self.label_hbox.style.font_desc
metrics = context.get_metrics(font_desc, context.get_language())
char_width = metrics.get_approximate_digit_width()
[w, h] = self.__get_close_icon_size()
tab_width = 15 * pango.PIXELS(char_width) + 2 * w
self.label_hbox.set_size_request(tab_width, -1);
else:
self.tab_label.set_ellipsize(pango.ELLIPSIZE_NONE)
self.label_hbox.set_size_request(-1, -1)
def __get_close_icon_size(self):
settings = self.label_hbox.get_settings()
return gtk.icon_size_lookup_for_settings(settings, gtk.ICON_SIZE_MENU)
def __tab_label_style_set_cb(self, widget, previous_style):
[w, h] = self.__get_close_icon_size()
self.tab_close_button.set_size_request (w + 5, h + 2)
self.update_tab_size()
class ActivityContainerSignalHelper(gobject.GObject):
"""A gobject whose sole purpose is to distribute signals for
an ActivityContainer object."""
__gsignals__ = {
'local-activity-started': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT])),
'local-activity-ended': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([gobject.TYPE_PYOBJECT, gobject.TYPE_PYOBJECT]))
}
def __init__(self, parent):
gobject.GObject.__init__(self)
self._parent = parent
def activity_started(self, activity_id):
self.emit('local-activity-started', self._parent, activity_id)
def activity_ended(self, activity_id):
self.emit('local-activity-ended', self._parent, activity_id)
class ActivityContainer(dbus.service.Object):
def __init__(self, service, bus):
self.activities = []
self.bus = bus
self.service = service
self._signal_helper = ActivityContainerSignalHelper(self)
dbus.service.Object.__init__(self, self.service, "/com/redhat/Sugar/Shell/ActivityContainer")
bus.add_signal_receiver(self.name_owner_changed, dbus_interface = "org.freedesktop.DBus", signal_name = "NameOwnerChanged")
self.window = gtk.Window()
self.window.connect("key-press-event", self.__key_press_event_cb)
self.window.set_title("OLPC Sugar")
self._fullscreen = False
self.notebook = gtk.Notebook()
self.notebook.set_scrollable(True)
tab_label = gtk.Label("Everyone")
self._start_page = StartPage(self._signal_helper)
self.notebook.append_page(self._start_page, tab_label)
self._start_page.show()
self.notebook.show()
self.notebook.connect("switch-page", self.notebook_tab_changed)
self.window.add(self.notebook)
self.window.connect("destroy", lambda w: gtk.main_quit())
self.current_activity = None
# Create our owner service
self._owner = ShellOwner()
self._presence_window = PresenceWindow(self)
self._presence_window.set_transient_for(self.window)
self._presence_window.set_decorated(False)
self._presence_window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DOCK)
self._presence_window.set_skip_taskbar_hint(True)
wm = WindowManager(self._presence_window)
wm.set_width(170, WindowManager.ABSOLUTE)
wm.set_height(1.0, WindowManager.SCREEN_RELATIVE)
wm.set_position(WindowManager.LEFT)
wm.manage()
self._chat_window = ChatWindow()
self._chat_window.set_transient_for(self.window)
self._chat_window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DOCK)
self._chat_window.set_decorated(False)
self._chat_window.set_skip_taskbar_hint(True)
self._chat_wm = WindowManager(self._chat_window)
self._chat_wm.set_width(420, WindowManager.ABSOLUTE)
self._chat_wm.set_height(380, WindowManager.ABSOLUTE)
self._chat_wm.set_position(WindowManager.TOP)
self._chat_wm.manage()
self._mesh_chat = MeshChat()
def show(self):
self.window.show()
def set_current_activity(self, activity):
self.current_activity = activity
self._presence_window.set_activity(activity)
if activity:
host_chat = activity.get_chat()
self._chat_window.set_chat(host_chat)
else:
self._chat_window.set_chat(self._mesh_chat)
# For some reason the substitution screw up window position
self._chat_wm.update()
def notebook_tab_changed(self, notebook, page, page_number):
new_activity = notebook.get_nth_page(page_number).get_data("sugar-activity")
if self.current_activity != None:
self.current_activity.lost_focus()
self.set_current_activity(new_activity)
if self.current_activity != None:
self.current_activity.got_focus()
def name_owner_changed(self, service_name, old_service_name, new_service_name):
#print "in name_owner_changed: svc=%s oldsvc=%s newsvc=%s"%(service_name, old_service_name, new_service_name)
for owner, activity in self.activities[:]:
if owner == old_service_name:
activity_id = activity.get_host_activity_id()
self._signal_helper.activity_ended(activity_id)
self.activities.remove((owner, activity))
#self.__print_activities()
@dbus.service.method("com.redhat.Sugar.Shell.ActivityContainer", \
in_signature="ss", \
out_signature="s", \
sender_keyword="sender")
def add_activity(self, activity_name, default_type, sender):
#print "hello world, activity_name = '%s', sender = '%s'"%(activity_name, sender)
activity = ActivityHost(self, activity_name, default_type)
self.activities.append((sender, activity))
activity_id = activity.get_host_activity_id()
self._signal_helper.activity_started(activity_id)
self.current_activity = activity
return activity_id
@dbus.service.method("com.redhat.Sugar.Shell.ActivityContainer", \
in_signature="sss", \
sender_keyword="sender")
def add_activity_with_id(self, activity_name, default_type, activity_id, sender):
activity = ActivityHost(self, activity_name, default_type, activity_id)
self.activities.append((sender, activity))
activity_id = activity.get_host_activity_id()
self._signal_helper.activity_started(activity_id)
self.current_activity = activity
def __print_activities(self):
print "__print_activities: %d activities registered" % len(self.activities)
i = 0
for owner, activity in self.activities:
print " %d: owner=%s activity_object_name=%s" % (i, owner, activity.dbus_object_name)
i += 1
def __key_press_event_cb(self, window, event):
if event.keyval == gtk.keysyms.F11:
if self._fullscreen:
window.unfullscreen()
self._fullscreen = False
else:
window.fullscreen()
self._fullscreen = True
class ConsoleLogger(dbus.service.Object):
def __init__(self):
session_bus = dbus.SessionBus()
bus_name = dbus.service.BusName('com.redhat.Sugar.Logger', bus=session_bus)
object_path = '/com/redhat/Sugar/Logger'
dbus.service.Object.__init__(self, bus_name, object_path)
self._window = gtk.Window()
self._window.set_title("Console")
self._window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DOCK)
self._window.set_decorated(False)
self._window.set_skip_taskbar_hint(True)
self._window.connect("delete_event", lambda w, e: w.hide_on_delete())
self._nb = gtk.Notebook()
self._window.add(self._nb)
self._nb.show()
self._consoles = {}
console_wm = WindowManager(self._window)
console_wm.set_width(0.7, WindowManager.SCREEN_RELATIVE)
console_wm.set_height(0.9, WindowManager.SCREEN_RELATIVE)
console_wm.set_position(WindowManager.BOTTOM)
console_wm.manage()
def _create_console(self, application):
sw = gtk.ScrolledWindow()
sw.set_policy(gtk.POLICY_AUTOMATIC,
gtk.POLICY_AUTOMATIC)
console = gtk.TextView()
console.set_wrap_mode(gtk.WRAP_WORD)
sw.add(console)
console.show()
self._nb.append_page(sw, gtk.Label(application))
sw.show()
return console
@dbus.service.method('com.redhat.Sugar.Logger')
def log(self, application, message):
if self._consoles.has_key(application):
console = self._consoles[application]
else:
console = self._create_console(application)
self._consoles[application] = console
buf = console.get_buffer()
buf.insert(buf.get_end_iter(), message)
class Shell(gobject.GObject):
__gsignals__ = {
'close': (gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
([])),
}
def __init__(self):
gobject.GObject.__init__(self)
def start(self):
console = ConsoleLogger()
log_writer = LogWriter("Shell", False)
log_writer.start()
session_bus = dbus.SessionBus()
service = dbus.service.BusName("com.redhat.Sugar.Shell", bus=session_bus)
activity_container = ActivityContainer(service, session_bus)
activity_container.window.connect('destroy', self.__activity_container_destroy_cb)
activity_container.show()
wm = WindowManager(activity_container.window)
wm.set_width(640, WindowManager.ABSOLUTE)
wm.set_height(480, WindowManager.ABSOLUTE)
wm.set_position(WindowManager.CENTER)
wm.show()
wm.manage()
def __activity_container_destroy_cb(self, activity_container):
self.emit('close')
if __name__ == "__main__":
shell = Shell()
shell.start()
try:
gtk.main()
except KeyboardInterrupt:
print 'Ctrl+c pressed, exiting...'