def _choose_activity(self): if not hasattr(self, '_activity_sw'): grid = Gtk.Grid() self._reflection.activity.load_overlay_area(grid) grid.show() collapse_button = EventIcon(icon_name='delete', pixel_size=BUTTON_SIZE) collapse_button.set_tooltip(_('Collapse')) collapse_button.connect('button-press-event', self._reflection.activity.collapse_overlay_area) grid.attach(collapse_button, 7, 0, 1, 1) collapse_button.show() bundle_icons = utils.get_bundle_icons() x = 0 y = 1 for bundle_id in bundle_icons.keys(): icon_path = bundle_icons[bundle_id] if icon_path is None: continue pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( icon_path, style.GRID_CELL_SIZE, style.GRID_CELL_SIZE) image = Gtk.Image.new_from_pixbuf(pixbuf) button = Gtk.ToolButton() button.set_icon_widget(image) image.show() button.connect('clicked', self._insert_activity, bundle_id) grid.attach(button, x, y, 1, 1) button.show() x += 1 if x > 6: y += 1 x = 0 self._reflection.activity.show_overlay_area() self._reflection.activity.reset_cursor()
class Picker(Gtk.Grid): def __init__(self, icon, label): Gtk.Grid.__init__(self) self._button = EventIcon(pixel_size=style.LARGE_ICON_SIZE, icon_name=icon) self.attach(self._button, 0, 0, 1, 1) self._button.hide() self._label = Gtk.Label(label) self.attach(self._label, 0, 1, 1, 1) self._label.hide() def show_all(self): self._button.show() self._label.show() self.show() def hide_all(self): self._button.hide() self._label.hide() self.hide() def connect(self, callback, arg): self._button.connect('button-press-event', callback, arg) def set_color(self, color): self._button.xo_color = color def set_icon(self, icon): self._button.set_icon_name(icon)
class Picker(Gtk.Grid): def __init__(self, icon, label): Gtk.Grid.__init__(self) self._button = EventIcon(pixel_size=style.LARGE_ICON_SIZE, icon_name=icon) self.attach(self._button, 0, 0, 1, 1) self._button.hide() self._label = Gtk.Label(label.replace(' ', '\n')) self._label.props.justify = Gtk.Justification.CENTER self.attach(self._label, 0, 1, 1, 1) self._label.hide() def show_all(self): self._button.show() self._label.show() self.show() def hide_all(self): self._button.hide() self._label.hide() self.hide() def connect(self, callback, arg): self._button.connect('activate', callback, arg) def set_color(self, color): self._button.xo_color = color def set_icon(self, icon): self._button.set_icon_name(icon)
def _make_entry_widgets(self): '''We need to create a button for the smiley, a text entry, and a send button. All of this, along with the chatbox, goes into a grid. --------------------------------------- | chat box | | smiley button | entry | send button | --------------------------------------- ''' if self._ebook_mode_detector.get_ebook_mode(): self._entry_height = int(style.GRID_CELL_SIZE * 1.5) else: self._entry_height = style.GRID_CELL_SIZE entry_width = Gdk.Screen.width() - \ 2 * (self._entry_height + style.GRID_CELL_SIZE) self._chat_height = Gdk.Screen.height() - self._entry_height - \ style.GRID_CELL_SIZE self._chat_width = Gdk.Screen.width() self.chatbox.set_size_request(self._chat_width, self._chat_height) self._entry_grid = Gtk.Grid() self._entry_grid.set_size_request( Gdk.Screen.width() - 2 * style.GRID_CELL_SIZE, self._entry_height) smiley_button = EventIcon(icon_name='smilies', pixel_size=self._entry_height) smiley_button.connect('button-press-event', self._smiley_button_cb) self._entry_grid.attach(smiley_button, 0, 0, 1, 1) smiley_button.show() self._entry = Gtk.Entry() self._entry.set_size_request(entry_width, self._entry_height) self._entry.modify_bg(Gtk.StateType.INSENSITIVE, style.COLOR_WHITE.get_gdk_color()) self._entry.modify_base(Gtk.StateType.INSENSITIVE, style.COLOR_WHITE.get_gdk_color()) self._entry.set_sensitive(False) self._entry.props.placeholder_text = \ _('You must be connected to a friend before starting to chat.') self._entry.connect('focus-in-event', self._entry_focus_in_cb) self._entry.connect('focus-out-event', self._entry_focus_out_cb) self._entry.connect('activate', self._entry_activate_cb) self._entry.connect('key-press-event', self._entry_key_press_cb) self._entry_grid.attach(self._entry, 1, 0, 1, 1) self._entry.show() send_button = EventIcon(icon_name='send', pixel_size=self._entry_height) send_button.connect('button-press-event', self._send_button_cb) self._entry_grid.attach(send_button, 2, 0, 1, 1) send_button.show()
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
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
def add_button(self, icon_name, description, clicked_cb=None): icon = EventIcon(icon_name=icon_name, pixel_size=(style.GRID_CELL_SIZE*2)/5, xo_color=XoColor('#ffffff,#ffffff')) icon.props.palette = Palette(description) self._top_bar.add(icon) if clicked_cb: def closure(widget, event): alloc = widget.get_allocation() if 0 < event.x < alloc.width and 0 < event.y < alloc.height: clicked_cb(widget) icon.connect('button-release-event', closure) icon.show() return icon
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
def _choose_activity(self): if not hasattr(self, '_activity_sw'): grid = Gtk.Grid() self._reflection.activity.load_overlay_area(grid) grid.show() collapse_button = EventIcon(icon_name='delete', pixel_size=BUTTON_SIZE) collapse_button.set_tooltip(_('Collapse')) collapse_button.connect( 'button-press-event', self._reflection.activity.collapse_overlay_area) grid.attach(collapse_button, 7, 0, 1, 1) collapse_button.show() bundle_icons = utils.get_bundle_icons() x = 0 y = 1 for bundle_id in bundle_icons.keys(): icon_path = bundle_icons[bundle_id] if icon_path is None: continue pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( icon_path, style.GRID_CELL_SIZE, style.GRID_CELL_SIZE) image = Gtk.Image.new_from_pixbuf(pixbuf) button = Gtk.ToolButton() button.set_icon_widget(image) image.show() button.connect('clicked', self._insert_activity, bundle_id) grid.attach(button, x, y, 1, 1) button.show() x += 1 if x > 6: y += 1 x = 0 self._reflection.activity.show_overlay_area() self._reflection.activity.reset_cursor()
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)
def __init__(self, parent): Gtk.EventBox.__init__(self) self._reflection = parent self._collapse = True self._collapse_id = None self.modify_bg(Gtk.StateType.NORMAL, style.COLOR_WHITE.get_gdk_color()) self._title_color = self._reflection.activity.fg_color.get_html() self._grid = Gtk.Grid() self.add(self._grid) self._grid.show() self._grid.set_row_spacing(style.DEFAULT_PADDING) self._grid.set_column_spacing(style.DEFAULT_SPACING) self._grid.set_column_homogeneous(True) self._grid.set_border_width(style.DEFAULT_PADDING) row = 0 self._expand_button = EventIcon(icon_name='expand', pixel_size=BUTTON_SIZE) self._collapse_id = self._expand_button.connect( 'button-press-event', self._expand_cb) self._expand_button.set_tooltip(_('Expand')) self._grid.attach(self._expand_button, 0, row, 1, 1) self._expand_button.show() self._title_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) self._title = Gtk.TextView() self._title.set_size_request(ENTRY_WIDTH, -1) self._title.set_wrap_mode(Gtk.WrapMode.WORD) self._title_tag = self._title.get_buffer().create_tag( 'title', foreground=self._title_color, weight=Pango.Weight.BOLD, size=12288) iter_text = self._title.get_buffer().get_iter_at_offset(0) self._title.get_buffer().insert_with_tags( iter_text, self._reflection.data['title'], self._title_tag) if self._reflection.activity.initiating: self._title.connect('focus-out-event', self._title_focus_out_cb) else: self._title.set_editable(False) self._title_align.add(self._title) self._title.show() self._grid.attach(self._title_align, 1, row, 5, 1) self._title_align.show() delete_button = EventIcon(icon_name='delete', pixel_size=BUTTON_SIZE) delete_button.set_tooltip(_('Delete')) delete_button.connect('button-press-event', self.__delete_cb) self._grid.attach(delete_button, 6, row, 1, 1) delete_button.show() ''' Notification that a new comment has been shared. ''' self.notify_button = EventIcon(icon_name='chat', pixel_size=BUTTON_SIZE) self._grid.attach(self.notify_button, 6, row, 1, 1) row += 1 self._time_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) self._time = Gtk.Label() self._time.set_size_request(ENTRY_WIDTH, -1) self._time.set_justify(Gtk.Justification.LEFT) self._time.set_use_markup(True) try: time_string = util.timestamp_to_elapsed_string( int(self._reflection.data['modification_time'])) except Exception as e: logging.error('Could not convert modification time %s: %s' % (self._reflection.data['modification_time'], e)) self._reflection.data['modification_time'] = \ self._reflection.data['creation_time'] time_string = util.timestamp_to_elapsed_string( int(self._reflection.data['modification_time'])) self._time.set_markup( '<span foreground="#808080"><small><b>%s</b></small></span>' % time_string) self._time_align.add(self._time) self._time.show() self._grid.attach(self._time_align, 1, row, 5, 1) self._time_align.show() row += 1 label = '' if 'tags' in self._reflection.data: for tag in self._reflection.data['tags']: if len(label) > 0: label += ', ' label += tag if self._reflection.activity.initiating and label == '': label = _('Add a #tag') self._tag_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) self._tag_view = Gtk.TextView() self._tag_view.set_size_request(ENTRY_WIDTH, -1) self._tag_view.set_wrap_mode(Gtk.WrapMode.WORD) self._tag_view.get_buffer().set_text(label) if self._reflection.activity.initiating: self._tag_view.connect('focus-in-event', self._tag_focus_in_cb, _('Add a #tag')) self._tag_view.connect('focus-out-event', self._tags_focus_out_cb) else: self._tag_view.set_editable(False) self._tag_align.add(self._tag_view) self._tag_view.show() self._grid.attach(self._tag_align, 1, row, 5, 1) if self._reflection.activity.initiating: self._new_tag = EventIcon(icon_name='ok', pixel_size=BUTTON_SIZE) self._new_tag.connect('button-press-event', self._tag_button_cb) self._grid.attach(self._new_tag, 6, row, 1, 1) row += 1 self._activities_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) self._make_activities_grid() self._grid.attach(self._activities_align, 1, row, 5, 1) self._activities_align.show() if self._reflection.activity.initiating: self._new_activity = EventIcon(icon_name='add-item', pixel_size=BUTTON_SIZE) self._new_activity.set_tooltip(_('Add new activity')) self._new_activity.connect('button-press-event', self._activity_button_cb) self._grid.attach(self._new_activity, 6, row, 1, 1) self._new_activity.show() row += 1 self._stars_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) grid = Gtk.Grid() if 'stars' in self._reflection.data: stars = self._reflection.data['stars'] else: stars = 0 self._star_icons = [] for i in range(NUMBER_OF_STARS): if i < stars: icon_name = 'star-filled' else: icon_name = 'star-empty' self._star_icons.append( EventIcon(icon_name=icon_name, pixel_size=STAR_SIZE)) if self._reflection.activity.initiating: self._star_icons[-1].connect('button-press-event', self._star_button_cb, i) grid.attach(self._star_icons[-1], i, 0, 1, 1) self._star_icons[-1].show() self._stars_align.add(grid) grid.show() self._grid.attach(self._stars_align, 1, row, 5, 1) row += 1 self._content_aligns = [] first_text = True first_image = True self._content_we_always_show = [] if 'content' in self._reflection.data: for i, item in enumerate(self._reflection.data['content']): # Add edit and delete buttons align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) obj = None if 'text' in item: obj = Gtk.TextView() obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) obj.get_buffer().set_text(item['text']) if self._reflection.activity.initiating: obj.connect('focus-in-event', self._text_focus_in_cb) obj.connect('focus-out-event', self._text_focus_out_cb, i) else: obj.set_editable(False) if first_text: self._content_we_always_show.append(align) first_text = False elif 'image' in item: try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( item['image'], PICTURE_WIDTH, PICTURE_HEIGHT) obj = Gtk.Image.new_from_pixbuf(pixbuf) if first_image: self._content_we_always_show.append(align) first_image = False except BaseException: logging.error('could not open %s' % item['image']) if obj is not None: align.add(obj) obj.show() self._grid.attach(align, 1, row, 5, 1) self._content_aligns.append(align) row += 1 self._row = row if self._reflection.activity.initiating: self._new_entry = Gtk.Entry() self._new_entry.props.placeholder_text = _('Write a reflection') self._new_entry.connect('activate', self._entry_activate_cb) self._grid.attach(self._new_entry, 1, row, 5, 1) self._content_we_always_show.append(self._new_entry) self._new_image = EventIcon(icon_name='add-picture', pixel_size=BUTTON_SIZE) self._new_image.set_tooltip(_('Add new image')) self._new_image.connect('button-press-event', self._image_button_cb) self._grid.attach(self._new_image, 6, row, 1, 1) self._content_we_always_show.append(self._new_image) for align in self._content_we_always_show: align.show() row += 1 self._comment_row = row self._comment_aligns = [] if 'comments' in self._reflection.data: for comment in self._reflection.data['comments']: obj = Gtk.TextView() obj.set_editable(False) obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) nick_tag = obj.get_buffer().create_tag( 'nick', foreground=comment['color'], weight=Pango.Weight.BOLD) iter_text = obj.get_buffer().get_iter_at_offset(0) obj.get_buffer().insert_with_tags(iter_text, comment['nick'] + ': ', nick_tag) iter_text = obj.get_buffer().get_end_iter() obj.get_buffer().insert(iter_text, comment['comment']) align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) align.add(obj) obj.show() self._grid.attach(align, 1, self._comment_row, 5, 1) self._comment_aligns.append(align) self._comment_row += 1 self._new_comment = Gtk.Entry() self._new_comment.props.placeholder_text = _('Make a comment') self._new_comment.connect('activate', self._comment_activate_cb) self._grid.attach(self._new_comment, 1, self._comment_row, 5, 1)
class ReflectionGrid(Gtk.EventBox): def __init__(self, parent): Gtk.EventBox.__init__(self) self._reflection = parent self._collapse = True self._collapse_id = None self.modify_bg(Gtk.StateType.NORMAL, style.COLOR_WHITE.get_gdk_color()) self._title_color = self._reflection.activity.fg_color.get_html() self._grid = Gtk.Grid() self.add(self._grid) self._grid.show() self._grid.set_row_spacing(style.DEFAULT_PADDING) self._grid.set_column_spacing(style.DEFAULT_SPACING) self._grid.set_column_homogeneous(True) self._grid.set_border_width(style.DEFAULT_PADDING) row = 0 self._expand_button = EventIcon(icon_name='expand', pixel_size=BUTTON_SIZE) self._collapse_id = self._expand_button.connect( 'button-press-event', self._expand_cb) self._expand_button.set_tooltip(_('Expand')) self._grid.attach(self._expand_button, 0, row, 1, 1) self._expand_button.show() self._title_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) self._title = Gtk.TextView() self._title.set_size_request(ENTRY_WIDTH, -1) self._title.set_wrap_mode(Gtk.WrapMode.WORD) self._title_tag = self._title.get_buffer().create_tag( 'title', foreground=self._title_color, weight=Pango.Weight.BOLD, size=12288) iter_text = self._title.get_buffer().get_iter_at_offset(0) self._title.get_buffer().insert_with_tags( iter_text, self._reflection.data['title'], self._title_tag) if self._reflection.activity.initiating: self._title.connect('focus-out-event', self._title_focus_out_cb) else: self._title.set_editable(False) self._title_align.add(self._title) self._title.show() self._grid.attach(self._title_align, 1, row, 5, 1) self._title_align.show() delete_button = EventIcon(icon_name='delete', pixel_size=BUTTON_SIZE) delete_button.set_tooltip(_('Delete')) delete_button.connect('button-press-event', self.__delete_cb) self._grid.attach(delete_button, 6, row, 1, 1) delete_button.show() ''' Notification that a new comment has been shared. ''' self.notify_button = EventIcon(icon_name='chat', pixel_size=BUTTON_SIZE) self._grid.attach(self.notify_button, 6, row, 1, 1) row += 1 self._time_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) self._time = Gtk.Label() self._time.set_size_request(ENTRY_WIDTH, -1) self._time.set_justify(Gtk.Justification.LEFT) self._time.set_use_markup(True) try: time_string = util.timestamp_to_elapsed_string( int(self._reflection.data['modification_time'])) except Exception as e: logging.error('Could not convert modification time %s: %s' % (self._reflection.data['modification_time'], e)) self._reflection.data['modification_time'] = \ self._reflection.data['creation_time'] time_string = util.timestamp_to_elapsed_string( int(self._reflection.data['modification_time'])) self._time.set_markup( '<span foreground="#808080"><small><b>%s</b></small></span>' % time_string) self._time_align.add(self._time) self._time.show() self._grid.attach(self._time_align, 1, row, 5, 1) self._time_align.show() row += 1 label = '' if 'tags' in self._reflection.data: for tag in self._reflection.data['tags']: if len(label) > 0: label += ', ' label += tag if self._reflection.activity.initiating and label == '': label = _('Add a #tag') self._tag_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) self._tag_view = Gtk.TextView() self._tag_view.set_size_request(ENTRY_WIDTH, -1) self._tag_view.set_wrap_mode(Gtk.WrapMode.WORD) self._tag_view.get_buffer().set_text(label) if self._reflection.activity.initiating: self._tag_view.connect('focus-in-event', self._tag_focus_in_cb, _('Add a #tag')) self._tag_view.connect('focus-out-event', self._tags_focus_out_cb) else: self._tag_view.set_editable(False) self._tag_align.add(self._tag_view) self._tag_view.show() self._grid.attach(self._tag_align, 1, row, 5, 1) if self._reflection.activity.initiating: self._new_tag = EventIcon(icon_name='ok', pixel_size=BUTTON_SIZE) self._new_tag.connect('button-press-event', self._tag_button_cb) self._grid.attach(self._new_tag, 6, row, 1, 1) row += 1 self._activities_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) self._make_activities_grid() self._grid.attach(self._activities_align, 1, row, 5, 1) self._activities_align.show() if self._reflection.activity.initiating: self._new_activity = EventIcon(icon_name='add-item', pixel_size=BUTTON_SIZE) self._new_activity.set_tooltip(_('Add new activity')) self._new_activity.connect('button-press-event', self._activity_button_cb) self._grid.attach(self._new_activity, 6, row, 1, 1) self._new_activity.show() row += 1 self._stars_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) grid = Gtk.Grid() if 'stars' in self._reflection.data: stars = self._reflection.data['stars'] else: stars = 0 self._star_icons = [] for i in range(NUMBER_OF_STARS): if i < stars: icon_name = 'star-filled' else: icon_name = 'star-empty' self._star_icons.append( EventIcon(icon_name=icon_name, pixel_size=STAR_SIZE)) if self._reflection.activity.initiating: self._star_icons[-1].connect('button-press-event', self._star_button_cb, i) grid.attach(self._star_icons[-1], i, 0, 1, 1) self._star_icons[-1].show() self._stars_align.add(grid) grid.show() self._grid.attach(self._stars_align, 1, row, 5, 1) row += 1 self._content_aligns = [] first_text = True first_image = True self._content_we_always_show = [] if 'content' in self._reflection.data: for i, item in enumerate(self._reflection.data['content']): # Add edit and delete buttons align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) obj = None if 'text' in item: obj = Gtk.TextView() obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) obj.get_buffer().set_text(item['text']) if self._reflection.activity.initiating: obj.connect('focus-in-event', self._text_focus_in_cb) obj.connect('focus-out-event', self._text_focus_out_cb, i) else: obj.set_editable(False) if first_text: self._content_we_always_show.append(align) first_text = False elif 'image' in item: try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( item['image'], PICTURE_WIDTH, PICTURE_HEIGHT) obj = Gtk.Image.new_from_pixbuf(pixbuf) if first_image: self._content_we_always_show.append(align) first_image = False except BaseException: logging.error('could not open %s' % item['image']) if obj is not None: align.add(obj) obj.show() self._grid.attach(align, 1, row, 5, 1) self._content_aligns.append(align) row += 1 self._row = row if self._reflection.activity.initiating: self._new_entry = Gtk.Entry() self._new_entry.props.placeholder_text = _('Write a reflection') self._new_entry.connect('activate', self._entry_activate_cb) self._grid.attach(self._new_entry, 1, row, 5, 1) self._content_we_always_show.append(self._new_entry) self._new_image = EventIcon(icon_name='add-picture', pixel_size=BUTTON_SIZE) self._new_image.set_tooltip(_('Add new image')) self._new_image.connect('button-press-event', self._image_button_cb) self._grid.attach(self._new_image, 6, row, 1, 1) self._content_we_always_show.append(self._new_image) for align in self._content_we_always_show: align.show() row += 1 self._comment_row = row self._comment_aligns = [] if 'comments' in self._reflection.data: for comment in self._reflection.data['comments']: obj = Gtk.TextView() obj.set_editable(False) obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) nick_tag = obj.get_buffer().create_tag( 'nick', foreground=comment['color'], weight=Pango.Weight.BOLD) iter_text = obj.get_buffer().get_iter_at_offset(0) obj.get_buffer().insert_with_tags(iter_text, comment['nick'] + ': ', nick_tag) iter_text = obj.get_buffer().get_end_iter() obj.get_buffer().insert(iter_text, comment['comment']) align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) align.add(obj) obj.show() self._grid.attach(align, 1, self._comment_row, 5, 1) self._comment_aligns.append(align) self._comment_row += 1 self._new_comment = Gtk.Entry() self._new_comment.props.placeholder_text = _('Make a comment') self._new_comment.connect('activate', self._comment_activate_cb) self._grid.attach(self._new_comment, 1, self._comment_row, 5, 1) def _star_button_cb(self, button, event, n): self.update_stars(n) if self._reflection.activity.sharing: self._reflection.activity.send_event( STAR_CMD, { "obj_id": self._reflection.data["obj_id"], "stars": n }) def update_stars(self, n): if 'stars' in self._reflection.data: oldn = self._reflection.data['stars'] else: oldn = 0 if n < oldn: # Erase stars, including one that was clicked for i in range(NUMBER_OF_STARS): if i < n: icon_name = 'star-filled' else: icon_name = 'star-empty' self._star_icons[i].set_icon_name(icon_name) self._reflection.data['stars'] = n else: # Add stars, including one that was clicked for i in range(NUMBER_OF_STARS): if i <= n: icon_name = 'star-filled' else: icon_name = 'star-empty' self._star_icons[i].set_icon_name(icon_name) self._reflection.data['stars'] = n + 1 self._reflection.set_modification_time() def _text_focus_in_cb(self, widget, event): rgba = Gdk.RGBA() rgba.red, rgba.green, rgba.blue = 0.9, 0.9, 0.9 rgba.alpha = 1. widget.override_background_color(Gtk.StateFlags.NORMAL, rgba) def _text_focus_out_cb(self, widget, event, entry): bounds = widget.get_buffer().get_bounds() text = widget.get_buffer().get_text(bounds[0], bounds[1], True) self._reflection.data['content'][entry]['text'] = text rgba = Gdk.RGBA() rgba.red, rgba.green, rgba.blue = 1., 1., 1. rgba.alpha = 1. widget.override_background_color(Gtk.StateFlags.NORMAL, rgba) def _tag_button_cb(self, button, event): bounds = self._tag_view.get_buffer().get_bounds() text = self._tag_view.get_buffer().get_text(bounds[0], bounds[1], True) self._process_tags(self._tag_view.get_buffer(), text) def _tag_focus_in_cb(self, widget, event, prompt=None): bounds = widget.get_buffer().get_bounds() text = widget.get_buffer().get_text(bounds[0], bounds[1], True) if text == prompt: widget.get_buffer().set_text('') rgba = Gdk.RGBA() rgba.red, rgba.green, rgba.blue = 0.9, 0.9, 0.9 rgba.alpha = 1. widget.override_background_color(Gtk.StateFlags.NORMAL, rgba) def _tags_focus_out_cb(self, widget, event): bounds = widget.get_buffer().get_bounds() text = widget.get_buffer().get_text(bounds[0], bounds[1], True) self._process_tags(widget.get_buffer(), text) rgba = Gdk.RGBA() rgba.red, rgba.green, rgba.blue = 1., 1., 1. rgba.alpha = 1. widget.override_background_color(Gtk.StateFlags.NORMAL, rgba) def _process_tags(self, text_buffer, text): ''' process tag data from textview ''' self._reflection.data['tags'] = [] label = '' tags = text.split() for tag in tags: if len(label) > 0: label += ', ' tag = tag.rstrip(',') tag = tag.rstrip(';') if tag[0] == '#': self._reflection.data['tags'].append(tag) label += tag else: self._reflection.data['tags'].append('#' + tag) label += '#' + tag text_buffer.set_text(label.replace('\12', '')) if self._reflection.activity.sharing: data = json.dumps(self._reflection.data['tags']) self._reflection.activity.send_event( TAG_CMD, { "obj_id": self._refelection.data["ob_id"], "reflection": data }) self._reflection.set_modification_time() # Update journal entry dsobj = datastore.get(self._reflection.data['obj_id']) logging.error('setting tags to %s' % label) dsobj.metadata['tags'] = label datastore.write(dsobj, update_mtime=False, reply_handler=self.datastore_write_cb, error_handler=self.datastore_write_error_cb) def add_tags(self, data): ''' process encoded tag data from share ''' tags = json.loads(data) self._reflection.data['tags'] = tags[:] label = '' for tag in tags: if len(label) > 0: label += ', ' label += tag self._tag_view.get_buffer().set_text(label) def _title_focus_out_cb(self, widget, event): ''' process title text from textview ''' bounds = widget.get_buffer().get_bounds() text = widget.get_buffer().get_text(bounds[0], bounds[1], True) self._reflection.data['title'] = text if self._reflection.activity.sharing: self._reflection.activity.send_event( TITLE_CMD, { "obj_id": self._reflection.data["obj_id"], "title": text }) self._reflection.set_modification_time() # Update journal entry dsobj = datastore.get(self._reflection.data['obj_id']) dsobj.metadata['title'] = text datastore.write(dsobj, update_mtime=False, reply_handler=self.datastore_write_cb, error_handler=self.datastore_write_error_cb) def datastore_write_cb(self): logging.debug('ds write cb') def datastore_write_error_cb(self, error): logging.error('datastore_write_error_cb: %r' % error) def update_title(self, text): ''' process title text from share ''' self._reflection.data['title'] = text self._title.get_buffer().set_text('') iter_text = self._title.get_buffer().get_iter_at_offset(0) self._title.get_buffer().insert_with_tags(iter_text, text, self._title_tag) def _comment_activate_cb(self, entry): text = entry.props.text if 'comments' not in self._reflection.data: self._reflection.data['comments'] = [] data = { 'nick': profile.get_nick_name(), 'color': self._reflection.activity.fg_color.get_html(), 'comment': text } self._reflection.data['comments'].append(data) self.add_new_comment(data) # Send the comment if self._reflection.activity.sharing: send_data = data.copy() send_data["obj_id"] = self._reflection.data["obj_id"] self._reflection.activity.send_event(COMMENT_CMD, send_data) entry.set_text('') # Update journal entry dsobj = datastore.get(self._reflection.data['obj_id']) if 'comments' in dsobj.metadata: data = json.loads(dsobj.metadata['comments']) else: data = [] data.append({ 'from': profile.get_nick_name(), 'message': text, 'icon-color': profile.get_color().to_string() }) dsobj.metadata['comments'] = json.dumps(data) datastore.write(dsobj, update_mtime=False, reply_handler=self.datastore_write_cb, error_handler=self.datastore_write_error_cb) def add_new_comment(self, comment): obj = Gtk.TextView() obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) nick_tag = obj.get_buffer().create_tag('nick', foreground=comment['color'], weight=Pango.Weight.BOLD) iter_text = obj.get_buffer().get_iter_at_offset(0) obj.get_buffer().insert_with_tags(iter_text, comment['nick'] + ': ', nick_tag) iter_text = obj.get_buffer().get_end_iter() obj.get_buffer().insert(iter_text, comment['comment']) align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) align.add(obj) obj.show() self._grid.insert_row(self._comment_row) self._grid.attach(align, 1, self._comment_row, 5, 1) self._comment_row += 1 align.show() def _entry_activate_cb(self, entry): text = entry.props.text if 'content' not in self._reflection.data: self._reflection.data['content'] = [] self._reflection.data['content'].append({'text': text}) self._reflection.set_modification_time() self.add_new_reflection(text) # Send the reflection if self._reflection.activity.sharing: self._reflection.activity.send_event( REFLECTION_CMD, { "obj_id": self._reflection.data["obj_id"], "reflection": text }) entry.set_text('') def add_new_reflection(self, text): i = len(self._reflection.data['content']) obj = Gtk.TextView() obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) obj.get_buffer().set_text(text) obj.connect('focus-in-event', self._text_focus_in_cb) obj.connect('focus-out-event', self._text_focus_out_cb, i - 1) align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) align.add(obj) obj.show() self._grid.insert_row(self._row) self._grid.attach(align, 1, self._row, 5, 1) self._row += 1 align.show() def _activity_button_cb(self, button, event): self._reflection.activity.busy_cursor() GObject.idle_add(self._choose_activity) def _choose_activity(self): if not hasattr(self, '_activity_sw'): grid = Gtk.Grid() self._reflection.activity.load_overlay_area(grid) grid.show() bundle_icons = utils.get_bundle_icons() x = 0 y = 0 for bundle_id in bundle_icons.keys(): icon_path = bundle_icons[bundle_id] if icon_path is None: continue pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( icon_path, style.GRID_CELL_SIZE, style.GRID_CELL_SIZE) image = Gtk.Image.new_from_pixbuf(pixbuf) button = Gtk.ToolButton() button.set_icon_widget(image) image.show() button.connect('clicked', self._insert_activity, bundle_id) grid.attach(button, x, y, 1, 1) button.show() x += 1 if x > 6: y += 1 x = 0 self._reflection.activity.show_overlay_area() self._reflection.activity.reset_cursor() def _insert_activity(self, widget, bundle_id): ''' Add activity from UI ''' self._reflection.activity.hide_overlay_area() self.add_activity(bundle_id) if self._reflection.activity.sharing: self._reflection.activity.send_event( ACTIVITY_CMD, { "obj_id": self._reflection.data["obj_id"], "bundle_id": bundle_id }) def add_activity(self, bundle_id): ''' Add activity from sharer ''' if 'activities' not in self._reflection.data: self._reflection.data['activities'] = [] self._reflection.data['activities'].append( utils.bundle_id_to_icon(bundle_id)) self._reflection.set_modification_time() self._activities_align.remove(self._activities_grid) self._make_activities_grid() def _make_activities_grid(self): column = 0 self._activities_grid = Gtk.Grid() self._activities = [] if 'activities' in self._reflection.data: for icon_path in self._reflection.data['activities']: if icon_path is None: continue try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( icon_path, BUTTON_SIZE, BUTTON_SIZE) except Exception as e: logging.error('Could not find icon %s: %s' % (icon_path, e)) continue self._activities.append(Gtk.Image.new_from_pixbuf(pixbuf)) self._activities_grid.attach(self._activities[-1], column, 0, 1, 1) self._activities[-1].show() column += 1 else: label = Gtk.Label('Add an activity') self._activities_grid.attach(label, 0, 0, 5, 1) label.show() self._activities_align.add(self._activities_grid) self._activities_grid.show() def _image_button_cb(self, button, event): self._reflection.activity.busy_cursor() GObject.idle_add(self._choose_image) def _choose_image(self): from sugar3.graphics.objectchooser import ObjectChooser try: from sugar3.graphics.objectchooser import FILTER_TYPE_GENERIC_MIME except BaseException: FILTER_TYPE_GENERIC_MIME = 'generic_mime' from sugar3 import mime chooser = None name = None if hasattr(mime, 'GENERIC_TYPE_IMAGE'): # See #2398 if 'image/svg+xml' not in \ mime.get_generic_type(mime.GENERIC_TYPE_IMAGE).mime_types: mime.get_generic_type( mime.GENERIC_TYPE_IMAGE).mime_types.append('image/svg+xml') try: chooser = ObjectChooser(parent=self._reflection.activity, what_filter=mime.GENERIC_TYPE_IMAGE, filter_type=FILTER_TYPE_GENERIC_MIME, show_preview=True) except BaseException: chooser = ObjectChooser(parent=self._reflection.activity, what_filter=mime.GENERIC_TYPE_IMAGE) else: try: chooser = ObjectChooser(parent=self, what_filter=None) except TypeError: chooser = ObjectChooser( None, self._reflection.activity, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT) if chooser is not None: try: result = chooser.run() if result == Gtk.ResponseType.ACCEPT: jobject = chooser.get_selected_object() if jobject and jobject.file_path: name = jobject.metadata['title'] mime_type = jobject.metadata['mime_type'] _logger.debug('result of choose: %s (%s)' % (name, str(mime_type))) finally: chooser.destroy() del chooser if name is not None: pixbuf = self.add_new_picture(jobject.file_path) self._reflection.set_modification_time() if self._reflection.activity.sharing and pixbuf is not None: self._reflection.activity.send_event( PICTURE_CMD, { "basename": os.path.basename(jobject.file_path), "data": utils.pixbuf_to_base64(pixbuf) }) self._reflection.activity.send_event( IMAGE_REFLECTION_CMD, { "obj_id": self._reflection.data["obj_id"], "basename": os.path.basename(jobject.file_path) }) self._reflection.activity.reset_cursor() def add_new_picture(self, path): try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( path, PICTURE_WIDTH, PICTURE_HEIGHT) obj = Gtk.Image.new_from_pixbuf(pixbuf) except BaseException: logging.error('could not open %s' % path) return None align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) align.add(obj) obj.show() self._grid.insert_row(self._row) self._grid.attach(align, 1, self._row, 5, 1) self._row += 1 align.show() if 'content' not in self._reflection.data: self._reflection.data['content'] = [] self._reflection.data['content'].append({'image': path}) if self._reflection.activity.sharing: return pixbuf def _expand_cb(self, button, event): self._grid.set_row_spacing(style.DEFAULT_SPACING) if self._collapse_id is not None: button.disconnect(self._collapse_id) button.set_icon_name('collapse') button.set_tooltip(_('Collapse')) self._collapse_id = button.connect('button-press-event', self._collapse_cb) self._tag_align.show() if hasattr(self, '_new_tag'): self._new_tag.show() self._stars_align.show() for align in self._content_aligns: align.show() for align in self._comment_aligns: align.show() self._new_comment.show() def _collapse_cb(self, button, event): self._grid.set_row_spacing(0) if self._collapse_id is not None: button.disconnect(self._collapse_id) button.set_icon_name('expand') button.set_tooltip(_('Expand')) self._collapse_id = button.connect('button-press-event', self._expand_cb) self._tag_align.hide() if hasattr(self, '_new_tag'): self._new_tag.hide() self._stars_align.hide() for align in self._content_aligns: if align not in self._content_we_always_show: align.hide() for align in self._comment_aligns: align.hide() self._new_comment.hide() def __delete_cb(self, button, event): self._reflection.activity.delete_item(self._reflection.data['obj_id']) self.hide()
class Chat(activity.Activity): def __init__(self, handle): pservice = presenceservice.get_instance() self.owner = pservice.get_owner() self._ebook_mode_detector = EbookModeDetector() self.chatbox = ChatBox(self.owner, self._ebook_mode_detector.get_ebook_mode()) self.chatbox.connect('open-on-journal', self.__open_on_journal) self.chatbox.connect('new-message', self._search_entry_on_new_message_cb) super(Chat, self).__init__(handle) self._entry = None self._has_alert = False self._has_osk = False self._setup_canvas() self._entry.grab_focus() toolbar_box = ToolbarBox() self.set_toolbar_box(toolbar_box) self._activity_toolbar_button = ActivityToolbarButton(self) self._activity_toolbar_button.connect('clicked', self._fixed_resize_cb) toolbar_box.toolbar.insert(self._activity_toolbar_button, 0) self._activity_toolbar_button.show() self.search_entry = iconentry.IconEntry() self.search_entry.set_size_request(Gdk.Screen.width() / 3, -1) self.search_entry.set_icon_from_name(iconentry.ICON_ENTRY_PRIMARY, 'entry-search') self.search_entry.add_clear_button() self.search_entry.connect('activate', self._search_entry_activate_cb) self.search_entry.connect('changed', self._search_entry_activate_cb) self.connect('key-press-event', self._search_entry_key_press_cb) self._search_item = Gtk.ToolItem() self._search_item.add(self.search_entry) toolbar_box.toolbar.insert(self._search_item, -1) self._search_prev = ToolButton('go-previous-paired') self._search_prev.set_tooltip(_('Previous')) self._search_prev.props.accelerator = "<Shift><Ctrl>g" self._search_prev.connect('clicked', self._search_prev_cb) self._search_prev.props.sensitive = False toolbar_box.toolbar.insert(self._search_prev, -1) self._search_next = ToolButton('go-next-paired') self._search_next.set_tooltip(_('Next')) self._search_next.props.accelerator = "<Ctrl>g" self._search_next.connect('clicked', self._search_next_cb) self._search_next.props.sensitive = False toolbar_box.toolbar.insert(self._search_next, -1) separator = Gtk.SeparatorToolItem() separator.props.draw = False separator.set_expand(True) toolbar_box.toolbar.insert(separator, -1) toolbar_box.toolbar.insert(StopButton(self), -1) toolbar_box.show_all() # Chat is room or one to one: self._chat_is_room = False self.text_channel = None self.element = Gst.ElementFactory.make('playbin', 'Player') if self.shared_activity: # we are joining the activity following an invite self._alert(_('Off-line'), _('Joining the Chat.')) self._entry.props.placeholder_text = \ _('Please wait for a connection before starting to chat.') self.connect('joined', self._joined_cb) if self.get_shared(): # we have already joined self._joined_cb(self) elif handle.uri: # XMPP non-sugar3 incoming chat, not sharable self._activity_toolbar_button.props.page.share.props.visible = \ False self._one_to_one_connection(handle.uri) else: # we are creating the activity if not self.metadata or self.metadata.get( 'share-scope', activity.SCOPE_PRIVATE) == \ activity.SCOPE_PRIVATE: # if we are in private session self._alert(_('Off-line'), _('Share, or invite someone.')) else: # resume of shared activity from journal object without invite self._entry.props.placeholder_text = \ _('Please wait for a connection before starting to chat.') self.connect('shared', self._shared_cb) def _search_entry_key_press_cb(self, activity, event): keyname = Gdk.keyval_name(event.keyval).lower() if keyname == 'f': if Gdk.ModifierType.CONTROL_MASK & event.state: self.search_entry.grab_focus() elif keyname == 'escape': self.search_entry.props.text = '' self._entry.grab_focus() def _search_entry_on_new_message_cb(self, chatbox): self._search_entry_activate_cb(self.search_entry) def _search_entry_activate_cb(self, entry): for i in range(0, self.chatbox.number_of_textboxes()): textbox = self.chatbox.get_textbox(i) _buffer = textbox.get_buffer() start_mark = _buffer.get_mark('start') end_mark = _buffer.get_mark('end') if start_mark is None or end_mark is None: continue _buffer.delete_mark(start_mark) _buffer.delete_mark(end_mark) self.chatbox.highlight_text = (None, None, None) self.chatbox.set_search_text(entry.props.text) self._update_search_buttons() def _update_search_buttons(self, ): if len(self.chatbox.search_text) == 0: self._search_prev.props.sensitive = False self._search_next.props.sensitive = False else: # If next or previous result exists self._search_prev.props.sensitive = \ self.chatbox.check_next('backward') self._search_next.props.sensitive = \ self.chatbox.check_next('forward') def _search_prev_cb(self, button): if button.props.sensitive: self.chatbox.search('backward') self._update_search_buttons() def _search_next_cb(self, button): if button.props.sensitive: self.chatbox.search('forward') self._update_search_buttons() def _fixed_resize_cb(self, widget=None, rect=None): ''' If a toolbar opens or closes, we need to resize the vbox holding out scrolling window. ''' if self._has_alert: dy = style.GRID_CELL_SIZE else: dy = 0 if self._has_osk: if Gdk.Screen.width() > Gdk.Screen.height(): dy += OSK_HEIGHT[0] else: dy += OSK_HEIGHT[1] if self._toolbar_expanded(): self.chatbox.set_size_request( self._chat_width, self._chat_height - style.GRID_CELL_SIZE - dy) self._fixed.move(self._entry_grid, style.GRID_CELL_SIZE, self._chat_height - style.GRID_CELL_SIZE - dy) else: self.chatbox.set_size_request(self._chat_width, self._chat_height - dy) self._fixed.move(self._entry_grid, style.GRID_CELL_SIZE, self._chat_height - dy) self.chatbox.resize_conversation(dy) def _setup_canvas(self): ''' Create a canvas ''' self._fixed = Gtk.Fixed() self._fixed.set_size_request( Gdk.Screen.width(), Gdk.Screen.height() - style.GRID_CELL_SIZE) self._fixed.connect('size-allocate', self._fixed_resize_cb) self.set_canvas(self._fixed) self._fixed.show() self._entry_widgets = self._make_entry_widgets() self._fixed.put(self.chatbox, 0, 0) self.chatbox.show() self._fixed.put(self._entry_grid, style.GRID_CELL_SIZE, self._chat_height) self._entry_grid.show() Gdk.Screen.get_default().connect('size-changed', self._configure_cb) def _configure_cb(self, event): self._fixed.set_size_request( Gdk.Screen.width(), Gdk.Screen.height() - style.GRID_CELL_SIZE) if self._ebook_mode_detector.get_ebook_mode(): self._entry_height = int(style.GRID_CELL_SIZE * 1.5) else: self._entry_height = style.GRID_CELL_SIZE entry_width = Gdk.Screen.width() - \ 2 * (self._entry_height + style.GRID_CELL_SIZE) self._entry.set_size_request(entry_width, self._entry_height) self._entry_grid.set_size_request( Gdk.Screen.width() - 2 * style.GRID_CELL_SIZE, self._entry_height) self._chat_height = Gdk.Screen.height() - self._entry_height - \ style.GRID_CELL_SIZE self._chat_width = Gdk.Screen.width() self.chatbox.set_size_request(self._chat_width, self._chat_height) self.chatbox.resize_all() width = int(Gdk.Screen.width() - 2 * style.GRID_CELL_SIZE) if self._ebook_mode_detector.get_ebook_mode(): height = int(Gdk.Screen.height() - 8 * style.GRID_CELL_SIZE) else: height = int(Gdk.Screen.height() - 5 * style.GRID_CELL_SIZE) self._smiley_table.set_size_request(width, height) self._smiley_toolbar.set_size_request(width, -1) self._smiley_window.set_size_request(width, -1) self._fixed_resize_cb() def _create_smiley_table(self, width): pixel_size = (style.STANDARD_ICON_SIZE + style.LARGE_ICON_SIZE) / 2 spacing = style.DEFAULT_SPACING button_size = pixel_size + spacing smilies_columns = int(width / button_size) pad = (width - smilies_columns * button_size) / 2 table = Gtk.Grid() table.set_row_spacing(spacing) table.set_column_spacing(spacing) table.set_border_width(pad) queue = [] def _create_smiley_icon_idle_cb(): try: x, y, path, code = queue.pop() except IndexError: self.unbusy() return False pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( path, pixel_size, pixel_size) image = Gtk.Image.new_from_pixbuf(pixbuf) box = Gtk.EventBox() box.add(image) box.connect('button-press-event', self._add_smiley_to_entry, code) table.attach(box, x, y, 1, 1) box.show_all() return True x = 0 y = 0 smilies.init() for i in range(len(smilies.THEME)): path, hint, codes = smilies.THEME[i] queue.append([x, y, path, codes[0]]) x += 1 if x == smilies_columns: y += 1 x = 0 queue.reverse() GLib.idle_add(_create_smiley_icon_idle_cb) return table def _add_smiley_to_entry(self, icon, event, text): pos = self._entry.props.cursor_position self._entry.insert_text(text, pos) self._entry.grab_focus() self._entry.set_position(pos + len(text)) self._hide_smiley_window() def _shared_cb(self, sender): self._setup() def _one_to_one_connection(self, tp_channel): '''Handle a private invite from a non-sugar3 XMPP client.''' if self.shared_activity or self.text_channel: return bus_name, connection, channel = json.loads(tp_channel) logger.debug('GOT XMPP: %s %s %s', bus_name, connection, channel) conn = {} conn_proxy = dbus.Bus().get_object(bus_name, connection) conn[TelepathyGLib.IFACE_CONNECTION_INTERFACE_ALIASING] = \ dbus.Interface( conn_proxy, TelepathyGLib.IFACE_CONNECTION_INTERFACE_ALIASING) self._one_to_one_connection_ready_cb(bus_name, channel, conn) def _one_to_one_connection_ready_cb(self, bus_name, channel, conn): '''Callback for Connection for one to one connection''' text_channel = {} text_proxy = dbus.Bus().get_object(bus_name, channel) text_channel[TelepathyGLib.IFACE_CHANNEL] = \ dbus.Interface(text_proxy, TelepathyGLib.IFACE_CHANNEL) text_channel[TelepathyGLib.IFACE_CHANNEL_TYPE_TEXT] = \ dbus.Interface(text_proxy, TelepathyGLib.IFACE_CHANNEL_TYPE_TEXT) text_channel[TelepathyGLib.IFACE_CHANNEL_INTERFACE_GROUP] = \ dbus.Interface( text_proxy, TelepathyGLib.IFACE_CHANNEL_INTERFACE_GROUP) self.text_channel = TextChannelWrapper(text_channel, conn) self.text_channel.set_received_callback(self._received_cb) self.text_channel.handle_pending_messages() self.text_channel.set_closed_callback( self._one_to_one_connection_closed_cb) self._chat_is_room = False self._alert(_('On-line'), _('Private chat.')) # XXX How do we detect the sender going offline? self._entry.set_sensitive(True) self.smiley_button.set_sensitive(True) self.send_button.set_sensitive(True) self._entry.props.placeholder_text = None self._entry.grab_focus() def _one_to_one_connection_closed_cb(self): '''Callback for when the text channel closes.''' self._alert(_('Off-line'), _('Left the chat.')) def _setup(self): self.text_channel = TextChannelWrapper( self.shared_activity.telepathy_text_chan, self.shared_activity.telepathy_conn) self.text_channel.set_received_callback(self._received_cb) self._alert(_('On-line'), _('Connected.')) self.shared_activity.connect('buddy-joined', self._buddy_joined_cb) self.shared_activity.connect('buddy-left', self._buddy_left_cb) self._chat_is_room = True self._entry.set_sensitive(True) self.smiley_button.set_sensitive(True) self.send_button.set_sensitive(True) self._entry.props.placeholder_text = None self._entry.grab_focus() def _joined_cb(self, sender): '''Joined a shared activity.''' if not self.shared_activity: return logger.debug('Joined a shared chat') for buddy in self.shared_activity.get_joined_buddies(): self._buddy_already_exists(buddy) self._setup() def _received_cb(self, buddy, text): '''Show message that was received.''' if buddy: if type(buddy) is dict: nick = buddy['nick'] else: nick = buddy.props.nick else: nick = '???' logger.debug('Received message from %s: %s', nick, text) self.chatbox.add_text(buddy, text) if self.owner.props.nick in text: self.play_sound('said_nick') ''' vscroll = self.chatbox.get_vadjustment() if vscroll.get_property('value') != vscroll.get_property('upper'): self._alert(_('New message'), _('New message from %s' % nick)) ''' if not self.has_focus: self.notify_user(_('Message from %s') % buddy, text) def _toolbar_expanded(self): if self._activity_toolbar_button.is_expanded(): return True return False def _alert(self, title, text=None): alert = NotifyAlert(timeout=5) alert.props.title = title alert.props.msg = text self.add_alert(alert) alert.connect('response', self._alert_cancel_cb) alert.show() self._has_alert = True self._fixed_resize_cb() def _alert_cancel_cb(self, alert, response_id): self.remove_alert(alert) self._has_alert = False self._fixed_resize_cb() def __open_on_journal(self, widget, url): '''Ask the journal to display a URL''' logger.debug('Create journal entry for URL: %s', url) jobject = datastore.create() metadata = { 'title': '%s: %s' % (_('URL from Chat'), url), 'title_set_by_user': '******', 'icon-color': profile.get_color().to_string(), 'mime_type': 'text/uri-list', } for k, v in list(metadata.items()): jobject.metadata[k] = v file_path = os.path.join(get_activity_root(), 'instance', '%i_' % time.time()) open(file_path, 'w').write(url + '\r\n') os.chmod(file_path, 0o755) jobject.set_file_path(file_path) datastore.write(jobject) show_object_in_journal(jobject.object_id) jobject.destroy() os.unlink(file_path) def _buddy_joined_cb(self, sender, buddy): '''Show a buddy who joined''' if buddy == self.owner: return self.chatbox.add_text(buddy, _('%s joined the chat') % buddy.props.nick, status_message=True) self.play_sound('login') def _buddy_left_cb(self, sender, buddy): '''Show a buddy who joined''' if buddy == self.owner: return self.chatbox.add_text(buddy, _('%s left the chat') % buddy.props.nick, status_message=True) self.play_sound('logout') def _buddy_already_exists(self, buddy): '''Show a buddy already in the chat.''' if buddy == self.owner: return self.chatbox.add_text(buddy, _('%s is here') % buddy.props.nick, status_message=True) def can_close(self): '''Perform cleanup before closing. Close text channel of a one to one XMPP chat. ''' if self._chat_is_room is False: if self.text_channel is not None: self.text_channel.close() return True def _make_entry_widgets(self): '''We need to create a button for the smiley, a text entry, and a send button. All of this, along with the chatbox, goes into a grid. --------------------------------------- | chat box | | smiley button | entry | send button | --------------------------------------- ''' if self._ebook_mode_detector.get_ebook_mode(): self._entry_height = int(style.GRID_CELL_SIZE * 1.5) else: self._entry_height = style.GRID_CELL_SIZE entry_width = Gdk.Screen.width() - \ 2 * (self._entry_height + style.GRID_CELL_SIZE) self._chat_height = Gdk.Screen.height() - self._entry_height - \ style.GRID_CELL_SIZE self._chat_width = Gdk.Screen.width() self.chatbox.set_size_request(self._chat_width, self._chat_height) self._entry_grid = Gtk.Grid() self._entry_grid.set_size_request( Gdk.Screen.width() - 2 * style.GRID_CELL_SIZE, self._entry_height) self.smiley_button = EventIcon(icon_name='smilies', pixel_size=self._entry_height) self.smiley_button.connect('button-press-event', self._smiley_button_cb) self._entry_grid.attach(self.smiley_button, 0, 0, 1, 1) self.smiley_button.show() self._entry = Gtk.Entry() self._entry.set_size_request(entry_width, self._entry_height) self._entry.modify_bg(Gtk.StateType.INSENSITIVE, style.COLOR_WHITE.get_gdk_color()) self._entry.modify_base(Gtk.StateType.INSENSITIVE, style.COLOR_WHITE.get_gdk_color()) self._entry.props.placeholder_text = \ _('You must be connected to a friend before starting to chat.') self._entry.connect('focus-in-event', self._entry_focus_in_cb) self._entry.connect('focus-out-event', self._entry_focus_out_cb) self._entry.connect('activate', self._entry_activate_cb) self._entry.connect('key-press-event', self._entry_key_press_cb) self._entry_grid.attach(self._entry, 1, 0, 1, 1) self._entry.show() self.send_button = EventIcon(icon_name='send', pixel_size=self._entry_height) self.send_button.connect('button-press-event', self._send_button_cb) self._entry_grid.attach(self.send_button, 2, 0, 1, 1) self.send_button.show() if not self.get_shared(): self._entry.set_sensitive(False) self.smiley_button.set_sensitive(False) self.send_button.set_sensitive(False) def _get_icon_pixbuf(self, name): icon_theme = Gtk.IconTheme.get_default() icon_info = icon_theme.lookup_icon(name, style.LARGE_ICON_SIZE, 0) pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( icon_info.get_filename(), style.LARGE_ICON_SIZE, style.LARGE_ICON_SIZE) del icon_info return pixbuf def _entry_focus_in_cb(self, entry, event): self._hide_smiley_window() if self._ebook_mode_detector.get_ebook_mode(): self._has_osk = True self._fixed_resize_cb() def _entry_focus_out_cb(self, entry, event): if self._ebook_mode_detector.get_ebook_mode(): self._has_osk = False self._fixed_resize_cb() def _entry_key_press_cb(self, widget, event): '''Check for scrolling keys. Check if the user pressed Page Up, Page Down, Home or End and scroll the window according the pressed key. ''' vadj = self.chatbox.get_vadjustment() if event.keyval == Gdk.KEY_Page_Down: value = vadj.get_value() + vadj.page_size if value > vadj.upper - vadj.page_size: value = vadj.upper - vadj.page_size vadj.set_value(value) elif event.keyval == Gdk.KEY_Page_Up: vadj.set_value(vadj.get_value() - vadj.page_size) elif event.keyval == Gdk.KEY_Home and \ event.get_state() & Gdk.ModifierType.CONTROL_MASK: vadj.set_value(vadj.lower) elif event.keyval == Gdk.KEY_End and \ event.get_state() & Gdk.ModifierType.CONTROL_MASK: vadj.set_value(vadj.upper - vadj.page_size) def _smiley_button_cb(self, widget, event): self._show_smiley_window() def _send_button_cb(self, widget, event): self._entry_activate_cb(self._entry) def _entry_activate_cb(self, entry): self.chatbox._scroll_auto = True text = entry.props.text if text: logger.debug('Adding text to chatbox: %s: %s' % (self.owner, text)) self.chatbox.add_text(self.owner, text) entry.props.text = '' if self.text_channel: logger.debug('sending to text_channel: %s' % (text)) self.text_channel.send(text) else: logger.debug('Tried to send message but text channel ' 'not connected.') def write_file(self, file_path): '''Store chat log in Journal. Handling the Journal is provided by Activity - we only need to define this method. ''' logger.debug('write_file: writing %s' % file_path) self.chatbox.add_log_timestamp() f = open(file_path, 'w') try: f.write(self.chatbox.get_log()) finally: f.close() self.metadata['mime_type'] = 'text/plain' def read_file(self, file_path): '''Load a chat log from the Journal. Handling the Journal is provided by Activity - we only need to define this method. ''' logger.debug('read_file: reading %s' % file_path) log = open(file_path).readlines() last_line_was_timestamp = False for line in log: if line.endswith('\t\t\n'): if last_line_was_timestamp is False: timestamp = line.strip().split('\t')[0] self.chatbox.add_separator(timestamp) last_line_was_timestamp = True else: timestamp, nick, color, status, text = line.strip().split('\t') status_message = bool(int(status)) self.chatbox.add_text({ 'nick': nick, 'color': color }, text, status_message) last_line_was_timestamp = False def play_sound(self, event): SOUNDS_PATH = os.path.join(get_bundle_path(), 'sounds') SOUNDS = { 'said_nick': os.path.join(SOUNDS_PATH, 'alert.wav'), 'login': os.path.join(SOUNDS_PATH, 'login.wav'), 'logout': os.path.join(SOUNDS_PATH, 'logout.wav') } self.element.set_state(Gst.State.NULL) self.element.set_property('uri', 'file://%s' % SOUNDS[event]) self.element.set_state(Gst.State.PLAYING) def _create_smiley_window(self): grid = Gtk.Grid() width = int(Gdk.Screen.width() - 2 * style.GRID_CELL_SIZE) self._smiley_toolbar = SmileyToolbar(self) height = style.GRID_CELL_SIZE self._smiley_toolbar.set_size_request(width, height) grid.attach(self._smiley_toolbar, 0, 0, 1, 1) self._smiley_toolbar.show() self._smiley_table = Gtk.ScrolledWindow() self._smiley_table.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC) self._smiley_table.modify_bg(Gtk.StateType.NORMAL, style.COLOR_BLACK.get_gdk_color()) if self._ebook_mode_detector.get_ebook_mode(): height = int(Gdk.Screen.height() - 8 * style.GRID_CELL_SIZE) else: height = int(Gdk.Screen.height() - 4 * style.GRID_CELL_SIZE) self._smiley_table.set_size_request(width, height) table = self._create_smiley_table(width) self._smiley_table.add_with_viewport(table) table.show_all() grid.attach(self._smiley_table, 0, 1, 1, 1) self._smiley_table.show() self._smiley_window = Gtk.ScrolledWindow() self._smiley_window.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.NEVER) self._smiley_window.set_shadow_type(Gtk.ShadowType.ETCHED_IN) self._smiley_window.set_size_request(width, -1) self._smiley_window.add_with_viewport(grid) def _key_press_event_cb(widget, event): if event.keyval == Gdk.KEY_Escape: self._hide_smiley_window() return True return False self.connect('key-press-event', _key_press_event_cb) grid.show() self._fixed.put(self._smiley_window, style.GRID_CELL_SIZE, 0) def _show_smiley_window(self): if not hasattr(self, '_smiley_window'): self.busy() self._create_smiley_window() self._smiley_window.show() def _hide_smiley_window(self): if hasattr(self, '_smiley_window'): self._smiley_window.hide()
def __init__(self, parent): Gtk.EventBox.__init__(self) self._reflection = parent self._collapse = True self._collapse_id = None self.modify_bg( Gtk.StateType.NORMAL, style.COLOR_WHITE.get_gdk_color()) self._title_color = self._reflection.activity.fg_color.get_html() self._grid = Gtk.Grid() self.add(self._grid) self._grid.show() self._grid.set_row_spacing(style.DEFAULT_PADDING) self._grid.set_column_spacing(style.DEFAULT_SPACING) self._grid.set_column_homogeneous(True) self._grid.set_border_width(style.DEFAULT_PADDING) row = 0 self._expand_button = EventIcon(icon_name='expand', pixel_size=BUTTON_SIZE) self._collapse_id = self._expand_button.connect('button-press-event', self._expand_cb) self._expand_button.set_tooltip(_('Expand')) self._grid.attach(self._expand_button, 0, row, 1, 1) self._expand_button.show() self._title_align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) self._title = Gtk.TextView() self._title.set_size_request(ENTRY_WIDTH, -1) self._title.set_wrap_mode(Gtk.WrapMode.WORD) self._title_tag = self._title.get_buffer().create_tag( 'title', foreground=self._title_color, weight=Pango.Weight.BOLD, size=12288) iter_text = self._title.get_buffer().get_iter_at_offset(0) self._title.get_buffer().insert_with_tags( iter_text, self._reflection.data['title'], self._title_tag) if self._reflection.activity.initiating: self._title.connect('focus-out-event', self._title_focus_out_cb) else: self._title.set_editable(False) self._title_align.add(self._title) self._title.show() self._grid.attach(self._title_align, 1, row, 5, 1) self._title_align.show() delete_button = EventIcon(icon_name='delete', pixel_size=BUTTON_SIZE) delete_button.set_tooltip(_('Delete')) delete_button.connect('button-press-event', self.__delete_cb) self._grid.attach(delete_button, 6, row, 1, 1) delete_button.show() ''' Notification that a new comment has been shared. ''' self.notify_button = EventIcon(icon_name='chat', pixel_size=BUTTON_SIZE) self._grid.attach(self.notify_button, 6, row, 1, 1) row += 1 self._time_align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) self._time = Gtk.Label() self._time.set_size_request(ENTRY_WIDTH, -1) self._time.set_justify(Gtk.Justification.LEFT) self._time.set_use_markup(True) try: time_string = util.timestamp_to_elapsed_string( int(self._reflection.data['modification_time'])) except Exception as e: logging.error('Could not convert modification time %s: %s' % (self._reflection.data['modification_time'], e)) self._reflection.data['modification_time'] = \ self._reflection.data['creation_time'] time_string = util.timestamp_to_elapsed_string( int(self._reflection.data['modification_time'])) self._time.set_markup( '<span foreground="#808080"><small><b>%s</b></small></span>' % time_string) self._time_align.add(self._time) self._time.show() self._grid.attach(self._time_align, 1, row, 5, 1) self._time_align.show() row += 1 label = '' if 'tags' in self._reflection.data: for tag in self._reflection.data['tags']: if len(label) > 0: label += ', ' label += tag if self._reflection.activity.initiating and label == '': label = _('Add a #tag') self._tag_align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) self._tag_view = Gtk.TextView() self._tag_view.set_size_request(ENTRY_WIDTH, -1) self._tag_view.set_wrap_mode(Gtk.WrapMode.WORD) self._tag_view.get_buffer().set_text(label) if self._reflection.activity.initiating: self._tag_view.connect('focus-in-event', self._tag_focus_in_cb, _('Add a #tag')) self._tag_view.connect('focus-out-event', self._tags_focus_out_cb) else: self._tag_view.set_editable(False) self._tag_align.add(self._tag_view) self._tag_view.show() self._grid.attach(self._tag_align, 1, row, 5, 1) if self._reflection.activity.initiating: self._new_tag = EventIcon(icon_name='ok', pixel_size=BUTTON_SIZE) self._new_tag.connect('button-press-event', self._tag_button_cb) self._grid.attach(self._new_tag, 6, row, 1, 1) row += 1 self._activities_align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) self._make_activities_grid() self._grid.attach(self._activities_align, 1, row, 5, 1) self._activities_align.show() if self._reflection.activity.initiating: self._new_activity = EventIcon(icon_name='add-item', pixel_size=BUTTON_SIZE) self._new_activity.set_tooltip(_('Add new activity')) self._new_activity.connect('button-press-event', self._activity_button_cb) self._grid.attach(self._new_activity, 6, row, 1, 1) self._new_activity.show() row += 1 self._stars_align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) grid = Gtk.Grid() if 'stars' in self._reflection.data: stars = self._reflection.data['stars'] else: stars = 0 self._star_icons = [] for i in range(NUMBER_OF_STARS): if i < stars: icon_name = 'star-filled' else: icon_name = 'star-empty' self._star_icons.append(EventIcon(icon_name=icon_name, pixel_size=STAR_SIZE)) if self._reflection.activity.initiating: self._star_icons[-1].connect('button-press-event', self._star_button_cb, i) grid.attach(self._star_icons[-1], i, 0, 1, 1) self._star_icons[-1].show() self._stars_align.add(grid) grid.show() self._grid.attach(self._stars_align, 1, row, 5, 1) row += 1 self._content_aligns = [] first_text = True first_image = True self._content_we_always_show = [] if 'content' in self._reflection.data: for i, item in enumerate(self._reflection.data['content']): # Add edit and delete buttons align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) obj = None if 'text' in item: obj = Gtk.TextView() obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) obj.get_buffer().set_text(item['text']) if self._reflection.activity.initiating: obj.connect('focus-in-event', self._text_focus_in_cb) obj.connect( 'focus-out-event', self._text_focus_out_cb, i) else: obj.set_editable(False) if first_text: self._content_we_always_show.append(align) first_text = False elif 'image' in item: try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( item['image'], PICTURE_WIDTH, PICTURE_HEIGHT) obj = Gtk.Image.new_from_pixbuf(pixbuf) if first_image: self._content_we_always_show.append(align) first_image = False except: logging.error('could not open %s' % item['image']) if obj is not None: align.add(obj) obj.show() self._grid.attach(align, 1, row, 5, 1) self._content_aligns.append(align) row += 1 self._row = row if self._reflection.activity.initiating: self._new_entry = Gtk.Entry() self._new_entry.props.placeholder_text = _('Write a reflection') self._new_entry.connect('activate', self._entry_activate_cb) self._grid.attach(self._new_entry, 1, row, 5, 1) self._content_we_always_show.append(self._new_entry) self._new_image = EventIcon(icon_name='add-picture', pixel_size=BUTTON_SIZE) self._new_image.set_tooltip(_('Add new image')) self._new_image.connect('button-press-event', self._image_button_cb) self._grid.attach(self._new_image, 6, row, 1, 1) self._content_we_always_show.append(self._new_image) for align in self._content_we_always_show: align.show() row += 1 self._comment_row = row self._comment_aligns = [] if 'comments' in self._reflection.data: for comment in self._reflection.data['comments']: obj = Gtk.TextView() obj.set_editable(False) obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) nick_tag = obj.get_buffer().create_tag( 'nick', foreground=comment['color'], weight=Pango.Weight.BOLD) iter_text = obj.get_buffer().get_iter_at_offset(0) obj.get_buffer().insert_with_tags( iter_text, comment['nick'] + ': ', nick_tag) iter_text = obj.get_buffer().get_end_iter() obj.get_buffer().insert(iter_text, comment['comment']) align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) align.add(obj) obj.show() self._grid.attach(align, 1, self._comment_row, 5, 1) self._comment_aligns.append(align) self._comment_row += 1 self._new_comment = Gtk.Entry() self._new_comment.props.placeholder_text = _('Make a comment') self._new_comment.connect('activate', self._comment_activate_cb) self._grid.attach(self._new_comment, 1, self._comment_row, 5, 1)
class ReflectionGrid(Gtk.EventBox): def __init__(self, parent): Gtk.EventBox.__init__(self) self._reflection = parent self._collapse = True self._collapse_id = None self.modify_bg( Gtk.StateType.NORMAL, style.COLOR_WHITE.get_gdk_color()) self._title_color = self._reflection.activity.fg_color.get_html() self._grid = Gtk.Grid() self.add(self._grid) self._grid.show() self._grid.set_row_spacing(style.DEFAULT_PADDING) self._grid.set_column_spacing(style.DEFAULT_SPACING) self._grid.set_column_homogeneous(True) self._grid.set_border_width(style.DEFAULT_PADDING) row = 0 self._expand_button = EventIcon(icon_name='expand', pixel_size=BUTTON_SIZE) self._collapse_id = self._expand_button.connect('button-press-event', self._expand_cb) self._expand_button.set_tooltip(_('Expand')) self._grid.attach(self._expand_button, 0, row, 1, 1) self._expand_button.show() self._title_align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) self._title = Gtk.TextView() self._title.set_size_request(ENTRY_WIDTH, -1) self._title.set_wrap_mode(Gtk.WrapMode.WORD) self._title_tag = self._title.get_buffer().create_tag( 'title', foreground=self._title_color, weight=Pango.Weight.BOLD, size=12288) iter_text = self._title.get_buffer().get_iter_at_offset(0) self._title.get_buffer().insert_with_tags( iter_text, self._reflection.data['title'], self._title_tag) if self._reflection.activity.initiating: self._title.connect('focus-out-event', self._title_focus_out_cb) else: self._title.set_editable(False) self._title_align.add(self._title) self._title.show() self._grid.attach(self._title_align, 1, row, 5, 1) self._title_align.show() delete_button = EventIcon(icon_name='delete', pixel_size=BUTTON_SIZE) delete_button.set_tooltip(_('Delete')) delete_button.connect('button-press-event', self.__delete_cb) self._grid.attach(delete_button, 6, row, 1, 1) delete_button.show() ''' Notification that a new comment has been shared. ''' self.notify_button = EventIcon(icon_name='chat', pixel_size=BUTTON_SIZE) self._grid.attach(self.notify_button, 6, row, 1, 1) row += 1 self._time_align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) self._time = Gtk.Label() self._time.set_size_request(ENTRY_WIDTH, -1) self._time.set_justify(Gtk.Justification.LEFT) self._time.set_use_markup(True) try: time_string = util.timestamp_to_elapsed_string( int(self._reflection.data['modification_time'])) except Exception as e: logging.error('Could not convert modification time %s: %s' % (self._reflection.data['modification_time'], e)) self._reflection.data['modification_time'] = \ self._reflection.data['creation_time'] time_string = util.timestamp_to_elapsed_string( int(self._reflection.data['modification_time'])) self._time.set_markup( '<span foreground="#808080"><small><b>%s</b></small></span>' % time_string) self._time_align.add(self._time) self._time.show() self._grid.attach(self._time_align, 1, row, 5, 1) self._time_align.show() row += 1 label = '' if 'tags' in self._reflection.data: for tag in self._reflection.data['tags']: if len(label) > 0: label += ', ' label += tag if self._reflection.activity.initiating and label == '': label = _('Add a #tag') self._tag_align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) self._tag_view = Gtk.TextView() self._tag_view.set_size_request(ENTRY_WIDTH, -1) self._tag_view.set_wrap_mode(Gtk.WrapMode.WORD) self._tag_view.get_buffer().set_text(label) if self._reflection.activity.initiating: self._tag_view.connect('focus-in-event', self._tag_focus_in_cb, _('Add a #tag')) self._tag_view.connect('focus-out-event', self._tags_focus_out_cb) else: self._tag_view.set_editable(False) self._tag_align.add(self._tag_view) self._tag_view.show() self._grid.attach(self._tag_align, 1, row, 5, 1) if self._reflection.activity.initiating: self._new_tag = EventIcon(icon_name='ok', pixel_size=BUTTON_SIZE) self._new_tag.connect('button-press-event', self._tag_button_cb) self._grid.attach(self._new_tag, 6, row, 1, 1) row += 1 self._activities_align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) self._make_activities_grid() self._grid.attach(self._activities_align, 1, row, 5, 1) self._activities_align.show() if self._reflection.activity.initiating: self._new_activity = EventIcon(icon_name='add-item', pixel_size=BUTTON_SIZE) self._new_activity.set_tooltip(_('Add new activity')) self._new_activity.connect('button-press-event', self._activity_button_cb) self._grid.attach(self._new_activity, 6, row, 1, 1) self._new_activity.show() row += 1 self._stars_align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) grid = Gtk.Grid() if 'stars' in self._reflection.data: stars = self._reflection.data['stars'] else: stars = 0 self._star_icons = [] for i in range(NUMBER_OF_STARS): if i < stars: icon_name = 'star-filled' else: icon_name = 'star-empty' self._star_icons.append(EventIcon(icon_name=icon_name, pixel_size=STAR_SIZE)) if self._reflection.activity.initiating: self._star_icons[-1].connect('button-press-event', self._star_button_cb, i) grid.attach(self._star_icons[-1], i, 0, 1, 1) self._star_icons[-1].show() self._stars_align.add(grid) grid.show() self._grid.attach(self._stars_align, 1, row, 5, 1) row += 1 self._content_aligns = [] first_text = True first_image = True self._content_we_always_show = [] if 'content' in self._reflection.data: for i, item in enumerate(self._reflection.data['content']): # Add edit and delete buttons align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) obj = None if 'text' in item: obj = Gtk.TextView() obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) obj.get_buffer().set_text(item['text']) if self._reflection.activity.initiating: obj.connect('focus-in-event', self._text_focus_in_cb) obj.connect( 'focus-out-event', self._text_focus_out_cb, i) else: obj.set_editable(False) if first_text: self._content_we_always_show.append(align) first_text = False elif 'image' in item: try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( item['image'], PICTURE_WIDTH, PICTURE_HEIGHT) obj = Gtk.Image.new_from_pixbuf(pixbuf) if first_image: self._content_we_always_show.append(align) first_image = False except: logging.error('could not open %s' % item['image']) if obj is not None: align.add(obj) obj.show() self._grid.attach(align, 1, row, 5, 1) self._content_aligns.append(align) row += 1 self._row = row if self._reflection.activity.initiating: self._new_entry = Gtk.Entry() self._new_entry.props.placeholder_text = _('Write a reflection') self._new_entry.connect('activate', self._entry_activate_cb) self._grid.attach(self._new_entry, 1, row, 5, 1) self._content_we_always_show.append(self._new_entry) self._new_image = EventIcon(icon_name='add-picture', pixel_size=BUTTON_SIZE) self._new_image.set_tooltip(_('Add new image')) self._new_image.connect('button-press-event', self._image_button_cb) self._grid.attach(self._new_image, 6, row, 1, 1) self._content_we_always_show.append(self._new_image) for align in self._content_we_always_show: align.show() row += 1 self._comment_row = row self._comment_aligns = [] if 'comments' in self._reflection.data: for comment in self._reflection.data['comments']: obj = Gtk.TextView() obj.set_editable(False) obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) nick_tag = obj.get_buffer().create_tag( 'nick', foreground=comment['color'], weight=Pango.Weight.BOLD) iter_text = obj.get_buffer().get_iter_at_offset(0) obj.get_buffer().insert_with_tags( iter_text, comment['nick'] + ': ', nick_tag) iter_text = obj.get_buffer().get_end_iter() obj.get_buffer().insert(iter_text, comment['comment']) align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) align.add(obj) obj.show() self._grid.attach(align, 1, self._comment_row, 5, 1) self._comment_aligns.append(align) self._comment_row += 1 self._new_comment = Gtk.Entry() self._new_comment.props.placeholder_text = _('Make a comment') self._new_comment.connect('activate', self._comment_activate_cb) self._grid.attach(self._new_comment, 1, self._comment_row, 5, 1) def _star_button_cb(self, button, event, n): self.update_stars(n) if self._reflection.activity.sharing: self._reflection.activity.send_event(STAR_CMD, {"obj_id": self._reflection.data["obj_id"], "stars": n}) def update_stars(self, n): if 'stars' in self._reflection.data: oldn = self._reflection.data['stars'] else: oldn = 0 if n < oldn: # Erase stars, including one that was clicked for i in range(NUMBER_OF_STARS): if i < n: icon_name = 'star-filled' else: icon_name = 'star-empty' self._star_icons[i].set_icon_name(icon_name) self._reflection.data['stars'] = n else: # Add stars, including one that was clicked for i in range(NUMBER_OF_STARS): if i <= n: icon_name = 'star-filled' else: icon_name = 'star-empty' self._star_icons[i].set_icon_name(icon_name) self._reflection.data['stars'] = n + 1 self._reflection.set_modification_time() def _text_focus_in_cb(self, widget, event): rgba = Gdk.RGBA() rgba.red, rgba.green, rgba.blue = 0.9, 0.9, 0.9 rgba.alpha = 1. widget.override_background_color(Gtk.StateFlags.NORMAL, rgba) def _text_focus_out_cb(self, widget, event, entry): bounds = widget.get_buffer().get_bounds() text = widget.get_buffer().get_text(bounds[0], bounds[1], True) self._reflection.data['content'][entry]['text'] = text rgba = Gdk.RGBA() rgba.red, rgba.green, rgba.blue = 1., 1., 1. rgba.alpha = 1. widget.override_background_color(Gtk.StateFlags.NORMAL, rgba) def _tag_button_cb(self, button, event): bounds = self._tag_view.get_buffer().get_bounds() text = self._tag_view.get_buffer().get_text(bounds[0], bounds[1], True) self._process_tags(self._tag_view.get_buffer(), text) def _tag_focus_in_cb(self, widget, event, prompt=None): bounds = widget.get_buffer().get_bounds() text = widget.get_buffer().get_text(bounds[0], bounds[1], True) if text == prompt: widget.get_buffer().set_text('') rgba = Gdk.RGBA() rgba.red, rgba.green, rgba.blue = 0.9, 0.9, 0.9 rgba.alpha = 1. widget.override_background_color(Gtk.StateFlags.NORMAL, rgba) def _tags_focus_out_cb(self, widget, event): bounds = widget.get_buffer().get_bounds() text = widget.get_buffer().get_text(bounds[0], bounds[1], True) self._process_tags(widget.get_buffer(), text) rgba = Gdk.RGBA() rgba.red, rgba.green, rgba.blue = 1., 1., 1. rgba.alpha = 1. widget.override_background_color(Gtk.StateFlags.NORMAL, rgba) def _process_tags(self, text_buffer, text): ''' process tag data from textview ''' self._reflection.data['tags'] = [] label = '' tags = text.split() for tag in tags: if len(label) > 0: label += ', ' tag = tag.rstrip(',') tag = tag.rstrip(';') if tag[0] == '#': self._reflection.data['tags'].append(tag) label += tag else: self._reflection.data['tags'].append('#' + tag) label += '#' + tag text_buffer.set_text(label.replace('\12', '')) if self._reflection.activity.sharing: data = json.dumps(self._reflection.data['tags']) self._reflection.activity.send_event(TAG_CMD, {"obj_id": self._refelection.data["ob_id"], "reflection": data}) self._reflection.set_modification_time() # Update journal entry dsobj = datastore.get(self._reflection.data['obj_id']) logging.error('setting tags to %s' % label) dsobj.metadata['tags'] = label datastore.write(dsobj, update_mtime=False, reply_handler=self.datastore_write_cb, error_handler=self.datastore_write_error_cb) def add_tags(self, data): ''' process encoded tag data from share ''' tags = json.loads(data) self._reflection.data['tags'] = tags[:] label = '' for tag in tags: if len(label) > 0: label += ', ' label += tag self._tag_view.get_buffer().set_text(label) def _title_focus_out_cb(self, widget, event): ''' process title text from textview ''' bounds = widget.get_buffer().get_bounds() text = widget.get_buffer().get_text(bounds[0], bounds[1], True) self._reflection.data['title'] = text if self._reflection.activity.sharing: self._reflection.activity.send_event(TITLE_CMD, {"obj_id": self._reflection.data["obj_id"], "title": text}) self._reflection.set_modification_time() # Update journal entry dsobj = datastore.get(self._reflection.data['obj_id']) dsobj.metadata['title'] = text datastore.write(dsobj, update_mtime=False, reply_handler=self.datastore_write_cb, error_handler=self.datastore_write_error_cb) def datastore_write_cb(self): logging.debug('ds write cb') def datastore_write_error_cb(self, error): logging.error('datastore_write_error_cb: %r' % error) def update_title(self, text): ''' process title text from share ''' self._reflection.data['title'] = text self._title.get_buffer().set_text('') iter_text = self._title.get_buffer().get_iter_at_offset(0) self._title.get_buffer().insert_with_tags( iter_text, text, self._title_tag) def _comment_activate_cb(self, entry): text = entry.props.text if not 'comments' in self._reflection.data: self._reflection.data['comments'] = [] data = {'nick': profile.get_nick_name(), 'color': self._reflection.activity.fg_color.get_html(), 'comment': text} self._reflection.data['comments'].append(data) self.add_new_comment(data) # Send the comment if self._reflection.activity.sharing: send_data = data.copy() send_data["obj_id"] = self._reflection.data["obj_id"] self._reflection.activity.send_event(COMMENT_CMD, send_data) entry.set_text('') # Update journal entry dsobj = datastore.get(self._reflection.data['obj_id']) if 'comments' in dsobj.metadata: data = json.loads(dsobj.metadata['comments']) else: data = [] data.append({'from': profile.get_nick_name(), 'message': text, 'icon-color': profile.get_color().to_string()}) dsobj.metadata['comments'] = json.dumps(data) datastore.write(dsobj, update_mtime=False, reply_handler=self.datastore_write_cb, error_handler=self.datastore_write_error_cb) def add_new_comment(self, comment): obj = Gtk.TextView() obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) nick_tag = obj.get_buffer().create_tag( 'nick', foreground=comment['color'], weight=Pango.Weight.BOLD) iter_text = obj.get_buffer().get_iter_at_offset(0) obj.get_buffer().insert_with_tags( iter_text, comment['nick'] + ': ', nick_tag) iter_text = obj.get_buffer().get_end_iter() obj.get_buffer().insert(iter_text, comment['comment']) align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) align.add(obj) obj.show() self._grid.insert_row(self._comment_row) self._grid.attach(align, 1, self._comment_row, 5, 1) self._comment_row += 1 align.show() def _entry_activate_cb(self, entry): text = entry.props.text if not 'content' in self._reflection.data: self._reflection.data['content'] = [] self._reflection.data['content'].append({'text': text}) self._reflection.set_modification_time() self.add_new_reflection(text) # Send the reflection if self._reflection.activity.sharing: self._reflection.activity.send_event(REFLECTION_CMD, {"obj_id": self._reflection.data["obj_id"], "reflection": text}) entry.set_text('') def add_new_reflection(self, text): i = len(self._reflection.data['content']) obj = Gtk.TextView() obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) obj.get_buffer().set_text(text) obj.connect('focus-in-event', self._text_focus_in_cb) obj.connect('focus-out-event', self._text_focus_out_cb, i - 1) align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) align.add(obj) obj.show() self._grid.insert_row(self._row) self._grid.attach(align, 1, self._row, 5, 1) self._row += 1 align.show() def _activity_button_cb(self, button, event): self._reflection.activity.busy_cursor() GObject.idle_add(self._choose_activity) def _choose_activity(self): if not hasattr(self, '_activity_sw'): grid = Gtk.Grid() self._reflection.activity.load_overlay_area(grid) grid.show() bundle_icons = utils.get_bundle_icons() x = 0 y = 0 for bundle_id in bundle_icons.keys(): icon_path = bundle_icons[bundle_id] if icon_path is None: continue pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( icon_path, style.GRID_CELL_SIZE, style.GRID_CELL_SIZE) image = Gtk.Image.new_from_pixbuf(pixbuf) button = Gtk.ToolButton() button.set_icon_widget(image) image.show() button.connect('clicked', self._insert_activity, bundle_id) grid.attach(button, x, y, 1, 1) button.show() x += 1 if x > 6: y += 1 x = 0 self._reflection.activity.show_overlay_area() self._reflection.activity.reset_cursor() def _insert_activity(self, widget, bundle_id): ''' Add activity from UI ''' self._reflection.activity.hide_overlay_area() self.add_activity(bundle_id) if self._reflection.activity.sharing: self._reflection.activity.send_event(ACTIVITY_CMD, {"obj_id": self._reflection.data["obj_id"], "bundle_id": bundle_id}) def add_activity(self, bundle_id): ''' Add activity from sharer ''' if not 'activities' in self._reflection.data: self._reflection.data['activities'] = [] self._reflection.data['activities'].append( utils.bundle_id_to_icon(bundle_id)) self._reflection.set_modification_time() self._activities_align.remove(self._activities_grid) self._make_activities_grid() def _make_activities_grid(self): column = 0 self._activities_grid = Gtk.Grid() self._activities = [] if 'activities' in self._reflection.data: for icon_path in self._reflection.data['activities']: if icon_path is None: continue try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( icon_path, BUTTON_SIZE, BUTTON_SIZE) except Exception as e: logging.error('Could not find icon %s: %s' % (icon_path, e)) continue self._activities.append(Gtk.Image.new_from_pixbuf(pixbuf)) self._activities_grid.attach( self._activities[-1], column, 0, 1, 1) self._activities[-1].show() column += 1 else: label = Gtk.Label('Add an activity') self._activities_grid.attach(label, 0, 0, 5, 1) label.show() self._activities_align.add(self._activities_grid) self._activities_grid.show() def _image_button_cb(self, button, event): self._reflection.activity.busy_cursor() GObject.idle_add(self._choose_image) def _choose_image(self): from sugar3.graphics.objectchooser import ObjectChooser try: from sugar3.graphics.objectchooser import FILTER_TYPE_GENERIC_MIME except: FILTER_TYPE_GENERIC_MIME = 'generic_mime' from sugar3 import mime chooser = None name = None if hasattr(mime, 'GENERIC_TYPE_IMAGE'): # See #2398 if 'image/svg+xml' not in \ mime.get_generic_type(mime.GENERIC_TYPE_IMAGE).mime_types: mime.get_generic_type( mime.GENERIC_TYPE_IMAGE).mime_types.append('image/svg+xml') try: chooser = ObjectChooser(parent=self._reflection.activity, what_filter=mime.GENERIC_TYPE_IMAGE, filter_type=FILTER_TYPE_GENERIC_MIME, show_preview=True) except: chooser = ObjectChooser(parent=self._reflection.activity, what_filter=mime.GENERIC_TYPE_IMAGE) else: try: chooser = ObjectChooser(parent=self, what_filter=None) except TypeError: chooser = ObjectChooser( None, self._reflection.activity, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT) if chooser is not None: try: result = chooser.run() if result == Gtk.ResponseType.ACCEPT: jobject = chooser.get_selected_object() if jobject and jobject.file_path: name = jobject.metadata['title'] mime_type = jobject.metadata['mime_type'] _logger.debug('result of choose: %s (%s)' % (name, str(mime_type))) finally: chooser.destroy() del chooser if name is not None: pixbuf = self.add_new_picture(jobject.file_path) self._reflection.set_modification_time() if self._reflection.activity.sharing and pixbuf is not None: self._reflection.activity.send_event(PICTURE_CMD, {"basename": os.path.basename(jobject.file_path), "data": utils.pixbuf_to_base64(pixbuf)}) self._reflection.activity.send_event(IMAGE_REFLECTION_CMD, {"obj_id": self._reflection.data["obj_id"], "basename": os.path.basename(jobject.file_path)}) self._reflection.activity.reset_cursor() def add_new_picture(self, path): try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size( path, PICTURE_WIDTH, PICTURE_HEIGHT) obj = Gtk.Image.new_from_pixbuf(pixbuf) except: logging.error('could not open %s' % jobject.file_path) return None align = Gtk.Alignment.new( xalign=0, yalign=0.5, xscale=0, yscale=0) align.add(obj) obj.show() self._grid.insert_row(self._row) self._grid.attach(align, 1, self._row, 5, 1) self._row += 1 align.show() if not 'content' in self._reflection.data: self._reflection.data['content'] = [] self._reflection.data['content'].append({'image': path}) if self._reflection.activity.sharing: return pixbuf def _expand_cb(self, button, event): self._grid.set_row_spacing(style.DEFAULT_SPACING) if self._collapse_id is not None: button.disconnect(self._collapse_id) button.set_icon_name('collapse') button.set_tooltip(_('Collapse')) self._collapse_id = button.connect('button-press-event', self._collapse_cb) self._tag_align.show() if hasattr(self, '_new_tag'): self._new_tag.show() self._stars_align.show() for align in self._content_aligns: align.show() for align in self._comment_aligns: align.show() self._new_comment.show() def _collapse_cb(self, button, event): self._grid.set_row_spacing(0) if self._collapse_id is not None: button.disconnect(self._collapse_id) button.set_icon_name('expand') button.set_tooltip(_('Expand')) self._collapse_id = button.connect('button-press-event', self._expand_cb) self._tag_align.hide() if hasattr(self, '_new_tag'): self._new_tag.hide() self._stars_align.hide() for align in self._content_aligns: if not align in self._content_we_always_show: align.hide() for align in self._comment_aligns: align.hide() self._new_comment.hide() def __delete_cb(self, button, event): self._reflection.activity.delete_item(self._reflection.data['obj_id']) self.hide()
class ReflectionGrid(Gtk.EventBox): def __init__(self, parent): Gtk.EventBox.__init__(self) self._reflection = parent self._collapse = True self._collapse_id = None self.modify_bg(Gtk.StateType.NORMAL, style.COLOR_WHITE.get_gdk_color()) self._title_color = self._reflection.activity.fg_color.get_html() self._grid = Gtk.Grid() self.add(self._grid) self._grid.show() self._grid.set_row_spacing(style.DEFAULT_PADDING) self._grid.set_column_spacing(style.DEFAULT_SPACING) self._grid.set_column_homogeneous(True) self._grid.set_border_width(style.DEFAULT_PADDING) row = 0 self._expand_button = EventIcon(icon_name="expand", pixel_size=BUTTON_SIZE) self._collapse_id = self._expand_button.connect("button-press-event", self._expand_cb) self._grid.attach(self._expand_button, 0, row, 1, 1) self._expand_button.show() self._title_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) self._title = Gtk.TextView() self._title.set_size_request(ENTRY_WIDTH, -1) self._title.set_wrap_mode(Gtk.WrapMode.WORD) self._title_tag = self._title.get_buffer().create_tag( "title", foreground=self._title_color, weight=Pango.Weight.BOLD, size=12288 ) iter_text = self._title.get_buffer().get_iter_at_offset(0) self._title.get_buffer().insert_with_tags(iter_text, self._reflection.data["title"], self._title_tag) if self._reflection.activity.initiating: self._title.connect("focus-out-event", self._title_focus_out_cb) else: self._title.set_editable(False) self._title_align.add(self._title) self._title.show() self._grid.attach(self._title_align, 1, row, 5, 1) self._title_align.show() """ Notification that a new comment has been shared. """ self.notify_button = EventIcon(icon_name="chat", pixel_size=BUTTON_SIZE) self._grid.attach(self.notify_button, 6, row, 1, 1) row += 1 self._time_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) self._time = Gtk.Label() self._time.set_size_request(ENTRY_WIDTH, -1) self._time.set_justify(Gtk.Justification.LEFT) self._time.set_use_markup(True) try: time_string = util.timestamp_to_elapsed_string(int(self._reflection.data["modification_time"])) except Exception as e: logging.error( "Could not convert modification time %s: %s" % (self._reflection.data["modification_time"], e) ) self._reflection.data["modification_time"] = self._reflection.data["creation_time"] time_string = util.timestamp_to_elapsed_string(int(self._reflection.data["modification_time"])) self._time.set_markup('<span foreground="#808080"><small><b>%s</b></small></span>' % time_string) self._time_align.add(self._time) self._time.show() self._grid.attach(self._time_align, 1, row, 5, 1) self._time_align.show() row += 1 label = "" if "tags" in self._reflection.data: for tag in self._reflection.data["tags"]: if len(label) > 0: label += ", " label += tag if self._reflection.activity.initiating and label == "": label = _("Add a #tag") self._tag_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) self._tag_view = Gtk.TextView() self._tag_view.set_size_request(ENTRY_WIDTH, -1) self._tag_view.set_wrap_mode(Gtk.WrapMode.WORD) self._tag_view.get_buffer().set_text(label) if self._reflection.activity.initiating: self._tag_view.connect("focus-in-event", self._tag_focus_in_cb, _("Add a #tag")) self._tag_view.connect("focus-out-event", self._tags_focus_out_cb) else: self._tag_view.set_editable(False) self._tag_align.add(self._tag_view) self._tag_view.show() self._grid.attach(self._tag_align, 1, row, 5, 1) if self._reflection.activity.initiating: self._new_tag = EventIcon(icon_name="ok", pixel_size=BUTTON_SIZE) self._new_tag.connect("button-press-event", self._tag_button_cb) self._grid.attach(self._new_tag, 6, row, 1, 1) row += 1 self._activities_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) self._make_activities_grid() self._grid.attach(self._activities_align, 1, row, 5, 1) self._activities_align.show() if self._reflection.activity.initiating: self._new_activity = EventIcon(icon_name="add-item", pixel_size=BUTTON_SIZE) self._new_activity.connect("button-press-event", self._activity_button_cb) self._grid.attach(self._new_activity, 6, row, 1, 1) self._new_activity.show() row += 1 self._stars_align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) grid = Gtk.Grid() if "stars" in self._reflection.data: stars = self._reflection.data["stars"] else: stars = 0 self._star_icons = [] for i in range(NUMBER_OF_STARS): if i < stars: icon_name = "star-filled" else: icon_name = "star-empty" self._star_icons.append(EventIcon(icon_name=icon_name, pixel_size=STAR_SIZE)) if self._reflection.activity.initiating: self._star_icons[-1].connect("button-press-event", self._star_button_cb, i) grid.attach(self._star_icons[-1], i, 0, 1, 1) self._star_icons[-1].show() self._stars_align.add(grid) grid.show() self._grid.attach(self._stars_align, 1, row, 5, 1) row += 1 self._content_aligns = [] first_text = True first_image = True self._content_we_always_show = [] if "content" in self._reflection.data: for i, item in enumerate(self._reflection.data["content"]): # Add edit and delete buttons align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) obj = None if "text" in item: obj = Gtk.TextView() obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) obj.get_buffer().set_text(item["text"]) if self._reflection.activity.initiating: obj.connect("focus-in-event", self._text_focus_in_cb) obj.connect("focus-out-event", self._text_focus_out_cb, i) else: obj.set_editable(False) if first_text: self._content_we_always_show.append(align) first_text = False elif "image" in item: try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(item["image"], PICTURE_WIDTH, PICTURE_HEIGHT) obj = Gtk.Image.new_from_pixbuf(pixbuf) if first_image: self._content_we_always_show.append(align) first_image = False except: logging.error("could not open %s" % item["image"]) if obj is not None: align.add(obj) obj.show() self._grid.attach(align, 1, row, 5, 1) self._content_aligns.append(align) row += 1 self._row = row if self._reflection.activity.initiating: self._new_entry = Gtk.Entry() self._new_entry.props.placeholder_text = _("Write a reflection") self._new_entry.connect("activate", self._entry_activate_cb) self._grid.attach(self._new_entry, 1, row, 5, 1) self._content_we_always_show.append(self._new_entry) self._new_image = EventIcon(icon_name="add-picture", pixel_size=BUTTON_SIZE) self._new_image.connect("button-press-event", self._image_button_cb) self._grid.attach(self._new_image, 6, row, 1, 1) self._content_we_always_show.append(self._new_image) for align in self._content_we_always_show: align.show() row += 1 self._comment_row = row self._comment_aligns = [] if "comments" in self._reflection.data: for comment in self._reflection.data["comments"]: obj = Gtk.TextView() obj.set_editable(False) obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) nick_tag = obj.get_buffer().create_tag("nick", foreground=comment["color"], weight=Pango.Weight.BOLD) iter_text = obj.get_buffer().get_iter_at_offset(0) obj.get_buffer().insert_with_tags(iter_text, comment["nick"] + ": ", nick_tag) iter_text = obj.get_buffer().get_end_iter() obj.get_buffer().insert(iter_text, comment["comment"]) align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) align.add(obj) obj.show() self._grid.attach(align, 1, self._comment_row, 5, 1) self._comment_aligns.append(align) self._comment_row += 1 self._new_comment = Gtk.Entry() self._new_comment.props.placeholder_text = _("Make a comment") self._new_comment.connect("activate", self._comment_activate_cb) self._grid.attach(self._new_comment, 1, self._comment_row, 5, 1) def _star_button_cb(self, button, event, n): self.update_stars(n) if self._reflection.activity.sharing: self._reflection.activity.send_event("%s|%s|%d" % (STAR_CMD, self._reflection.data["obj_id"], n)) def update_stars(self, n): if "stars" in self._reflection.data: oldn = self._reflection.data["stars"] else: oldn = 0 if n < oldn: # Erase stars, including one that was clicked for i in range(NUMBER_OF_STARS): if i < n: icon_name = "star-filled" else: icon_name = "star-empty" self._star_icons[i].set_icon_name(icon_name) self._reflection.data["stars"] = n else: # Add stars, including one that was clicked for i in range(NUMBER_OF_STARS): if i <= n: icon_name = "star-filled" else: icon_name = "star-empty" self._star_icons[i].set_icon_name(icon_name) self._reflection.data["stars"] = n + 1 self._reflection.set_modification_time() def _text_focus_in_cb(self, widget, event): rgba = Gdk.RGBA() rgba.red, rgba.green, rgba.blue = 0.9, 0.9, 0.9 rgba.alpha = 1.0 widget.override_background_color(Gtk.StateFlags.NORMAL, rgba) def _text_focus_out_cb(self, widget, event, entry): bounds = widget.get_buffer().get_bounds() text = widget.get_buffer().get_text(bounds[0], bounds[1], True) self._reflection.data["content"][entry]["text"] = text rgba = Gdk.RGBA() rgba.red, rgba.green, rgba.blue = 1.0, 1.0, 1.0 rgba.alpha = 1.0 widget.override_background_color(Gtk.StateFlags.NORMAL, rgba) def _tag_button_cb(self, button, event): bounds = self._tag_view.get_buffer().get_bounds() text = self._tag_view.get_buffer().get_text(bounds[0], bounds[1], True) self._process_tags(self._tag_view.get_buffer(), text) def _tag_focus_in_cb(self, widget, event, prompt=None): bounds = widget.get_buffer().get_bounds() text = widget.get_buffer().get_text(bounds[0], bounds[1], True) if text == prompt: widget.get_buffer().set_text("") rgba = Gdk.RGBA() rgba.red, rgba.green, rgba.blue = 0.9, 0.9, 0.9 rgba.alpha = 1.0 widget.override_background_color(Gtk.StateFlags.NORMAL, rgba) def _tags_focus_out_cb(self, widget, event): bounds = widget.get_buffer().get_bounds() text = widget.get_buffer().get_text(bounds[0], bounds[1], True) self._process_tags(widget.get_buffer(), text) rgba = Gdk.RGBA() rgba.red, rgba.green, rgba.blue = 1.0, 1.0, 1.0 rgba.alpha = 1.0 widget.override_background_color(Gtk.StateFlags.NORMAL, rgba) def _process_tags(self, text_buffer, text): """ process tag data from textview """ self._reflection.data["tags"] = [] label = "" tags = text.split() for tag in tags: if len(label) > 0: label += ", " tag = tag.rstrip(",") tag = tag.rstrip(";") if tag[0] == "#": self._reflection.data["tags"].append(tag) label += tag else: self._reflection.data["tags"].append("#" + tag) label += "#" + tag text_buffer.set_text(label.replace("\12", "")) if self._reflection.activity.sharing: data = json.dumps(self._reflection.data["tags"]) self._reflection.activity.send_event("%s|%s|%s" % (TAG_CMD, self._reflection.data["obj_id"], data)) self._reflection.set_modification_time() # Update journal entry dsobj = datastore.get(self._reflection.data["obj_id"]) logging.error("setting tags to %s" % label) dsobj.metadata["tags"] = label datastore.write( dsobj, update_mtime=False, reply_handler=self.datastore_write_cb, error_handler=self.datastore_write_error_cb, ) def add_tags(self, data): """ process encoded tag data from share """ tags = json.loads(data) self._reflection.data["tags"] = tags[:] label = "" for tag in tags: if len(label) > 0: label += ", " label += tag self._tag_view.get_buffer().set_text(label) def _title_focus_out_cb(self, widget, event): """ process title text from textview """ bounds = widget.get_buffer().get_bounds() text = widget.get_buffer().get_text(bounds[0], bounds[1], True) self._reflection.data["title"] = text if self._reflection.activity.sharing: self._reflection.activity.send_event("%s|%s|%s" % (TITLE_CMD, self._reflection.data["obj_id"], text)) self._reflection.set_modification_time() # Update journal entry dsobj = datastore.get(self._reflection.data["obj_id"]) dsobj.metadata["title"] = text datastore.write( dsobj, update_mtime=False, reply_handler=self.datastore_write_cb, error_handler=self.datastore_write_error_cb, ) def datastore_write_cb(self): logging.debug("ds write cb") def datastore_write_error_cb(self, error): logging.error("datastore_write_error_cb: %r" % error) def update_title(self, text): """ process title text from share """ self._reflection.data["title"] = text self._title.get_buffer().set_text("") iter_text = self._title.get_buffer().get_iter_at_offset(0) self._title.get_buffer().insert_with_tags(iter_text, text, self._title_tag) def _comment_activate_cb(self, entry): text = entry.props.text if not "comments" in self._reflection.data: self._reflection.data["comments"] = [] data = { "nick": profile.get_nick_name(), "color": self._reflection.activity.fg_color.get_html(), "comment": text, } self._reflection.data["comments"].append(data) self.add_new_comment(data) # Send the comment if self._reflection.activity.sharing: self._reflection.activity.send_event( "%s|%s|%s|%s|%s" % (COMMENT_CMD, self._reflection.data["obj_id"], data["nick"], data["color"], data["comment"]) ) entry.set_text("") # Update journal entry dsobj = datastore.get(self._reflection.data["obj_id"]) if "comments" in dsobj.metadata: data = json.loads(dsobj.metadata["comments"]) else: data = [] data.append({"from": profile.get_nick_name(), "message": text, "icon-color": profile.get_color().to_string()}) dsobj.metadata["comments"] = json.dumps(data) datastore.write( dsobj, update_mtime=False, reply_handler=self.datastore_write_cb, error_handler=self.datastore_write_error_cb, ) def add_new_comment(self, comment): obj = Gtk.TextView() obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) nick_tag = obj.get_buffer().create_tag("nick", foreground=comment["color"], weight=Pango.Weight.BOLD) iter_text = obj.get_buffer().get_iter_at_offset(0) obj.get_buffer().insert_with_tags(iter_text, comment["nick"] + ": ", nick_tag) iter_text = obj.get_buffer().get_end_iter() obj.get_buffer().insert(iter_text, comment["comment"]) align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) align.add(obj) obj.show() self._grid.insert_row(self._comment_row) self._grid.attach(align, 1, self._comment_row, 5, 1) self._comment_row += 1 align.show() def _entry_activate_cb(self, entry): text = entry.props.text if not "content" in self._reflection.data: self._reflection.data["content"] = [] self._reflection.data["content"].append({"text": text}) self._reflection.set_modification_time() self.add_new_reflection(text) # Send the reflection if self._reflection.activity.sharing: self._reflection.activity.send_event("%s|%s|%s" % (REFLECTION_CMD, self._reflection.data["obj_id"], text)) entry.set_text("") def add_new_reflection(self, text): i = len(self._reflection.data["content"]) obj = Gtk.TextView() obj.set_size_request(ENTRY_WIDTH, -1) obj.set_wrap_mode(Gtk.WrapMode.WORD) obj.get_buffer().set_text(text) obj.connect("focus-in-event", self._text_focus_in_cb) obj.connect("focus-out-event", self._text_focus_out_cb, i - 1) align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) align.add(obj) obj.show() self._grid.insert_row(self._row) self._grid.attach(align, 1, self._row, 5, 1) self._row += 1 align.show() def _activity_button_cb(self, button, event): self._reflection.activity.busy_cursor() GObject.idle_add(self._choose_activity) def _choose_activity(self): if not hasattr(self, "_activity_sw"): grid = Gtk.Grid() self._reflection.activity.load_overlay_area(grid) grid.show() bundle_icons = utils.get_bundle_icons() x = 0 y = 0 for bundle_id in bundle_icons.keys(): icon_path = bundle_icons[bundle_id] if icon_path is None: continue pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_path, style.GRID_CELL_SIZE, style.GRID_CELL_SIZE) image = Gtk.Image.new_from_pixbuf(pixbuf) button = Gtk.ToolButton() button.set_icon_widget(image) image.show() button.connect("clicked", self._insert_activity, bundle_id) grid.attach(button, x, y, 1, 1) button.show() x += 1 if x > 6: y += 1 x = 0 self._reflection.activity.show_overlay_area() self._reflection.activity.reset_cursor() def _insert_activity(self, widget, bundle_id): """ Add activity from UI """ self._reflection.activity.hide_overlay_area() self.add_activity(bundle_id) if self._reflection.activity.sharing: self._reflection.activity.send_event( "%s|%s|%s" % (ACTIVITY_CMD, self._reflection.data["obj_id"], bundle_id) ) def add_activity(self, bundle_id): """ Add activity from sharer """ if not "activities" in self._reflection.data: self._reflection.data["activities"] = [] self._reflection.data["activities"].append(utils.bundle_id_to_icon(bundle_id)) self._reflection.set_modification_time() self._activities_align.remove(self._activities_grid) self._make_activities_grid() def _make_activities_grid(self): column = 0 self._activities_grid = Gtk.Grid() self._activities = [] if "activities" in self._reflection.data: for icon_path in self._reflection.data["activities"]: if icon_path is None: continue try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(icon_path, BUTTON_SIZE, BUTTON_SIZE) except Exception as e: logging.error("Could not find icon %s: %s" % (icon_path, e)) continue self._activities.append(Gtk.Image.new_from_pixbuf(pixbuf)) self._activities_grid.attach(self._activities[-1], column, 0, 1, 1) self._activities[-1].show() column += 1 else: label = Gtk.Label("Add an activity") self._activities_grid.attach(label, 0, 0, 5, 1) label.show() self._activities_align.add(self._activities_grid) self._activities_grid.show() def _image_button_cb(self, button, event): self._reflection.activity.busy_cursor() GObject.idle_add(self._choose_image) def _choose_image(self): from sugar3.graphics.objectchooser import ObjectChooser try: from sugar3.graphics.objectchooser import FILTER_TYPE_GENERIC_MIME except: FILTER_TYPE_GENERIC_MIME = "generic_mime" from sugar3 import mime chooser = None name = None if hasattr(mime, "GENERIC_TYPE_IMAGE"): # See #2398 if "image/svg+xml" not in mime.get_generic_type(mime.GENERIC_TYPE_IMAGE).mime_types: mime.get_generic_type(mime.GENERIC_TYPE_IMAGE).mime_types.append("image/svg+xml") try: chooser = ObjectChooser( parent=self._reflection.activity, what_filter=mime.GENERIC_TYPE_IMAGE, filter_type=FILTER_TYPE_GENERIC_MIME, show_preview=True, ) except: chooser = ObjectChooser(parent=self._reflection.activity, what_filter=mime.GENERIC_TYPE_IMAGE) else: try: chooser = ObjectChooser(parent=self, what_filter=None) except TypeError: chooser = ObjectChooser( None, self._reflection.activity, Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT ) if chooser is not None: try: result = chooser.run() if result == Gtk.ResponseType.ACCEPT: jobject = chooser.get_selected_object() if jobject and jobject.file_path: name = jobject.metadata["title"] mime_type = jobject.metadata["mime_type"] _logger.debug("result of choose: %s (%s)" % (name, str(mime_type))) finally: chooser.destroy() del chooser if name is not None: pixbuf = self.add_new_picture(jobject.file_path) self._reflection.set_modification_time() if self._reflection.activity.sharing and pixbuf is not None: self._reflection.activity.send_event( "%s|%s|%s" % (PICTURE_CMD, os.path.basename(jobject.file_path), utils.pixbuf_to_base64(pixbuf)) ) self._reflection.activity.send_event( "%s|%s|%s" % (IMAGE_REFLECTION_CMD, self._reflection.data["obj_id"], os.path.basename(jobject.file_path)) ) self._reflection.activity.reset_cursor() def add_new_picture(self, path): try: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(path, PICTURE_WIDTH, PICTURE_HEIGHT) obj = Gtk.Image.new_from_pixbuf(pixbuf) except: logging.error("could not open %s" % jobject.file_path) return None align = Gtk.Alignment.new(xalign=0, yalign=0.5, xscale=0, yscale=0) align.add(obj) obj.show() self._grid.insert_row(self._row) self._grid.attach(align, 1, self._row, 5, 1) self._row += 1 align.show() if not "content" in self._reflection.data: self._reflection.data["content"] = [] self._reflection.data["content"].append({"image": path}) if self._reflection.activity.sharing: return pixbuf def _expand_cb(self, button, event): self._grid.set_row_spacing(style.DEFAULT_SPACING) if self._collapse_id is not None: button.disconnect(self._collapse_id) button.set_icon_name("collapse") self._collapse_id = button.connect("button-press-event", self._collapse_cb) self._tag_align.show() if hasattr(self, "_new_tag"): self._new_tag.show() self._stars_align.show() for align in self._content_aligns: align.show() for align in self._comment_aligns: align.show() self._new_comment.show() def _collapse_cb(self, button, event): self._grid.set_row_spacing(0) if self._collapse_id is not None: button.disconnect(self._collapse_id) button.set_icon_name("expand") self._collapse_id = button.connect("button-press-event", self._expand_cb) self._tag_align.hide() if hasattr(self, "_new_tag"): self._new_tag.hide() self._stars_align.hide() for align in self._content_aligns: if not align in self._content_we_always_show: align.hide() for align in self._comment_aligns: align.hide() self._new_comment.hide()