Move the view to his own module
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import gtk
|
||||
import dbus
|
||||
|
||||
import conf
|
||||
from sugar.activity import Activity
|
||||
from sugar.presence import PresenceService
|
||||
from sugar.canvas.IconColor import IconColor
|
||||
from sugar.p2p import Stream
|
||||
from sugar.p2p import network
|
||||
|
||||
class ActivityHost:
|
||||
def __init__(self, shell, window):
|
||||
self._shell = shell
|
||||
self._window = window
|
||||
self._xid = window.get_xid()
|
||||
self._pservice = PresenceService.get_instance()
|
||||
|
||||
bus = dbus.SessionBus()
|
||||
proxy_obj = bus.get_object(Activity.get_service_name(self._xid),
|
||||
Activity.get_object_path(self._xid))
|
||||
|
||||
self._activity = dbus.Interface(proxy_obj, Activity.ACTIVITY_INTERFACE)
|
||||
self._id = self._activity.get_id()
|
||||
self._type = self._activity.get_type()
|
||||
self._gdk_window = gtk.gdk.window_foreign_new(self._xid)
|
||||
|
||||
registry = conf.get_activity_registry()
|
||||
info = registry.get_activity(self._type)
|
||||
self._icon_name = info.get_icon()
|
||||
|
||||
def get_id(self):
|
||||
return self._id
|
||||
|
||||
def get_xid(self):
|
||||
return self._xid
|
||||
|
||||
def get_icon_name(self):
|
||||
return self._icon_name
|
||||
|
||||
def get_icon_color(self):
|
||||
activity = self._pservice.get_activity(self._id)
|
||||
if activity != None:
|
||||
return IconColor(activity.get_color())
|
||||
else:
|
||||
return conf.get_profile().get_color()
|
||||
|
||||
def share(self):
|
||||
self._activity.share()
|
||||
|
||||
def invite(self, buddy):
|
||||
if not self.get_shared():
|
||||
self.share()
|
||||
|
||||
issuer = self._pservice.get_owner().get_name()
|
||||
service = buddy.get_service_of_type("_presence_olpc._tcp")
|
||||
stream = Stream.Stream.new_from_service(service, start_reader=False)
|
||||
writer = stream.new_writer(service)
|
||||
writer.custom_request("invite", None, None, issuer,
|
||||
self._type, self._id)
|
||||
|
||||
def get_shared(self):
|
||||
return self._activity.get_shared()
|
||||
|
||||
def get_type(self):
|
||||
return self._type
|
||||
|
||||
def present(self):
|
||||
self._window.activate(gtk.get_current_event_time())
|
||||
|
||||
def show_dialog(self, dialog):
|
||||
dialog.show()
|
||||
dialog.window.set_transient_for(self._gdk_window)
|
||||
@@ -0,0 +1,128 @@
|
||||
import logging
|
||||
|
||||
import gtk
|
||||
import dbus
|
||||
import dbus.service
|
||||
|
||||
class Console(gtk.ScrolledWindow):
|
||||
def __init__(self):
|
||||
gtk.ScrolledWindow.__init__(self)
|
||||
|
||||
self._show_debug = False
|
||||
|
||||
self.set_policy(gtk.POLICY_AUTOMATIC,
|
||||
gtk.POLICY_AUTOMATIC)
|
||||
|
||||
self._textview = gtk.TextView()
|
||||
self._textview.set_wrap_mode(gtk.WRAP_WORD)
|
||||
self.add(self._textview)
|
||||
self._textview.show()
|
||||
|
||||
buf = self._textview.get_buffer()
|
||||
self._debug_tag = buf.create_tag("debug")
|
||||
self._debug_tag.set_property("invisible", True)
|
||||
|
||||
def get_show_debug(self):
|
||||
return self._show_debug
|
||||
|
||||
def set_show_debug(self, show_debug):
|
||||
self._show_debug = show_debug
|
||||
self._debug_tag.set_property("invisible", not show_debug)
|
||||
|
||||
def log(self, level, message):
|
||||
msg = message + '\n'
|
||||
buf = self._textview.get_buffer()
|
||||
it = buf.get_end_iter()
|
||||
|
||||
if level == logging.DEBUG:
|
||||
buf.insert_with_tags(it, msg, self._debug_tag)
|
||||
else:
|
||||
buf.insert(it, msg)
|
||||
|
||||
class ConsoleDbusService(dbus.service.Object):
|
||||
def __init__(self, console, bus_name):
|
||||
dbus.service.Object.__init__(self, bus_name, '/org/laptop/Sugar/Console')
|
||||
self._console = console
|
||||
|
||||
@dbus.service.method('org.laptop.Sugar.Console',
|
||||
in_signature="saias", out_signature="")
|
||||
def log(self, module_id, levels, messages):
|
||||
self._console.log(module_id, levels, messages)
|
||||
|
||||
class ConsoleWindow(gtk.Window):
|
||||
def __init__(self):
|
||||
gtk.Window.__init__(self)
|
||||
|
||||
self.set_default_size(620, 440)
|
||||
self.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
|
||||
self.set_title("Console")
|
||||
self.connect("delete_event", lambda w, e: w.hide_on_delete())
|
||||
|
||||
vbox = gtk.VBox()
|
||||
|
||||
toolbar = gtk.Toolbar()
|
||||
|
||||
self._debug_toggle = gtk.ToggleToolButton()
|
||||
self._debug_toggle.connect('toggled', self.__debug_toggled_cb)
|
||||
self._debug_toggle.set_label('Debug')
|
||||
toolbar.insert(self._debug_toggle, -1)
|
||||
self._debug_toggle.show()
|
||||
|
||||
self._ignore_toggle = False
|
||||
|
||||
vbox.pack_start(toolbar, False)
|
||||
toolbar.show()
|
||||
|
||||
self._nb = gtk.Notebook()
|
||||
self._nb.connect('switch-page', self.__page_changed_cb)
|
||||
vbox.pack_start(self._nb)
|
||||
self._nb.show()
|
||||
|
||||
self.add(vbox)
|
||||
vbox.show()
|
||||
|
||||
self._consoles = {}
|
||||
|
||||
session_bus = dbus.SessionBus()
|
||||
bus_name = dbus.service.BusName('org.laptop.Sugar.Console', bus=session_bus)
|
||||
ConsoleDbusService(self, bus_name)
|
||||
|
||||
def _add_console(self, page_id):
|
||||
console = Console()
|
||||
page = self._nb.append_page(console, gtk.Label(page_id))
|
||||
console.show()
|
||||
|
||||
self._consoles[page_id] = console
|
||||
|
||||
return console
|
||||
|
||||
def _get_console(self, page_id):
|
||||
if not self._consoles.has_key(page_id):
|
||||
console = self._add_console(page_id)
|
||||
else:
|
||||
console = self._consoles[page_id]
|
||||
return console
|
||||
|
||||
def __debug_toggled_cb(self, button):
|
||||
if not self._ignore_toggle:
|
||||
console = self._nb.get_nth_page(self._nb.get_current_page())
|
||||
if console:
|
||||
console.set_show_debug(button.get_active())
|
||||
|
||||
def __page_changed_cb(self, notebook, page, page_num):
|
||||
console = self._nb.get_nth_page(page_num)
|
||||
|
||||
self._ignore_toggle = True
|
||||
self._debug_toggle.set_active(console.get_show_debug())
|
||||
self._ignore_toggle = False
|
||||
|
||||
def set_page(self, page_id):
|
||||
page = self._nb.page_num(self._consoles[page_id])
|
||||
self._nb.set_current_page(page)
|
||||
|
||||
def log(self, page_id, levels, messages):
|
||||
console = self._get_console(page_id)
|
||||
i = 0
|
||||
while i < len(levels):
|
||||
console.log(levels[i], messages[i])
|
||||
i += 1
|
||||
@@ -0,0 +1,36 @@
|
||||
import gtk
|
||||
|
||||
from gettext import gettext as _
|
||||
|
||||
import conf
|
||||
|
||||
class FirstTimeDialog(gtk.Dialog):
|
||||
def __init__(self):
|
||||
gtk.Dialog.__init__(self)
|
||||
|
||||
label = gtk.Label(_('Nick Name:'))
|
||||
label.set_alignment(0.0, 0.5)
|
||||
self.vbox.pack_start(label)
|
||||
label.show()
|
||||
|
||||
self._entry = gtk.Entry()
|
||||
self.vbox.pack_start(self._entry)
|
||||
self._entry.show()
|
||||
|
||||
button = gtk.Button(None, gtk.STOCK_OK)
|
||||
self.vbox.pack_start(button)
|
||||
button.connect('clicked', self.__ok_button_clicked_cb)
|
||||
button.show()
|
||||
|
||||
def __ok_button_clicked_cb(self, button):
|
||||
profile = conf.get_profile()
|
||||
profile.set_nick_name(self._entry.get_text())
|
||||
self.destroy()
|
||||
|
||||
def get_profile():
|
||||
profile = conf.get_profile()
|
||||
if profile.get_nick_name() == None:
|
||||
dialog = FirstTimeDialog()
|
||||
dialog.connect('destroy', self.__first_time_dialog_destroy_cb)
|
||||
dialog.show()
|
||||
return profile
|
||||
@@ -0,0 +1,104 @@
|
||||
from sugar.canvas.IconItem import IconItem
|
||||
from sugar.canvas.Grid import Grid
|
||||
from view.FriendPopup import FriendPopup
|
||||
|
||||
class _PopupShell:
|
||||
def __init__(self):
|
||||
self._popup_controller = None
|
||||
|
||||
def set_active(self, controller):
|
||||
if self._popup_controller:
|
||||
self._popup_controller._popdown()
|
||||
self._popup_controller = controller
|
||||
|
||||
class FriendIcon(IconItem):
|
||||
_popup_shell = _PopupShell()
|
||||
|
||||
def __init__(self, shell_model, friend):
|
||||
IconItem.__init__(self, icon_name='stock-buddy',
|
||||
color=friend.get_color(), size=96)
|
||||
|
||||
self._shell_model = shell_model
|
||||
self._friend = friend
|
||||
self._popup = None
|
||||
self._popup_distance = 0
|
||||
self._hover_popup = False
|
||||
self._popdown_on_leave = False
|
||||
|
||||
self.connect('popup', self._popup_cb)
|
||||
self.connect('popdown', self._popdown_cb)
|
||||
|
||||
def set_popup_distance(self, distance):
|
||||
self._popup_distance = distance
|
||||
|
||||
def get_friend(self):
|
||||
return self._friend
|
||||
|
||||
def _popdown(self):
|
||||
if self._popup:
|
||||
self._popup.destroy()
|
||||
self._popup = None
|
||||
|
||||
def _popup_cb(self, icon, x1, y1, x2, y2):
|
||||
self._popdown()
|
||||
|
||||
FriendIcon._popup_shell.set_active(None)
|
||||
|
||||
grid = Grid()
|
||||
self._popup = FriendPopup(grid, icon.get_friend())
|
||||
self._popup.connect('action', self._popup_action_cb)
|
||||
self._popup.connect('enter-notify-event',
|
||||
self._popup_enter_notify_event_cb)
|
||||
self._popup.connect('leave-notify-event',
|
||||
self._popup_leave_notify_event_cb)
|
||||
|
||||
distance = self._popup_distance
|
||||
|
||||
[grid_x1, grid_y1] = grid.convert_from_screen(x1, y1)
|
||||
[grid_x2, grid_y2] = grid.convert_from_screen(x2, y2)
|
||||
|
||||
grid_x = grid_x2 + distance
|
||||
if grid_x + self._popup.get_width() > Grid.ROWS:
|
||||
grid_x = grid_x1 - self._popup.get_width() + 1 - distance
|
||||
|
||||
grid_y = grid_y1
|
||||
|
||||
if grid_y < 0:
|
||||
grid_y = 0
|
||||
if grid_y + self._popup.get_width() > Grid.ROWS:
|
||||
grid_y = Grid.ROWS - self._popup.get_width()
|
||||
|
||||
grid.set_constraints(self._popup, grid_x, grid_y,
|
||||
self._popup.get_width(), self._popup.get_height())
|
||||
|
||||
self._popup.show()
|
||||
|
||||
FriendIcon._popup_shell.set_active(self)
|
||||
|
||||
def _popup_action_cb(self, popup, action):
|
||||
self._popdown()
|
||||
|
||||
buddy = self._friend.get_buddy()
|
||||
if buddy == None:
|
||||
return
|
||||
|
||||
if action == FriendPopup.ACTION_INVITE:
|
||||
activity = self._shell_model.get_current_activity()
|
||||
activity.invite(buddy)
|
||||
elif action == FriendPopup.ACTION_MAKE_FRIEND:
|
||||
friends = self._shell_model.get_friends()
|
||||
friends.add_buddy(buddy)
|
||||
|
||||
def _popdown_cb(self, friend):
|
||||
if not self._hover_popup:
|
||||
self._popdown()
|
||||
else:
|
||||
self._popdown_on_leave = True
|
||||
|
||||
def _popup_enter_notify_event_cb(self, widget, event):
|
||||
self._hover_popup = True
|
||||
|
||||
def _popup_leave_notify_event_cb(self, widget, event):
|
||||
self._hover_popup = False
|
||||
if self._popdown_on_leave:
|
||||
self._popdown()
|
||||
@@ -0,0 +1,83 @@
|
||||
import gtk
|
||||
import goocanvas
|
||||
import gobject
|
||||
|
||||
from sugar.canvas.CanvasView import CanvasView
|
||||
from sugar.canvas.CanvasBox import CanvasBox
|
||||
from sugar.canvas.IconItem import IconItem
|
||||
|
||||
class FriendPopup(gtk.Window):
|
||||
ACTION_MAKE_FRIEND = 0
|
||||
ACTION_INVITE = 1
|
||||
|
||||
__gsignals__ = {
|
||||
'action': (gobject.SIGNAL_RUN_FIRST,
|
||||
gobject.TYPE_NONE, ([int])),
|
||||
}
|
||||
|
||||
def __init__(self, grid, friend):
|
||||
gtk.Window.__init__(self, gtk.WINDOW_POPUP)
|
||||
|
||||
self._friend = friend
|
||||
self._hover = False
|
||||
self._popdown_on_leave = False
|
||||
self._width = 13
|
||||
self._height = 10
|
||||
|
||||
canvas = CanvasView()
|
||||
self.add(canvas)
|
||||
canvas.show()
|
||||
|
||||
grid.set_constraints(canvas, 0, 0, self._width, self._height)
|
||||
|
||||
model = goocanvas.CanvasModelSimple()
|
||||
root = model.get_root_item()
|
||||
|
||||
color = friend.get_color()
|
||||
rect = goocanvas.Rect(fill_color=color.get_fill_color(),
|
||||
stroke_color=color.get_stroke_color(),
|
||||
line_width=3)
|
||||
grid.set_constraints(rect, 0, 0, self._width, self._height)
|
||||
root.add_child(rect)
|
||||
|
||||
text = goocanvas.Text(text=friend.get_name(), font="Sans bold 18",
|
||||
fill_color='black', anchor=gtk.ANCHOR_SW)
|
||||
grid.set_constraints(text, 1, 3, self._width, self._height)
|
||||
root.add_child(text)
|
||||
|
||||
separator = goocanvas.Path(data='M 15 0 L 185 0', line_width=3,
|
||||
fill_color='black')
|
||||
grid.set_constraints(separator, 0, 4)
|
||||
root.add_child(separator)
|
||||
|
||||
box = CanvasBox(grid, CanvasBox.HORIZONTAL, 1)
|
||||
grid.set_constraints(box, 0, 5)
|
||||
|
||||
icon = IconItem(icon_name='stock-make-friend')
|
||||
icon.connect('clicked', self._action_clicked_cb,
|
||||
FriendPopup.ACTION_MAKE_FRIEND)
|
||||
box.set_constraints(icon, 3, 3)
|
||||
box.add_child(icon)
|
||||
|
||||
icon = IconItem(icon_name='stock-chat')
|
||||
box.set_constraints(icon, 3, 3)
|
||||
box.add_child(icon)
|
||||
|
||||
icon = IconItem(icon_name='stock-invite')
|
||||
icon.connect('clicked', self._action_clicked_cb,
|
||||
FriendPopup.ACTION_INVITE)
|
||||
box.set_constraints(icon, 3, 3)
|
||||
box.add_child(icon)
|
||||
|
||||
root.add_child(box)
|
||||
|
||||
canvas.set_model(model)
|
||||
|
||||
def _action_clicked_cb(self, icon, action):
|
||||
self.emit('action', action)
|
||||
|
||||
def get_width(self):
|
||||
return self._width
|
||||
|
||||
def get_height(self):
|
||||
return self._height
|
||||
@@ -0,0 +1,11 @@
|
||||
SUBDIRS = frame home
|
||||
|
||||
sugardir = $(pkgdatadir)/shell/view
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
ActivityHost.py \
|
||||
ConsoleWindow.py \
|
||||
FirstTimeDialog.py \
|
||||
FriendIcon.py \
|
||||
FriendPopup.py \
|
||||
Shell.py
|
||||
@@ -0,0 +1,74 @@
|
||||
import gtk
|
||||
import gobject
|
||||
import wnck
|
||||
|
||||
from view.home.HomeWindow import HomeWindow
|
||||
from view.ActivityHost import ActivityHost
|
||||
from view.frame.Frame import Frame
|
||||
from globalkeys import KeyGrabber
|
||||
import sugar
|
||||
|
||||
class Shell(gobject.GObject):
|
||||
def __init__(self, model):
|
||||
gobject.GObject.__init__(self)
|
||||
|
||||
self._model = model
|
||||
self._screen = wnck.screen_get_default()
|
||||
|
||||
self._key_grabber = KeyGrabber()
|
||||
self._key_grabber.connect('key-pressed', self.__global_key_pressed_cb)
|
||||
self._key_grabber.grab('F1')
|
||||
self._key_grabber.grab('F2')
|
||||
self._key_grabber.grab('F3')
|
||||
self._key_grabber.grab('F4')
|
||||
self._key_grabber.grab('F5')
|
||||
self._key_grabber.grab('F6')
|
||||
|
||||
self._home_window = HomeWindow(self.get_model())
|
||||
self._home_window.show()
|
||||
self.set_zoom_level(sugar.ZOOM_HOME)
|
||||
|
||||
self._screen.connect('window-opened', self.__window_opened_cb)
|
||||
self._screen.connect('window-closed', self.__window_closed_cb)
|
||||
self._screen.connect('active-window-changed',
|
||||
self.__active_window_changed_cb)
|
||||
|
||||
self._frame = Frame(self)
|
||||
self._frame.show_and_hide(10)
|
||||
|
||||
def __global_key_pressed_cb(self, grabber, key):
|
||||
if key == 'F1':
|
||||
self.set_zoom_level(sugar.ZOOM_ACTIVITY)
|
||||
elif key == 'F2':
|
||||
self.set_zoom_level(sugar.ZOOM_HOME)
|
||||
elif key == 'F3':
|
||||
self.set_zoom_level(sugar.ZOOM_FRIENDS)
|
||||
elif key == 'F4':
|
||||
self.set_zoom_level(sugar.ZOOM_MESH)
|
||||
elif key == 'F5':
|
||||
self._frame.toggle_visibility()
|
||||
elif key == 'F6':
|
||||
self._model.start_activity('org.sugar.Terminal')
|
||||
|
||||
def __window_opened_cb(self, screen, window):
|
||||
if window.get_window_type() == wnck.WINDOW_NORMAL:
|
||||
self._model.add_activity(ActivityHost(self, window))
|
||||
|
||||
def __active_window_changed_cb(self, screen):
|
||||
window = screen.get_active_window()
|
||||
if window and window.get_window_type() == wnck.WINDOW_NORMAL:
|
||||
self._model.set_current_activity(window.get_xid())
|
||||
|
||||
def __window_closed_cb(self, screen, window):
|
||||
if window.get_window_type() == wnck.WINDOW_NORMAL:
|
||||
self._model.remove_activity(window.get_xid())
|
||||
|
||||
def get_model(self):
|
||||
return self._model
|
||||
|
||||
def set_zoom_level(self, level):
|
||||
if level == sugar.ZOOM_ACTIVITY:
|
||||
self._screen.toggle_showing_desktop(False)
|
||||
else:
|
||||
self._screen.toggle_showing_desktop(True)
|
||||
self._home_window.set_zoom_level(level)
|
||||
@@ -0,0 +1,83 @@
|
||||
import gtk
|
||||
import goocanvas
|
||||
import logging
|
||||
|
||||
import conf
|
||||
from sugar.canvas.IconItem import IconItem
|
||||
from sugar.canvas.IconColor import IconColor
|
||||
from sugar.presence import PresenceService
|
||||
from sugar.canvas.CanvasBox import CanvasBox
|
||||
|
||||
class ActivityItem(IconItem):
|
||||
def __init__(self, activity):
|
||||
icon_name = activity.get_icon()
|
||||
IconItem.__init__(self, icon_name=icon_name, color=IconColor('white'))
|
||||
self._activity = activity
|
||||
|
||||
def get_bundle_id(self):
|
||||
return self._activity.get_id()
|
||||
|
||||
class InviteItem(IconItem):
|
||||
def __init__(self, invite):
|
||||
IconItem.__init__(self, icon_name=invite.get_icon(),
|
||||
color=invite.get_color())
|
||||
self._invite = invite
|
||||
|
||||
def get_activity_id(self):
|
||||
return self._invite.get_activity_id()
|
||||
|
||||
def get_bundle_id(self):
|
||||
return self._invite.get_bundle_id()
|
||||
|
||||
def get_invite(self):
|
||||
return self._invite
|
||||
|
||||
class BottomPanel(CanvasBox):
|
||||
def __init__(self, grid, shell_model):
|
||||
CanvasBox.__init__(self, grid, CanvasBox.HORIZONTAL, 1)
|
||||
|
||||
self._shell_model = shell_model
|
||||
self._invite_to_item = {}
|
||||
self._invites = shell_model.get_invites()
|
||||
|
||||
registry = conf.get_activity_registry()
|
||||
for activity in registry.list_activities():
|
||||
if activity.get_show_launcher():
|
||||
self.add_activity(activity)
|
||||
|
||||
for invite in self._invites:
|
||||
self.add_invite(invite)
|
||||
self._invites.connect('invite-added', self.__invite_added_cb)
|
||||
self._invites.connect('invite-removed', self.__invite_removed_cb)
|
||||
|
||||
def __activity_clicked_cb(self, icon):
|
||||
self._shell_model.start_activity(icon.get_bundle_id())
|
||||
|
||||
def __invite_clicked_cb(self, icon):
|
||||
self._invites.remove_invite(icon.get_invite())
|
||||
self._shell_model.join_activity(icon.get_bundle_id(),
|
||||
icon.get_activity_id())
|
||||
|
||||
def __invite_added_cb(self, invites, invite):
|
||||
self.add_invite(invite)
|
||||
|
||||
def __invite_removed_cb(self, invites, invite):
|
||||
self.remove_invite(invite)
|
||||
|
||||
def add_activity(self, activity):
|
||||
item = ActivityItem(activity)
|
||||
item.connect('clicked', self.__activity_clicked_cb)
|
||||
self.set_constraints(item, 3, 3)
|
||||
self.add_child(item)
|
||||
|
||||
def add_invite(self, invite):
|
||||
item = InviteItem(invite)
|
||||
item.connect('clicked', self.__invite_clicked_cb)
|
||||
self.set_constraints(item, 3, 3)
|
||||
self.add_child(item, 0)
|
||||
|
||||
self._invite_to_item[invite] = item
|
||||
|
||||
def remove_invite(self, invite):
|
||||
self.remove_child(self._invite_to_item[invite])
|
||||
del self._invite_to_item[invite]
|
||||
@@ -0,0 +1,70 @@
|
||||
import gtk
|
||||
import gobject
|
||||
import goocanvas
|
||||
|
||||
from view.frame.BottomPanel import BottomPanel
|
||||
from view.frame.RightPanel import RightPanel
|
||||
from view.frame.TopPanel import TopPanel
|
||||
from view.frame.PanelWindow import PanelWindow
|
||||
from sugar.canvas.Grid import Grid
|
||||
|
||||
class Frame:
|
||||
def __init__(self, shell):
|
||||
self._windows = []
|
||||
|
||||
shell_model = shell.get_model()
|
||||
|
||||
model = goocanvas.CanvasModelSimple()
|
||||
root = model.get_root_item()
|
||||
|
||||
grid = Grid()
|
||||
|
||||
bg = goocanvas.Rect(fill_color="#4f4f4f", line_width=0)
|
||||
grid.set_constraints(bg, 0, 0, 80, 60)
|
||||
root.add_child(bg)
|
||||
|
||||
panel = BottomPanel(grid, shell_model)
|
||||
grid.set_constraints(panel, 5, 55)
|
||||
root.add_child(panel)
|
||||
|
||||
panel_window = PanelWindow(grid, model, 0, 55, 80, 5)
|
||||
self._windows.append(panel_window)
|
||||
|
||||
panel = TopPanel(grid, shell)
|
||||
root.add_child(panel)
|
||||
|
||||
panel_window = PanelWindow(grid, model, 0, 0, 80, 5)
|
||||
self._windows.append(panel_window)
|
||||
|
||||
panel = RightPanel(grid, shell_model)
|
||||
grid.set_constraints(panel, 75, 5)
|
||||
root.add_child(panel)
|
||||
|
||||
panel_window = PanelWindow(grid, model, 75, 5, 5, 50)
|
||||
self._windows.append(panel_window)
|
||||
|
||||
panel_window = PanelWindow(grid, model, 0, 5, 5, 50)
|
||||
self._windows.append(panel_window)
|
||||
|
||||
def __hide_timeout_cb(self):
|
||||
self.hide()
|
||||
return False
|
||||
|
||||
def show_and_hide(self, seconds):
|
||||
self.show()
|
||||
gobject.timeout_add(seconds * 1000, self.__hide_timeout_cb)
|
||||
|
||||
def show(self):
|
||||
for panel in self._windows:
|
||||
panel.show()
|
||||
|
||||
def hide(self):
|
||||
for panel in self._windows:
|
||||
panel.hide()
|
||||
|
||||
def toggle_visibility(self):
|
||||
for panel in self._windows:
|
||||
if panel.props.visible:
|
||||
panel.hide()
|
||||
else:
|
||||
panel.show()
|
||||
@@ -0,0 +1,8 @@
|
||||
sugardir = $(pkgdatadir)/shell/view/frame
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
RightPanel.py \
|
||||
PanelWindow.py \
|
||||
Frame.py \
|
||||
TopPanel.py \
|
||||
BottomPanel.py
|
||||
@@ -0,0 +1,27 @@
|
||||
import gtk
|
||||
import goocanvas
|
||||
|
||||
from sugar.canvas.CanvasView import CanvasView
|
||||
|
||||
class PanelWindow(gtk.Window):
|
||||
def __init__(self, grid, model, x, y, width, height):
|
||||
gtk.Window.__init__(self)
|
||||
|
||||
self._grid = grid
|
||||
|
||||
self.set_decorated(False)
|
||||
|
||||
self.realize()
|
||||
self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DIALOG)
|
||||
self.window.set_accept_focus(False)
|
||||
|
||||
screen = gtk.gdk.screen_get_default()
|
||||
self.window.set_transient_for(screen.get_root_window())
|
||||
|
||||
view = CanvasView()
|
||||
view.show()
|
||||
self.add(view)
|
||||
view.set_model(model)
|
||||
|
||||
self._grid.set_constraints(self, x, y, width, height)
|
||||
self._grid.set_constraints(view, x, y, width, height)
|
||||
@@ -0,0 +1,81 @@
|
||||
import goocanvas
|
||||
|
||||
from sugar.canvas.IconItem import IconItem
|
||||
from sugar.canvas.IconColor import IconColor
|
||||
from sugar.canvas.CanvasBox import CanvasBox
|
||||
from sugar.presence import PresenceService
|
||||
from view.FriendIcon import FriendIcon
|
||||
from model.Friends import Friend
|
||||
|
||||
class RightPanel(CanvasBox):
|
||||
def __init__(self, grid, shell_model):
|
||||
CanvasBox.__init__(self, grid, CanvasBox.VERTICAL, 1)
|
||||
self._shell_model = shell_model
|
||||
self._friends = shell_model.get_friends()
|
||||
self._activity_ps = None
|
||||
self._joined_hid = -1
|
||||
self._left_hid = -1
|
||||
self._buddies = {}
|
||||
|
||||
self._pservice = PresenceService.get_instance()
|
||||
self._pservice.connect('activity-appeared',
|
||||
self.__activity_appeared_cb)
|
||||
|
||||
shell_model.connect('activity-changed', self.__activity_changed_cb)
|
||||
|
||||
def add(self, buddy):
|
||||
friend = Friend(buddy.get_name(), buddy.get_color())
|
||||
icon = FriendIcon(self._shell_model, friend)
|
||||
icon.set_popup_distance(1)
|
||||
self.set_constraints(icon, 3, 3)
|
||||
self.add_child(icon)
|
||||
|
||||
self._buddies[buddy.get_name()] = icon
|
||||
|
||||
def remove(self, buddy):
|
||||
i = self.find_child(self._buddies[buddy.get_name()])
|
||||
self.remove_child(i)
|
||||
|
||||
def clear(self):
|
||||
while (self.get_n_children() > 0):
|
||||
self.remove_child(0)
|
||||
self._buddies = {}
|
||||
|
||||
def __activity_appeared_cb(self, pservice, activity_ps):
|
||||
activity = self._shell_model.get_current_activity()
|
||||
if activity and activity_ps.get_id() == activity.get_id():
|
||||
self._set_activity_ps(activity_ps)
|
||||
|
||||
def _set_activity_ps(self, activity_ps):
|
||||
if self._activity_ps == activity_ps:
|
||||
return
|
||||
|
||||
if self._joined_hid > 0:
|
||||
self._activity_ps.disconnect(self._joined_hid)
|
||||
self._joined_hid = -1
|
||||
if self._left_hid > 0:
|
||||
self._activity_ps.disconnect(self._left_hid)
|
||||
self._left_hid = -1
|
||||
|
||||
self._activity_ps = activity_ps
|
||||
|
||||
self.clear()
|
||||
|
||||
if activity_ps != None:
|
||||
for buddy in activity_ps.get_joined_buddies():
|
||||
self.add(buddy)
|
||||
|
||||
self._joined_hid = activity_ps.connect(
|
||||
'buddy-joined', self.__buddy_joined_cb)
|
||||
self._left_hid = activity_ps.connect(
|
||||
'buddy-left', self.__buddy_left_cb)
|
||||
|
||||
def __activity_changed_cb(self, group, activity):
|
||||
activity_ps = self._pservice.get_activity(activity.get_id())
|
||||
self._set_activity_ps(activity_ps)
|
||||
|
||||
def __buddy_joined_cb(self, activity, buddy):
|
||||
self.add(buddy)
|
||||
|
||||
def __buddy_left_cb(self, activity, buddy):
|
||||
self.remove(buddy)
|
||||
@@ -0,0 +1,70 @@
|
||||
import goocanvas
|
||||
|
||||
from sugar.canvas.CanvasBox import CanvasBox
|
||||
from sugar.canvas.IconItem import IconItem
|
||||
import sugar
|
||||
|
||||
class TopPanel(goocanvas.Group):
|
||||
def __init__(self, grid, shell):
|
||||
goocanvas.Group.__init__(self)
|
||||
|
||||
self._grid = grid
|
||||
self._shell = shell
|
||||
|
||||
box = CanvasBox(grid, CanvasBox.HORIZONTAL, 1)
|
||||
self._grid.set_constraints(box, 5, 0)
|
||||
self.add_child(box)
|
||||
|
||||
icon = IconItem(icon_name='stock-zoom-activity')
|
||||
icon.connect('clicked', self.__level_clicked_cb, sugar.ZOOM_ACTIVITY)
|
||||
box.set_constraints(icon, 3, 3)
|
||||
box.add_child(icon)
|
||||
|
||||
icon = IconItem(icon_name='stock-zoom-home')
|
||||
icon.connect('clicked', self.__level_clicked_cb, sugar.ZOOM_HOME)
|
||||
box.set_constraints(icon, 3, 3)
|
||||
box.add_child(icon)
|
||||
|
||||
icon = IconItem(icon_name='stock-zoom-friends')
|
||||
icon.connect('clicked', self.__level_clicked_cb, sugar.ZOOM_FRIENDS)
|
||||
box.set_constraints(icon, 3, 3)
|
||||
box.add_child(icon)
|
||||
|
||||
icon = IconItem(icon_name='stock-zoom-mesh')
|
||||
icon.connect('clicked', self.__level_clicked_cb, sugar.ZOOM_MESH)
|
||||
box.set_constraints(icon, 3, 3)
|
||||
box.add_child(icon)
|
||||
|
||||
box = CanvasBox(grid, CanvasBox.HORIZONTAL, 1)
|
||||
self._grid.set_constraints(box, 60, 0)
|
||||
self.add_child(box)
|
||||
|
||||
icon = IconItem(icon_name='stock-share')
|
||||
icon.connect('clicked', self.__share_clicked_cb)
|
||||
box.set_constraints(icon, 3, 3)
|
||||
box.add_child(icon)
|
||||
|
||||
icon = IconItem(icon_name='stock-invite')
|
||||
icon.connect('clicked', self.__invite_clicked_cb)
|
||||
box.set_constraints(icon, 3, 3)
|
||||
box.add_child(icon)
|
||||
|
||||
icon = IconItem(icon_name='stock-chat')
|
||||
icon.connect('clicked', self.__chat_clicked_cb)
|
||||
box.set_constraints(icon, 3, 3)
|
||||
box.add_child(icon)
|
||||
|
||||
def __level_clicked_cb(self, item, level):
|
||||
self._shell.set_zoom_level(level)
|
||||
|
||||
def __share_clicked_cb(self, item):
|
||||
shell_model = self._shell.get_model()
|
||||
activity = shell_model.get_current_activity()
|
||||
if activity != None:
|
||||
activity.share()
|
||||
|
||||
def __invite_clicked_cb(self, item):
|
||||
pass
|
||||
|
||||
def __chat_clicked_cb(self, item):
|
||||
pass
|
||||
@@ -0,0 +1,114 @@
|
||||
import math
|
||||
|
||||
import goocanvas
|
||||
|
||||
from sugar.canvas.IconItem import IconItem
|
||||
|
||||
class PieceIcon(IconItem):
|
||||
def __init__(self, piece_item, **kwargs):
|
||||
IconItem.__init__(self, size=48, **kwargs)
|
||||
self._piece_item = piece_item
|
||||
|
||||
def construct(self):
|
||||
angle_start = self._piece_item.get_angle_start()
|
||||
angle_end = self._piece_item.get_angle_end()
|
||||
radius = self.get_parent().get_radius()
|
||||
inner_radius = self.get_parent().get_inner_radius()
|
||||
|
||||
icon_radius = (radius + inner_radius) / 2
|
||||
icon_angle = (angle_start + angle_end) / 2
|
||||
x = icon_radius * math.cos(icon_angle)
|
||||
y = - icon_radius * math.sin(icon_angle)
|
||||
|
||||
icon_width = self.get_property('size')
|
||||
icon_height = self.get_property('size')
|
||||
self.set_property('x', x - icon_width / 2)
|
||||
self.set_property('y', y - icon_height / 2)
|
||||
|
||||
class PieceItem(goocanvas.Path):
|
||||
def __init__(self, angle_start, angle_end, **kwargs):
|
||||
goocanvas.Path.__init__(self, **kwargs)
|
||||
self._angle_start = angle_start
|
||||
self._angle_end = angle_end
|
||||
|
||||
self.set_property('fill-color', '#ffffff')
|
||||
self.set_property('stroke-color', '#e2e2e2')
|
||||
self.set_property('line-width', 4)
|
||||
|
||||
def get_icon(self):
|
||||
return self._icon
|
||||
|
||||
def set_icon(self, icon_name, color):
|
||||
self._icon = PieceIcon(self, icon_name=icon_name, color=color)
|
||||
self.get_parent().add_child(self._icon)
|
||||
self._icon.construct()
|
||||
|
||||
def get_angle_start(self):
|
||||
return self._angle_start
|
||||
|
||||
def get_angle_end(self):
|
||||
return self._angle_end
|
||||
|
||||
def construct(self):
|
||||
r = self.get_parent().get_radius()
|
||||
|
||||
data = 'M0,0 '
|
||||
|
||||
dx = r * math.cos(self._angle_start)
|
||||
dy = - r * math.sin(self._angle_start)
|
||||
|
||||
data += 'l%f,%f ' % (dx, dy)
|
||||
|
||||
dx = r * math.cos(self._angle_end)
|
||||
dy = - r * math.sin(self._angle_end)
|
||||
|
||||
data += 'A%f,%f 0 0,0 %f,%f ' % (r, r, dx, dy)
|
||||
|
||||
data += 'z'
|
||||
|
||||
self.set_property('data', data)
|
||||
|
||||
class DonutItem(goocanvas.Group):
|
||||
def __init__(self, radius, **kwargs):
|
||||
goocanvas.Group.__init__(self, **kwargs)
|
||||
self._radius = radius
|
||||
self._angle_start = 0
|
||||
|
||||
bg = goocanvas.Ellipse(radius_x=radius, radius_y=radius,
|
||||
fill_color='#f1f1f1', line_width=0)
|
||||
self.add_child(bg)
|
||||
|
||||
self._inner_radius = radius / 2
|
||||
fg = goocanvas.Ellipse(radius_x=self._inner_radius,
|
||||
radius_y=self._inner_radius,
|
||||
fill_color='#e2e2e2', line_width=0)
|
||||
self.add_child(fg)
|
||||
|
||||
def add_piece(self, perc, icon_name, color):
|
||||
# FIXME can't override set_parent on the
|
||||
# PieceItem and there is no signal. So we
|
||||
# call a construct method on the childs for now.
|
||||
|
||||
angle_end = self._angle_start + perc * 2 * math.pi / 100
|
||||
piece_item = PieceItem(self._angle_start, angle_end)
|
||||
self._angle_start = angle_end
|
||||
|
||||
self.add_child(piece_item, 1)
|
||||
piece_item.construct()
|
||||
piece_item.set_icon(icon_name, color)
|
||||
|
||||
return piece_item
|
||||
|
||||
def remove_piece(self, piece_item):
|
||||
index = self.find_child(piece_item)
|
||||
self.remove_child(index)
|
||||
|
||||
icon = piece_item.get_icon()
|
||||
index = self.find_child(icon)
|
||||
self.remove_child(index)
|
||||
|
||||
def get_radius(self):
|
||||
return self._radius
|
||||
|
||||
def get_inner_radius(self):
|
||||
return self._inner_radius
|
||||
@@ -0,0 +1,33 @@
|
||||
import random
|
||||
|
||||
import goocanvas
|
||||
|
||||
from view.home.IconLayout import IconLayout
|
||||
from view.home.MyIcon import MyIcon
|
||||
from view.FriendIcon import FriendIcon
|
||||
|
||||
class FriendsGroup(goocanvas.Group):
|
||||
def __init__(self, shell_model):
|
||||
goocanvas.Group.__init__(self)
|
||||
|
||||
self._shell_model = shell_model
|
||||
self._icon_layout = IconLayout(1200, 900)
|
||||
self._friends = shell_model.get_friends()
|
||||
|
||||
me = MyIcon(100)
|
||||
me.translate(600 - (me.get_property('size') / 2),
|
||||
450 - (me.get_property('size') / 2))
|
||||
self.add_child(me)
|
||||
|
||||
for friend in self._friends:
|
||||
self.add_friend(friend)
|
||||
|
||||
self._friends.connect('friend-added', self._friend_added_cb)
|
||||
|
||||
def add_friend(self, friend):
|
||||
icon = FriendIcon(self._shell_model, friend)
|
||||
self.add_child(icon)
|
||||
self._icon_layout.add_icon(icon)
|
||||
|
||||
def _friend_added_cb(self, data_model, friend):
|
||||
self.add_friend(friend)
|
||||
@@ -0,0 +1,52 @@
|
||||
import goocanvas
|
||||
|
||||
from view.home.DonutItem import DonutItem
|
||||
from view.home.MyIcon import MyIcon
|
||||
|
||||
class TasksItem(DonutItem):
|
||||
def __init__(self, shell_model):
|
||||
DonutItem.__init__(self, 250)
|
||||
|
||||
self._items = {}
|
||||
|
||||
self._shell_model = shell_model
|
||||
self._shell_model.connect('activity_opened', self.__activity_opened_cb)
|
||||
self._shell_model.connect('activity_closed', self.__activity_closed_cb)
|
||||
|
||||
def __activity_opened_cb(self, model, activity):
|
||||
self._add(activity)
|
||||
|
||||
def __activity_closed_cb(self, model, activity):
|
||||
self._remove(activity)
|
||||
|
||||
def _remove(self, activity):
|
||||
item = self._items[activity.get_id()]
|
||||
self.remove_piece(item)
|
||||
del self._items[activity.get_id()]
|
||||
|
||||
def _add(self, activity):
|
||||
icon_name = activity.get_icon_name()
|
||||
icon_color = activity.get_icon_color()
|
||||
|
||||
item = self.add_piece(100 / 8, icon_name, icon_color)
|
||||
item.get_icon().connect('clicked',
|
||||
self.__activity_icon_clicked_cb,
|
||||
activity)
|
||||
|
||||
self._items[activity.get_id()] = item
|
||||
|
||||
def __activity_icon_clicked_cb(self, item, activity):
|
||||
activity.present()
|
||||
|
||||
class HomeGroup(goocanvas.Group):
|
||||
def __init__(self, shell_model):
|
||||
goocanvas.Group.__init__(self)
|
||||
|
||||
tasks = TasksItem(shell_model)
|
||||
tasks.translate(600, 450)
|
||||
self.add_child(tasks)
|
||||
|
||||
me = MyIcon(150)
|
||||
me.translate(600 - (me.get_property('size') / 2),
|
||||
450 - (me.get_property('size') / 2))
|
||||
self.add_child(me)
|
||||
@@ -0,0 +1,50 @@
|
||||
import gtk
|
||||
import goocanvas
|
||||
import cairo
|
||||
|
||||
from sugar.canvas.CanvasView import CanvasView
|
||||
from view.home.MeshGroup import MeshGroup
|
||||
from view.home.HomeGroup import HomeGroup
|
||||
from view.home.FriendsGroup import FriendsGroup
|
||||
import sugar
|
||||
|
||||
class HomeWindow(gtk.Window):
|
||||
def __init__(self, shell_model):
|
||||
gtk.Window.__init__(self)
|
||||
self._shell_model = shell_model
|
||||
|
||||
self.realize()
|
||||
self.window.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DESKTOP)
|
||||
|
||||
self._nb = gtk.Notebook()
|
||||
self._nb.set_show_border(False)
|
||||
self._nb.set_show_tabs(False)
|
||||
|
||||
self.add(self._nb)
|
||||
self._nb.show()
|
||||
|
||||
self._add_page(HomeGroup(shell_model))
|
||||
self._add_page(FriendsGroup(shell_model))
|
||||
self._add_page(MeshGroup())
|
||||
|
||||
def _add_page(self, group):
|
||||
view = CanvasView()
|
||||
self._nb.append_page(view)
|
||||
view.show()
|
||||
|
||||
model = goocanvas.CanvasModelSimple()
|
||||
root = model.get_root_item()
|
||||
view.set_model(model)
|
||||
|
||||
bg = goocanvas.Rect(width=1900, height=1200,
|
||||
line_width=0, fill_color='#e2e2e2')
|
||||
root.add_child(bg)
|
||||
root.add_child(group)
|
||||
|
||||
def set_zoom_level(self, level):
|
||||
if level == sugar.ZOOM_HOME:
|
||||
self._nb.set_current_page(0)
|
||||
elif level == sugar.ZOOM_FRIENDS:
|
||||
self._nb.set_current_page(1)
|
||||
elif level == sugar.ZOOM_MESH:
|
||||
self._nb.set_current_page(2)
|
||||
@@ -0,0 +1,34 @@
|
||||
import random
|
||||
|
||||
class IconLayout:
|
||||
def __init__(self, width, height):
|
||||
self._icons = []
|
||||
self._width = width
|
||||
self._height = height
|
||||
|
||||
def add_icon(self, icon):
|
||||
self._icons.append(icon)
|
||||
self._layout_icon(icon)
|
||||
|
||||
def remove_icon(self, icon):
|
||||
self._icons.remove(icon)
|
||||
|
||||
def _is_valid_position(self, icon, x, y):
|
||||
icon_size = icon.props.size
|
||||
border = 20
|
||||
|
||||
if not (border < x < self._width - icon_size - border and \
|
||||
border < y < self._height - icon_size - border):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _layout_icon(self, icon):
|
||||
while True:
|
||||
x = random.random() * self._width
|
||||
y = random.random() * self._height
|
||||
if self._is_valid_position(icon, x, y):
|
||||
break
|
||||
|
||||
icon.props.x = x
|
||||
icon.props.y = y
|
||||
@@ -0,0 +1,10 @@
|
||||
sugardir = $(pkgdatadir)/shell/view/home
|
||||
sugar_PYTHON = \
|
||||
__init__.py \
|
||||
DonutItem.py \
|
||||
FriendsGroup.py \
|
||||
IconLayout.py \
|
||||
HomeGroup.py \
|
||||
HomeWindow.py \
|
||||
MeshGroup.py \
|
||||
MyIcon.py
|
||||
@@ -0,0 +1,82 @@
|
||||
import random
|
||||
|
||||
import goocanvas
|
||||
|
||||
import conf
|
||||
from sugar.canvas.IconItem import IconItem
|
||||
from sugar.canvas.IconItem import IconColor
|
||||
from sugar.presence import PresenceService
|
||||
from view.home.IconLayout import IconLayout
|
||||
|
||||
class ActivityItem(IconItem):
|
||||
def __init__(self, activity, service):
|
||||
self._service = service
|
||||
self._activity = activity
|
||||
|
||||
IconItem.__init__(self, icon_name=self.get_icon_name(),
|
||||
color=self.get_color(), size=96)
|
||||
|
||||
def get_id(self):
|
||||
return self._activity.get_id()
|
||||
|
||||
def get_icon_name(self):
|
||||
registry = conf.get_activity_registry()
|
||||
info = registry.get_activity_from_type(self._service.get_type())
|
||||
|
||||
return info.get_icon()
|
||||
|
||||
def get_color(self):
|
||||
return IconColor(self._activity.get_color())
|
||||
|
||||
def get_service(self):
|
||||
return self._service
|
||||
|
||||
class MeshGroup(goocanvas.Group):
|
||||
def __init__(self):
|
||||
goocanvas.Group.__init__(self)
|
||||
self._icon_layout = IconLayout(1200, 900)
|
||||
self._activities = {}
|
||||
|
||||
self._pservice = PresenceService.get_instance()
|
||||
self._pservice.connect("service-appeared", self._service_appeared_cb)
|
||||
self._pservice.connect('activity-disappeared', self._activity_disappeared_cb)
|
||||
|
||||
for service in self._pservice.get_services():
|
||||
self._check_service(service)
|
||||
|
||||
def _service_appeared_cb(self, pservice, service):
|
||||
self._check_service(service)
|
||||
|
||||
def _check_service(self, service):
|
||||
registry = conf.get_activity_registry()
|
||||
if registry.get_activity_from_type(service.get_type()) != None:
|
||||
activity_id = service.get_activity_id()
|
||||
if not self.has_activity(activity_id):
|
||||
activity = self._pservice.get_activity(activity_id)
|
||||
if activity != None:
|
||||
self.add_activity(activity, service)
|
||||
|
||||
def has_activity(self, activity_id):
|
||||
return self._activities.has_key(activity_id)
|
||||
|
||||
def add_activity(self, activity, service):
|
||||
item = ActivityItem(activity, service)
|
||||
item.connect('clicked', self._activity_clicked_cb)
|
||||
self._icon_layout.add_icon(item)
|
||||
self.add_child(item)
|
||||
|
||||
self._activities[item.get_id()] = item
|
||||
|
||||
def _activity_disappeared_cb(self, pservice, activity):
|
||||
if self._activities.has_key(activity.get_id()):
|
||||
self.remove_child(self._activities[activity.get_id()])
|
||||
del self._activities[activity.get_id()]
|
||||
|
||||
def _activity_clicked_cb(self, item):
|
||||
default_type = item.get_service().get_type()
|
||||
registry = conf.get_activity_registry()
|
||||
|
||||
bundle_id = registry.get_activity_from_type(default_type).get_id()
|
||||
activity_id = item.get_id()
|
||||
|
||||
self._shell.join_activity(bundle_id, activity_id)
|
||||
@@ -0,0 +1,10 @@
|
||||
import conf
|
||||
from sugar.canvas.IconItem import IconItem
|
||||
from sugar.canvas.IconColor import IconColor
|
||||
|
||||
class MyIcon(IconItem):
|
||||
def __init__(self, size):
|
||||
profile = conf.get_profile()
|
||||
|
||||
IconItem.__init__(self, icon_name='stock-buddy',
|
||||
color=profile.get_color(), size=size)
|
||||
Reference in New Issue
Block a user