class ScrollingDetector(GObject.GObject):
    '''
    The scrolling detector sends signals when a scrolled window is scrolled and
    when a scrolled window stops scrolling. Only one `scroll-start` signal will be
        emitted until scrolling stops.

    The `scroll-start` signal is emitted when scrolling begins and
    The `scroll-end` signal is emitted when scrolling ends
    Neither of these two signals have any arguments

    Args:
        scrolled_window (Gtk.ScrolledWindow): A GTK scrolled window object for which
            scrolling is to be detected

        timeout (int): time in milliseconds to establish the interval for which
            scrolling is detected
    '''

    scroll_start_signal = GObject.Signal('scroll-start')
    scroll_end_signal = GObject.Signal('scroll-end')

    def __init__(self, scrolled_window, timeout=100):
        self._scrolled_window = scrolled_window
        self._timeout = timeout
        self.is_scrolling = False
        self._prev_value = 0

        self.connect_scrolled_window()
        GObject.GObject.__init__(self)

    def connect_scrolled_window(self):
        '''
        Connects scrolling detector to a scrolled window.
        Detects scrolling when the vertical scrollbar
        adjustment value is changed

        Should be used to link an instance of a scrolling detector
        to a Scrolled Window, after setting scrolled_window
        '''
        adj = self._scrolled_window.get_vadjustment()
        adj.connect('value-changed', self._value_changed_cb)

    def _check_scroll_cb(self, adj):
        if (adj.props.value == self._prev_value):
            self.is_scrolling = False
            self.scroll_end_signal.emit()
            return False

        self._prev_value = adj.props.value
        return True

    def _value_changed_cb(self, adj):
        if (self.is_scrolling):
            return

        self.is_scrolling = True
        self.scroll_start_signal.emit()
        self._prev_value = adj.props.value
        GLib.timeout_add(self._timeout, self._check_scroll_cb, adj)
Example #2
0
class Foo(GObject.GObject):
    my_acc_signal = GObject.Signal(return_type=GObject.TYPE_INT,
                                   flags=GObject.SignalFlags.RUN_LAST,
                                   accumulator=my_accumulator,
                                   accu_data="accum data")

    my_other_acc_signal = GObject.Signal(
        return_type=GObject.TYPE_BOOLEAN,
        flags=GObject.SignalFlags.RUN_LAST,
        accumulator=GObject.signal_accumulator_true_handled)

    my_acc_first_wins = GObject.Signal(
        return_type=GObject.TYPE_BOOLEAN,
        flags=GObject.SignalFlags.RUN_LAST,
        accumulator=GObject.signal_accumulator_first_wins)
Example #3
0
    class Decorated(GObject.GObject):
        value = 0

        @GObject.Signal
        def pushed(self):
            """this will push"""
            self.value += 1

        @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST)
        def pulled(self):
            self.value -= 1

        stomped = GObject.Signal('stomped',
                                 arg_types=(int, ),
                                 doc='this will stomp')
        unnamed = GObject.Signal()
Example #4
0
class NormalCommentRow(CommentRow):

    refresh = GObject.Signal('refresh')

    def __init__(self, api: RedditAPI, data, depth, toplevel_cv):
        super().__init__(data, depth)
        self._api = api
        self._toplevel_cv = toplevel_cv
        self._top = None
        self._sub = None

        self._box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.add(self._box)
        self._box.show()

    def get_sub(self):
        return self._sub

    def do_event(self, event):
        if self._top is not None:
            return self._top.do_event(event)

    def recurse(self):
        self._top = posttopbar.PostTopBar(self._api, self.data,
                                          self._toplevel_cv)
        self._top.hide_toggled.connect(self.__hide_toggled_cb)
        self._box.add(self._top)
        self._top.show()

        self._label = newmarkdown.make_html_widget(self.data['body_html'])
        self._box.add(self._label)
        self._label.show()

        self._sub = None
        self._revealer = None
        if self.data.get('replies'):
            self._revealer = Gtk.Revealer(
                transition_type=Gtk.RevealerTransitionType.SLIDE_DOWN,
                reveal_child=True)
            self._box.add(self._revealer)
            self._revealer.show()

            self._sub = _CommentsView(self._api,
                                      self.data['replies']['data']['children'],
                                      self._toplevel_cv,
                                      depth=self.depth + 1)
            self._revealer.add(self._sub)
            self._sub.show()
        else:
            self._top.expand.hide()

    def __hide_toggled_cb(self, top, hidden):
        if self._revealer is not None:
            self._revealer.props.reveal_child = hidden

    def do_activated(self):
        if self._revealer is not None:
            rc = not self._revealer.props.reveal_child
            self._revealer.props.reveal_child = rc
            self._top.expand.props.active = not rc
Example #5
0
class ColorPicker(EventIcon):

    color_changed_signal = GObject.Signal('color-changed',
                                          arg_types=([object]))

    def __init__(self, picker):
        EventIcon.__init__(self,
                           icon_name='computer-xo',
                           pixel_size=style.LARGE_ICON_SIZE)
        self._picker = picker
        self._color = None

        self.connect('activate', self.__activate_cb, picker)

    def set_color(self, color):
        if self._picker == _PREVIOUS_FILL_COLOR:
            self._color = XoColor(_get_previous_fill_color(color))
        elif self._picker == _PREVIOUS_STROKE_COLOR:
            self._color = XoColor(_get_previous_stroke_color(color))
        elif self._picker == _NEXT_FILL_COLOR:
            self._color = XoColor(_get_next_fill_color(color))
        elif self._picker == _NEXT_STROKE_COLOR:
            self._color = XoColor(_get_next_stroke_color(color))
        else:
            self._color = color
        self.props.xo_color = self._color

    color = GObject.Property(type=object, setter=set_color)

    def __activate_cb(self, button, picker):
        if picker != _CURRENT_COLOR:
            self.color_changed_signal.emit(self._color)
Example #6
0
class GenderPicker(EventIcon):
    gender_changed_signal = GObject.Signal('gender-changed',
                                           arg_types=([str]))

    def __init__(self, color, gender):
        EventIcon.__init__(self, icon_name='%s-6' % (gender),
                           pixel_size=style.XLARGE_ICON_SIZE)
        self._gender = gender
        self._color = color

        self.set_gender()

        self.connect('button_press_event', self.__pressed_cb)

    def set_color(self, color, gender):
        self._color = color
        self.set_gender(gender)

    def set_gender(self, gender=''):
        if gender is not '' and self._gender == gender:
            self.props.xo_color = self._color
        else:
            self.props.xo_color = _NOCOLOR

    gender = GObject.property(type=object, setter=set_gender)

    def __pressed_cb(self, button, event):
        self.gender_changed_signal.emit(self._gender)
Example #7
0
class TokenManager(GObject.GObject):
    '''
    This class manages a single account token.
    '''

    value_changed = GObject.Signal('value-changed')
    user_name = None
    is_anonymous = False

    def __init__(self):
        super().__init__()

    def refresh(self, done_callback: typing.Callable[[], typing.Any]):
        '''
        Request a refresh of the token, if applicable to this type of account
        '''
        raise NotImplementedError()

    def wrap_path(self, path: str) -> str:
        '''
        Take a path and put the domain part in front of it
        '''
        raise NotImplementedError()

    def add_message_headers(self, msg: Soup.Message):
        '''
        Mutate the soup message and add any required headers to signify
        this token
        '''
        raise NotImplementedError()

    def serialize(self) -> dict:
        raise NotImplementedError()
Example #8
0
class LoadMoreCommentsRow(CommentRow):

    got_more_comments = GObject.Signal('got-more-comments', arg_types=[object])

    def __init__(self, api: RedditAPI, data, depth, toplevel_cv):
        super().__init__(data, depth)

        self._api = api
        self._toplevel_cv = toplevel_cv
        self._more_button = None

    def recurse(self):
        self._more_button = Gtk.Button.new_with_label(
            'Show {} more comments...'.format(self.data['count']))
        self._more_button.connect('clicked', self.__load_more_cb)
        self._more_button.get_style_context().add_class('load-more')
        self.add(self._more_button)
        self._more_button.show()

    def do_focus_in_event(self, event):
        if self._more_button is not None:
            self._more_button.grab_focus()

    def __load_more_cb(self, button):
        button.props.sensitive = False
        self._api.load_more(self._toplevel_cv.get_link_name(), self.data,
                            self.__loaded_more_cb)

    def __loaded_more_cb(self, comments):
        self.got_more_comments.emit(comments)
Example #9
0
class Observable(GObject.GObject):
    changed_signal = GObject.Signal('changed')

    def serialize(self) -> PopoType:
        raise NotImplimentedError()

    def deserialize(self, value: PopoType):
        raise NotImplimentedError()
Example #10
0
class SubmitWindow(GObject.GObject):

    done = GObject.Signal('done', arg_types=[str, str])

    def __init__(self, api: RedditAPI, sub=''):
        GObject.GObject.__init__(self)
        self._api = api

        self._b = Gtk.Builder.new_from_resource(
            '/today/sam/reddit-is-gtk/submit-window.ui')
        self.window = self._b.get_object('submit-window')

        self._b.get_object('sub-entry').props.text = sub
        self._b.get_object('submit-button').connect(
            'clicked', self.__submit_clicked_cb)

    def show(self):
        self.window.show()

    def __submit_clicked_cb(self, button):
        submit = self._b.get_object('submit-button')
        submit.props.label = 'Submitting...'
        submit.props.sensitive = False

        data = {'title': self._b.get_object('title-entry').props.text,
                'sr': self._b.get_object('sub-entry').props.text}
        stack = self._b.get_object('link-self-stack')
        if stack.props.visible_child_name == 'link':
            data['kind'] = 'link'
            data['url'] = self._b.get_object('link-entry').props.text
        else:
            data['kind'] = 'self'
            buf = self._b.get_object('self-textview').props.buffer
            data['text'] = buf.get_text(buf.get_start_iter(),
                                        buf.get_end_iter(), False)
        self._api.submit(data, self.__submit_done_cb)

    def __submit_done_cb(self, data):
        data = data['json']
        if data.get('errors'):
            errors = data['errors']
            error_name, error_text, error_name_lower = errors[0]

            error = self._b.get_object('error-label')
            error.props.label = error_text
            error.show()

            submit = self._b.get_object('submit-button')
            submit.props.sensitive = True
            submit.props.label = 'Submit'
        else:
            uri = data['data']['url']
            self.done.emit(self._b.get_object('sub-entry').props.text,
                           uri)
            self.window.hide()
            self.window.destroy()
Example #11
0
class AgePicker(Gtk.Grid):

    age_changed_signal = GObject.Signal('age-changed',
                                        arg_types=([int]))

    def __init__(self, color, gender, age):
        Gtk.Grid.__init__(self)
        self._color = color
        self._gender = gender
        self._age = age

        if self._gender is '':
            # Used for graphic only; does not set user's gender preference.
            self._gender = 'female'

        self._icon = EventIcon(icon_name='%s-%d' % (self._gender, self._age),
                               pixel_size=style.LARGE_ICON_SIZE)
        self._icon.connect('button-press-event', self.__pressed_cb)
        self.attach(self._icon, 0, 0, 1, 1)
        self._icon.show()

        label = Gtk.Label()
        label.set_text(AGE_LABELS[self._age])
        self.attach(label, 0, 1, 1, 1)
        label.show()

        self.set_age()

    def set_color(self, color, age):
        self._color = color
        self.set_age(age)

    def set_age(self, age=None):
        if age in AGES:
            age_index = AGES.index(age)
        else:
            age_index = None

        if age_index == self._age:
            self._icon.props.xo_color = self._color
        else:
            self._icon.props.xo_color = _NOCOLOR
        self._icon.show()

    age = GObject.property(type=object, setter=set_age)

    def set_gender(self, gender):
        self._icon.set_icon_name('%s-%d' % (gender, self._age))
        self._icon.show()

    gender = GObject.property(type=object, setter=set_gender)

    def __pressed_cb(self, button, event):
        self.age_changed_signal.emit(self._age)
Example #12
0
class GenderPicker(Gtk.Grid):

    gender_changed_signal = GObject.Signal('gender-changed', arg_types=([str]))

    def __init__(self):
        Gtk.Grid.__init__(self)
        self.set_row_spacing(style.DEFAULT_SPACING)
        self.set_column_spacing(style.DEFAULT_SPACING)

        self._gender = load_gender()
        self._buttons = []
        self._nocolor = XoColor('#010101,#ffffff')
        self._color = XoColor()

        for i, gender in enumerate(GENDERS):
            self._buttons.append(
                EventIcon(pixel_size=style.XLARGE_ICON_SIZE,
                          icon_name='%s-6' % (gender)))
            self._buttons[-1].connect('button-press-event',
                                      self._button_press_cb, i)
            self.attach(self._buttons[-1], i * 2, 0, 1, 1)
            self._buttons[-1].show()

        self.reset_button = EventIcon(pixel_size=style.SMALL_ICON_SIZE,
                                      icon_name='entry-cancel')
        self.reset_button.connect('button-press-event',
                                  self._reset_button_press_cb)
        self.attach(self.reset_button, 1, 0, 1, 1)
        self.reset_button.xo_color = XoColor('#010101,#a0a0a0')
        self.reset_button.show()

    def _reset_button_press_cb(self, widget, event):
        self._set_gender('')
        for i in range(len(GENDERS)):
            self._buttons[i].xo_color = self._nocolor

    def _button_press_cb(self, widget, event, gender_index):
        if event.button == 1 and event.type == Gdk.EventType.BUTTON_PRESS:
            self._set_gender(GENDERS[gender_index])
            self._buttons[gender_index].xo_color = self._color
            self._buttons[1 - gender_index].xo_color = self._nocolor

    def get_gender(self):
        return self._gender

    def _set_gender(self, gender):
        self.gender_changed_signal.emit(gender)
        self._gender = gender

    def update_color(self, color):
        self._color = color
        if self._gender in GENDERS:
            self._buttons[GENDERS.index(self._gender)].xo_color = self._color
Example #13
0
class Server(GObject.GObject):
    session_started = GObject.Signal("session-started", arg_types=(object, ))

    def _incoming_connection_cb(self, service, connection, user_data):
        session = Session(connection)
        self.session_started.emit(session)
        session.read_data()

    def start(self):
        service = Gio.SocketService()
        service.connect("incoming", self._incoming_connection_cb)
        return service.add_any_inet_port(None)
class ScrollingDetector(GObject.GObject):
    """
    ScollingDetector emit signals when a ScrolledWindow starts and
    finish scrolling. Other widets can use that information to
    avoid do performance expensive operations.
    """

    scroll_start_signal = GObject.Signal('scroll-start')
    scroll_end_signal = GObject.Signal('scroll-end')

    def __init__(self, scrolled_window, timeout=100):
        self._scrolled_window = scrolled_window
        self._timeout = timeout
        self.is_scrolling = False
        self._prev_value = 0

        self.connect_scrolled_window()
        GObject.GObject.__init__(self)

    def connect_scrolled_window(self):
        adj = self._scrolled_window.get_vadjustment()
        adj.connect('value-changed', self._value_changed_cb)

    def _check_scroll_cb(self, adj):
        if (adj.props.value == self._prev_value):
            self.is_scrolling = False
            self.scroll_end_signal.emit()
            return False

        self._prev_value = adj.props.value
        return True

    def _value_changed_cb(self, adj):
        if (self.is_scrolling):
            return

        self.is_scrolling = True
        self.scroll_start_signal.emit()
        self._prev_value = adj.props.value
        GLib.timeout_add(self._timeout, self._check_scroll_cb, adj)
class _ReplyPopoverContents(Gtk.Box):

    posted = GObject.Signal('posted', arg_types=[str])

    def __init__(self, api: RedditAPI, data, header_bar=None, **kwargs):
        Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
        self.data = data
        self._api = api

        sw = Gtk.ScrolledWindow()
        sw.set_size_request(500, 300)
        self.add(sw)
        self._textview = Gtk.TextView()
        self._textview.props.wrap_mode = Gtk.WrapMode.WORD
        self._textview.set_size_request(500, 300)
        self._textview.connect('event', self.__event_cb)
        sw.add(self._textview)

        self._done = Gtk.Button(label='Post Reply')
        self._done.connect('clicked', self.__done_clicked_cb)
        if header_bar is not None:
            header_bar.pack_end(self._done)
            self._done.get_style_context().add_class('suggested-action')
            self._done.show()
        else:
            self.add(self._done)

        self.show_all()

    def __event_cb(self, textview, event):
        shortcuts = {'<Ctrl>Return': (self.__done_clicked_cb, [None])}
        return process_shortcuts(shortcuts, event)

    def __done_clicked_cb(self, button):
        self._done.props.label = 'Sending...'
        self._done.props.sensitive = False
        b = self._textview.props.buffer
        text = b.get_text(b.get_start_iter(), b.get_end_iter(), False)
        self._api.reply(self.data['name'], text, self.__reply_done_cb)

    def __reply_done_cb(self, data):
        new_id = data['json']['data']['things'][0]['data']['id']
        self.posted.emit(new_id)

        parent = self.get_parent()
        if not isinstance(parent, Gtk.Popover):
            parent = self.get_toplevel()
            assert isinstance(parent, Gtk.Dialog)
        parent.hide()
        parent.destroy()
        self.destroy()
Example #16
0
class BaseComponent(GObject.GObject):
    updated_signal = GObject.Signal('updated')

    def __init__(self, **props):
        super().__init__()

    def update(self, updated_list=[]):
        pass

    def destroy(self):
        pass

    def get_widgets(self):
        return []
class TabAdd(Gtk.HBox):
    __gtype_name__ = 'BrowseTabAdd'

    tab_added = GObject.Signal('tab-added', arg_types=[str])

    def __init__(self):
        GObject.GObject.__init__(self)

        add_tab_icon = Icon(icon_name='add')
        button = Gtk.Button()
        button.drag_dest_set(0, [], 0)
        button.props.relief = Gtk.ReliefStyle.NONE
        button.props.focus_on_click = False
        icon_box = Gtk.HBox()
        icon_box.pack_start(add_tab_icon, True, False, 0)
        button.add(icon_box)
        button.connect('clicked', self.__button_clicked_cb)
        button.connect('drag-data-received', self.__drag_cb)
        button.connect('drag-motion', self.__drag_motion_cb)
        button.connect('drag-drop', self.__drag_drop_cb)
        button.set_name('browse-tab-add')
        self.pack_start(button, True, True, 0)
        add_tab_icon.show()
        icon_box.show()
        button.show()

    def __drag_motion_cb(self, widget, context, x, y, time):
        Gdk.drag_status(context, Gdk.DragAction.MOVE, time)
        return True

    def __drag_drop_cb(self, widget, context, x, y, time):
        context_targets = context.list_targets()
        for target in context_targets:
            if str(target) not in ('TIMESTAMP', 'TARGETS', 'MULTIPLE'):
                widget.drag_get_data(context, target, time)
        return True

    def __drag_cb(self, widget, drag_context, x, y, data, info, time):
        uris = data.get_uris()
        for uri in uris:
            self.tab_added.emit(uri)

    def __button_clicked_cb(self, button):
        self.tab_added.emit(None)
Example #18
0
class AddNewBar(Gtk.Box):

    activate = GObject.Signal('activate', arg_types=[str])

    def __init__(self, placeholder=None):
        Gtk.Box.__init__(self, orientation=Gtk.Orientation.HORIZONTAL)

        self._button = EventIcon(icon_name='list-add')
        self._button.connect('button-release-event',
                             self.__button_release_event_cb)
        self._button.fill_color = style.COLOR_TOOLBAR_GREY.get_svg()
        self._button.set_tooltip(_('Add New'))
        self.pack_start(self._button, False, True, 0)
        self._button.show()

        self._entry = iconentry.IconEntry()
        self._entry.connect('key-press-event', self.__key_press_cb)
        if placeholder is None:
            placeholder = _('Add new entry')
        self._entry.set_placeholder_text(placeholder)
        self._entry.add_clear_button()
        self.pack_start(self._entry, True, True, 0)
        self._entry.show()

    def get_entry(self):
        return self._entry

    def get_button(self):
        return self._button

    def __key_press_cb(self, window, event):
        if event.keyval == Gdk.KEY_Return:
            return self._maybe_activate()

    def __button_release_event_cb(self, button, event):
        self._maybe_activate()

    def _maybe_activate(self):
        if self._entry.props.text:
            self.activate.emit(self._entry.props.text)
            self._entry.props.text = ''
            return True
class MoreItemRow(Gtk.ListBoxRow):

    load_more = GObject.Signal('load-more', arg_types=[str])

    def __init__(self, after):
        Gtk.ListBoxRow.__init__(self)
        self._after = after

        if after is not None:
            b = Gtk.Button(label='Load More')
        else:
            b = Gtk.Button(label='End of Listing')
            b.props.sensitive = False
        b.connect('clicked', self.__clicked_cb)
        self.add(b)
        b.show()

    def __clicked_cb(self, button):
        self.hide()
        self.load_more.emit(self._after)
        self.destroy()
Example #20
0
class EmptyView(Gtk.Bin):

    action = GObject.Signal('action')

    def __init__(self, title, action=None):
        Gtk.Bin.__init__(self)

        builder = Gtk.Builder.new_from_resource(
            '/today/sam/reddit-is-gtk/empty-view.ui')
        self._g = builder.get_object
        self.add(self._g('box'))
        self.get_child().show()

        self._g('title').props.label = title

        if action is not None:
            self._g('action').props.label = action
            self._g('action').props.visible = True
            self._g('action').connect('clicked', self.__action_clicked_cb)

    def __action_clicked_cb(self, button):
        self.action.emit()
Example #21
0
class LoadFullCommentsRow(Gtk.ListBoxRow):

    load_full = GObject.Signal('load_full')

    def __init__(self):
        Gtk.ListBoxRow.__init__(self)

        self._ib = Gtk.InfoBar(message_type=Gtk.MessageType.QUESTION)
        self._ib.connect('response', self.__response_cb)
        self.add(self._ib)
        self._ib.show()

        label = Gtk.Label(label='Showing only a subset of comments')
        self._ib.get_content_area().add(label)
        label.show()

        self._button = self._ib.add_button('Show All Comments', 1)

    def grab_focus(self):
        self._button.grab_focus()

    def __response_cb(self, ib, response):
        self.load_full.emit()
        self._button.props.label = 'Loading...'
Example #22
0
class MoreItemsRow(Gtk.ListBoxRow):

    load_more = GObject.Signal('load-more', arg_types=[str])

    def __init__(self, after):
        Gtk.ListBoxRow.__init__(self)
        self.is_loading_state = False
        self._after = after

        if after is not None:
            self._btn = Gtk.Button(label='Load More')
        else:
            self._btn = Gtk.Button(
                label='End of Listing',
                sensitive=False,
            )
        self._btn.connect('clicked', self.__clicked_cb)
        self.add(self._btn)
        self._btn.show()

    def __clicked_cb(self, button):
        self.activate()

    def activate(self):
        self.load_more.emit(self._after)

    def show_loading_state(self):
        self.is_loading_state = True

        self._btn.remove(self._btn.get_child())
        spinner = Gtk.Spinner()
        spinner.start()
        self._btn.add(spinner)
        spinner.show()

        self._btn.props.sensitive = False
Example #23
0
class Session(GObject.GObject):
    message_received = GObject.Signal("message-received", arg_types=(object, ))

    def __init__(self, connection):
        GObject.GObject.__init__(self)

        self._connection = connection
        self._request = StringIO()
        self._message = MessageBuffer()
        self._parse_g = None
        self._ready = False
        self._send_queue = deque()
        self._sending = False

    def _response_write_cb(self, stream, result, user_data):
        stream.write_bytes_finish(result)
        self._ready = True

    def _do_handshake(self):
        self._request.seek(0)
        response = protocol.make_handshake(self._request)

        stream = self._connection.get_output_stream()
        stream.write_bytes_async(GLib.Bytes.new(response.encode("utf-8")),
                                 GLib.PRIORITY_DEFAULT, None,
                                 self._response_write_cb, None)

    def read_data(self):
        stream = self._connection.get_input_stream()
        stream.read_bytes_async(8192, GLib.PRIORITY_DEFAULT, None,
                                self._read_data_cb, None)

    def _read_data_cb(self, stream, result, user_data):
        data = stream.read_bytes_finish(result).get_data()
        logger.debug("Got data, length %d" % len(data))

        if not data:
            return

        if self._ready:
            self._message.append(data)

            while self._message.available > 0:
                if self._parse_g is None:
                    self._parse_g = protocol.parse_message(self._message)

                parsed_message = self._parse_g.next()
                if parsed_message:
                    self._parse_g = None

                    received = None
                    if parsed_message.tp == protocol.OPCODE_TEXT:
                        received = Message(Message.TYPE_TEXT,
                                           parsed_message.data)
                        logger.debug("Received text message %s" %
                                     received.data)
                    elif parsed_message.tp == protocol.OPCODE_BINARY:
                        received = Message(Message.TYPE_BINARY,
                                           parsed_message.data)
                        logger.debug("Received binary message, length %s" %
                                     len(received.data))
                    if received:
                        self.message_received.emit(received)
                else:
                    break
        else:
            self._request.write(data)
            if data.endswith("\r\n\r\n"):
                self._do_handshake()

        self.read_data()

    def _message_write_cb(self, stream, result, callback):
        written = stream.write_bytes_finish(result)
        if callback:
            callback(written)

        self._sending = False

        self._send_from_queue()

    def send_message(self, message, callback=None, binary=False):
        if binary:
            logger.debug("Sending binary message, length %s" % len(message))
        else:
            logger.debug("Sending text message %s" % message)

        protocol_message = protocol.make_message(message, binary)
        self._send_queue.append((protocol_message, callback))
        self._send_from_queue()

    def _send_from_queue(self):
        if self._sending:
            return

        if not self._send_queue:
            return

        stream = self._connection.get_output_stream()
        message, callback = self._send_queue.popleft()
        stream.write_bytes_async(GLib.Bytes.new(message),
                                 GLib.PRIORITY_DEFAULT, None,
                                 self._message_write_cb, callback)

        self._sending = True
Example #24
0
class _IntroBox(Gtk.VBox):
    done_signal = GObject.Signal('done', arg_types=([object]))

    PAGE_NAME = 0
    PAGE_COLOR = 1
    PAGE_GENDER = 2
    PAGE_AGE = 3

    PAGE_FIRST = min(PAGE_NAME, PAGE_COLOR, PAGE_GENDER, PAGE_AGE)
    PAGE_LAST = max(PAGE_NAME, PAGE_COLOR, PAGE_GENDER, PAGE_AGE)

    def __init__(self, start_on_age_page):
        Gtk.VBox.__init__(self)
        self.set_border_width(style.zoom(30))

        self._page = self.PAGE_NAME
        self._name_page = _NamePage(self)
        self._color_page = _ColorPage()
        self._gender_page = _GenderPage()
        self._age_page = _AgePage(None)
        self._current_page = None
        self._next_button = None

        settings = Gio.Settings('org.sugarlabs.user')
        default_nick = settings.get_string('default-nick')
        if default_nick != 'disabled':
            self._page = self.PAGE_COLOR
            if default_nick == 'system':
                pwd_entry = pwd.getpwuid(os.getuid())
                default_nick = (pwd_entry.pw_gecos.split(',')[0]
                                or pwd_entry.pw_name)
            self._name_page.set_name(default_nick)

        # XXX should also consider whether or not there is a nick
        nick = settings.get_string('nick')
        if start_on_age_page and nick:
            self._page = self.PAGE_AGE

        self._setup_page()

    def _setup_page(self):
        for child in self.get_children():
            self.remove(child)

        def _setup_name_page(self):
            self._current_page = self._name_page

        def _setup_color_page(self):
            self._current_page = self._color_page

        def _setup_gender_page(self):
            if self._color_page.get_color() is not None:
                self._gender_page.update_color(self._color_page.get_color())
            self._current_page = self._gender_page

        def _setup_age_page(self):
            if self._gender_page.get_gender() is not None:
                self._age_page.update_gender(self._gender_page.get_gender())
            if self._color_page.get_color() is not None:
                self._age_page.update_color(self._color_page.get_color())
            self._current_page = self._age_page

        setup_methods = {
            self.PAGE_NAME: _setup_name_page,
            self.PAGE_COLOR: _setup_color_page,
            self.PAGE_GENDER: _setup_gender_page,
            self.PAGE_AGE: _setup_age_page
        }

        setup_methods[self._page](self)
        self.pack_start(self._current_page, True, True, 0)
        self._current_page.show()

        button_box = Gtk.HButtonBox()
        if self._page == self.PAGE_FIRST:
            button_box.set_layout(Gtk.ButtonBoxStyle.END)
        else:
            button_box.set_layout(Gtk.ButtonBoxStyle.EDGE)
            back_button = Gtk.Button(_('Back'))
            image = Icon(icon_name='go-left')
            back_button.set_image(image)
            back_button.connect('clicked', self._back_activated_cb)
            button_box.pack_start(back_button, True, True, 0)
            back_button.show()

        self._next_button = Gtk.Button()
        image = Icon(icon_name='go-right')
        self._next_button.set_image(image)

        if self._page == self.PAGE_LAST:
            self._next_button.set_label(_('Done'))
            self._next_button.connect('clicked', self._done_activated_cb)
        else:
            self._next_button.set_label(_('Next'))
            self._next_button.connect('clicked', self._next_activated_cb)

        self._current_page.activate()

        self._update_next_button()
        button_box.pack_start(self._next_button, True, True, 0)
        self._next_button.show()

        self._current_page.connect('notify::valid',
                                   self._page_valid_changed_cb)

        self.pack_start(button_box, False, True, 0)
        button_box.show()

    def _update_next_button(self):
        self._next_button.set_sensitive(self._current_page.props.valid)

    def _page_valid_changed_cb(self, page, pspec):
        self._update_next_button()

    def _back_activated_cb(self, widget):
        self.back()

    def back(self):
        if self._page != self.PAGE_FIRST:
            self._page -= 1
            self._setup_page()

    def _next_activated_cb(self, widget):
        self.next()

    def next(self):
        if self._current_page.props.valid:
            if self._page == self.PAGE_LAST:
                self.done()
            else:
                self._page += 1
                self._setup_page()

    def _done_activated_cb(self, widget):
        self.done()

    def done(self):
        user_profile = UserProfile()
        user_profile.nickname = self._name_page.get_name()
        user_profile.colors = self._color_page.get_color()
        user_profile.gender = self._gender_page.get_gender()
        user_profile.age = self._age_page.get_age()

        self.done_signal.emit(user_profile)
Example #25
0
class IncomingFileTransfer(_BaseFileTransfer):
    '''
    An incoming file transfer from another buddy.  You need to first accept
    the transfer (either to memory or to a file).  Then you need to listen
    to the state and wait until the transfer is completed.  Then you can
    read the file that it was saved to, or access the
    :class:`Gio.MemoryOutputStream` from the `output` property.

    The `output` property is different depending on how the file was accepted.
    If the file was accepted to a file on the file system, it is a string
    representing the path to the file.  If the file was accepted to memory,
    it is a :class:`Gio.MemoryOutputStream`.
    '''

    ready = GObject.Signal('ready', arg_types=[object])

    def __init__(self, connection, object_path, props):
        _BaseFileTransfer.__init__(self)

        channel = {}
        proxy = dbus.Bus().get_object(connection.bus_name, object_path)
        channel[PROPERTIES_IFACE] = dbus.Interface(proxy, PROPERTIES_IFACE)
        channel[CHANNEL] = dbus.Interface(proxy, CHANNEL)
        channel[CHANNEL_TYPE_FILE_TRANSFER] = dbus.Interface(
            proxy, CHANNEL_TYPE_FILE_TRANSFER)
        self.set_channel(channel)

        self.connect('notify::state', self.__notify_state_cb)

        self._destination_path = None
        self._output_stream = None
        self._socket_address = None
        self._socket = None
        self._splicer = None

    def accept_to_file(self, destination_path):
        '''
        Accept the file transfer and write it to a new file.  The file must
        already exist.

        Args:
            destination_path (str): the path where a new file will be
                created and saved to
        '''
        if os.path.exists(destination_path):
            raise ValueError('Destination path already exists: %r' %
                             destination_path)

        self._destination_path = destination_path
        self._accept()

    def accept_to_memory(self):
        '''
        Accept the file transfer.  Once the state is FT_STATE_OPEN, a
        :class:`Gio.MemoryOutputStream` accessible via the output prop.
        '''
        self._destination_path = None
        self._accept()

    def _accept(self):
        channel_ft = self.channel[CHANNEL_TYPE_FILE_TRANSFER]
        self._socket_address = channel_ft.AcceptFile(
            SOCKET_ADDRESS_TYPE_UNIX,
            SOCKET_ACCESS_CONTROL_LOCALHOST,
            '',
            0,
            byte_arrays=True)

    def __notify_state_cb(self, file_transfer, pspec):
        _logger.debug('__notify_state_cb %r', self.props.state)
        if self.props.state == FT_STATE_OPEN:
            # Need to hold a reference to the socket so that python doesn't
            # close the fd when it goes out of scope
            self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
            self._socket.connect(self._socket_address)
            input_stream = Gio.UnixInputStream.new(self._socket.fileno(), True)

            if self._destination_path is not None:
                destination_file = Gio.File.new_for_path(
                    self._destination_path)
                if self.initial_offset == 0:
                    self._output_stream = destination_file.create(
                        Gio.FileCreateFlags.PRIVATE, None)
                else:
                    self._output_stream = destination_file.append_to()
            else:
                if hasattr(Gio.MemoryOutputStream, 'new_resizable'):
                    self._output_stream = \
                        Gio.MemoryOutputStream.new_resizable()
                else:
                    self._output_stream = Gio.MemoryOutputStream()

            self._output_stream.splice_async(
                input_stream, Gio.OutputStreamSpliceFlags.CLOSE_SOURCE
                | Gio.OutputStreamSpliceFlags.CLOSE_TARGET, GLib.PRIORITY_LOW,
                None, self.__splice_done_cb, None)

    def __splice_done_cb(self, output_stream, res, user):
        _logger.debug('__splice_done_cb')
        self.ready.emit(self._destination_path or self._output_stream)

    @GObject.Property
    def output(self):
        return self._destination_path or self._output_stream
Example #26
0
class CollabWrapper(GObject.GObject):
    '''
    The wrapper provides a high level abstraction over the
    collaboration system.  The wrapper deals with setting up the
    channels, encoding and decoding messages, initialization and
    alerting the caller to the status.

    An activity instance is initially private, but may be shared.  Once
    shared, an instance will remain shared for as long as the activity
    runs.  On stop, the journal will preserve the instance as shared,
    and on resume the instance will be shared again.

    When the caller shares an activity instance, they are the leader,
    and other buddies may join.  The instance is now a shared activity.

    When the caller joins a shared activity, the leader will call
    `get_data`, and the caller's `set_data` will be called with the
    result.

    The `joined` signal is emitted when the caller joins a shared
    activity.  One or more `buddy_joined` signals will be emitted before
    this signal.  The signal is not emitted to the caller who first
    shared the activity.  There are no arguments.

    The `buddy_joined` signal is emitted when another buddy joins the
    shared activity.  At least one will be emitted before the `joined`
    signal.  The caller will never be mentioned, but is assumed to be
    part of the set.  The signal passes a
    :class:`sugar3.presence.buddy.Buddy` as the only argument.

    The `buddy_left` signal is emitted when another user leaves the
    shared activity.  The signal is not emitted during quit.  The signal
    passes a :class:`sugar3.presence.buddy.Buddy` as the only argument.

    Any buddy may call `post` to send a message to all buddies.  Each
    buddy will receive a `message` signal.

    The `message` signal is emitted when a `post` is received from any
    buddy.  The signal has two arguments.  The first is a
    :class:`sugar3.presence.buddy.Buddy`. The second is the message.

    Any buddy may call `send_file_memory` or `send_file_file` to
    transfer a file to all buddies.  A description is to be given.
    Each buddy will receive an `incoming_file` signal.

    The `incoming_file` signal is emitted when a file transfer is
    received.  The signal has two arguments.  The first is a
    :class:`IncomingFileTransfer`.  The second is the description.
    '''

    message = GObject.Signal('message', arg_types=[object, object])
    joined = GObject.Signal('joined')
    buddy_joined = GObject.Signal('buddy_joined', arg_types=[object])
    buddy_left = GObject.Signal('buddy_left', arg_types=[object])
    incoming_file = GObject.Signal('incoming_file', arg_types=[object, object])

    def __init__(self, activity):
        _logger.debug('__init__')
        GObject.GObject.__init__(self)
        self.activity = activity
        self.shared_activity = activity.shared_activity
        self._leader = False
        self._init_waiting = False
        self._text_channel = None
        self._owner = presenceservice.get_instance().get_owner()

    def setup(self):
        '''
        Setup must be called so that the activity can join or share
        if appropriate.

        .. note::
            As soon as setup is called, any signal, `get_data` or
            `set_data` call may occur.  This means that the activity
            must have set up enough so these functions can work.  For
            example, call setup at the end of the activity
            `__init__` function.
        '''
        _logger.debug('setup')
        # Some glue to know if we are launching, joining, or resuming
        # a shared activity.
        if self.shared_activity:
            # We're joining the activity.
            self.activity.connect("joined", self.__joined_cb)

            if self.activity.get_shared():
                _logger.debug('calling _joined_cb')
                self.__joined_cb(self)
            else:
                _logger.debug('Joining activity...')
                self._alert(_('Joining activity...'),
                            _('Please wait for the connection...'))
        else:
            self._leader = True
            if not self.activity.metadata or self.activity.metadata.get(
                    'share-scope', SCOPE_PRIVATE) == \
                    SCOPE_PRIVATE:
                # We are creating a new activity instance.
                _logger.debug('Off-line')
            else:
                # We are sharing an old activity instance.
                _logger.debug('On-line')
                self._alert(_('Resuming shared activity...'),
                            _('Please wait for the connection...'))
            self.activity.connect('shared', self.__shared_cb)

    def _alert(self, title, msg=None):
        a = NotifyAlert()
        a.props.title = title
        a.props.msg = msg
        self.activity.add_alert(a)
        a.connect('response', lambda a, r: self.activity.remove_alert(a))
        a.show()

    def __shared_cb(self, sender):
        ''' Callback for when activity is shared. '''
        _logger.debug('__shared_cb')
        # FIXME: may be called twice, but we should only act once
        self.shared_activity = self.activity.shared_activity
        self._setup_text_channel()
        self._listen_for_channels()

    def __joined_cb(self, sender):
        '''Callback for when an activity is joined.'''
        _logger.debug('__joined_cb')
        self.shared_activity = self.activity.shared_activity
        if not self.shared_activity:
            return

        self._setup_text_channel()
        self._listen_for_channels()
        self._init_waiting = True
        self.post({'action': ACTION_INIT_REQUEST})

        for buddy in self.shared_activity.get_joined_buddies():
            self.buddy_joined.emit(buddy)

        self.joined.emit()

    def _setup_text_channel(self):
        ''' Set up a text channel to use for collaboration. '''
        _logger.debug('_setup_text_channel')
        self._text_channel = _TextChannelWrapper(
            self.shared_activity.telepathy_text_chan,
            self.shared_activity.telepathy_conn)

        # Tell the text channel what callback to use for incoming
        # text messages.
        self._text_channel.set_received_callback(self.__received_cb)

        # Tell the text channel what callbacks to use when buddies
        # come and go.
        self.shared_activity.connect('buddy-joined', self.__buddy_joined_cb)
        self.shared_activity.connect('buddy-left', self.__buddy_left_cb)

    def _listen_for_channels(self):
        _logger.debug('_listen_for_channels')
        conn = self.shared_activity.telepathy_conn
        conn.connect_to_signal('NewChannels', self.__new_channels_cb)

    def __new_channels_cb(self, channels):
        _logger.debug('__new_channels_cb')
        conn = self.shared_activity.telepathy_conn
        for path, props in channels:
            if props[CHANNEL + '.Requested']:
                continue  # This channel was requested by me

            channel_type = props[CHANNEL + '.ChannelType']
            if channel_type == CHANNEL_TYPE_FILE_TRANSFER:
                self._handle_ft_channel(conn, path, props)

    def _handle_ft_channel(self, conn, path, props):
        _logger.debug('_handle_ft_channel')
        ft = IncomingFileTransfer(conn, path, props)
        if ft.description == ACTION_INIT_RESPONSE:
            ft.connect('ready', self.__ready_cb)
            ft.accept_to_memory()
        else:
            desc = json.loads(ft.description)
            self.incoming_file.emit(ft, desc)

    def __ready_cb(self, ft, stream):
        _logger.debug('__ready_cb')
        if self._init_waiting:
            stream.close(None)
            # FIXME:  The data prop seems to just be the raw pointer
            gbytes = stream.steal_as_bytes()
            data = gbytes.get_data()
            _logger.debug('Got init data from buddy: %r', data)
            data = json.loads(data)
            self.activity.set_data(data)
            self._init_waiting = False

    def __received_cb(self, buddy, msg):
        '''Process a message when it is received.'''
        _logger.debug('__received_cb')
        action = msg.get('action')
        if action == ACTION_INIT_REQUEST:
            if self._leader:
                data = self.activity.get_data()
                if data is not None:
                    data = json.dumps(data)
                    OutgoingBlobTransfer(buddy,
                                         self.shared_activity.telepathy_conn,
                                         data.encode(), self.get_client_name(),
                                         ACTION_INIT_RESPONSE,
                                         ACTIVITY_FT_MIME)
            return

        if buddy:
            nick = buddy.props.nick
        else:
            nick = '???'
        _logger.debug('Received message from %s: %r', nick, msg)
        self.message.emit(buddy, msg)

    def send_file_memory(self, buddy, data, description):
        '''
        Send a one to one file transfer from memory to a buddy.  The
        buddy will get the file transfer and description through the
        `incoming_transfer` signal.

        Args:
            buddy (sugar3.presence.buddy.Buddy), buddy to send to.
            data (str), the data to send.
            description (object), a json encodable description for the
                transfer.  This will be given to the
                `incoming_transfer` signal at the buddy.
        '''
        OutgoingBlobTransfer(buddy, self.shared_activity.telepathy_conn, data,
                             self.get_client_name(), json.dumps(description),
                             ACTIVITY_FT_MIME)

    def send_file_file(self, buddy, path, description):
        '''
        Send a one to one file transfer from a filesystem path to a
        given buddy.  The buddy will get the file transfer and
        description through the `incoming_transfer` signal.

        Args:
            buddy (sugar3.presence.buddy.Buddy), buddy to send to.
            path (str), path of the file containing the data to send.
            description (object), a json encodable description for the
                transfer.  This will be given to the
                `incoming_transfer` signal at the buddy.
        '''
        OutgoingFileTransfer(buddy, self.shared_activity.telepathy_conn, path,
                             self.get_client_name(), json.dumps(description),
                             ACTIVITY_FT_MIME)

    def post(self, msg):
        '''
        Send a message to all buddies.  If the activity is not shared,
        no message is sent.

        Args:
            msg (object): json encodable object to send,
                eg. :class:`dict` or :class:`str`.
        '''
        if self._text_channel is not None:
            self._text_channel.post(msg)

    def __buddy_joined_cb(self, sender, buddy):
        '''A buddy joined.'''
        self.buddy_joined.emit(buddy)

    def __buddy_left_cb(self, sender, buddy):
        '''A buddy left.'''
        self.buddy_left.emit(buddy)

    def get_client_name(self):
        '''
        Get the name of the activity's telepathy client.

        Returns: str, telepathy client name
        '''
        return CLIENT + '.' + self.activity.get_bundle_id()

    @GObject.property
    def leader(self):
        '''
        Boolean of if this client is the leader in this activity.  The
        way the leader is decided may change, however there should only
        ever be one leader for an activity.
        '''
        return self._leader

    @GObject.property
    def owner(self):
        '''
        Ourselves, :class:`sugar3.presence.buddy.Owner`
        '''
        return self._owner
class CollabWrapper(GObject.GObject):
    '''
    The collaboration wrapper provides a high level abstraction over the
    collaboration system.  The wrapper deals with setting up the channels,
    encoding and decoding messages, initialization and alerting the user
    to the status.

    When a user joins the activity, it will query the leader for the
    contents.  The leader will return the result of the activity's
    `get_data` function which will be passed to the `set_data` function
    on the new user's computer.

    The `message` signal is called when a message is received from a
    buddy.  It has 2 arguments.  The first is the buddy, as a
    :class:`sugar3.presence.buddy.Buddy`. The second is the decoded
    content of the message, same as that posted by the other instance.

    The `joined` signal is emitted when the buddy joins a running
    activity.  If the user shares and activity, the joined signal
    is not emitted.  By the time this signal is emitted, the channels
    will be setup so all messages will flow through.

    The `buddy_joined` and `buddy_left` signals are emitted when
    another user joins or leaves the activity.  They both a
    :class:`sugar3.presence.buddy.Buddy` as their only argument.

    The `incoming_file` signal is emitted when a file transfer is
    received from a buddy.  The first argument is the object representing
    the transfer, as a
    :class:`sugar3.presence.filetransfer.IncomingFileTransfer`.  The seccond
    argument is the description, as passed to the `send_file_*` function
    on the sender's client
    '''

    message = GObject.Signal('message', arg_types=[object, object])
    joined = GObject.Signal('joined')
    buddy_joined = GObject.Signal('buddy_joined', arg_types=[object])
    buddy_left = GObject.Signal('buddy_left', arg_types=[object])
    incoming_file = GObject.Signal('incoming_file', arg_types=[object, object])

    def __init__(self, activity):
        GObject.GObject.__init__(self)
        self.activity = activity
        self.shared_activity = activity.shared_activity
        self._leader = False
        self._init_waiting = False
        self._text_channel = None

    def setup(self):
        '''
        Setup must be called to so that the activity can join or share
        if appropriate.

        .. note::
            As soon as setup is called, any signal, `get_data` or
            `set_data` call must be made.  This means that your
            activity must have set up enough so these functions can
            work.  For example, place this at the end of the activity's
            `__init__` function.
        '''
        # Some glue to know if we are launching, joining, or resuming
        # a shared activity.
        if self.shared_activity:
            # We're joining the activity.
            self.activity.connect("joined", self.__joined_cb)

            if self.activity.get_shared():
                _logger.debug('calling _joined_cb')
                self.__joined_cb(self)
            else:
                _logger.debug('Joining activity...')
                self._alert(_('Joining activity...'),
                            _('Please wait for the connection...'))
        else:
            if not self.activity.metadata or self.activity.metadata.get(
                    'share-scope', SCOPE_PRIVATE) == \
                    SCOPE_PRIVATE:
                # We are creating a new activity instance.
                _logger.debug('Off-line')
            else:
                # We are sharing an old activity instance.
                _logger.debug('On-line')
                self._alert(_('Resuming shared activity...'),
                            _('Please wait for the connection...'))
            self.activity.connect('shared', self.__shared_cb)

    def _alert(self, title, msg=None):
        a = NotifyAlert()
        a.props.title = title
        a.props.msg = msg
        self.activity.add_alert(a)
        a.connect('response', lambda a, r: self.activity.remove_alert(a))
        a.show()

    def __shared_cb(self, sender):
        ''' Callback for when activity is shared. '''
        self.shared_activity = self.activity.shared_activity
        self._setup_text_channel()
        self._listen_for_channels()

        self._leader = True
        _logger.debug('I am sharing...')

    def __joined_cb(self, sender):
        '''Callback for when an activity is joined.'''
        self.shared_activity = self.activity.shared_activity
        if not self.shared_activity:
            return

        self._setup_text_channel()
        self._listen_for_channels()
        self._init_waiting = True
        self.post({'action': ACTION_INIT_REQUEST})

        _logger.debug('I joined a shared activity.')
        self.joined.emit()

    def _setup_text_channel(self):
        ''' Set up a text channel to use for collaboration. '''
        self._text_channel = _TextChannelWrapper(
            self.shared_activity.telepathy_text_chan,
            self.shared_activity.telepathy_conn)

        # Tell the text channel what callback to use for incoming
        # text messages.
        self._text_channel.set_received_callback(self.__received_cb)

        # Tell the text channel what callbacks to use when buddies
        # come and go.
        self.shared_activity.connect('buddy-joined', self.__buddy_joined_cb)
        self.shared_activity.connect('buddy-left', self.__buddy_left_cb)

    def _listen_for_channels(self):
        conn = self.shared_activity.telepathy_conn
        conn.connect_to_signal('NewChannels', self.__new_channels_cb)

    def __new_channels_cb(self, channels):
        conn = self.shared_activity.telepathy_conn
        for path, props in channels:
            if props[CHANNEL + '.Requested']:
                continue  # This channel was requested by me

            channel_type = props[CHANNEL + '.ChannelType']
            if channel_type == CHANNEL_TYPE_FILE_TRANSFER:
                self._handle_ft_channel(conn, path, props)

    def _handle_ft_channel(self, conn, path, props):
        ft = IncomingFileTransfer(conn, path, props)
        if ft.description == ACTION_INIT_RESPONSE:
            ft.connect('notify::state', self.__notify_ft_state_cb)
            ft.accept_to_memory()
        else:
            desc = json.loads(ft.description)
            self.incoming_file.emit(ft, desc)

    def __notify_ft_state_cb(self, ft, pspec):
        if ft.props.state == FT_STATE_COMPLETED and self._init_waiting:
            stream = ft.props.output
            stream.close(None)
            # FIXME:  The data prop seems to just be the raw pointer
            gbytes = stream.steal_as_bytes()
            data = gbytes.get_data()
            logging.debug('Got init data from buddy:  %s', data)
            data = json.loads(data)
            self.activity.set_data(data)
            self._init_waiting = False

    def __received_cb(self, buddy, msg):
        '''Process a message when it is received.'''
        action = msg.get('action')
        if action == ACTION_INIT_REQUEST and self._leader:
            data = self.activity.get_data()
            data = json.dumps(data)
            OutgoingBlobTransfer(buddy,
                                 self.shared_activity.telepathy_conn, data,
                                 self.get_client_name(), ACTION_INIT_RESPONSE,
                                 ACTIVITY_FT_MIME)
            return

        if buddy:
            nick = buddy.props.nick
        else:
            nick = '???'
        _logger.debug('Received message from %s: %r', nick, msg)
        self.message.emit(buddy, msg)

    def send_file_memory(self, buddy, data, description):
        '''
        Send a 1-to-1 transfer from memory to a given buddy.  They will
        get the file transfer and description through the `incoming_transfer`
        signal.

        Args:
            buddy (sugar3.presence.buddy.Buddy), buddy to offer the transfer to
            data (str), the data to offer to the buddy via the transfer
            description (object), a json encodable description for the
                transfer.  This will be given to the `incoming_transfer` signal
                of the transfer
        '''
        OutgoingBlobTransfer(buddy, self.shared_activity.telepathy_conn, data,
                             self.get_client_name(), json.dumps(description),
                             ACTIVITY_FT_MIME)

    def send_file_file(self, buddy, path, description):
        '''
        Send a 1-to-1 transfer from a file to a given buddy.  They will
        get the file transfer and description through the `incoming_transfer`
        signal.

        Args:
            buddy (sugar3.presence.buddy.Buddy), buddy to offer the transfer to
            path (str), path of the file to send to the buddy
            description (object), a json encodable description for the
                transfer.  This will be given to the `incoming_transfer` signal
                of the transfer
        '''
        OutgoingFileTransfer(buddy, self.shared_activity.telepathy_conn, path,
                             self.get_client_name(), json.dumps(description),
                             ACTIVITY_FT_MIME)

    def post(self, msg):
        '''
        Broadcast a message to the other buddies if the activity is
        shared.  If it is not shared, the message will not be send
        at all.

        Args:
            msg (object): json encodable object to send to the other
                buddies, eg. :class:`dict` or :class:`str`.
        '''
        if self._text_channel is not None:
            self._text_channel.post(msg)

    def __buddy_joined_cb(self, sender, buddy):
        '''A buddy joined.'''
        self.buddy_joined.emit(buddy)

    def __buddy_left_cb(self, sender, buddy):
        '''A buddy left.'''
        self.buddy_left.emit(buddy)

    def get_client_name(self):
        '''
        Get the name of the activity's telepathy client.

        Returns: str, telepathy client name
        '''
        return CLIENT + '.' + self.activity.get_bundle_id()
Example #28
0
class AgePicker(Gtk.Grid):

    age_changed_signal = GObject.Signal('age-changed', arg_types=([int]))

    def __init__(self, gender, page=None):
        Gtk.Grid.__init__(self)

        self.set_row_spacing(style.DEFAULT_SPACING)
        self.set_column_spacing(style.DEFAULT_SPACING)

        self._group_labels = get_group_labels()

        self._page = page
        self._gender = gender
        self._age = self.get_age()
        self._pickers = []
        self._nocolor = XoColor('#010101,#ffffff')
        self._color = XoColor()

        if self._gender not in GENDERS:
            self._gender = 'female'

        gender_index = GENDERS.index(self._gender)
        age_index = age_to_index(self._age)

        width = Gdk.Screen.width()

        num_ages = len(self._group_labels.AGES)
        for i in range(num_ages):
            self._pickers.append(
                Picker(self._group_labels.ICONS[i][gender_index],
                       _(self._group_labels.LABELS[i])))
            self._pickers[i].connect(self._button_activate_cb, i)

        self._fixed = Gtk.Fixed()
        fixed_size = width - 4 * style.GRID_CELL_SIZE
        self._fixed.set_size_request(fixed_size, -1)
        self.attach(self._fixed, 0, 0, 1, 1)
        self._fixed.show()

        self._age_adj = Gtk.Adjustment(value=age_index, lower=0,
                                       upper=num_ages - 1, step_incr=1,
                                       page_incr=3, page_size=0)
        self._age_adj.connect('value-changed', self.__age_adj_changed_cb)

        self._age_slider = Gtk.HScale()
        self._age_slider.set_draw_value(False)
        self._age_slider.set_adjustment(self._age_adj)
        self.attach(self._age_slider, 0, 1, 1, 1)

        for i in range(num_ages):
            self._fixed.put(self._pickers[i], 0, 0)

        self._configure(width)

        Gdk.Screen.get_default().connect('size-changed', self._configure_cb)

    def _configure_cb(self, event=None):
        width = Gdk.Screen.width()
        self._configure(width)

    def _configure(self, width):
        fixed_size = width - 4 * style.GRID_CELL_SIZE
        self._fixed.set_size_request(fixed_size, -1)

        num_ages = len(self._group_labels.AGES)

        dx = int((fixed_size - style.LARGE_ICON_SIZE) / (num_ages - 1))
        for i in range(num_ages):
            self._fixed.move(self._pickers[i], dx * i, 0)

        if num_ages + 2 < width / style.LARGE_ICON_SIZE:
            for i in range(num_ages):
                self._pickers[i].show_all()
            self._age_slider.hide()
        else:
            self._age_slider.show()
            value = self._age_adj.get_value()
            self._set_age_picker(int(value + 0.5))

    def get_label(self):
        return self._group_labels.GROUP_LABEL

    def _set_age_picker(self, age_index):
        for i in range(len(self._group_labels.AGES)):
            if i == age_index:
                self._pickers[i].show_all()
            else:
                self._pickers[i].hide_all()
        self._do_selected(age_index)

    def __age_adj_changed_cb(self, widget):
        value = self._age_adj.get_value()
        self._set_age_picker(int(value + 0.5))

    def _do_selected(self, age_index):
        if self._age is not None:
            i = age_to_index(self._age)
            self._pickers[i].set_color(self._nocolor)
        self._set_age(self._group_labels.AGES[age_index])
        self._pickers[age_index].set_color(self._color)

    def _button_activate_cb(self, widget, age_index):
        self._do_selected(age_index)

    def get_age(self):
        if self._page is None:
            return load_age()
        elif hasattr(self, '_age'):
            if self._age is None:
                return None
            i = age_to_index(self._age)
            return self._group_labels.AGES[i]
        return None

    def _set_age(self, age):
        if self._page is None:
            if age != self._age:
                self.age_changed_signal.emit(age)
        else:
            self._page.set_valid(True)
        self._age = age

    def update_color(self, color):
        self._color = color
        if self._age is not None:
            i = age_to_index(self._age)
            self._pickers[i].set_color(self._color)

    def update_gender(self, gender):
        self._gender = gender

        if self._gender in GENDERS:
            gender_index = GENDERS.index(self._gender)
        else:
            gender_index = 0

        for i in range(len(self._group_labels.AGES)):
            self._pickers[i].set_icon(
                self._group_labels.ICONS[i][gender_index])
Example #29
0
class AboutMe(SectionView):

    age_changed_signal = GObject.Signal('age-changed', arg_types=([int]))
    gender_changed_signal = GObject.Signal('gender-changed', arg_types=([str]))

    def __init__(self, model, alerts):
        SectionView.__init__(self)

        self._model = model
        self.restart_alerts = alerts if alerts else set()
        self.props.is_deferrable = False
        self._nick_sid = 0
        self._color_valid = True
        self._nick_valid = True
        self._color = None
        self._gender = ''
        self._age = None

        self.set_border_width(style.DEFAULT_SPACING * 2)
        self.set_spacing(style.DEFAULT_SPACING)

        self._color = XoColor(self._model.get_color())

        self._original_nick = self._model.get_nick()
        self._setup_color()
        self._setup_nick()
        self._setup_gender()
        self._setup_age()

        self._update_pickers(self._color)

        self._nick_entry.set_text(self._original_nick)
        self._color_valid = True
        self._nick_valid = True
        self.needs_restart = False

        self._nick_entry.connect('changed', self.__nick_changed_cb)

        for picker in self._pickers.values():
            picker.connect('color-changed', self.__color_changed_cb)

        self._gender_pickers.connect('gender-changed',
                                     self.__gender_changed_cb)
        self._age_pickers.connect('age-changed', self.__age_changed_cb)

    def _setup_nick(self):
        grid = Gtk.Grid()
        grid.set_row_spacing(style.DEFAULT_SPACING)
        grid.set_column_spacing(style.DEFAULT_SPACING)

        self._nick_entry = Gtk.Entry()
        self._nick_entry.set_width_chars(25)
        grid.attach(self._nick_entry, 0, 0, 1, 1)
        self._nick_entry.show()

        alert_grid = Gtk.Grid()
        self._nick_alert = InlineAlert()
        alert_grid.attach(self._nick_alert, 0, 0, 1, 1)
        if 'nick' in self.restart_alerts:
            self._nick_alert.props.msg = self.restart_msg
            self._nick_alert.show()

        center_in_panel = Gtk.Alignment.new(0.5, 0, 0, 0)
        center_in_panel.add(grid)
        grid.show()

        center_alert = Gtk.Alignment.new(0.5, 0, 0, 0)
        center_alert.add(alert_grid)
        alert_grid.show()

        self.pack_start(center_in_panel, False, False, 0)
        self.pack_start(center_alert, False, False, 0)
        center_in_panel.show()
        center_alert.show()

    def _setup_color(self):
        grid = Gtk.Grid()
        grid.set_row_spacing(style.DEFAULT_SPACING)
        grid.set_column_spacing(style.DEFAULT_SPACING)

        self._color_alert = None

        self._pickers = {
            _PREVIOUS_FILL_COLOR: ColorPicker(_PREVIOUS_FILL_COLOR),
            _NEXT_FILL_COLOR: ColorPicker(_NEXT_FILL_COLOR),
            _CURRENT_COLOR: ColorPicker(_CURRENT_COLOR),
            _NEXT_STROKE_COLOR: ColorPicker(_NEXT_STROKE_COLOR),
            _PREVIOUS_STROKE_COLOR: ColorPicker(_PREVIOUS_STROKE_COLOR),
        }

        label_color = Gtk.Label(label=_('Click to change your color:'))
        label_color.modify_fg(Gtk.StateType.NORMAL,
                              style.COLOR_SELECTION_GREY.get_gdk_color())
        grid.attach(label_color, 0, 0, 3, 1)
        label_color.show()

        current = 0
        for picker_index in sorted(self._pickers.keys()):
            if picker_index == _CURRENT_COLOR:
                left_separator = Gtk.SeparatorToolItem()
                grid.attach(left_separator, current, 1, 1, 1)
                left_separator.show()
                current += 1

            picker = self._pickers[picker_index]
            picker.show()
            grid.attach(picker, current, 1, 1, 1)
            current += 1

            if picker_index == _CURRENT_COLOR:
                right_separator = Gtk.SeparatorToolItem()
                right_separator.show()
                grid.attach(right_separator, current, 1, 1, 1)
                current += 1

        label_color_error = Gtk.Label()
        grid.attach(label_color_error, 0, 2, 3, 1)
        label_color_error.show()

        self._color_alert = InlineAlert()
        grid.attach(self._color_alert, 0, 3, 3, 1)
        if 'color' in self.restart_alerts:
            self._color_alert.props.msg = self.restart_msg
            self._color_alert.show()

        center_in_panel = Gtk.Alignment.new(0.5, 0, 0, 0)
        center_in_panel.add(grid)
        grid.show()

        self.pack_start(center_in_panel, False, False, 0)
        center_in_panel.show()

    def _setup_gender(self):
        self._saved_gender = load_gender()

        self._gender_pickers = GenderPicker()

        grid = Gtk.Grid()
        grid.set_row_spacing(style.DEFAULT_SPACING)
        grid.set_column_spacing(style.DEFAULT_SPACING)

        label_gender = Gtk.Label(label=_('Select gender:'))
        label_gender.modify_fg(Gtk.StateType.NORMAL,
                               style.COLOR_SELECTION_GREY.get_gdk_color())
        grid.attach(label_gender, 0, 0, 1, 1)
        label_gender.show()

        grid.attach(self._gender_pickers, 0, 1, 1, 1)
        self._gender_pickers.show()

        center_in_panel = Gtk.Alignment.new(0.5, 0, 0, 0)
        center_in_panel.add(grid)
        grid.show()

        self.pack_start(center_in_panel, False, False, 0)
        center_in_panel.show()

    def _setup_age(self):
        self._saved_age = load_age()

        grid = Gtk.Grid()
        grid.set_row_spacing(style.DEFAULT_SPACING)
        grid.set_column_spacing(style.DEFAULT_SPACING)

        self._age_pickers = AgePicker(self._saved_gender)
        center_in_panel = Gtk.Alignment.new(0.5, 0, 0, 0)
        center_in_panel.add(self._age_pickers)
        self._age_pickers.show()

        label = self._age_pickers.get_label()

        label_age = Gtk.Label(label=_(label))
        label_age.modify_fg(Gtk.StateType.NORMAL,
                            style.COLOR_SELECTION_GREY.get_gdk_color())
        left_align = Gtk.Alignment.new(0, 0, 0, 0)
        left_align.add(label_age)
        label_age.show()
        grid.attach(left_align, 0, 0, 1, 1)
        left_align.show()

        grid.attach(center_in_panel, 0, 1, 1, 1)
        center_in_panel.show()

        center_in_panel = Gtk.Alignment.new(0.5, 0, 0, 0)
        center_in_panel.add(grid)
        grid.show()
        self.pack_start(center_in_panel, False, False, 0)
        center_in_panel.show()

    def setup(self):
        pass

    def undo(self):
        self._model.undo()
        self._nick_alert.hide()
        self._color_alert.hide()

        # Undo gender or age changes
        save_gender(self._saved_gender)
        save_age(self._saved_age)

    def _update_pickers(self, color):
        for picker in self._pickers.values():
            picker.props.color = color
        self._gender_pickers.update_color(color)
        self._age_pickers.update_color(color)

    def _validate(self):
        if self._nick_valid and self._color_valid:
            self.props.is_valid = True
        else:
            self.props.is_valid = False

    def __nick_changed_cb(self, widget, data=None):
        if self._nick_sid:
            GLib.source_remove(self._nick_sid)
        self._nick_sid = GLib.timeout_add(self._APPLY_TIMEOUT,
                                          self.__nick_timeout_cb, widget)

    def __nick_timeout_cb(self, widget):
        self._nick_sid = 0

        if widget.get_text() == self._model.get_nick():
            self.restart_alerts.remove('nick')
            if not self.restart_alerts:
                self.needs_restart = False
            self._nick_alert.hide()
            return False
        try:
            self._model.set_nick(widget.get_text())
        except ValueError, detail:
            self._nick_alert.props.msg = detail
            self._nick_valid = False
            self._nick_alert.show()
        else:
Example #30
0
class SessionManager(GObject.GObject):

    shutdown_signal = GObject.Signal('shutdown')

    MODE_LOGOUT = 0
    MODE_SHUTDOWN = 1
    MODE_REBOOT = 2

    SHUTDOWN_TIMEOUT = 1
    MAX_SHUTDOWN_TRIES = 10

    def __init__(self):
        GObject.GObject.__init__(self)

        address = SugarExt.xsmp_init()
        os.environ['SESSION_MANAGER'] = address
        SugarExt.xsmp_run()

        self.session = SugarExt.Session.create_global()
        self._shell_model = shell.get_model()
        self._shutdown_tries = 0
        self._logout_mode = None

    def start(self):
        self.session.start()
        self.session.connect('shutdown_completed',
                             self.__shutdown_completed_cb)

    def initiate_shutdown(self, logout_mode):
        self._logout_mode = logout_mode
        self.shutdown_signal.emit()
        self.session.initiate_shutdown()

    def cancel_shutdown(self):
        self.session.cancel_shutdown()
        self._shutdown_tries = 0
        self._logout_mode = None

    def __shutdown_completed_cb(self, session):
        if self._logout_mode is not None:
            if self._try_shutdown():
                GObject.timeout_add(self.SHUTDOWN_TIMEOUT, self._try_shutdown)

    def _try_shutdown(self):
        if len(self._shell_model) > 0:
            self._shutdown_tries += 1
            if self._shutdown_tries < self.MAX_SHUTDOWN_TRIES:
                # returning True, the timeout_add_seconds will try
                # again in the specified seconds
                return True

        self.shutdown_completed()
        return False

    def logout(self):
        self.initiate_shutdown(self.MODE_LOGOUT)

    def shutdown(self):
        self.initiate_shutdown(self.MODE_SHUTDOWN)

    def reboot(self):
        self.initiate_shutdown(self.MODE_REBOOT)

    def shutdown_completed(self):
        if self._logout_mode != self.MODE_LOGOUT:
            bus = dbus.SystemBus()
            if have_systemd():
                try:
                    proxy = bus.get_object('org.freedesktop.login1',
                                           '/org/freedesktop/login1')
                    pm = dbus.Interface(proxy,
                                        'org.freedesktop.login1.Manager')

                    if self._logout_mode == self.MODE_SHUTDOWN:
                        pm.PowerOff(False)
                    elif self._logout_mode == self.MODE_REBOOT:
                        pm.Reboot(True)
                except:
                    logging.exception('Can not stop sugar')
                    self.session.cancel_shutdown()
                    return
            else:
                CONSOLEKIT_DBUS_PATH = '/org/freedesktop/ConsoleKit/Manager'
                try:
                    proxy = bus.get_object('org.freedesktop.ConsoleKit',
                                           CONSOLEKIT_DBUS_PATH)
                    pm = dbus.Interface(proxy,
                                        'org.freedesktop.ConsoleKit.Manager')

                    if self._logout_mode == self.MODE_SHUTDOWN:
                        pm.Stop()
                    elif self._logout_mode == self.MODE_REBOOT:
                        pm.Restart()
                except:
                    logging.exception('Can not stop sugar')
                    self.session.cancel_shutdown()
                    return

        SugarExt.xsmp_shutdown()
        Gtk.main_quit()