Daniel Brötzmann pushed to branch message-selection at gajim / gajim
Commits: cdb976dc by wurstsalat at 2022-10-30T14:27:07+01:00 feat: Add action to copy multiple messages
Fixes #10975
- - - - -
7 changed files:
- gajim/data/style/gajim.css - gajim/gtk/const.py - gajim/gtk/control.py - + gajim/gtk/conversation/message_selection.py - gajim/gtk/conversation/rows/message.py - gajim/gtk/conversation/rows/widgets.py - gajim/gtk/conversation/view.py
Changes:
===================================== gajim/data/style/gajim.css ===================================== @@ -686,6 +686,15 @@ #GroupchatConfig stacksidebar > scrolledwindow { } #GroupchatConfig treeview { padding: 5px; }
+.message-selection-box { + background-color: alpha(rgb(70, 70, 70), 0.9); + padding: 12px; + border-radius: 6px; +} +.message-selection-box > label { + color: rgb(230, 230, 230); +} + .groupchat-state box { background-color: alpha(rgb(70, 70, 70), 0.9); padding: 12px;
===================================== gajim/gtk/const.py ===================================== @@ -192,6 +192,7 @@ def __str__(self): ('input-strike', None, False), ('input-clear', None, False), ('show-emoji-chooser', None, False), + ('activate-message-selection', 'u', True), ('correct-message', None, False), ('quote', 's', False), ('mention', 's', False),
===================================== gajim/gtk/control.py ===================================== @@ -23,6 +23,7 @@ import logging import time
+from gi.repository import Gio from gi.repository import Gtk from gi.repository import GLib
@@ -48,8 +49,9 @@ from gajim.common.modules.contacts import GroupchatContact from gajim.common.modules.httpupload import HTTPFileTransfer from gajim.common.storage.archive import ConversationRow -from gajim.gtk.conversation.view import ConversationView
+from gajim.gui.conversation.message_selection import MessageSelection +from gajim.gui.conversation.view import ConversationView from gajim.gui.conversation.jump_to_end_button import JumpToEndButton from gajim.gui.builder import get_builder from gajim.gui.groupchat_roster import GroupchatRoster @@ -79,6 +81,11 @@ def __init__(self) -> None: self._groupchat_state = GroupchatState() self._ui.conv_view_overlay.add_overlay(self._groupchat_state)
+ self._message_selection = MessageSelection() + self._message_selection.connect('copy', self._on_copy_selection) + self._message_selection.connect('cancel', self._on_cancel_selection) + self._ui.conv_view_overlay.add_overlay(self._message_selection) + self._jump_to_end_button = JumpToEndButton() self._jump_to_end_button.connect('clicked', self._on_jump_to_end) self._ui.conv_view_overlay.add_overlay(self._jump_to_end_button) @@ -90,6 +97,9 @@ def __init__(self) -> None: # Used with encryption plugins self.sendmessage = False
+ app.window.get_action('activate-message-selection').connect( + 'activate', self._on_activate_message_selection) + self.widget = cast(Gtk.Box, self._ui.get_object('control_box')) self.widget.show_all()
@@ -452,6 +462,22 @@ def _on_autoscroll_changed(self, if app.window.is_chat_active(self.contact.account, self.contact.jid): app.window.mark_as_read(self.contact.account, self.contact.jid)
+ def _on_activate_message_selection(self, + _action: Gio.SimpleAction, + param: GLib.Variant + ) -> None: + + log_line_id = param.get_uint32() + self._scrolled_view.enable_message_selection(log_line_id) + self._message_selection.set_no_show_all(False) + self._message_selection.show_all() + + def _on_copy_selection(self, _widget: MessageSelection) -> None: + self._scrolled_view.copy_selected_messages() + + def _on_cancel_selection(self, _widget: MessageSelection) -> None: + self._scrolled_view.disable_message_selection() + def _on_jump_to_end(self, _button: Gtk.Button) -> None: self.reset_view()
===================================== gajim/gtk/conversation/message_selection.py ===================================== @@ -0,0 +1,58 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see http://www.gnu.org/licenses/. + +from __future__ import annotations + +from gi.repository import GObject +from gi.repository import Gtk + +from gajim.common.i18n import _ + + +class MessageSelection(Gtk.Grid): + + __gsignals__ = { + 'copy': (GObject.SignalFlags.RUN_LAST, None, ()), + 'cancel': (GObject.SignalFlags.RUN_LAST, None, ()), + } + + def __init__(self) -> None: + Gtk.Grid.__init__(self, row_spacing=18, column_spacing=6) + self.set_no_show_all(True) + self.set_halign(Gtk.Align.CENTER) + self.set_valign(Gtk.Align.END) + + self.get_style_context().add_class('message-selection-box') + + label = Gtk.Label(label=_('Click messages to select them')) + self.attach(label, 0, 0, 2, 1) + + copy_button = Gtk.Button(label=_('Copy Text')) + copy_button.get_style_context().add_class('suggested-action') + copy_button.connect('clicked', self._on_copy_clicked) + self.attach(copy_button, 0, 1, 1, 1) + + cancel_button = Gtk.Button(label=_('Cancel')) + cancel_button.connect('clicked', self._on_cancel_clicked) + self.attach(cancel_button, 1, 1, 1, 1) + + def _on_copy_clicked(self, _button: Gtk.Button) -> None: + self.set_no_show_all(True) + self.hide() + self.emit('copy') + + def _on_cancel_clicked(self, _button: Gtk.Button) -> None: + self.set_no_show_all(True) + self.hide() + self.emit('cancel')
===================================== gajim/gtk/conversation/rows/message.py ===================================== @@ -299,17 +299,20 @@ def is_mergeable(self, message: MessageRow) -> bool: return False return abs(message.timestamp - self.timestamp) < MERGE_TIMEFRAME
+ def get_text(self) -> str: + return self._message_widget.get_text() + def on_copy_message(self, _widget: Gtk.Widget) -> None: time_format = from_one_line(app.settings.get('chat_timestamp_format')) timestamp_formatted = self.timestamp.strftime(time_format)
clip = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) - text = self._message_widget.get_text() + text = self.get_text() clip.set_text(f'{timestamp_formatted} - {self.name}: {text}', -1)
def on_quote_message(self, _widget: Gtk.Widget) -> None: app.window.activate_action( - 'quote', GLib.Variant('s', self._message_widget.get_text())) + 'quote', GLib.Variant('s', self.get_text()))
def on_correct_message(self, _widget: Gtk.Widget) -> None: app.window.activate_action('correct-message', None)
===================================== gajim/gtk/conversation/rows/widgets.py ===================================== @@ -79,7 +79,7 @@ def _on_click(self, _button: Gtk.Button) -> None: show_retract = False if isinstance(self._contact, GroupchatContact): if not self._contact.is_joined: - self._create_popover(show_retract=False) + self._create_popover() return
contact = self._contact.get_resource(self._name) @@ -99,13 +99,14 @@ def _on_click(self, _button: Gtk.Button) -> None: show_correction = app.window.is_message_correctable( self._contact, self._row.message_id)
- self._create_popover(show_retract=show_retract, - show_correction=show_correction) + self._create_popover(show_retract, + show_correction)
def _create_popover(self, show_retract: bool = False, show_correction: bool = False ) -> None: + menu_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6) menu_box.get_style_context().add_class('padding-6')
@@ -135,6 +136,14 @@ def _create_popover(self, 'edit-copy-symbolic', Gtk.IconSize.MENU)) menu_box.add(copy_button)
+ select_button = Gtk.ModelButton() + select_button.set_halign(Gtk.Align.START) + select_button.connect('clicked', self._on_activate_message_selection) + select_button.set_label(Q_('?Message row action:Select Messages…')) + select_button.set_image(Gtk.Image.new_from_icon_name( + 'edit-select-symbolic', Gtk.IconSize.MENU)) + menu_box.add(select_button) + if show_correction: correct_button = Gtk.ModelButton() correct_button.set_halign(Gtk.Align.START) @@ -164,6 +173,11 @@ def _create_popover(self, popover.connect('closed', self._on_closed) popover.popup()
+ def _on_activate_message_selection(self, _button: Gtk.ModelButton) -> None: + app.window.activate_action( + 'activate-message-selection', + GLib.Variant('u', self._row.log_line_id)) + @staticmethod def _on_closed(popover: Gtk.Popover) -> None: GLib.idle_add(popover.destroy)
===================================== gajim/gtk/conversation/view.py ===================================== @@ -27,6 +27,7 @@ from datetime import datetime from datetime import timedelta
+from gi.repository import Gdk from gi.repository import Gtk from gi.repository import GObject from gi.repository import Gio @@ -41,6 +42,7 @@ from gajim.common import types from gajim.common import events from gajim.common.helpers import AdditionalDataDict +from gajim.common.helpers import from_one_line from gajim.common.helpers import to_user_string from gajim.common.helpers import get_start_of_day from gajim.common.modules.contacts import GroupchatContact @@ -141,6 +143,33 @@ def __init__(self) -> None: app.window.get_action('scroll-view-down').connect( 'activate', self._on_scroll_view)
+ def copy_selected_messages(self) -> None: + time_format = from_one_line(app.settings.get('chat_timestamp_format')) + selection_text = '' + for row in cast(list[BaseRow], self._list_box.get_selected_rows()): + if isinstance(row, MessageRow): + timestamp_formatted = row.timestamp.strftime(time_format) + selection_text += (f'{timestamp_formatted} - {row.name}: ' + f'{row.get_text()}\n') + + clip = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD) + clip.set_text(selection_text, -1) + + self.disable_message_selection() + + def enable_message_selection(self, + log_line_id: Optional[int] + ) -> None: + + self._list_box.set_selection_mode(Gtk.SelectionMode.MULTIPLE) + if log_line_id is not None: + row = self.get_row_by_log_line_id(log_line_id) + self._list_box.select_row(row) + + def disable_message_selection(self) -> None: + self._list_box.unselect_all() + self._list_box.set_selection_mode(Gtk.SelectionMode.NONE) + def _on_scroll_view(self, action: Gio.SimpleAction, _param: Literal[None]) -> None:
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/commit/cdb976dc654515bda4be52926b1fb35d7...