Daniel Brötzmann pushed to branch master at gajim / gajim
Commits: ee5e4ef0 by wurstsalat at 2021-12-23T12:22:52+01:00 Add emoji parsing for Windows
- - - - - 482e4fea by wurstsalat at 2021-12-23T12:28:53+01:00 MessageInput: Rename buffer var
- - - - -
3 changed files:
- gajim/common/styling.py - gajim/gtk/conversation/plain_widget.py - gajim/gtk/message_input.py
Changes:
===================================== gajim/common/styling.py ===================================== @@ -24,6 +24,8 @@
from gi.repository import GLib
+from gajim.gui.emoji_data import emoji_data + PRE = '`' STRONG = '*' STRIKE = '~' @@ -47,6 +49,8 @@ ADDRESS_RX = r'(\b(?P<protocol>(xmpp|mailto)+:)?[\w-]*@(.*?.)+[\w]+([?].*?(?=([\s),]|$)))?)' ADDRESS_RX = re.compile(ADDRESS_RX)
+EMOJI_RX = emoji_data.get_regex() +EMOJI_RX = re.compile(EMOJI_RX)
SD = 0 SD_POS = 1 @@ -90,6 +94,11 @@ class MailAddress(BaseUri): name: str = field(default='mailadr', init=False)
+@dataclass +class Emoji(StyleObject): + name: str = field(default='emoji', init=False) + + @dataclass class Block(StyleObject):
@@ -105,6 +114,7 @@ class PlainBlock(Block): name: str = field(default='plain', init=False) spans: list[Span] = field(default_factory=list) uris: list[BaseUri] = field(default_factory=list) + emojis: list[Emoji] = field(default_factory=list)
@dataclass @@ -189,6 +199,7 @@ def process(text: Union[str, bytes], nested: bool = False) -> ParsingResult: for line in block.text.splitlines(keepends=True): block.spans += _parse_line(line, offset, offset_bytes) block.uris += _parse_uris(line, offset, offset_bytes) + block.emojis += _parse_emojis(line, offset, offset_bytes) offset += len(line) offset_bytes += len(line.encode())
@@ -309,6 +320,23 @@ def make(match: Match[str], is_address: bool) -> BaseUri: return uris
+def _parse_emojis(line: str, offset: int, offset_bytes: int) -> list[Emoji]: + emojis: list[Emoji] = [] + + def make(match: Match[str]) -> Emoji: + return _make_emoji(line, + match.start(), + match.end() - 1, + offset, + offset_bytes) + + for match in EMOJI_RX.finditer(line): + emoji = make(match) + emojis.append(emoji) + + return emojis + + def _handle_pre_span(line: str, index: int, offset: int, @@ -386,6 +414,19 @@ def _make_link(line: str, text=text)
+def _make_emoji(line: str, + start: int, + end: int, + offset: int, + offset_bytes: int) -> Emoji: + text = line[start:end + 1] + start += offset + end += offset + 1 + return Emoji(start=start, + end=end, + text=text) + + def _is_span_empty(sd: str, index: int, stack: list[tuple[str, int]]) -> bool: start_sd = stack[-1][SD] if start_sd != sd:
===================================== gajim/gtk/conversation/plain_widget.py ===================================== @@ -31,6 +31,9 @@ from gajim.common.structs import URI from gajim.common.styling import PlainBlock
+from gajim.gui.emoji_data import emoji_pixbufs +from gajim.gui.emoji_data import get_emoji_pixbuf + from gajim.gui_menu_builder import get_conv_action_context_menu from gajim.gui_menu_builder import get_conv_uri_context_menu
@@ -205,6 +208,33 @@ def print_text_with_styling(self, block: PlainBlock) -> None: end_iter = buffer_.get_iter_at_offset(uri.end) buffer_.apply_tag_by_name(uri.name, start_iter, end_iter)
+ for emoji in block.emojis: + start_iter = buffer_.get_iter_at_offset(emoji.start) + end_iter = buffer_.get_iter_at_offset(emoji.end) + if emoji_pixbufs.complete: + # Only search for pixbuf if loading is completed + pixbuf = get_emoji_pixbuf(emoji.text) + if pixbuf is None: + buffer_.insert(end_iter, emoji.text) + else: + pixbuf = pixbuf.copy() + anchor = buffer_.create_child_anchor(end_iter) + anchor.plaintext = emoji.text + img = Gtk.Image.new_from_pixbuf(pixbuf) + img.show() + self.add_child_at_anchor(img, anchor) + buffer_.delete( + buffer_.get_iter_at_offset(emoji.start), + buffer_.get_iter_at_offset(emoji.end)) + else: + # Set marks and save them so we can replace emojis + # once loading is complete + start_mark = buffer_.create_mark(None, end_iter, True) + buffer_.insert(end_iter, emoji.text) + end_mark = buffer_.create_mark(None, end_iter, True) + emoji_pixbufs.append_marks( + self, start_mark, end_mark, emoji.text) + def add_action_phrase(self, text: str) -> None: buffer_ = self.get_buffer() buffer_.insert(buffer_.get_start_iter(), text.strip())
===================================== gajim/gtk/message_input.py ===================================== @@ -102,11 +102,11 @@ def _on_focus_out(self, _widget, _event): return False
def _clear_tags(self) -> None: - _buffer = self.get_buffer() - start, end = _buffer.get_bounds() - _buffer.remove_all_tags(start, end) + buf = self.get_buffer() + start, end = buf.get_bounds() + buf.remove_all_tags(start, end)
- def _on_text_changed(self, buffer_: Gtk.TextBuffer) -> None: + def _on_text_changed(self, buf: Gtk.TextBuffer) -> None: text = self.get_text() if not text: return @@ -116,19 +116,19 @@ def _on_text_changed(self, buffer_: Gtk.TextBuffer) -> None: for block in result.blocks: if block.name == 'plain': for span in block.spans: - start_iter = buffer_.get_iter_at_offset(span.start) - end_iter = buffer_.get_iter_at_offset(span.end) - buffer_.apply_tag_by_name(span.name, start_iter, end_iter) + start_iter = buf.get_iter_at_offset(span.start) + end_iter = buf.get_iter_at_offset(span.end) + buf.apply_tag_by_name(span.name, start_iter, end_iter)
def insert_text(self, text: str) -> None: self.get_buffer().insert_at_cursor(text)
def insert_newline(self) -> None: - buffer_ = self.get_buffer() - buffer_.insert_at_cursor('\n') - mark = buffer_.get_insert() - iter_ = buffer_.get_iter_at_mark(mark) - if buffer_.get_end_iter().equal(iter_): + buf = self.get_buffer() + buf.insert_at_cursor('\n') + mark = buf.get_insert() + iter_ = buf.get_iter_at_mark(mark) + if buf.get_end_iter().equal(iter_): GLib.idle_add(scroll_to_end, self.get_parent())
def has_text(self) -> bool: @@ -150,63 +150,63 @@ def toggle_speller(self, activate: bool) -> None:
@staticmethod def _after_paste_clipboard(textview: Gtk.TextView) -> None: - buffer_ = textview.get_buffer() - mark = buffer_.get_insert() - iter_ = buffer_.get_iter_at_mark(mark) - if iter_.get_offset() == buffer_.get_end_iter().get_offset(): + buf = textview.get_buffer() + mark = buf.get_insert() + iter_ = buf.get_iter_at_mark(mark) + if iter_.get_offset() == buf.get_end_iter().get_offset(): GLib.idle_add(scroll_to_end, textview.get_parent())
def _get_active_iters(self) -> Tuple[Gtk.TextIter, Gtk.TextIter]: - _buffer = self.get_buffer() - return_val = _buffer.get_selection_bounds() + buf = self.get_buffer() + return_val = buf.get_selection_bounds() if return_val: # if something is selected start, end = return_val[0], return_val[1] else: - start, end = _buffer.get_bounds() + start, end = buf.get_bounds() return (start, end)
def apply_formatting(self, formatting: str) -> None: format_char = FORMAT_CHARS[formatting]
- _buffer = self.get_buffer() + buf = self.get_buffer() start, end = self._get_active_iters() start_offset = start.get_offset() end_offset = end.get_offset()
- text = _buffer.get_text(start, end, True) + text = buf.get_text(start, end, True) if text.startswith(format_char) and text.endswith(format_char): # (Selected) text begins and ends with formatting chars # -> remove them - _buffer.delete( + buf.delete( start, - _buffer.get_iter_at_offset(start_offset + 1)) - _buffer.delete( - _buffer.get_iter_at_offset(end_offset - 2), - _buffer.get_iter_at_offset(end_offset - 1)) + buf.get_iter_at_offset(start_offset + 1)) + buf.delete( + buf.get_iter_at_offset(end_offset - 2), + buf.get_iter_at_offset(end_offset - 1)) return
- ext_start = _buffer.get_iter_at_offset(start_offset - 1) - ext_end = _buffer.get_iter_at_offset(end_offset + 1) - ext_text = _buffer.get_text(ext_start, ext_end, True) + ext_start = buf.get_iter_at_offset(start_offset - 1) + ext_end = buf.get_iter_at_offset(end_offset + 1) + ext_text = buf.get_text(ext_start, ext_end, True) if ext_text.startswith(format_char) and ext_text.endswith(format_char): # (Selected) text is surrounded by formatting chars -> remove them - _buffer.delete( + buf.delete( ext_start, - _buffer.get_iter_at_offset(start_offset)) - _buffer.delete( - _buffer.get_iter_at_offset(end_offset - 1), - _buffer.get_iter_at_offset(end_offset)) + buf.get_iter_at_offset(start_offset)) + buf.delete( + buf.get_iter_at_offset(end_offset - 1), + buf.get_iter_at_offset(end_offset)) return
# No formatting chars found at start/end or surrounding -> add them - _buffer.insert(start, format_char, -1) - _buffer.insert( - _buffer.get_iter_at_offset(end_offset + 1), + buf.insert(start, format_char, -1) + buf.insert( + buf.get_iter_at_offset(end_offset + 1), format_char, -1) - _buffer.select_range( - _buffer.get_iter_at_offset(start_offset), - _buffer.get_iter_at_offset(end_offset + 2)) + buf.select_range( + buf.get_iter_at_offset(start_offset), + buf.get_iter_at_offset(end_offset + 2))
def replace_emojis(self) -> None: theme = app.settings.get('emoticons_theme') @@ -229,35 +229,35 @@ def _replace(anchor): _replace(iter_.get_child_anchor())
def _replace_char_at_iter(self, iter_, new_char): - buffer_ = self.get_buffer() + buf = self.get_buffer() iter_2 = iter_.copy() iter_2.forward_char() - buffer_.delete(iter_, iter_2) - buffer_.insert(iter_, new_char) + buf.delete(iter_, iter_2) + buf.insert(iter_, new_char)
def insert_emoji(self, codepoint, pixbuf): - buffer_ = self.get_buffer() - if buffer_.get_char_count(): + buf = self.get_buffer() + if buf.get_char_count(): # buffer contains text - buffer_.insert_at_cursor(' ') + buf.insert_at_cursor(' ')
- insert_mark = buffer_.get_insert() - insert_iter = buffer_.get_iter_at_mark(insert_mark) + insert_mark = buf.get_insert() + insert_iter = buf.get_iter_at_mark(insert_mark)
if pixbuf is None: - buffer_.insert(insert_iter, codepoint) + buf.insert(insert_iter, codepoint) else: - anchor = buffer_.create_child_anchor(insert_iter) + anchor = buf.create_child_anchor(insert_iter) image = Gtk.Image.new_from_pixbuf(pixbuf) image.codepoint = codepoint image.show() self.add_child_at_anchor(image, anchor) - buffer_.insert_at_cursor(' ') + buf.insert_at_cursor(' ')
def clear(self, *args): - _buffer = self.get_buffer() - start, end = _buffer.get_bounds() - _buffer.delete(start, end) + buf = self.get_buffer() + start, end = buf.get_bounds() + buf.delete(start, end)
def save_undo(self, text: str) -> None: self._undo_list.append(text) @@ -266,7 +266,7 @@ def save_undo(self, text: str) -> None: self.undo_pressed = False
def undo(self, *args): - _buffer = self.get_buffer() + buf = self.get_buffer() if self._undo_list: - _buffer.set_text(self._undo_list.pop()) + buf.set_text(self._undo_list.pop()) self.undo_pressed = True
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/3ca29b0389eb05e02079cd6f723e2fcd...