Пример #1
0
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
Пример #2
0
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
Пример #3
0
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