Daniel Brötzmann pushed to branch master at gajim / gajim
Commits:
-
ee5e4ef0
by wurstsalat at 2021-12-23T12:22:52+01:00
-
482e4fea
by wurstsalat at 2021-12-23T12:28:53+01:00
3 changed files:
Changes:
| ... | ... | @@ -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:
|
| ... | ... | @@ -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())
|
| ... | ... | @@ -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
|