Daniel Brötzmann pushed to branch master at gajim / gajim

Commits:

3 changed files:

Changes:

  • gajim/common/styling.py
    ... ... @@ -24,6 +24,8 @@
    24 24
     
    
    25 25
     from gi.repository import GLib
    
    26 26
     
    
    27
    +from gajim.gui.emoji_data import emoji_data
    
    28
    +
    
    27 29
     PRE = '`'
    
    28 30
     STRONG = '*'
    
    29 31
     STRIKE = '~'
    
    ... ... @@ -47,6 +49,8 @@
    47 49
     ADDRESS_RX = r'(\b(?P<protocol>(xmpp|mailto)+:)?[\w-]*@(.*?\.)+[\w]+([\?].*?(?=([\s\),]|$)))?)'
    
    48 50
     ADDRESS_RX = re.compile(ADDRESS_RX)
    
    49 51
     
    
    52
    +EMOJI_RX = emoji_data.get_regex()
    
    53
    +EMOJI_RX = re.compile(EMOJI_RX)
    
    50 54
     
    
    51 55
     SD = 0
    
    52 56
     SD_POS = 1
    
    ... ... @@ -90,6 +94,11 @@ class MailAddress(BaseUri):
    90 94
         name: str = field(default='mailadr', init=False)
    
    91 95
     
    
    92 96
     
    
    97
    +@dataclass
    
    98
    +class Emoji(StyleObject):
    
    99
    +    name: str = field(default='emoji', init=False)
    
    100
    +
    
    101
    +
    
    93 102
     @dataclass
    
    94 103
     class Block(StyleObject):
    
    95 104
     
    
    ... ... @@ -105,6 +114,7 @@ class PlainBlock(Block):
    105 114
         name: str = field(default='plain', init=False)
    
    106 115
         spans: list[Span] = field(default_factory=list)
    
    107 116
         uris: list[BaseUri] = field(default_factory=list)
    
    117
    +    emojis: list[Emoji] = field(default_factory=list)
    
    108 118
     
    
    109 119
     
    
    110 120
     @dataclass
    
    ... ... @@ -189,6 +199,7 @@ def process(text: Union[str, bytes], nested: bool = False) -> ParsingResult:
    189 199
                 for line in block.text.splitlines(keepends=True):
    
    190 200
                     block.spans += _parse_line(line, offset, offset_bytes)
    
    191 201
                     block.uris += _parse_uris(line, offset, offset_bytes)
    
    202
    +                block.emojis += _parse_emojis(line, offset, offset_bytes)
    
    192 203
                     offset += len(line)
    
    193 204
                     offset_bytes += len(line.encode())
    
    194 205
     
    
    ... ... @@ -309,6 +320,23 @@ def make(match: Match[str], is_address: bool) -> BaseUri:
    309 320
         return uris
    
    310 321
     
    
    311 322
     
    
    323
    +def _parse_emojis(line: str, offset: int, offset_bytes: int) -> list[Emoji]:
    
    324
    +    emojis: list[Emoji] = []
    
    325
    +
    
    326
    +    def make(match: Match[str]) -> Emoji:
    
    327
    +        return _make_emoji(line,
    
    328
    +                           match.start(),
    
    329
    +                           match.end() - 1,
    
    330
    +                           offset,
    
    331
    +                           offset_bytes)
    
    332
    +    
    
    333
    +    for match in EMOJI_RX.finditer(line):
    
    334
    +        emoji = make(match)
    
    335
    +        emojis.append(emoji)
    
    336
    +    
    
    337
    +    return emojis
    
    338
    +
    
    339
    +
    
    312 340
     def _handle_pre_span(line: str,
    
    313 341
                          index: int,
    
    314 342
                          offset: int,
    
    ... ... @@ -386,6 +414,19 @@ def _make_link(line: str,
    386 414
                     text=text)
    
    387 415
     
    
    388 416
     
    
    417
    +def _make_emoji(line: str,
    
    418
    +                start: int,
    
    419
    +                end: int,
    
    420
    +                offset: int,
    
    421
    +                offset_bytes: int) -> Emoji:
    
    422
    +    text = line[start:end + 1]
    
    423
    +    start += offset
    
    424
    +    end += offset + 1
    
    425
    +    return Emoji(start=start,
    
    426
    +                 end=end,
    
    427
    +                 text=text)
    
    428
    +
    
    429
    +
    
    389 430
     def _is_span_empty(sd: str, index: int, stack: list[tuple[str, int]]) -> bool:
    
    390 431
         start_sd = stack[-1][SD]
    
    391 432
         if start_sd != sd:
    

  • gajim/gtk/conversation/plain_widget.py
    ... ... @@ -31,6 +31,9 @@
    31 31
     from gajim.common.structs import URI
    
    32 32
     from gajim.common.styling import PlainBlock
    
    33 33
     
    
    34
    +from gajim.gui.emoji_data import emoji_pixbufs
    
    35
    +from gajim.gui.emoji_data import get_emoji_pixbuf
    
    36
    +
    
    34 37
     from gajim.gui_menu_builder import get_conv_action_context_menu
    
    35 38
     from gajim.gui_menu_builder import get_conv_uri_context_menu
    
    36 39
     
    
    ... ... @@ -205,6 +208,33 @@ def print_text_with_styling(self, block: PlainBlock) -> None:
    205 208
                 end_iter = buffer_.get_iter_at_offset(uri.end)
    
    206 209
                 buffer_.apply_tag_by_name(uri.name, start_iter, end_iter)
    
    207 210
     
    
    211
    +        for emoji in block.emojis:
    
    212
    +            start_iter = buffer_.get_iter_at_offset(emoji.start)
    
    213
    +            end_iter = buffer_.get_iter_at_offset(emoji.end)
    
    214
    +            if emoji_pixbufs.complete:
    
    215
    +                # Only search for pixbuf if loading is completed
    
    216
    +                pixbuf = get_emoji_pixbuf(emoji.text)
    
    217
    +                if pixbuf is None:
    
    218
    +                    buffer_.insert(end_iter, emoji.text)
    
    219
    +                else:
    
    220
    +                    pixbuf = pixbuf.copy()
    
    221
    +                    anchor = buffer_.create_child_anchor(end_iter)
    
    222
    +                    anchor.plaintext = emoji.text
    
    223
    +                    img = Gtk.Image.new_from_pixbuf(pixbuf)
    
    224
    +                    img.show()
    
    225
    +                    self.add_child_at_anchor(img, anchor)
    
    226
    +                    buffer_.delete(
    
    227
    +                        buffer_.get_iter_at_offset(emoji.start),
    
    228
    +                        buffer_.get_iter_at_offset(emoji.end))
    
    229
    +            else:
    
    230
    +                # Set marks and save them so we can replace emojis
    
    231
    +                # once loading is complete
    
    232
    +                start_mark = buffer_.create_mark(None, end_iter, True)
    
    233
    +                buffer_.insert(end_iter, emoji.text)
    
    234
    +                end_mark = buffer_.create_mark(None, end_iter, True)
    
    235
    +                emoji_pixbufs.append_marks(
    
    236
    +                    self, start_mark, end_mark, emoji.text)
    
    237
    +
    
    208 238
         def add_action_phrase(self, text: str) -> None:
    
    209 239
             buffer_ = self.get_buffer()
    
    210 240
             buffer_.insert(buffer_.get_start_iter(), text.strip())
    

  • gajim/gtk/message_input.py
    ... ... @@ -102,11 +102,11 @@ def _on_focus_out(self, _widget, _event):
    102 102
             return False
    
    103 103
     
    
    104 104
         def _clear_tags(self) -> None:
    
    105
    -        _buffer = self.get_buffer()
    
    106
    -        start, end = _buffer.get_bounds()
    
    107
    -        _buffer.remove_all_tags(start, end)
    
    105
    +        buf = self.get_buffer()
    
    106
    +        start, end = buf.get_bounds()
    
    107
    +        buf.remove_all_tags(start, end)
    
    108 108
     
    
    109
    -    def _on_text_changed(self, buffer_: Gtk.TextBuffer) -> None:
    
    109
    +    def _on_text_changed(self, buf: Gtk.TextBuffer) -> None:
    
    110 110
             text = self.get_text()
    
    111 111
             if not text:
    
    112 112
                 return
    
    ... ... @@ -116,19 +116,19 @@ def _on_text_changed(self, buffer_: Gtk.TextBuffer) -> None:
    116 116
             for block in result.blocks:
    
    117 117
                 if block.name == 'plain':
    
    118 118
                     for span in block.spans:
    
    119
    -                    start_iter = buffer_.get_iter_at_offset(span.start)
    
    120
    -                    end_iter = buffer_.get_iter_at_offset(span.end)
    
    121
    -                    buffer_.apply_tag_by_name(span.name, start_iter, end_iter)
    
    119
    +                    start_iter = buf.get_iter_at_offset(span.start)
    
    120
    +                    end_iter = buf.get_iter_at_offset(span.end)
    
    121
    +                    buf.apply_tag_by_name(span.name, start_iter, end_iter)
    
    122 122
     
    
    123 123
         def insert_text(self, text: str) -> None:
    
    124 124
             self.get_buffer().insert_at_cursor(text)
    
    125 125
     
    
    126 126
         def insert_newline(self) -> None:
    
    127
    -        buffer_ = self.get_buffer()
    
    128
    -        buffer_.insert_at_cursor('\n')
    
    129
    -        mark = buffer_.get_insert()
    
    130
    -        iter_ = buffer_.get_iter_at_mark(mark)
    
    131
    -        if buffer_.get_end_iter().equal(iter_):
    
    127
    +        buf = self.get_buffer()
    
    128
    +        buf.insert_at_cursor('\n')
    
    129
    +        mark = buf.get_insert()
    
    130
    +        iter_ = buf.get_iter_at_mark(mark)
    
    131
    +        if buf.get_end_iter().equal(iter_):
    
    132 132
                 GLib.idle_add(scroll_to_end, self.get_parent())
    
    133 133
     
    
    134 134
         def has_text(self) -> bool:
    
    ... ... @@ -150,63 +150,63 @@ def toggle_speller(self, activate: bool) -> None:
    150 150
     
    
    151 151
         @staticmethod
    
    152 152
         def _after_paste_clipboard(textview: Gtk.TextView) -> None:
    
    153
    -        buffer_ = textview.get_buffer()
    
    154
    -        mark = buffer_.get_insert()
    
    155
    -        iter_ = buffer_.get_iter_at_mark(mark)
    
    156
    -        if iter_.get_offset() == buffer_.get_end_iter().get_offset():
    
    153
    +        buf = textview.get_buffer()
    
    154
    +        mark = buf.get_insert()
    
    155
    +        iter_ = buf.get_iter_at_mark(mark)
    
    156
    +        if iter_.get_offset() == buf.get_end_iter().get_offset():
    
    157 157
                 GLib.idle_add(scroll_to_end, textview.get_parent())
    
    158 158
     
    
    159 159
         def _get_active_iters(self) -> Tuple[Gtk.TextIter, Gtk.TextIter]:
    
    160
    -        _buffer = self.get_buffer()
    
    161
    -        return_val = _buffer.get_selection_bounds()
    
    160
    +        buf = self.get_buffer()
    
    161
    +        return_val = buf.get_selection_bounds()
    
    162 162
             if return_val:  # if something is selected
    
    163 163
                 start, end = return_val[0], return_val[1]
    
    164 164
             else:
    
    165
    -            start, end = _buffer.get_bounds()
    
    165
    +            start, end = buf.get_bounds()
    
    166 166
             return (start, end)
    
    167 167
     
    
    168 168
         def apply_formatting(self, formatting: str) -> None:
    
    169 169
             format_char = FORMAT_CHARS[formatting]
    
    170 170
     
    
    171
    -        _buffer = self.get_buffer()
    
    171
    +        buf = self.get_buffer()
    
    172 172
             start, end = self._get_active_iters()
    
    173 173
             start_offset = start.get_offset()
    
    174 174
             end_offset = end.get_offset()
    
    175 175
     
    
    176
    -        text = _buffer.get_text(start, end, True)
    
    176
    +        text = buf.get_text(start, end, True)
    
    177 177
             if text.startswith(format_char) and text.endswith(format_char):
    
    178 178
                 # (Selected) text begins and ends with formatting chars
    
    179 179
                 # -> remove them
    
    180
    -            _buffer.delete(
    
    180
    +            buf.delete(
    
    181 181
                     start,
    
    182
    -                _buffer.get_iter_at_offset(start_offset + 1))
    
    183
    -            _buffer.delete(
    
    184
    -                _buffer.get_iter_at_offset(end_offset - 2),
    
    185
    -                _buffer.get_iter_at_offset(end_offset - 1))
    
    182
    +                buf.get_iter_at_offset(start_offset + 1))
    
    183
    +            buf.delete(
    
    184
    +                buf.get_iter_at_offset(end_offset - 2),
    
    185
    +                buf.get_iter_at_offset(end_offset - 1))
    
    186 186
                 return
    
    187 187
     
    
    188
    -        ext_start = _buffer.get_iter_at_offset(start_offset - 1)
    
    189
    -        ext_end = _buffer.get_iter_at_offset(end_offset + 1)
    
    190
    -        ext_text = _buffer.get_text(ext_start, ext_end, True)
    
    188
    +        ext_start = buf.get_iter_at_offset(start_offset - 1)
    
    189
    +        ext_end = buf.get_iter_at_offset(end_offset + 1)
    
    190
    +        ext_text = buf.get_text(ext_start, ext_end, True)
    
    191 191
             if ext_text.startswith(format_char) and ext_text.endswith(format_char):
    
    192 192
                 # (Selected) text is surrounded by formatting chars -> remove them
    
    193
    -            _buffer.delete(
    
    193
    +            buf.delete(
    
    194 194
                     ext_start,
    
    195
    -                _buffer.get_iter_at_offset(start_offset))
    
    196
    -            _buffer.delete(
    
    197
    -                _buffer.get_iter_at_offset(end_offset - 1),
    
    198
    -                _buffer.get_iter_at_offset(end_offset))
    
    195
    +                buf.get_iter_at_offset(start_offset))
    
    196
    +            buf.delete(
    
    197
    +                buf.get_iter_at_offset(end_offset - 1),
    
    198
    +                buf.get_iter_at_offset(end_offset))
    
    199 199
                 return
    
    200 200
     
    
    201 201
             # No formatting chars found at start/end or surrounding -> add them
    
    202
    -        _buffer.insert(start, format_char, -1)
    
    203
    -        _buffer.insert(
    
    204
    -            _buffer.get_iter_at_offset(end_offset + 1),
    
    202
    +        buf.insert(start, format_char, -1)
    
    203
    +        buf.insert(
    
    204
    +            buf.get_iter_at_offset(end_offset + 1),
    
    205 205
                 format_char,
    
    206 206
                 -1)
    
    207
    -        _buffer.select_range(
    
    208
    -            _buffer.get_iter_at_offset(start_offset),
    
    209
    -            _buffer.get_iter_at_offset(end_offset + 2))
    
    207
    +        buf.select_range(
    
    208
    +            buf.get_iter_at_offset(start_offset),
    
    209
    +            buf.get_iter_at_offset(end_offset + 2))
    
    210 210
     
    
    211 211
         def replace_emojis(self) -> None:
    
    212 212
             theme = app.settings.get('emoticons_theme')
    
    ... ... @@ -229,35 +229,35 @@ def _replace(anchor):
    229 229
                 _replace(iter_.get_child_anchor())
    
    230 230
     
    
    231 231
         def _replace_char_at_iter(self, iter_, new_char):
    
    232
    -        buffer_ = self.get_buffer()
    
    232
    +        buf = self.get_buffer()
    
    233 233
             iter_2 = iter_.copy()
    
    234 234
             iter_2.forward_char()
    
    235
    -        buffer_.delete(iter_, iter_2)
    
    236
    -        buffer_.insert(iter_, new_char)
    
    235
    +        buf.delete(iter_, iter_2)
    
    236
    +        buf.insert(iter_, new_char)
    
    237 237
     
    
    238 238
         def insert_emoji(self, codepoint, pixbuf):
    
    239
    -        buffer_ = self.get_buffer()
    
    240
    -        if buffer_.get_char_count():
    
    239
    +        buf = self.get_buffer()
    
    240
    +        if buf.get_char_count():
    
    241 241
                 # buffer contains text
    
    242
    -            buffer_.insert_at_cursor(' ')
    
    242
    +            buf.insert_at_cursor(' ')
    
    243 243
     
    
    244
    -        insert_mark = buffer_.get_insert()
    
    245
    -        insert_iter = buffer_.get_iter_at_mark(insert_mark)
    
    244
    +        insert_mark = buf.get_insert()
    
    245
    +        insert_iter = buf.get_iter_at_mark(insert_mark)
    
    246 246
     
    
    247 247
             if pixbuf is None:
    
    248
    -            buffer_.insert(insert_iter, codepoint)
    
    248
    +            buf.insert(insert_iter, codepoint)
    
    249 249
             else:
    
    250
    -            anchor = buffer_.create_child_anchor(insert_iter)
    
    250
    +            anchor = buf.create_child_anchor(insert_iter)
    
    251 251
                 image = Gtk.Image.new_from_pixbuf(pixbuf)
    
    252 252
                 image.codepoint = codepoint
    
    253 253
                 image.show()
    
    254 254
                 self.add_child_at_anchor(image, anchor)
    
    255
    -        buffer_.insert_at_cursor(' ')
    
    255
    +        buf.insert_at_cursor(' ')
    
    256 256
     
    
    257 257
         def clear(self, *args):
    
    258
    -        _buffer = self.get_buffer()
    
    259
    -        start, end = _buffer.get_bounds()
    
    260
    -        _buffer.delete(start, end)
    
    258
    +        buf = self.get_buffer()
    
    259
    +        start, end = buf.get_bounds()
    
    260
    +        buf.delete(start, end)
    
    261 261
     
    
    262 262
         def save_undo(self, text: str) -> None:
    
    263 263
             self._undo_list.append(text)
    
    ... ... @@ -266,7 +266,7 @@ def save_undo(self, text: str) -> None:
    266 266
             self.undo_pressed = False
    
    267 267
     
    
    268 268
         def undo(self, *args):
    
    269
    -        _buffer = self.get_buffer()
    
    269
    +        buf = self.get_buffer()
    
    270 270
             if self._undo_list:
    
    271
    -            _buffer.set_text(self._undo_list.pop())
    
    271
    +            buf.set_text(self._undo_list.pop())
    
    272 272
             self.undo_pressed = True