class Note(Gtk.Window): @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, return_type=bool, accumulator=GObject.signal_accumulator_true_handled) def update(self): pass @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, return_type=bool, accumulator=GObject.signal_accumulator_true_handled) def removed(self): pass def __init__(self, app, info={}): self.app = app self.showing = False self.is_pinned = False self.x = info.get('x', 0) self.y = info.get('y', 0) self.height = info.get('height', self.app.settings.get_uint('default-height')) self.width = info.get('width', self.app.settings.get_uint('default-width')) title = info.get('title', '') text = info.get('text', '') self.color = info.get('color', self.app.settings.get_string('default-color')) super(Note, self).__init__( skip_taskbar_hint=True, # skip_pager_hint=False, type_hint=Gdk.WindowTypeHint.UTILITY, default_height=self.height, default_width=self.width, resizable=True, deletable=False, name='sticky-note' ) if self.color == 'random': self.color = random.choice(list(COLORS.keys())) context = self.get_style_context() context.add_class(self.color) if self.app.settings.get_boolean('desktop-window-state'): self.stick() # title bar self.title_bar = Gtk.Box(height_request=30, name='title-box') self.connect('button-press-event', self.on_title_click) # formatting items are shown here more_menu_icon = Gtk.Image.new_from_icon_name('view-more', Gtk.IconSize.BUTTON) more_menu_button = Gtk.Button(image=more_menu_icon, relief=Gtk.ReliefStyle.NONE, name='window-button', valign=Gtk.Align.CENTER) more_menu_button.connect('clicked', self.show_more_menu) more_menu_button.connect('button-press-event', self.on_title_click) more_menu_button.set_tooltip_text(_("Format")) self.title_bar.pack_start(more_menu_button, False, False, 0) # used to show the edit title icon when the title is hovered self.title_hover = Gtk.EventBox() self.title_bar.pack_start(self.title_hover, True, True, 4) self.title_hover.connect('enter-notify-event', self.set_edit_button_visibility) self.title_hover.connect('leave-notify-event', self.set_edit_button_visibility) self.title_box = Gtk.Box() self.title_hover.add(self.title_box) self.title = Gtk.Label(label=title, margin_top=4) self.title_box.pack_start(self.title, False, False, 0) edit_title_icon = Gtk.Image.new_from_icon_name('edit', Gtk.IconSize.BUTTON) self.edit_title_button = Gtk.Button(image=edit_title_icon, relief=Gtk.ReliefStyle.NONE, name='window-button', valign=Gtk.Align.CENTER, no_show_all=True) self.edit_title_button.connect('clicked', self.set_title) self.edit_title_button.connect('button-press-event', self.on_title_click) self.edit_title_button.set_tooltip_text(_("Format")) self.title_box.pack_start(self.edit_title_button, False, False, 0) close_icon = Gtk.Image.new_from_icon_name('window-close', Gtk.IconSize.BUTTON) close_button = Gtk.Button(image=close_icon, relief=Gtk.ReliefStyle.NONE, name='window-button', valign=Gtk.Align.CENTER) close_button.connect('clicked', self.remove) close_button.connect('button-press-event', self.on_title_click) close_button.set_tooltip_text(_("Delete Note")) self.title_bar.pack_end(close_button, False, False, 0) add_icon = Gtk.Image.new_from_icon_name('add', Gtk.IconSize.BUTTON) add_button = Gtk.Button(image=add_icon, relief=Gtk.ReliefStyle.NONE, name='window-button', valign=Gtk.Align.CENTER) add_button.connect('clicked', self.app.new_note) add_button.connect('button-press-event', self.on_title_click) add_button.set_tooltip_text(_("New Note")) self.title_bar.pack_end(add_button, False, False, 0) # test_icon = Gtk.Image.new_from_icon_name('system-run-symbolic', Gtk.IconSize.BUTTON) # test_button = Gtk.Button(image=test_icon, relief=Gtk.ReliefStyle.NONE, name='window-button', valign=Gtk.Align.CENTER) # test_button.connect('clicked', self.test) # test_button.connect('button-press-event', self.on_title_click) # self.title_bar.pack_end(test_button, False, False, 0) self.set_titlebar(self.title_bar) # buffer self.buffer = NoteBuffer() # text view self.view = Gtk.TextView(wrap_mode=Gtk.WrapMode.WORD_CHAR, populate_all=True, buffer=self.buffer) self.buffer.set_view(self.view) spell_checker = Gspell.TextView.get_from_gtk_text_view(self.view) spell_checker.basic_setup() self.app.settings.bind('inline-spell-check', spell_checker, 'inline-spell-checking', Gio.SettingsBindFlags.GET) self.view.set_left_margin(10) self.view.set_right_margin(10) self.view.set_top_margin(10) self.view.set_bottom_margin(10) self.view.connect('populate-popup', lambda w, p: self.add_context_menu_items(p)) self.view.connect('key-press-event', self.on_key_press) scroll = Gtk.ScrolledWindow() self.add(scroll) scroll.add(self.view) self.buffer.set_from_internal_markup(text) self.changed_id = self.buffer.connect('content-changed', self.changed) self.connect('configure-event', self.handle_update) self.connect('show', self.on_show) self.connect('window-state-event', self.update_window_state) self.move(self.x, self.y) self.show_all() def test(self, *args): self.buffer.test() def handle_update(self, *args): if self.showing: self.showing = False return (new_x, new_y) = self.get_position() (new_width, new_height) = self.get_size() if self.x == new_x and self.y == new_y and self.height == new_height and self.width == new_width: return self.x = new_x self.y = new_y self.height = new_height self.width = new_width self.emit('update') def on_show(self, *args): self.showing = True def on_key_press(self, v, event): if event.get_state() & Gdk.ModifierType.CONTROL_MASK: if event.get_state() & Gdk.ModifierType.SHIFT_MASK: if event.get_keyval()[1] == Gdk.KEY_Up: self.buffer.shift(True) return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_Down: self.buffer.shift(False) return Gdk.EVENT_STOP if event.get_keyval()[1] == Gdk.KEY_z: self.buffer.undo() return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_y: self.buffer.redo() return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_e: self.buffer.toggle_checklist() return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_l: self.buffer.toggle_bullets() return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_b: self.buffer.tag_selection('bold') return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_i: self.buffer.tag_selection('italic') return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_u: self.buffer.tag_selection('underline') return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_h: self.buffer.tag_selection('header') return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_k: self.buffer.tag_selection('strikethrough') return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_g: self.buffer.tag_selection('highlight') return Gdk.EVENT_STOP elif event.keyval in (Gdk.KEY_Return, Gdk.KEY_ISO_Enter, Gdk.KEY_KP_Enter): return self.buffer.on_return() return Gdk.EVENT_PROPAGATE def update_window_state(self, w, event): self.is_stuck = event.new_window_state & Gdk.WindowState.STICKY # for some reason, the ABOVE flag is never actually being set, even when it should be # self.is_pinned = event.new_window_state & Gdk.WindowState.ABOVE def on_title_click(self, w, event): if event.button == 3: menu = Gtk.Menu() self.add_context_menu_items(menu, True) menu.popup(None, None, None, None, event.button, event.time) return Gdk.EVENT_STOP return Gdk.EVENT_PROPAGATE def restore(self, time=0): if not self.get_visible(): self.present_with_time(time) self.move(self.x, self.y) self.get_window().focus(time) self.get_window().raise_() def changed(self, *args): self.emit('update') def get_info(self): (x, y) = self.get_position() (width, height) = self.get_size() info = { 'x': x, 'y': y, 'height': height, 'width': width, 'color': self.color, 'title': self.title.get_text(), 'text': self.buffer.get_internal_markup() } return info def add_context_menu_items(self, popup, is_title=False): if not is_title: popup.append(Gtk.SeparatorMenuItem(visible=True)) self.undo_item = Gtk.MenuItem(label=_("undo"), visible=True, sensitive=self.buffer.can_undo) self.undo_item.connect('activate', self.buffer.undo) popup.append(self.undo_item) self.redo_item = Gtk.MenuItem(label=_("redo"), visible=True, sensitive=self.buffer.can_redo) self.redo_item.connect('activate', self.buffer.redo) popup.append(self.redo_item) popup.append(Gtk.SeparatorMenuItem(visible=True)) label = _("Set Title") if self.title.get_text() == '' else _('Edit Title') edit_title = Gtk.MenuItem(label=label, visible=True) edit_title.connect('activate', self.set_title) popup.append(edit_title) remove_item = Gtk.MenuItem(label=_("Remove Note"), visible=True) remove_item.connect('activate', self.remove) popup.append(remove_item) if is_title: popup.append(Gtk.SeparatorMenuItem(visible=True)) if self.is_stuck: label = _("Only on This Workspace") def on_activate(*args): self.unstick() else: label = _("Always on Visible Workspace") def on_activate(*args): self.stick() stick_menu_item = Gtk.MenuItem(label=label, visible=True) stick_menu_item.connect('activate', on_activate) popup.append(stick_menu_item) def on_activate(*args): self.set_keep_above(not self.is_pinned) self.is_pinned = not self.is_pinned pin_menu_item = Gtk.CheckMenuItem(active=self.is_pinned, label=_("Always on Top"), visible=True) pin_menu_item.connect('activate', on_activate) popup.append(pin_menu_item) def show_more_menu(self, button): menu = Gtk.Menu() color_menu = Gtk.Menu() color_item = Gtk.MenuItem(label=_("Set Note Color"), submenu=color_menu, visible=True) menu.append(color_item) for color, color_name in COLORS.items(): menu_item = Gtk.MenuItem(label=color_name, visible=True) menu_item.connect('activate', self.set_color, color) color_menu.append(menu_item) menu.append(Gtk.SeparatorMenuItem(visible=True)) self.checklist_item = Gtk.MenuItem(label=_("Toggle Checklist"), visible=True) self.checklist_item.connect('activate', self.buffer.toggle_checklist) menu.append(self.checklist_item) self.bullet_item = Gtk.MenuItem(label=_("Toggle Bullets"), visible=True) self.bullet_item.connect('activate', self.buffer.toggle_bullets) menu.append(self.bullet_item) menu.append(Gtk.SeparatorMenuItem(visible=True)) bold_item = Gtk.MenuItem(label=_("Bold"), visible=True) bold_item.connect('activate', self.apply_format, 'bold') menu.append(bold_item) italic_item = Gtk.MenuItem(label=_("Italic"), visible=True) italic_item.connect('activate', self.apply_format, 'italic') menu.append(italic_item) underline_item = Gtk.MenuItem(label=_("Underline"), visible=True) underline_item.connect('activate', self.apply_format, 'underline') menu.append(underline_item) strikethrough_item = Gtk.MenuItem(label=_("Strikethrough"), visible=True) strikethrough_item.connect('activate', self.apply_format, 'strikethrough') menu.append(strikethrough_item) highlight_item = Gtk.MenuItem(label=_("Highlight"), visible=True) highlight_item.connect('activate', self.apply_format, 'highlight') menu.append(highlight_item) header_item = Gtk.MenuItem(label=_("Header"), visible=True) header_item.connect('activate', self.apply_format, 'header') menu.append(header_item) menu.popup_at_widget(button, Gdk.Gravity.SOUTH, Gdk.Gravity.NORTH_WEST, None) def set_color(self, menu, color): if color == self.color: return self.get_style_context().remove_class(self.color) self.get_style_context().add_class(color) self.color = color self.emit('update') def apply_format(self, m, format_type): self.buffer.tag_selection(format_type) def remove(self, *args): self.emit('removed') self.destroy() def set_edit_button_visibility(self, *args): pointer_device = self.get_display().get_default_seat().get_pointer() (mouse_x, mouse_y) = self.title_hover.get_window().get_device_position(pointer_device)[1:3] dimensions = self.title_hover.get_allocation() has_mouse = mouse_x >= 0 and mouse_x < dimensions.width and mouse_y >= 0 and mouse_y < dimensions.height if not isinstance(self.title, Gtk.Entry) and has_mouse: self.edit_title_button.show() else: self.edit_title_button.hide() def set_title(self, *args): self.title_text = self.title.get_text() self.title_box.remove(self.title) self.title = Gtk.Entry(text=self.title_text, visible=True) self.title_box.pack_start(self.title, False, False, 0) self.title.key_id = self.title.connect('key-press-event', self.save_title) self.title.focus_id = self.title.connect('focus-out-event', self.save_title) self.title_box.reorder_child(self.title, 0) self.set_edit_button_visibility() self.title.grab_focus() def save_title(self, w, event): save = False if event.type == Gdk.EventType.FOCUS_CHANGE: save = True else: if event.keyval in (Gdk.KEY_Return, Gdk.KEY_ISO_Enter, Gdk.KEY_KP_Enter): save = True elif event.keyval != Gdk.KEY_Escape: return Gdk.EVENT_PROPAGATE self.title.disconnect(self.title.key_id) self.title.disconnect(self.title.focus_id) if save: self.title_text = self.title.get_text() self.view.grab_focus() self.title_box.remove(self.title) self.title = Gtk.Label(label=self.title_text, visible=True) self.title_box.pack_start(self.title, False, False, 0) self.title_box.reorder_child(self.title, 0) self.set_edit_button_visibility() if save: self.emit('update') return Gdk.EVENT_STOP
class Note(Gtk.Window): @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, return_type=bool, accumulator=GObject.signal_accumulator_true_handled) def update(self): pass @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, return_type=bool, accumulator=GObject.signal_accumulator_true_handled) def removed(self): pass def __init__(self, app, parent, info={}): self.app = app self.showing = False self.is_pinned = False self.x = info.get('x', 0) self.y = info.get('y', 0) self.height = info.get('height', self.app.settings.get_uint('default-height')) self.width = info.get('width', self.app.settings.get_uint('default-width')) title = info.get('title', '') text = info.get('text', '') self.color = info.get('color', self.app.settings.get_string('default-color')) super(Note, self).__init__(skip_taskbar_hint=True, transient_for=parent, type_hint=Gdk.WindowTypeHint.UTILITY, default_height=self.height, default_width=self.width, resizable=True, deletable=False, name='sticky-note') if self.color == 'random': self.color = random.choice(list(COLORS.keys())) context = self.get_style_context() context.add_class(self.color) if self.app.settings.get_boolean('desktop-window-state'): self.stick() # title bar self.title_bar = Gtk.Box(height_request=30, name='title-bar') self.title_bar.connect('button-press-event', self.on_title_click) color_icon = Gtk.Image.new_from_icon_name('sticky-color', Gtk.IconSize.BUTTON) color_button = Gtk.MenuButton(image=color_icon, relief=Gtk.ReliefStyle.NONE, name='window-button', valign=Gtk.Align.CENTER) color_button.connect('button-press-event', self.on_title_click) color_button.set_tooltip_text(_("Format")) self.title_bar.pack_start(color_button, False, False, 0) # used to show the edit title icon when the title is hovered self.title_hover = HoverBox() self.title_bar.pack_start(self.title_hover, True, True, 4) self.title_box = Gtk.Box() self.title_hover.add(self.title_box) self.title = Gtk.Label(label=title, margin_top=4, name='title') self.title_box.pack_start(self.title, False, False, 0) self.title_style_manager = XApp.StyleManager(widget=self.title_box) edit_title_icon = Gtk.Image.new_from_icon_name('sticky-edit', Gtk.IconSize.BUTTON) self.edit_title_button = Gtk.Button(image=edit_title_icon, relief=Gtk.ReliefStyle.NONE, name='window-button', valign=Gtk.Align.CENTER) self.edit_title_button.connect('clicked', self.set_title) self.edit_title_button.connect('button-press-event', self.on_title_click) self.edit_title_button.set_tooltip_text(_("Rename")) self.title_box.pack_start(self.edit_title_button, False, False, 0) self.title_hover.set_child_widget(self.edit_title_button) close_icon = Gtk.Image.new_from_icon_name('sticky-delete', Gtk.IconSize.BUTTON) close_button = Gtk.Button(image=close_icon, relief=Gtk.ReliefStyle.NONE, name='window-button', valign=Gtk.Align.CENTER) close_button.connect('clicked', self.remove) close_button.connect('button-press-event', self.on_title_click) close_button.set_tooltip_text(_("Delete Note")) self.title_bar.pack_end(close_button, False, False, 0) add_icon = Gtk.Image.new_from_icon_name('sticky-add', Gtk.IconSize.BUTTON) add_button = Gtk.Button(image=add_icon, relief=Gtk.ReliefStyle.NONE, name='window-button', valign=Gtk.Align.CENTER) add_button.connect('clicked', self.app.new_note) add_button.connect('button-press-event', self.on_title_click) add_button.set_tooltip_text(_("New Note")) self.title_bar.pack_end(add_button, False, False, 0) text_icon = Gtk.Image.new_from_icon_name('sticky-text', Gtk.IconSize.BUTTON) text_button = Gtk.MenuButton(image=text_icon, relief=Gtk.ReliefStyle.NONE, name='window-button', valign=Gtk.Align.CENTER) text_button.connect('button-press-event', self.on_title_click) text_button.set_tooltip_text(_("Format")) self.title_bar.pack_end(text_button, False, False, 20) self.set_titlebar(self.title_bar) # buffer self.buffer = NoteBuffer() # text view self.view = Gtk.TextView(wrap_mode=Gtk.WrapMode.WORD_CHAR, populate_all=True, buffer=self.buffer) self.buffer.set_view(self.view) spell_checker = Gspell.TextView.get_from_gtk_text_view(self.view) spell_checker.basic_setup() self.app.settings.bind('inline-spell-check', spell_checker, 'inline-spell-checking', Gio.SettingsBindFlags.GET) self.view.set_left_margin(10) self.view.set_right_margin(10) self.view.set_top_margin(10) self.view.set_bottom_margin(10) self.view.connect('populate-popup', lambda w, p: self.add_context_menu_items(p)) self.view.connect('key-press-event', self.on_key_press) self.view_style_manager = XApp.StyleManager(widget=self.view) scroll = Gtk.ScrolledWindow() self.add(scroll) scroll.add(self.view) self.buffer.set_from_internal_markup(text) self.changed_id = self.buffer.connect('content-changed', self.changed) self.app.settings.connect('changed::font', self.set_font) self.set_font() self.create_format_menu(color_button, text_button) self.connect('configure-event', self.handle_update) self.connect('show', self.on_show) self.connect('window-state-event', self.update_window_state) self.move(self.x, self.y) self.show_all() def test(self, *args): self.buffer.test() def handle_update(self, *args): if self.showing: self.showing = False return (new_x, new_y) = self.get_position() (new_width, new_height) = self.get_size() if self.x == new_x and self.y == new_y and self.height == new_height and self.width == new_width: return self.x = new_x self.y = new_y self.height = new_height self.width = new_width self.emit('update') def on_show(self, *args): self.showing = True def on_key_press(self, v, event): if event.get_state() & Gdk.ModifierType.CONTROL_MASK: if event.get_state() & Gdk.ModifierType.SHIFT_MASK: if event.get_keyval()[1] == Gdk.KEY_Up: self.buffer.shift(True) return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_Down: self.buffer.shift(False) return Gdk.EVENT_STOP if event.get_keyval()[1] == Gdk.KEY_z: self.buffer.undo() return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_y: self.buffer.redo() return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_e: self.buffer.toggle_checklist() return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_l: self.buffer.toggle_bullets() return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_b: self.buffer.tag_selection('bold') return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_i: self.buffer.tag_selection('italic') return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_f: self.buffer.tag_selection('monospace') return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_u: self.buffer.tag_selection('underline') return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_h: self.buffer.tag_selection('header') return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_k: self.buffer.tag_selection('strikethrough') return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_g: self.buffer.tag_selection('highlight') return Gdk.EVENT_STOP elif event.keyval in (Gdk.KEY_Return, Gdk.KEY_ISO_Enter, Gdk.KEY_KP_Enter): return self.buffer.on_return() return Gdk.EVENT_PROPAGATE def update_window_state(self, w, event): self.is_stuck = event.new_window_state & Gdk.WindowState.STICKY # for some reason, the ABOVE flag is never actually being set, even when it should be # self.is_pinned = event.new_window_state & Gdk.WindowState.ABOVE def on_title_click(self, w, event): if event.button == 3: menu = Gtk.Menu() self.add_context_menu_items(menu, True) menu.popup(None, None, None, None, event.button, event.time) return Gdk.EVENT_STOP return Gdk.EVENT_PROPAGATE def restore(self, time=0): self.show() self.present_with_time(Gtk.get_current_event_time()) self.move(self.x, self.y) def changed(self, *args): self.emit('update') def get_info(self): (x, y) = self.get_position() (width, height) = self.get_size() info = { 'x': x, 'y': y, 'height': height, 'width': width, 'color': self.color, 'title': self.title.get_text(), 'text': self.buffer.get_internal_markup() } return info def add_context_menu_items(self, popup, is_title=False): if not is_title: popup.append(Gtk.SeparatorMenuItem(visible=True)) self.undo_item = Gtk.MenuItem(label=_("undo"), visible=True, sensitive=self.buffer.can_undo) self.undo_item.connect('activate', self.buffer.undo) popup.append(self.undo_item) self.redo_item = Gtk.MenuItem(label=_("redo"), visible=True, sensitive=self.buffer.can_redo) self.redo_item.connect('activate', self.buffer.redo) popup.append(self.redo_item) popup.append(Gtk.SeparatorMenuItem(visible=True)) label = _("Set Title") if self.title.get_text() == '' else _( 'Edit Title') edit_title = Gtk.MenuItem(label=label, visible=True) edit_title.connect('activate', self.set_title) popup.append(edit_title) remove_item = Gtk.MenuItem(label=_("Delete Note"), visible=True) remove_item.connect('activate', self.remove) popup.append(remove_item) if is_title: popup.append(Gtk.SeparatorMenuItem(visible=True)) if self.is_stuck: label = _("Only on This Workspace") def on_activate(*args): self.unstick() else: label = _("Always on Visible Workspace") def on_activate(*args): self.stick() stick_menu_item = Gtk.MenuItem(label=label, visible=True) stick_menu_item.connect('activate', on_activate) popup.append(stick_menu_item) def on_activate(*args): self.set_keep_above(not self.is_pinned) self.is_pinned = not self.is_pinned pin_menu_item = Gtk.CheckMenuItem(active=self.is_pinned, label=_("Always on Top"), visible=True) pin_menu_item.connect('activate', on_activate) popup.append(pin_menu_item) def create_format_menu(self, color_button, text_button): menu = Gtk.Menu() for color, color_name in sorted(COLORS.items(), key=lambda item: item[1]): color_code = COLOR_CODES[color] menu_item = Gtk.MenuItem(label=color_name, visible=True) menu_item.get_child().set_markup( "<span foreground='%s'>\u25A6</span> %s" % (color_code, color_name)) menu_item.connect('activate', self.set_color, color) menu.append(menu_item) color_button.set_popup(menu) menu = Gtk.Menu() bold_item = Gtk.MenuItem(label=_("Bold"), visible=True) bold_item.get_child().set_markup("<b>%s</b>" % _("Bold")) bold_item.connect('activate', self.apply_format, 'bold') menu.append(bold_item) italic_item = Gtk.MenuItem(label=_("Italic"), visible=True) italic_item.get_child().set_markup("<i>%s</i>" % _("Italic")) italic_item.connect('activate', self.apply_format, 'italic') menu.append(italic_item) monospace_item = Gtk.MenuItem(label=_("Fixed Width"), visible=True) monospace_item.get_child().set_markup("<tt>%s</tt>" % _("Fixed Width")) monospace_item.connect('activate', self.apply_format, 'monospace') menu.append(monospace_item) underline_item = Gtk.MenuItem(label=_("Underline"), visible=True) underline_item.get_child().set_markup("<u>%s</u>" % _("Underline")) underline_item.connect('activate', self.apply_format, 'underline') menu.append(underline_item) strikethrough_item = Gtk.MenuItem(label=_("Strikethrough"), visible=True) strikethrough_item.get_child().set_markup("<s>%s</s>" % _("Strikethrough")) strikethrough_item.connect('activate', self.apply_format, 'strikethrough') menu.append(strikethrough_item) highlight_item = Gtk.MenuItem(label=_("Highlight"), visible=True) highlight_item.get_child().set_markup( "<span background='yellow' foreground='black'>%s</span>" % _("Highlight")) highlight_item.connect('activate', self.apply_format, 'highlight') menu.append(highlight_item) header_item = Gtk.MenuItem(label=_("Header"), visible=True) header_item.get_child().set_markup("<span size='large'>%s</span>" % _("Header")) header_item.connect('activate', self.apply_format, 'header') menu.append(header_item) menu.append(Gtk.SeparatorMenuItem(visible=True)) self.checklist_item = Gtk.MenuItem(label="\u25A2 %s" % _("Toggle Checklist"), visible=True) self.checklist_item.connect('activate', self.buffer.toggle_checklist) menu.append(self.checklist_item) self.bullet_item = Gtk.MenuItem(label="\u25CF %s" % _("Toggle Bullets"), visible=True) self.bullet_item.connect('activate', self.buffer.toggle_bullets) menu.append(self.bullet_item) text_button.set_popup(menu) def set_color(self, menu, color): if color == self.color: return self.get_style_context().remove_class(self.color) self.get_style_context().add_class(color) self.color = color self.emit('update') def set_font(self, *args): self.title_style_manager.set_from_pango_font_string( self.app.settings.get_string('font')) self.view_style_manager.set_from_pango_font_string( self.app.settings.get_string('font')) def apply_format(self, m, format_type): self.buffer.tag_selection(format_type) def remove(self, *args): # this is ugly but I'm not sure how to make it look better :) if (self.app.settings.get_boolean('disable-delete-confirm') or confirm(_("Delete Note"), _("Are you sure you want to remove this note?"), self, self.app.settings, 'disable-delete-confirm')): self.emit('removed') self.destroy() def set_title(self, *args): self.title_text = self.title.get_text() self.title_box.remove(self.title) self.title = Gtk.Entry(text=self.title_text, visible=True, name='title') self.title_box.pack_start(self.title, False, False, 0) self.title.key_id = self.title.connect('key-press-event', self.save_title) self.title.focus_id = self.title.connect('focus-out-event', self.save_title) self.title_box.reorder_child(self.title, 0) self.title_hover.disable() self.title.grab_focus() def save_title(self, w, event): save = False if event.type == Gdk.EventType.FOCUS_CHANGE: save = True else: if event.keyval in (Gdk.KEY_Return, Gdk.KEY_ISO_Enter, Gdk.KEY_KP_Enter): save = True elif event.keyval != Gdk.KEY_Escape: return Gdk.EVENT_PROPAGATE self.title.disconnect(self.title.key_id) self.title.disconnect(self.title.focus_id) if save: self.title_text = self.title.get_text() self.view.grab_focus() self.title_box.remove(self.title) self.title = Gtk.Label(label=self.title_text, visible=True, name='title') self.title_box.pack_start(self.title, False, False, 0) self.title_box.reorder_child(self.title, 0) self.title_hover.enable() if save: self.emit('update') return Gdk.EVENT_STOP
class Note(Gtk.Window): @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, return_type=bool, accumulator=GObject.signal_accumulator_true_handled) def update(self): pass @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST, return_type=bool, accumulator=GObject.signal_accumulator_true_handled) def removed(self): pass def __init__(self, app, info={}): self.app = app self.showing = False self.x = info.get('x', 0) self.y = info.get('y', 0) self.height = info.get('height', self.app.settings.get_uint('default-height')) self.width = info.get('width', self.app.settings.get_uint('default-width')) title = info.get('title', '') text = info.get('text', '') self.color = info.get('color', self.app.settings.get_string('default-color')) super(Note, self).__init__( skip_taskbar_hint=True, # skip_pager_hint=False, type_hint=Gdk.WindowTypeHint.UTILITY, default_height=self.height, default_width=self.width, resizable=True, deletable=False, name='sticky-note') if self.color == 'random': self.color = random.choice(COLORS) context = self.get_style_context() context.add_class(self.color) self.stick() # title bar self.title_box = Gtk.Box(height_request=30, name='title-box') self.connect('button-press-event', self.on_title_click) self.title = Gtk.Label(label=title) self.title_box.pack_start(self.title, False, False, 0) close_icon = Gtk.Image.new_from_icon_name('window-close', Gtk.IconSize.BUTTON) close_button = Gtk.Button(image=close_icon, relief=Gtk.ReliefStyle.NONE, name='window-button', valign=Gtk.Align.CENTER) close_button.connect('clicked', self.remove) close_button.connect('button-press-event', self.on_title_click) self.title_box.pack_end(close_button, False, False, 0) add_icon = Gtk.Image.new_from_icon_name('add', Gtk.IconSize.BUTTON) add_button = Gtk.Button(image=add_icon, relief=Gtk.ReliefStyle.NONE, name='window-button', valign=Gtk.Align.CENTER) add_button.connect('clicked', self.app.new_note) add_button.connect('button-press-event', self.on_title_click) self.title_box.pack_end(add_button, False, False, 0) test_icon = Gtk.Image.new_from_icon_name('system-run-symbolic', Gtk.IconSize.BUTTON) test_button = Gtk.Button(image=test_icon, relief=Gtk.ReliefStyle.NONE, name='window-button', valign=Gtk.Align.CENTER) test_button.connect('clicked', self.test) test_button.connect('button-press-event', self.on_title_click) self.title_box.pack_end(test_button, False, False, 0) self.set_titlebar(self.title_box) # buffer self.buffer = NoteBuffer() tag = self.buffer.create_tag('red', foreground='red') # text view self.view = Gtk.TextView(wrap_mode=Gtk.WrapMode.WORD_CHAR, populate_all=True, buffer=self.buffer) self.buffer.view = self.view #fixme: this is an ugly hack so that we can add checkboxes and bullet points from the buffer self.view.set_left_margin(10) self.view.set_right_margin(10) self.view.set_top_margin(10) self.view.set_bottom_margin(10) self.view.connect('populate-popup', lambda w, p: self.add_context_menu_items(p)) self.view.connect('key-press-event', self.on_key_press) scroll = Gtk.ScrolledWindow() self.add(scroll) scroll.add(self.view) # self.buffer.begin_not_undoable_action() self.buffer.set_from_internal_markup(text) # self.buffer.end_not_undoable_action() self.changed_id = self.buffer.connect('changed', self.changed) self.connect('configure-event', self.handle_update) self.connect('show', self.on_show) self.move(self.x, self.y) self.show_all() def test(self, *args): self.buffer.tag_selection('red') # print(self.get_text_test()) def handle_update(self, *args): if self.showing: self.showing = False return (new_x, new_y) = self.get_position() (new_width, new_height) = self.get_size() if self.x == new_x and self.y == new_y and self.height == new_height and self.width == new_width: return self.x = new_x self.y = new_y self.height = new_height self.width = new_width self.emit('update') def on_show(self, *args): self.showing = True def on_key_press(self, v, event): # moving to note_buffer # if event.get_keyval()[1] in (Gdk.KEY_Return, Gdk.KEY_ISO_Enter, Gdk.KEY_KP_Enter): # return self.maybe_repeat() if event.get_state() & Gdk.ModifierType.CONTROL_MASK: if event.get_keyval()[1] == Gdk.KEY_z: self.buffer.undo() return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_y: self.buffer.redo() return Gdk.EVENT_STOP elif event.get_keyval()[1] == Gdk.KEY_e: self.buffer.toggle_checklist() return Gdk.EVENT_STOP return Gdk.EVENT_PROPAGATE def on_title_click(self, w, event): if event.button == 3: menu = Gtk.Menu() self.add_context_menu_items(menu, True) menu.popup(None, None, None, None, event.button, event.time) return Gdk.EVENT_STOP return Gdk.EVENT_PROPAGATE def restore(self, time=0): if not self.get_visible(): self.present_with_time(time) self.move(self.x, self.y) self.get_window().focus(time) self.get_window().raise_() def changed(self, *args): self.emit('update') def get_info(self): (x, y) = self.get_position() (width, height) = self.get_size() info = { 'x': x, 'y': y, 'height': height, 'width': width, 'color': self.color, 'title': self.title.get_text(), 'text': self.buffer.get_internal_markup() } return info # moving to note_buffer # def get_text(self): # (start, end) = self.buffer.get_bounds() # raw_text = self.buffer.get_slice(start, end, True) # inserted_objects = [] # current_tags = [] # text = '' # index = 0 # while True: # index = raw_text.find('\ufffc', index+1) # if index == -1: # break # inserted_objects.append(self.buffer.get_iter_at_offset(index)) # # current_iter: is our placeholder for where we start copying from # # current_insert: the next occurrence of an inserted object (bullet, checkbox, etc) # # next_iter: is where we find the next set of tag open/close in the buffer # current_iter = start.copy() # current_insert = inserted_objects.pop(0) if len(inserted_objects) > 0 else end # while True: # next_iter = current_iter.copy() # next_iter.forward_to_tag_toggle() # # if there happens to be an inserted object before the next tag, we handle that first, but otherwise we # # want to close out our tags first, though it probably doesn't matter since the check boxes are affected # # by the formatting and vice-versa # if current_insert.compare(next_iter) < 0: # text += self.buffer.get_slice(current_iter, current_insert, False).replace('#', '##') # try: # checked = current_insert.get_child_anchor().get_widgets()[0].get_active() # text += '#check:' + str(int(checked)) # except: # pass # current_iter = current_insert.copy() # current_iter.forward_char() # current_insert = inserted_objects.pop(0) if len(inserted_objects) > 0 else end # else: # text += self.buffer.get_slice(current_iter, next_iter, False).replace('#', '##') # # if not all tags are closed, we still need to keep track of them, but leaving them in the list will # # cause an infinite loop, so we hold on to them in unclosed_tags and re-add them after exiting the loop # unclosed_tags = [] # tags = next_iter.get_toggled_tags(False) # while len(current_tags) and len(tags): # tag = current_tags.pop() # if len(tags) == 0 or tag not in tags: # unclosed_tags.append(tag) # continue # text += '#tag:%s:' % tag.props.name # tags.remove(tag) # current_tags += unclosed_tags # tags = next_iter.get_toggled_tags(True) # while len(tags): # tag = tags.pop() # name = tag.props.name # text += '#tag:%s:' % tag.props.name # current_tags.append(tag) # current_iter = next_iter # if current_iter.compare(end) == 0: # break # return text # # moving to note_buffer # def set_text(self, text): # current_index = 0 # open_tags = {} # while True: # next_index = text.find('#', current_index) # if next_index == -1: # self.buffer.insert(self.buffer.get_end_iter(), text[current_index:]) # break # self.buffer.insert(self.buffer.get_end_iter(), text[current_index:next_index]) # if text[next_index:next_index+2] == '##': # self.buffer.insert(self.buffer.get_end_iter(), '#') # current_index = next_index + 2 # elif text[next_index:next_index+6] == '#check': # checked = bool(int(text[next_index+7])) # self.add_check_button(self.buffer.get_end_iter(), checked=checked) # current_index = next_index + 8 # elif text[next_index:next_index+4] == '#tag': # end_tag_index = text.find(':', next_index+6) # tag_name = text[next_index+5:end_tag_index] # if tag_name in open_tags: # mark = open_tags.pop(tag_name) # start = self.buffer.get_iter_at_mark(mark) # end = self.buffer.get_end_iter() # self.buffer.apply_tag_by_name(tag_name, start, end) # self.buffer.delete_mark(mark) # else: # open_tags[tag_name] = self.buffer.create_mark(None, self.buffer.get_end_iter(), True) # current_index = next_index + 6 + len(tag_name) def add_context_menu_items(self, popup, is_title=False): if not is_title: popup.append(Gtk.SeparatorMenuItem(visible=True)) self.undo_item = Gtk.MenuItem(label=_("undo"), visible=True, sensitive=self.buffer.can_undo) self.undo_item.connect('activate', self.buffer.undo) popup.append(self.undo_item) self.redo_item = Gtk.MenuItem(label=_("redo"), visible=True, sensitive=self.buffer.can_redo) self.redo_item.connect('activate', self.buffer.redo) popup.append(self.redo_item) popup.append(Gtk.SeparatorMenuItem(visible=True)) self.checklist_item = Gtk.MenuItem(label=_("Toggle Checklist"), visible=True) self.checklist_item.connect('activate', self.buffer.toggle_checklist) popup.append(self.checklist_item) self.bullet_item = Gtk.MenuItem(label=_("Toggle Bullets"), visible=True) self.bullet_item.connect('activate', self.buffer.toggle_bullets) popup.append(self.bullet_item) popup.append(Gtk.SeparatorMenuItem(visible=True)) color_menu = Gtk.Menu() color_item = Gtk.MenuItem(label=_("Set Color"), submenu=color_menu, visible=True) popup.append(color_item) for color, color_name in COLORS.items(): menu_item = Gtk.MenuItem(label=color_name, visible=True) menu_item.connect('activate', self.set_color, color) color_menu.append(menu_item) label = _("Set Title") if self.title.get_text() == '' else _( 'Edit Title') edit_title = Gtk.MenuItem(label=label, visible=True) edit_title.connect('activate', self.set_title) popup.append(edit_title) remove_item = Gtk.MenuItem(label=_("Remove"), visible=True) remove_item.connect('activate', self.remove) popup.append(remove_item) def set_color(self, menu, color): if color == self.color: return self.get_style_context().remove_class(self.color) self.get_style_context().add_class(color) self.color = color self.emit('update') def remove(self, *args): self.emit('removed') self.destroy() def set_title(self, *args): self.title_text = self.title.get_text() self.title_box.remove(self.title) self.title = Gtk.Entry(text=self.title_text, visible=True) self.title_box.pack_start(self.title, True, True, 0) self.title.connect('key-press-event', self.save_title) self.title.connect('focus-out-event', self.save_title) self.title.grab_focus() def save_title(self, w, event): save = False enter_keys = (Gdk.KEY_Return, Gdk.KEY_ISO_Enter, Gdk.KEY_KP_Enter) if event.type == Gdk.EventType.FOCUS_CHANGE or event.keyval in enter_keys: self.title_text = self.title.get_text() save = True elif event.keyval != Gdk.KEY_Escape: return Gdk.EVENT_PROPAGATE self.view.grab_focus() self.title_box.remove(self.title) self.title = Gtk.Label(label=self.title_text, visible=True) self.title_box.pack_start(self.title, False, False, 0) if save: self.emit('update') return Gdk.EVENT_STOP
class NoteEntry(Gtk.Container): initialized = False def __init__(self, item): super(NoteEntry, self).__init__(height_request=150, width_request=150, valign=Gtk.Align.START, halign=Gtk.Align.CENTER, margin=10) self.item = item self.set_has_window(False) self.buffer = NoteBuffer() self.text = Gtk.TextView(wrap_mode=Gtk.WrapMode.WORD_CHAR, populate_all=True, buffer=self.buffer, visible=True, sensitive=False) self.buffer.set_view(self.text) self.buffer.set_from_internal_markup(item.text) self.text.set_parent(self) self.initialized = True self.show_all() def do_size_allocate(self, allocation): Gtk.Widget.do_size_allocate(self, allocation) rect = Gdk.Rectangle() rect.x = allocation.x + 10 rect.y = allocation.y + 10 rect.width = allocation.width - 20 rect.height = allocation.height - 20 self.text.size_allocate(rect) self.set_clip(allocation) def do_get_preferred_height(self): return (150, 150) def do_get_preferred_height_for_width(self, width): return (150, 150) def do_get_preferred_width(self): return (150, 150) def do_get_preferred_width_for_height(self, height): return (150, 150) def do_destroy(self): if not self.initialized: return self.text.unparent() self.initialized = False Gtk.Container.do_destroy(self) def do_forall(self, include_internals, callback, *args): if include_internals: callback(self.text, *args)