Philipp Hörist pushed to branch master at gajim / gajim
Commits:
-
eebbe3e8
by wurstsalat at 2022-11-30T21:34:12+00:00
23 changed files:
- gajim/common/configpaths.py
- gajim/common/helpers.py
- gajim/common/setting_values.py
- gajim/common/settings.py
- gajim/common/styling.py
- − gajim/data/emoticons/noto/LICENSE
- − gajim/data/emoticons/noto/noto.png
- − gajim/data/emoticons/twemoji/LICENSE
- − gajim/data/emoticons/twemoji/twemoji.png
- gajim/data/gui/message_actions_box.ui
- gajim/data/gui/preferences.ui
- gajim/data/style/gajim.css
- gajim/gtk/application.py
- gajim/gtk/chat_stack.py
- gajim/gtk/conversation/plain_widget.py
- − gajim/gtk/emoji_chooser.py
- − gajim/gtk/emoji_data.py
- − gajim/gtk/emoji_data.pyi
- gajim/gtk/message_actions_box.py
- gajim/gtk/message_input.py
- gajim/gtk/preferences.py
- pyproject.toml
- setup.cfg
Changes:
| ... | ... | @@ -125,7 +125,6 @@ def __init__(self) -> None: |
| 125 | 125 | source_paths = [
|
| 126 | 126 | ('DATA', basedir / 'data'),
|
| 127 | 127 | ('STYLE', basedir / 'data' / 'style'),
|
| 128 | - ('EMOTICONS', basedir / 'data' / 'emoticons'),
|
|
| 129 | 128 | ('GUI', basedir / 'data' / 'gui'),
|
| 130 | 129 | ('ICONS', basedir / 'data' / 'icons'),
|
| 131 | 130 | ('HOME', Path.home()),
|
| ... | ... | @@ -216,8 +215,6 @@ def init(self): |
| 216 | 215 | ('PLUGINS_IMAGES', 'plugins_images',
|
| 217 | 216 | PathLocation.CACHE, PathType.FOLDER),
|
| 218 | 217 | ('PLUGINS_USER', 'plugins', PathLocation.DATA, PathType.FOLDER),
|
| 219 | - ('MY_EMOTS',
|
|
| 220 | - 'emoticons', PathLocation.DATA, PathType.FOLDER_OPTIONAL),
|
|
| 221 | 218 | ('MY_ICONSETS',
|
| 222 | 219 | 'iconsets', PathLocation.DATA, PathType.FOLDER_OPTIONAL),
|
| 223 | 220 |
| ... | ... | @@ -766,22 +766,6 @@ def version_condition(current_version: str, required_version: str) -> bool: |
| 766 | 766 | return True
|
| 767 | 767 | |
| 768 | 768 | |
| 769 | -def get_available_emoticon_themes() -> list[str]:
|
|
| 770 | - files: list[Path] = []
|
|
| 771 | - for folder in configpaths.get('EMOTICONS').iterdir():
|
|
| 772 | - if not folder.is_dir():
|
|
| 773 | - continue
|
|
| 774 | - files += [theme for theme in folder.iterdir() if theme.is_file()]
|
|
| 775 | - |
|
| 776 | - my_emots = configpaths.get('MY_EMOTS')
|
|
| 777 | - if my_emots.is_dir():
|
|
| 778 | - files += list(my_emots.iterdir())
|
|
| 779 | - |
|
| 780 | - emoticons_themes = ['font']
|
|
| 781 | - emoticons_themes += [file.stem for file in files if file.suffix == '.png']
|
|
| 782 | - return sorted(emoticons_themes)
|
|
| 783 | - |
|
| 784 | - |
|
| 785 | 769 | def call_counter(func):
|
| 786 | 770 | def helper(self, restart=False):
|
| 787 | 771 | if restart:
|
| ... | ... | @@ -49,7 +49,6 @@ class _ACCOUNT_DEFAULT: |
| 49 | 49 | 'confirm_close_muc',
|
| 50 | 50 | 'confirm_on_window_delete',
|
| 51 | 51 | 'dev_force_bookmark_2',
|
| 52 | - 'dev_use_message_label',
|
|
| 53 | 52 | 'developer_modus',
|
| 54 | 53 | 'enable_emoji_shortcodes',
|
| 55 | 54 | 'enable_negative_priority',
|
| ... | ... | @@ -137,7 +136,6 @@ class _ACCOUNT_DEFAULT: |
| 137 | 136 | 'date_format',
|
| 138 | 137 | 'date_time_format',
|
| 139 | 138 | 'dictionary_url',
|
| 140 | - 'emoticons_theme',
|
|
| 141 | 139 | 'gc_refer_to_nick_char',
|
| 142 | 140 | 'global_proxy',
|
| 143 | 141 | 'last_save_dir',
|
| ... | ... | @@ -194,7 +192,6 @@ class _ACCOUNT_DEFAULT: |
| 194 | 192 | 'dev_use_message_label': True,
|
| 195 | 193 | 'developer_modus': False,
|
| 196 | 194 | 'dictionary_url': 'WIKTIONARY',
|
| 197 | - 'emoticons_theme': 'noto-emoticons',
|
|
| 198 | 195 | 'enable_emoji_shortcodes': True,
|
| 199 | 196 | 'enable_negative_priority': False,
|
| 200 | 197 | 'enable_file_preview': True,
|
| ... | ... | @@ -237,11 +237,6 @@ def _setup_installation_defaults() -> None: |
| 237 | 237 | if IS_PORTABLE:
|
| 238 | 238 | APP_SETTINGS['use_keyring'] = False
|
| 239 | 239 | |
| 240 | - if sys.platform in ('win32', 'darwin'):
|
|
| 241 | - # Cairo does not support color emojis fonts on
|
|
| 242 | - # Windows and MacOS
|
|
| 243 | - APP_SETTINGS['dev_use_message_label'] = False
|
|
| 244 | - |
|
| 245 | 240 | def _load_app_overrides(self) -> None:
|
| 246 | 241 | if not OVERRIDES_PATH.exists():
|
| 247 | 242 | return
|
| ... | ... | @@ -19,7 +19,6 @@ |
| 19 | 19 | from typing import Optional
|
| 20 | 20 | |
| 21 | 21 | import string
|
| 22 | -import sys
|
|
| 23 | 22 | import re
|
| 24 | 23 | from dataclasses import dataclass
|
| 25 | 24 | from dataclasses import field
|
| ... | ... | @@ -31,7 +30,6 @@ |
| 31 | 30 | from gajim.common.helpers import parse_uri as analyze_uri
|
| 32 | 31 | from gajim.common.helpers import validate_jid
|
| 33 | 32 | from gajim.common.text_helpers import jid_to_iri
|
| 34 | -from gajim.gui.emoji_data import emoji_data
|
|
| 35 | 33 | |
| 36 | 34 | PRE = '`'
|
| 37 | 35 | STRONG = '*'
|
| ... | ... | @@ -53,9 +51,6 @@ |
| 53 | 51 | URI_OR_JID_RX = re.compile(
|
| 54 | 52 | fr'(?P<uri>(?<![\w+.-]){regex.IRI})|(?P<jid>{regex.XMPP.jid})')
|
| 55 | 53 | |
| 56 | -EMOJI_RX = emoji_data.get_regex()
|
|
| 57 | -EMOJI_RX = re.compile(EMOJI_RX)
|
|
| 58 | - |
|
| 59 | 54 | SD = 0
|
| 60 | 55 | SD_POS = 1
|
| 61 | 56 | MAX_QUOTE_LEVEL = 20
|
| ... | ... | @@ -97,11 +92,6 @@ class MailAddress(BaseHyperlink): |
| 97 | 92 | name: str = field(default='mailadr', init=False)
|
| 98 | 93 | |
| 99 | 94 | |
| 100 | -@dataclass
|
|
| 101 | -class Emoji(StyleObject):
|
|
| 102 | - name: str = field(default='emoji', init=False)
|
|
| 103 | - |
|
| 104 | - |
|
| 105 | 95 | @dataclass
|
| 106 | 96 | class Block(StyleObject):
|
| 107 | 97 | |
| ... | ... | @@ -117,7 +107,6 @@ class PlainBlock(Block): |
| 117 | 107 | name: str = field(default='plain', init=False)
|
| 118 | 108 | spans: list[Span] = field(default_factory=list)
|
| 119 | 109 | uris: list[BaseHyperlink] = field(default_factory=list)
|
| 120 | - emojis: list[Emoji] = field(default_factory=list)
|
|
| 121 | 110 | |
| 122 | 111 | @classmethod
|
| 123 | 112 | def from_quote_match(cls, match: Match[str]) -> PlainBlock:
|
| ... | ... | @@ -208,10 +197,6 @@ def process(text: Union[str, bytes], level: int = 0) -> ParsingResult: |
| 208 | 197 | for line in block.text.splitlines(keepends=True):
|
| 209 | 198 | block.spans += _parse_line(line, offset, offset_bytes)
|
| 210 | 199 | block.uris += _parse_uris(line, offset, offset_bytes)
|
| 211 | - if sys.platform == 'darwin':
|
|
| 212 | - # block.emojis is used for replacing emojis with Gtk.Images
|
|
| 213 | - # Necessary for MessageTextview (darwin) only
|
|
| 214 | - block.emojis += _parse_emojis(line, offset)
|
|
| 215 | 200 | |
| 216 | 201 | offset += len(line)
|
| 217 | 202 | offset_bytes += len(line.encode())
|
| ... | ... | @@ -361,22 +346,6 @@ def make(start: int, end: int, is_jid: bool) -> Optional[BaseHyperlink]: |
| 361 | 346 | return uris
|
| 362 | 347 | |
| 363 | 348 | |
| 364 | -def _parse_emojis(line: str, offset: int) -> list[Emoji]:
|
|
| 365 | - emojis: list[Emoji] = []
|
|
| 366 | - |
|
| 367 | - def make(match: Match[str]) -> Emoji:
|
|
| 368 | - return _make_emoji(line,
|
|
| 369 | - match.start(),
|
|
| 370 | - match.end() - 1,
|
|
| 371 | - offset)
|
|
| 372 | - |
|
| 373 | - for match in EMOJI_RX.finditer(line):
|
|
| 374 | - emoji = make(match)
|
|
| 375 | - emojis.append(emoji)
|
|
| 376 | - |
|
| 377 | - return emojis
|
|
| 378 | - |
|
| 379 | - |
|
| 380 | 349 | def _handle_pre_span(line: str,
|
| 381 | 350 | index: int,
|
| 382 | 351 | offset: int,
|
| ... | ... | @@ -464,18 +433,6 @@ def _make_hyperlink(line: str, |
| 464 | 433 | text=text)
|
| 465 | 434 | |
| 466 | 435 | |
| 467 | -def _make_emoji(line: str,
|
|
| 468 | - start: int,
|
|
| 469 | - end: int,
|
|
| 470 | - offset: int) -> Emoji:
|
|
| 471 | - text = line[start:end + 1]
|
|
| 472 | - start += offset
|
|
| 473 | - end += offset + 1
|
|
| 474 | - return Emoji(start=start,
|
|
| 475 | - end=end,
|
|
| 476 | - text=text)
|
|
| 477 | - |
|
| 478 | - |
|
| 479 | 436 | def _is_span_empty(sd: str, index: int, stack: list[tuple[str, int]]) -> bool:
|
| 480 | 437 | start_sd = stack[-1][SD]
|
| 481 | 438 | if start_sd != sd:
|
| 1 | - Apache License
|
|
| 2 | - Version 2.0, January 2004
|
|
| 3 | - http://www.apache.org/licenses/
|
|
| 4 | - |
|
| 5 | - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
| 6 | - |
|
| 7 | - 1. Definitions.
|
|
| 8 | - |
|
| 9 | - "License" shall mean the terms and conditions for use, reproduction,
|
|
| 10 | - and distribution as defined by Sections 1 through 9 of this document.
|
|
| 11 | - |
|
| 12 | - "Licensor" shall mean the copyright owner or entity authorized by
|
|
| 13 | - the copyright owner that is granting the License.
|
|
| 14 | - |
|
| 15 | - "Legal Entity" shall mean the union of the acting entity and all
|
|
| 16 | - other entities that control, are controlled by, or are under common
|
|
| 17 | - control with that entity. For the purposes of this definition,
|
|
| 18 | - "control" means (i) the power, direct or indirect, to cause the
|
|
| 19 | - direction or management of such entity, whether by contract or
|
|
| 20 | - otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
| 21 | - outstanding shares, or (iii) beneficial ownership of such entity.
|
|
| 22 | - |
|
| 23 | - "You" (or "Your") shall mean an individual or Legal Entity
|
|
| 24 | - exercising permissions granted by this License.
|
|
| 25 | - |
|
| 26 | - "Source" form shall mean the preferred form for making modifications,
|
|
| 27 | - including but not limited to software source code, documentation
|
|
| 28 | - source, and configuration files.
|
|
| 29 | - |
|
| 30 | - "Object" form shall mean any form resulting from mechanical
|
|
| 31 | - transformation or translation of a Source form, including but
|
|
| 32 | - not limited to compiled object code, generated documentation,
|
|
| 33 | - and conversions to other media types.
|
|
| 34 | - |
|
| 35 | - "Work" shall mean the work of authorship, whether in Source or
|
|
| 36 | - Object form, made available under the License, as indicated by a
|
|
| 37 | - copyright notice that is included in or attached to the work
|
|
| 38 | - (an example is provided in the Appendix below).
|
|
| 39 | - |
|
| 40 | - "Derivative Works" shall mean any work, whether in Source or Object
|
|
| 41 | - form, that is based on (or derived from) the Work and for which the
|
|
| 42 | - editorial revisions, annotations, elaborations, or other modifications
|
|
| 43 | - represent, as a whole, an original work of authorship. For the purposes
|
|
| 44 | - of this License, Derivative Works shall not include works that remain
|
|
| 45 | - separable from, or merely link (or bind by name) to the interfaces of,
|
|
| 46 | - the Work and Derivative Works thereof.
|
|
| 47 | - |
|
| 48 | - "Contribution" shall mean any work of authorship, including
|
|
| 49 | - the original version of the Work and any modifications or additions
|
|
| 50 | - to that Work or Derivative Works thereof, that is intentionally
|
|
| 51 | - submitted to Licensor for inclusion in the Work by the copyright owner
|
|
| 52 | - or by an individual or Legal Entity authorized to submit on behalf of
|
|
| 53 | - the copyright owner. For the purposes of this definition, "submitted"
|
|
| 54 | - means any form of electronic, verbal, or written communication sent
|
|
| 55 | - to the Licensor or its representatives, including but not limited to
|
|
| 56 | - communication on electronic mailing lists, source code control systems,
|
|
| 57 | - and issue tracking systems that are managed by, or on behalf of, the
|
|
| 58 | - Licensor for the purpose of discussing and improving the Work, but
|
|
| 59 | - excluding communication that is conspicuously marked or otherwise
|
|
| 60 | - designated in writing by the copyright owner as "Not a Contribution."
|
|
| 61 | - |
|
| 62 | - "Contributor" shall mean Licensor and any individual or Legal Entity
|
|
| 63 | - on behalf of whom a Contribution has been received by Licensor and
|
|
| 64 | - subsequently incorporated within the Work.
|
|
| 65 | - |
|
| 66 | - 2. Grant of Copyright License. Subject to the terms and conditions of
|
|
| 67 | - this License, each Contributor hereby grants to You a perpetual,
|
|
| 68 | - worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
| 69 | - copyright license to reproduce, prepare Derivative Works of,
|
|
| 70 | - publicly display, publicly perform, sublicense, and distribute the
|
|
| 71 | - Work and such Derivative Works in Source or Object form.
|
|
| 72 | - |
|
| 73 | - 3. Grant of Patent License. Subject to the terms and conditions of
|
|
| 74 | - this License, each Contributor hereby grants to You a perpetual,
|
|
| 75 | - worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
| 76 | - (except as stated in this section) patent license to make, have made,
|
|
| 77 | - use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
| 78 | - where such license applies only to those patent claims licensable
|
|
| 79 | - by such Contributor that are necessarily infringed by their
|
|
| 80 | - Contribution(s) alone or by combination of their Contribution(s)
|
|
| 81 | - with the Work to which such Contribution(s) was submitted. If You
|
|
| 82 | - institute patent litigation against any entity (including a
|
|
| 83 | - cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
| 84 | - or a Contribution incorporated within the Work constitutes direct
|
|
| 85 | - or contributory patent infringement, then any patent licenses
|
|
| 86 | - granted to You under this License for that Work shall terminate
|
|
| 87 | - as of the date such litigation is filed.
|
|
| 88 | - |
|
| 89 | - 4. Redistribution. You may reproduce and distribute copies of the
|
|
| 90 | - Work or Derivative Works thereof in any medium, with or without
|
|
| 91 | - modifications, and in Source or Object form, provided that You
|
|
| 92 | - meet the following conditions:
|
|
| 93 | - |
|
| 94 | - (a) You must give any other recipients of the Work or
|
|
| 95 | - Derivative Works a copy of this License; and
|
|
| 96 | - |
|
| 97 | - (b) You must cause any modified files to carry prominent notices
|
|
| 98 | - stating that You changed the files; and
|
|
| 99 | - |
|
| 100 | - (c) You must retain, in the Source form of any Derivative Works
|
|
| 101 | - that You distribute, all copyright, patent, trademark, and
|
|
| 102 | - attribution notices from the Source form of the Work,
|
|
| 103 | - excluding those notices that do not pertain to any part of
|
|
| 104 | - the Derivative Works; and
|
|
| 105 | - |
|
| 106 | - (d) If the Work includes a "NOTICE" text file as part of its
|
|
| 107 | - distribution, then any Derivative Works that You distribute must
|
|
| 108 | - include a readable copy of the attribution notices contained
|
|
| 109 | - within such NOTICE file, excluding those notices that do not
|
|
| 110 | - pertain to any part of the Derivative Works, in at least one
|
|
| 111 | - of the following places: within a NOTICE text file distributed
|
|
| 112 | - as part of the Derivative Works; within the Source form or
|
|
| 113 | - documentation, if provided along with the Derivative Works; or,
|
|
| 114 | - within a display generated by the Derivative Works, if and
|
|
| 115 | - wherever such third-party notices normally appear. The contents
|
|
| 116 | - of the NOTICE file are for informational purposes only and
|
|
| 117 | - do not modify the License. You may add Your own attribution
|
|
| 118 | - notices within Derivative Works that You distribute, alongside
|
|
| 119 | - or as an addendum to the NOTICE text from the Work, provided
|
|
| 120 | - that such additional attribution notices cannot be construed
|
|
| 121 | - as modifying the License.
|
|
| 122 | - |
|
| 123 | - You may add Your own copyright statement to Your modifications and
|
|
| 124 | - may provide additional or different license terms and conditions
|
|
| 125 | - for use, reproduction, or distribution of Your modifications, or
|
|
| 126 | - for any such Derivative Works as a whole, provided Your use,
|
|
| 127 | - reproduction, and distribution of the Work otherwise complies with
|
|
| 128 | - the conditions stated in this License.
|
|
| 129 | - |
|
| 130 | - 5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
| 131 | - any Contribution intentionally submitted for inclusion in the Work
|
|
| 132 | - by You to the Licensor shall be under the terms and conditions of
|
|
| 133 | - this License, without any additional terms or conditions.
|
|
| 134 | - Notwithstanding the above, nothing herein shall supersede or modify
|
|
| 135 | - the terms of any separate license agreement you may have executed
|
|
| 136 | - with Licensor regarding such Contributions.
|
|
| 137 | - |
|
| 138 | - 6. Trademarks. This License does not grant permission to use the trade
|
|
| 139 | - names, trademarks, service marks, or product names of the Licensor,
|
|
| 140 | - except as required for reasonable and customary use in describing the
|
|
| 141 | - origin of the Work and reproducing the content of the NOTICE file.
|
|
| 142 | - |
|
| 143 | - 7. Disclaimer of Warranty. Unless required by applicable law or
|
|
| 144 | - agreed to in writing, Licensor provides the Work (and each
|
|
| 145 | - Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
| 146 | - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
| 147 | - implied, including, without limitation, any warranties or conditions
|
|
| 148 | - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
| 149 | - PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
| 150 | - appropriateness of using or redistributing the Work and assume any
|
|
| 151 | - risks associated with Your exercise of permissions under this License.
|
|
| 152 | - |
|
| 153 | - 8. Limitation of Liability. In no event and under no legal theory,
|
|
| 154 | - whether in tort (including negligence), contract, or otherwise,
|
|
| 155 | - unless required by applicable law (such as deliberate and grossly
|
|
| 156 | - negligent acts) or agreed to in writing, shall any Contributor be
|
|
| 157 | - liable to You for damages, including any direct, indirect, special,
|
|
| 158 | - incidental, or consequential damages of any character arising as a
|
|
| 159 | - result of this License or out of the use or inability to use the
|
|
| 160 | - Work (including but not limited to damages for loss of goodwill,
|
|
| 161 | - work stoppage, computer failure or malfunction, or any and all
|
|
| 162 | - other commercial damages or losses), even if such Contributor
|
|
| 163 | - has been advised of the possibility of such damages.
|
|
| 164 | - |
|
| 165 | - 9. Accepting Warranty or Additional Liability. While redistributing
|
|
| 166 | - the Work or Derivative Works thereof, You may choose to offer,
|
|
| 167 | - and charge a fee for, acceptance of support, warranty, indemnity,
|
|
| 168 | - or other liability obligations and/or rights consistent with this
|
|
| 169 | - License. However, in accepting such obligations, You may act only
|
|
| 170 | - on Your own behalf and on Your sole responsibility, not on behalf
|
|
| 171 | - of any other Contributor, and only if You agree to indemnify,
|
|
| 172 | - defend, and hold each Contributor harmless for any liability
|
|
| 173 | - incurred by, or claims asserted against, such Contributor by reason
|
|
| 174 | - of your accepting any such warranty or additional liability.
|
|
| 175 | - |
|
| 176 | - END OF TERMS AND CONDITIONS
|
|
| 177 | - |
|
| 178 | - APPENDIX: How to apply the Apache License to your work.
|
|
| 179 | - |
|
| 180 | - To apply the Apache License to your work, attach the following
|
|
| 181 | - boilerplate notice, with the fields enclosed by brackets "[]"
|
|
| 182 | - replaced with your own identifying information. (Don't include
|
|
| 183 | - the brackets!) The text should be enclosed in the appropriate
|
|
| 184 | - comment syntax for the file format. We also recommend that a
|
|
| 185 | - file or class name and description of purpose be included on the
|
|
| 186 | - same "printed page" as the copyright notice for easier
|
|
| 187 | - identification within third-party archives.
|
|
| 188 | - |
|
| 189 | - Copyright [yyyy] [name of copyright owner]
|
|
| 190 | - |
|
| 191 | - Licensed under the Apache License, Version 2.0 (the "License");
|
|
| 192 | - you may not use this file except in compliance with the License.
|
|
| 193 | - You may obtain a copy of the License at
|
|
| 194 | - |
|
| 195 | - http://www.apache.org/licenses/LICENSE-2.0
|
|
| 196 | - |
|
| 197 | - Unless required by applicable law or agreed to in writing, software
|
|
| 198 | - distributed under the License is distributed on an "AS IS" BASIS,
|
|
| 199 | - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
| 200 | - See the License for the specific language governing permissions and
|
|
| 201 | - limitations under the License. |
| 1 | -Attribution 4.0 International
|
|
| 2 | - |
|
| 3 | -=======================================================================
|
|
| 4 | - |
|
| 5 | -Creative Commons Corporation ("Creative Commons") is not a law firm and
|
|
| 6 | -does not provide legal services or legal advice. Distribution of
|
|
| 7 | -Creative Commons public licenses does not create a lawyer-client or
|
|
| 8 | -other relationship. Creative Commons makes its licenses and related
|
|
| 9 | -information available on an "as-is" basis. Creative Commons gives no
|
|
| 10 | -warranties regarding its licenses, any material licensed under their
|
|
| 11 | -terms and conditions, or any related information. Creative Commons
|
|
| 12 | -disclaims all liability for damages resulting from their use to the
|
|
| 13 | -fullest extent possible.
|
|
| 14 | - |
|
| 15 | -Using Creative Commons Public Licenses
|
|
| 16 | - |
|
| 17 | -Creative Commons public licenses provide a standard set of terms and
|
|
| 18 | -conditions that creators and other rights holders may use to share
|
|
| 19 | -original works of authorship and other material subject to copyright
|
|
| 20 | -and certain other rights specified in the public license below. The
|
|
| 21 | -following considerations are for informational purposes only, are not
|
|
| 22 | -exhaustive, and do not form part of our licenses.
|
|
| 23 | - |
|
| 24 | - Considerations for licensors: Our public licenses are
|
|
| 25 | - intended for use by those authorized to give the public
|
|
| 26 | - permission to use material in ways otherwise restricted by
|
|
| 27 | - copyright and certain other rights. Our licenses are
|
|
| 28 | - irrevocable. Licensors should read and understand the terms
|
|
| 29 | - and conditions of the license they choose before applying it.
|
|
| 30 | - Licensors should also secure all rights necessary before
|
|
| 31 | - applying our licenses so that the public can reuse the
|
|
| 32 | - material as expected. Licensors should clearly mark any
|
|
| 33 | - material not subject to the license. This includes other CC-
|
|
| 34 | - licensed material, or material used under an exception or
|
|
| 35 | - limitation to copyright. More considerations for licensors:
|
|
| 36 | - wiki.creativecommons.org/Considerations_for_licensors
|
|
| 37 | - |
|
| 38 | - Considerations for the public: By using one of our public
|
|
| 39 | - licenses, a licensor grants the public permission to use the
|
|
| 40 | - licensed material under specified terms and conditions. If
|
|
| 41 | - the licensor's permission is not necessary for any reason--for
|
|
| 42 | - example, because of any applicable exception or limitation to
|
|
| 43 | - copyright--then that use is not regulated by the license. Our
|
|
| 44 | - licenses grant only permissions under copyright and certain
|
|
| 45 | - other rights that a licensor has authority to grant. Use of
|
|
| 46 | - the licensed material may still be restricted for other
|
|
| 47 | - reasons, including because others have copyright or other
|
|
| 48 | - rights in the material. A licensor may make special requests,
|
|
| 49 | - such as asking that all changes be marked or described.
|
|
| 50 | - Although not required by our licenses, you are encouraged to
|
|
| 51 | - respect those requests where reasonable. More_considerations
|
|
| 52 | - for the public:
|
|
| 53 | - wiki.creativecommons.org/Considerations_for_licensees
|
|
| 54 | - |
|
| 55 | -=======================================================================
|
|
| 56 | - |
|
| 57 | -Creative Commons Attribution 4.0 International Public License
|
|
| 58 | - |
|
| 59 | -By exercising the Licensed Rights (defined below), You accept and agree
|
|
| 60 | -to be bound by the terms and conditions of this Creative Commons
|
|
| 61 | -Attribution 4.0 International Public License ("Public License"). To the
|
|
| 62 | -extent this Public License may be interpreted as a contract, You are
|
|
| 63 | -granted the Licensed Rights in consideration of Your acceptance of
|
|
| 64 | -these terms and conditions, and the Licensor grants You such rights in
|
|
| 65 | -consideration of benefits the Licensor receives from making the
|
|
| 66 | -Licensed Material available under these terms and conditions.
|
|
| 67 | - |
|
| 68 | - |
|
| 69 | -Section 1 -- Definitions.
|
|
| 70 | - |
|
| 71 | - a. Adapted Material means material subject to Copyright and Similar
|
|
| 72 | - Rights that is derived from or based upon the Licensed Material
|
|
| 73 | - and in which the Licensed Material is translated, altered,
|
|
| 74 | - arranged, transformed, or otherwise modified in a manner requiring
|
|
| 75 | - permission under the Copyright and Similar Rights held by the
|
|
| 76 | - Licensor. For purposes of this Public License, where the Licensed
|
|
| 77 | - Material is a musical work, performance, or sound recording,
|
|
| 78 | - Adapted Material is always produced where the Licensed Material is
|
|
| 79 | - synched in timed relation with a moving image.
|
|
| 80 | - |
|
| 81 | - b. Adapter's License means the license You apply to Your Copyright
|
|
| 82 | - and Similar Rights in Your contributions to Adapted Material in
|
|
| 83 | - accordance with the terms and conditions of this Public License.
|
|
| 84 | - |
|
| 85 | - c. Copyright and Similar Rights means copyright and/or similar rights
|
|
| 86 | - closely related to copyright including, without limitation,
|
|
| 87 | - performance, broadcast, sound recording, and Sui Generis Database
|
|
| 88 | - Rights, without regard to how the rights are labeled or
|
|
| 89 | - categorized. For purposes of this Public License, the rights
|
|
| 90 | - specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
|
| 91 | - Rights.
|
|
| 92 | - |
|
| 93 | - d. Effective Technological Measures means those measures that, in the
|
|
| 94 | - absence of proper authority, may not be circumvented under laws
|
|
| 95 | - fulfilling obligations under Article 11 of the WIPO Copyright
|
|
| 96 | - Treaty adopted on December 20, 1996, and/or similar international
|
|
| 97 | - agreements.
|
|
| 98 | - |
|
| 99 | - e. Exceptions and Limitations means fair use, fair dealing, and/or
|
|
| 100 | - any other exception or limitation to Copyright and Similar Rights
|
|
| 101 | - that applies to Your use of the Licensed Material.
|
|
| 102 | - |
|
| 103 | - f. Licensed Material means the artistic or literary work, database,
|
|
| 104 | - or other material to which the Licensor applied this Public
|
|
| 105 | - License.
|
|
| 106 | - |
|
| 107 | - g. Licensed Rights means the rights granted to You subject to the
|
|
| 108 | - terms and conditions of this Public License, which are limited to
|
|
| 109 | - all Copyright and Similar Rights that apply to Your use of the
|
|
| 110 | - Licensed Material and that the Licensor has authority to license.
|
|
| 111 | - |
|
| 112 | - h. Licensor means the individual(s) or entity(ies) granting rights
|
|
| 113 | - under this Public License.
|
|
| 114 | - |
|
| 115 | - i. Share means to provide material to the public by any means or
|
|
| 116 | - process that requires permission under the Licensed Rights, such
|
|
| 117 | - as reproduction, public display, public performance, distribution,
|
|
| 118 | - dissemination, communication, or importation, and to make material
|
|
| 119 | - available to the public including in ways that members of the
|
|
| 120 | - public may access the material from a place and at a time
|
|
| 121 | - individually chosen by them.
|
|
| 122 | - |
|
| 123 | - j. Sui Generis Database Rights means rights other than copyright
|
|
| 124 | - resulting from Directive 96/9/EC of the European Parliament and of
|
|
| 125 | - the Council of 11 March 1996 on the legal protection of databases,
|
|
| 126 | - as amended and/or succeeded, as well as other essentially
|
|
| 127 | - equivalent rights anywhere in the world.
|
|
| 128 | - |
|
| 129 | - k. You means the individual or entity exercising the Licensed Rights
|
|
| 130 | - under this Public License. Your has a corresponding meaning.
|
|
| 131 | - |
|
| 132 | - |
|
| 133 | -Section 2 -- Scope.
|
|
| 134 | - |
|
| 135 | - a. License grant.
|
|
| 136 | - |
|
| 137 | - 1. Subject to the terms and conditions of this Public License,
|
|
| 138 | - the Licensor hereby grants You a worldwide, royalty-free,
|
|
| 139 | - non-sublicensable, non-exclusive, irrevocable license to
|
|
| 140 | - exercise the Licensed Rights in the Licensed Material to:
|
|
| 141 | - |
|
| 142 | - a. reproduce and Share the Licensed Material, in whole or
|
|
| 143 | - in part; and
|
|
| 144 | - |
|
| 145 | - b. produce, reproduce, and Share Adapted Material.
|
|
| 146 | - |
|
| 147 | - 2. Exceptions and Limitations. For the avoidance of doubt, where
|
|
| 148 | - Exceptions and Limitations apply to Your use, this Public
|
|
| 149 | - License does not apply, and You do not need to comply with
|
|
| 150 | - its terms and conditions.
|
|
| 151 | - |
|
| 152 | - 3. Term. The term of this Public License is specified in Section
|
|
| 153 | - 6(a).
|
|
| 154 | - |
|
| 155 | - 4. Media and formats; technical modifications allowed. The
|
|
| 156 | - Licensor authorizes You to exercise the Licensed Rights in
|
|
| 157 | - all media and formats whether now known or hereafter created,
|
|
| 158 | - and to make technical modifications necessary to do so. The
|
|
| 159 | - Licensor waives and/or agrees not to assert any right or
|
|
| 160 | - authority to forbid You from making technical modifications
|
|
| 161 | - necessary to exercise the Licensed Rights, including
|
|
| 162 | - technical modifications necessary to circumvent Effective
|
|
| 163 | - Technological Measures. For purposes of this Public License,
|
|
| 164 | - simply making modifications authorized by this Section 2(a)
|
|
| 165 | - (4) never produces Adapted Material.
|
|
| 166 | - |
|
| 167 | - 5. Downstream recipients.
|
|
| 168 | - |
|
| 169 | - a. Offer from the Licensor -- Licensed Material. Every
|
|
| 170 | - recipient of the Licensed Material automatically
|
|
| 171 | - receives an offer from the Licensor to exercise the
|
|
| 172 | - Licensed Rights under the terms and conditions of this
|
|
| 173 | - Public License.
|
|
| 174 | - |
|
| 175 | - b. No downstream restrictions. You may not offer or impose
|
|
| 176 | - any additional or different terms or conditions on, or
|
|
| 177 | - apply any Effective Technological Measures to, the
|
|
| 178 | - Licensed Material if doing so restricts exercise of the
|
|
| 179 | - Licensed Rights by any recipient of the Licensed
|
|
| 180 | - Material.
|
|
| 181 | - |
|
| 182 | - 6. No endorsement. Nothing in this Public License constitutes or
|
|
| 183 | - may be construed as permission to assert or imply that You
|
|
| 184 | - are, or that Your use of the Licensed Material is, connected
|
|
| 185 | - with, or sponsored, endorsed, or granted official status by,
|
|
| 186 | - the Licensor or others designated to receive attribution as
|
|
| 187 | - provided in Section 3(a)(1)(A)(i).
|
|
| 188 | - |
|
| 189 | - b. Other rights.
|
|
| 190 | - |
|
| 191 | - 1. Moral rights, such as the right of integrity, are not
|
|
| 192 | - licensed under this Public License, nor are publicity,
|
|
| 193 | - privacy, and/or other similar personality rights; however, to
|
|
| 194 | - the extent possible, the Licensor waives and/or agrees not to
|
|
| 195 | - assert any such rights held by the Licensor to the limited
|
|
| 196 | - extent necessary to allow You to exercise the Licensed
|
|
| 197 | - Rights, but not otherwise.
|
|
| 198 | - |
|
| 199 | - 2. Patent and trademark rights are not licensed under this
|
|
| 200 | - Public License.
|
|
| 201 | - |
|
| 202 | - 3. To the extent possible, the Licensor waives any right to
|
|
| 203 | - collect royalties from You for the exercise of the Licensed
|
|
| 204 | - Rights, whether directly or through a collecting society
|
|
| 205 | - under any voluntary or waivable statutory or compulsory
|
|
| 206 | - licensing scheme. In all other cases the Licensor expressly
|
|
| 207 | - reserves any right to collect such royalties.
|
|
| 208 | - |
|
| 209 | - |
|
| 210 | -Section 3 -- License Conditions.
|
|
| 211 | - |
|
| 212 | -Your exercise of the Licensed Rights is expressly made subject to the
|
|
| 213 | -following conditions.
|
|
| 214 | - |
|
| 215 | - a. Attribution.
|
|
| 216 | - |
|
| 217 | - 1. If You Share the Licensed Material (including in modified
|
|
| 218 | - form), You must:
|
|
| 219 | - |
|
| 220 | - a. retain the following if it is supplied by the Licensor
|
|
| 221 | - with the Licensed Material:
|
|
| 222 | - |
|
| 223 | - i. identification of the creator(s) of the Licensed
|
|
| 224 | - Material and any others designated to receive
|
|
| 225 | - attribution, in any reasonable manner requested by
|
|
| 226 | - the Licensor (including by pseudonym if
|
|
| 227 | - designated);
|
|
| 228 | - |
|
| 229 | - ii. a copyright notice;
|
|
| 230 | - |
|
| 231 | - iii. a notice that refers to this Public License;
|
|
| 232 | - |
|
| 233 | - iv. a notice that refers to the disclaimer of
|
|
| 234 | - warranties;
|
|
| 235 | - |
|
| 236 | - v. a URI or hyperlink to the Licensed Material to the
|
|
| 237 | - extent reasonably practicable;
|
|
| 238 | - |
|
| 239 | - b. indicate if You modified the Licensed Material and
|
|
| 240 | - retain an indication of any previous modifications; and
|
|
| 241 | - |
|
| 242 | - c. indicate the Licensed Material is licensed under this
|
|
| 243 | - Public License, and include the text of, or the URI or
|
|
| 244 | - hyperlink to, this Public License.
|
|
| 245 | - |
|
| 246 | - 2. You may satisfy the conditions in Section 3(a)(1) in any
|
|
| 247 | - reasonable manner based on the medium, means, and context in
|
|
| 248 | - which You Share the Licensed Material. For example, it may be
|
|
| 249 | - reasonable to satisfy the conditions by providing a URI or
|
|
| 250 | - hyperlink to a resource that includes the required
|
|
| 251 | - information.
|
|
| 252 | - |
|
| 253 | - 3. If requested by the Licensor, You must remove any of the
|
|
| 254 | - information required by Section 3(a)(1)(A) to the extent
|
|
| 255 | - reasonably practicable.
|
|
| 256 | - |
|
| 257 | - 4. If You Share Adapted Material You produce, the Adapter's
|
|
| 258 | - License You apply must not prevent recipients of the Adapted
|
|
| 259 | - Material from complying with this Public License.
|
|
| 260 | - |
|
| 261 | - |
|
| 262 | -Section 4 -- Sui Generis Database Rights.
|
|
| 263 | - |
|
| 264 | -Where the Licensed Rights include Sui Generis Database Rights that
|
|
| 265 | -apply to Your use of the Licensed Material:
|
|
| 266 | - |
|
| 267 | - a. for the avoidance of doubt, Section 2(a)(1) grants You the right
|
|
| 268 | - to extract, reuse, reproduce, and Share all or a substantial
|
|
| 269 | - portion of the contents of the database;
|
|
| 270 | - |
|
| 271 | - b. if You include all or a substantial portion of the database
|
|
| 272 | - contents in a database in which You have Sui Generis Database
|
|
| 273 | - Rights, then the database in which You have Sui Generis Database
|
|
| 274 | - Rights (but not its individual contents) is Adapted Material; and
|
|
| 275 | - |
|
| 276 | - c. You must comply with the conditions in Section 3(a) if You Share
|
|
| 277 | - all or a substantial portion of the contents of the database.
|
|
| 278 | - |
|
| 279 | -For the avoidance of doubt, this Section 4 supplements and does not
|
|
| 280 | -replace Your obligations under this Public License where the Licensed
|
|
| 281 | -Rights include other Copyright and Similar Rights.
|
|
| 282 | - |
|
| 283 | - |
|
| 284 | -Section 5 -- Disclaimer of Warranties and Limitation of Liability.
|
|
| 285 | - |
|
| 286 | - a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
|
|
| 287 | - EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
|
|
| 288 | - AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
|
|
| 289 | - ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
|
|
| 290 | - IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
|
|
| 291 | - WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
|
| 292 | - PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
|
|
| 293 | - ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
|
|
| 294 | - KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
|
|
| 295 | - ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
|
|
| 296 | - |
|
| 297 | - b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
|
|
| 298 | - TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
|
|
| 299 | - NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
|
|
| 300 | - INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
|
|
| 301 | - COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
|
|
| 302 | - USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
|
|
| 303 | - ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
|
|
| 304 | - DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
|
|
| 305 | - IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
|
|
| 306 | - |
|
| 307 | - c. The disclaimer of warranties and limitation of liability provided
|
|
| 308 | - above shall be interpreted in a manner that, to the extent
|
|
| 309 | - possible, most closely approximates an absolute disclaimer and
|
|
| 310 | - waiver of all liability.
|
|
| 311 | - |
|
| 312 | - |
|
| 313 | -Section 6 -- Term and Termination.
|
|
| 314 | - |
|
| 315 | - a. This Public License applies for the term of the Copyright and
|
|
| 316 | - Similar Rights licensed here. However, if You fail to comply with
|
|
| 317 | - this Public License, then Your rights under this Public License
|
|
| 318 | - terminate automatically.
|
|
| 319 | - |
|
| 320 | - b. Where Your right to use the Licensed Material has terminated under
|
|
| 321 | - Section 6(a), it reinstates:
|
|
| 322 | - |
|
| 323 | - 1. automatically as of the date the violation is cured, provided
|
|
| 324 | - it is cured within 30 days of Your discovery of the
|
|
| 325 | - violation; or
|
|
| 326 | - |
|
| 327 | - 2. upon express reinstatement by the Licensor.
|
|
| 328 | - |
|
| 329 | - For the avoidance of doubt, this Section 6(b) does not affect any
|
|
| 330 | - right the Licensor may have to seek remedies for Your violations
|
|
| 331 | - of this Public License.
|
|
| 332 | - |
|
| 333 | - c. For the avoidance of doubt, the Licensor may also offer the
|
|
| 334 | - Licensed Material under separate terms or conditions or stop
|
|
| 335 | - distributing the Licensed Material at any time; however, doing so
|
|
| 336 | - will not terminate this Public License.
|
|
| 337 | - |
|
| 338 | - d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
|
|
| 339 | - License.
|
|
| 340 | - |
|
| 341 | - |
|
| 342 | -Section 7 -- Other Terms and Conditions.
|
|
| 343 | - |
|
| 344 | - a. The Licensor shall not be bound by any additional or different
|
|
| 345 | - terms or conditions communicated by You unless expressly agreed.
|
|
| 346 | - |
|
| 347 | - b. Any arrangements, understandings, or agreements regarding the
|
|
| 348 | - Licensed Material not stated herein are separate from and
|
|
| 349 | - independent of the terms and conditions of this Public License.
|
|
| 350 | - |
|
| 351 | - |
|
| 352 | -Section 8 -- Interpretation.
|
|
| 353 | - |
|
| 354 | - a. For the avoidance of doubt, this Public License does not, and
|
|
| 355 | - shall not be interpreted to, reduce, limit, restrict, or impose
|
|
| 356 | - conditions on any use of the Licensed Material that could lawfully
|
|
| 357 | - be made without permission under this Public License.
|
|
| 358 | - |
|
| 359 | - b. To the extent possible, if any provision of this Public License is
|
|
| 360 | - deemed unenforceable, it shall be automatically reformed to the
|
|
| 361 | - minimum extent necessary to make it enforceable. If the provision
|
|
| 362 | - cannot be reformed, it shall be severed from this Public License
|
|
| 363 | - without affecting the enforceability of the remaining terms and
|
|
| 364 | - conditions.
|
|
| 365 | - |
|
| 366 | - c. No term or condition of this Public License will be waived and no
|
|
| 367 | - failure to comply consented to unless expressly agreed to by the
|
|
| 368 | - Licensor.
|
|
| 369 | - |
|
| 370 | - d. Nothing in this Public License constitutes or may be interpreted
|
|
| 371 | - as a limitation upon, or waiver of, any privileges and immunities
|
|
| 372 | - that apply to the Licensor or You, including from the legal
|
|
| 373 | - processes of any jurisdiction or authority.
|
|
| 374 | - |
|
| 375 | - |
|
| 376 | -=======================================================================
|
|
| 377 | - |
|
| 378 | -Creative Commons is not a party to its public licenses.
|
|
| 379 | -Notwithstanding, Creative Commons may elect to apply one of its public
|
|
| 380 | -licenses to material it publishes and in those instances will be
|
|
| 381 | -considered the "Licensor." Except for the limited purpose of indicating
|
|
| 382 | -that material is shared under a Creative Commons public license or as
|
|
| 383 | -otherwise permitted by the Creative Commons policies published at
|
|
| 384 | -creativecommons.org/policies, Creative Commons does not authorize the
|
|
| 385 | -use of the trademark "Creative Commons" or any other trademark or logo
|
|
| 386 | -of Creative Commons without its prior written consent including,
|
|
| 387 | -without limitation, in connection with any unauthorized modifications
|
|
| 388 | -to any of its public licenses or any other arrangements,
|
|
| 389 | -understandings, or agreements concerning use of licensed material. For
|
|
| 390 | -the avoidance of doubt, this paragraph does not form part of the public
|
|
| 391 | -licenses.
|
|
| 392 | - |
|
| 393 | -Creative Commons may be contacted at creativecommons.org. |
|
| \ No newline at end of file |
| ... | ... | @@ -143,6 +143,7 @@ |
| 143 | 143 | <property name="can-focus">True</property>
|
| 144 | 144 | <property name="receives-default">True</property>
|
| 145 | 145 | <property name="tooltip-text" translatable="yes">Show a list of emojis</property>
|
| 146 | + <property name="action-name">win.show-emoji-chooser</property>
|
|
| 146 | 147 | <property name="relief">none</property>
|
| 147 | 148 | <child>
|
| 148 | 149 | <object class="GtkImage">
|
| ... | ... | @@ -688,57 +688,7 @@ |
| 688 | 688 | </packing>
|
| 689 | 689 | </child>
|
| 690 | 690 | <child>
|
| 691 | - <!-- n-columns=3 n-rows=3 -->
|
|
| 692 | - <object class="GtkGrid" id="emoji">
|
|
| 693 | - <property name="visible">True</property>
|
|
| 694 | - <property name="can-focus">False</property>
|
|
| 695 | - <property name="orientation">vertical</property>
|
|
| 696 | - <property name="row-spacing">12</property>
|
|
| 697 | - <child>
|
|
| 698 | - <object class="GtkLabel">
|
|
| 699 | - <property name="visible">True</property>
|
|
| 700 | - <property name="can-focus">False</property>
|
|
| 701 | - <property name="label" translatable="yes">Emoji</property>
|
|
| 702 | - <property name="xalign">0</property>
|
|
| 703 | - <style>
|
|
| 704 | - <class name="bold"/>
|
|
| 705 | - </style>
|
|
| 706 | - </object>
|
|
| 707 | - <packing>
|
|
| 708 | - <property name="left-attach">0</property>
|
|
| 709 | - <property name="top-attach">0</property>
|
|
| 710 | - </packing>
|
|
| 711 | - </child>
|
|
| 712 | - <child>
|
|
| 713 | - <placeholder/>
|
|
| 714 | - </child>
|
|
| 715 | - <child>
|
|
| 716 | - <placeholder/>
|
|
| 717 | - </child>
|
|
| 718 | - <child>
|
|
| 719 | - <placeholder/>
|
|
| 720 | - </child>
|
|
| 721 | - <child>
|
|
| 722 | - <placeholder/>
|
|
| 723 | - </child>
|
|
| 724 | - <child>
|
|
| 725 | - <placeholder/>
|
|
| 726 | - </child>
|
|
| 727 | - <child>
|
|
| 728 | - <placeholder/>
|
|
| 729 | - </child>
|
|
| 730 | - <child>
|
|
| 731 | - <placeholder/>
|
|
| 732 | - </child>
|
|
| 733 | - <child>
|
|
| 734 | - <placeholder/>
|
|
| 735 | - </child>
|
|
| 736 | - </object>
|
|
| 737 | - <packing>
|
|
| 738 | - <property name="expand">False</property>
|
|
| 739 | - <property name="fill">True</property>
|
|
| 740 | - <property name="position">1</property>
|
|
| 741 | - </packing>
|
|
| 691 | + <placeholder/>
|
|
| 742 | 692 | </child>
|
| 743 | 693 | </object>
|
| 744 | 694 | </child>
|
| ... | ... | @@ -748,18 +748,6 @@ #NotificationPopupTitle { |
| 748 | 748 | font-weight: bold;
|
| 749 | 749 | }
|
| 750 | 750 | |
| 751 | -/* Emoticon Popover (ModifierChooser) */
|
|
| 752 | -popover#EmoticonPopover button { background: none; border: none; box-shadow:none; padding: 0px;}
|
|
| 753 | -popover#EmoticonPopover button > label { font-size: 2.0rem; }
|
|
| 754 | -popover#EmoticonPopover flowboxchild > label { font-size: 2.0rem; }
|
|
| 755 | -popover#EmoticonPopover flowboxchild { padding-top: 5px; padding-bottom: 5px; }
|
|
| 756 | -popover#EmoticonPopover scrolledwindow { border: none; }
|
|
| 757 | -popover#EmoticonPopover { padding: 5px; background-color: @theme_unfocused_base_color; }
|
|
| 758 | - |
|
| 759 | -.emoji-chooser-heading { font-size: 1.15rem; font-weight: bold; padding: 5px;}
|
|
| 760 | -.emoji-chooser-flowbox { padding-left: 5px; padding-right: 11px; }
|
|
| 761 | -.emoji-modifier-chooser-flowbox { padding-left: 5px; }
|
|
| 762 | - |
|
| 763 | 751 | /* Settings widgets ======================================================== */
|
| 764 | 752 | |
| 765 | 753 | /* SettingsBox */
|
| ... | ... | @@ -244,12 +244,6 @@ def _startup(self) -> None: |
| 244 | 244 | idle.Monitor.set_interval(app.settings.get('autoawaytime') * 60,
|
| 245 | 245 | app.settings.get('autoxatime') * 60)
|
| 246 | 246 | |
| 247 | - if sys.platform == 'darwin':
|
|
| 248 | - # TODO: Remove if colored emoji rendering works well on
|
|
| 249 | - # Windows and MacOS
|
|
| 250 | - from gajim.gui.emoji_chooser import emoji_chooser
|
|
| 251 | - emoji_chooser.load()
|
|
| 252 | - |
|
| 253 | 247 | from gajim.gui_interface import Interface
|
| 254 | 248 | |
| 255 | 249 | self.interface = Interface()
|
| ... | ... | @@ -712,7 +712,6 @@ def _on_function_message(self, |
| 712 | 712 | self._chat_control.add_info_message(message)
|
| 713 | 713 | |
| 714 | 714 | def _on_send_message(self) -> None:
|
| 715 | - self._message_action_box.msg_textview.replace_emojis()
|
|
| 716 | 715 | message = self._message_action_box.msg_textview.get_text()
|
| 717 | 716 | if message.startswith('//'):
|
| 718 | 717 | # Escape sequence for chat commands
|
| ... | ... | @@ -14,32 +14,19 @@ |
| 14 | 14 | |
| 15 | 15 | from __future__ import annotations
|
| 16 | 16 | |
| 17 | -from typing import Any
|
|
| 18 | - |
|
| 19 | 17 | import logging
|
| 20 | -import sys
|
|
| 21 | 18 | |
| 22 | 19 | from gi.repository import Gtk
|
| 23 | 20 | from gi.repository import Pango
|
| 24 | 21 | from gi.repository import Gdk
|
| 25 | -from gi.repository import GdkPixbuf
|
|
| 26 | 22 | from gi.repository import GLib
|
| 27 | -from gi.repository import GObject
|
|
| 28 | 23 | |
| 29 | -from gajim.common import app
|
|
| 30 | -from gajim.common.const import StyleAttr
|
|
| 31 | 24 | from gajim.common.helpers import open_uri
|
| 32 | -from gajim.common.helpers import parse_uri
|
|
| 33 | -from gajim.common.structs import URI
|
|
| 34 | 25 | from gajim.common.styling import BaseHyperlink
|
| 35 | 26 | from gajim.common.styling import PlainBlock
|
| 36 | 27 | from gajim.common.styling import process_uris
|
| 37 | 28 | |
| 38 | 29 | from ..menus import get_conv_action_context_menu
|
| 39 | -from ..menus import get_conv_uri_context_menu
|
|
| 40 | -from ..emoji_data import emoji_pixbufs
|
|
| 41 | -from ..emoji_data import get_emoji_pixbuf
|
|
| 42 | -from ..util import get_cursor
|
|
| 43 | 30 | from ..util import make_pango_attributes
|
| 44 | 31 | |
| 45 | 32 | log = logging.getLogger('gajim.gui.conversaion.plain_widget')
|
| ... | ... | @@ -55,22 +42,11 @@ def __init__(self, account: str, selectable: bool) -> None: |
| 55 | 42 | |
| 56 | 43 | self._account = account
|
| 57 | 44 | |
| 58 | - # We use a Gtk.Textview on MacOS, since there is no support
|
|
| 59 | - # for rendering color fonts (Emojis) on MacOS yet, see:
|
|
| 60 | - # https://gitlab.freedesktop.org/cairo/cairo/-/merge_requests/289
|
|
| 61 | - # https://gitlab.freedesktop.org/cairo/cairo/-/merge_requests/9
|
|
| 62 | - if sys.platform == 'darwin':
|
|
| 63 | - if app.settings.get('dev_use_message_label'):
|
|
| 64 | - self._text_widget = MessageLabel(self._account, selectable)
|
|
| 65 | - else:
|
|
| 66 | - self._text_widget = MessageTextview(self._account)
|
|
| 67 | - else:
|
|
| 68 | - self._text_widget = MessageLabel(self._account, selectable)
|
|
| 45 | + self._text_widget = MessageLabel(self._account, selectable)
|
|
| 69 | 46 | self.add(self._text_widget)
|
| 70 | 47 | |
| 71 | 48 | def set_selectable(self, selectable: bool) -> None:
|
| 72 | - if isinstance(self._text_widget, MessageLabel):
|
|
| 73 | - self._text_widget.set_selectable(selectable)
|
|
| 49 | + self._text_widget.set_selectable(selectable)
|
|
| 74 | 50 | |
| 75 | 51 | def add_content(self, block: PlainBlock) -> None:
|
| 76 | 52 | self._text_widget.print_text_with_styling(block)
|
| ... | ... | @@ -152,295 +128,3 @@ def _on_focus_out(widget: MessageLabel, |
| 152 | 128 | _event: Gdk.EventFocus
|
| 153 | 129 | ) -> None:
|
| 154 | 130 | widget.get_style_context().add_class('transparent-selection') |
| 155 | - |
|
| 156 | - |
|
| 157 | -class MessageTextview(Gtk.TextView):
|
|
| 158 | - def __init__(self, account: str) -> None:
|
|
| 159 | - Gtk.TextView.__init__(self)
|
|
| 160 | - self.set_hexpand(True)
|
|
| 161 | - self.set_margin_start(0)
|
|
| 162 | - self.set_margin_end(0)
|
|
| 163 | - self.set_border_width(0)
|
|
| 164 | - self.set_left_margin(0)
|
|
| 165 | - self.set_right_margin(0)
|
|
| 166 | - self.set_has_tooltip(True)
|
|
| 167 | - self.set_editable(False)
|
|
| 168 | - self.set_cursor_visible(False)
|
|
| 169 | - self.set_wrap_mode(Gtk.WrapMode.WORD_CHAR)
|
|
| 170 | - |
|
| 171 | - self._handlers: dict[int, MessageTextview] = {}
|
|
| 172 | - |
|
| 173 | - id_ = self.connect('query-tooltip', self._query_tooltip)
|
|
| 174 | - self._handlers[id_] = self
|
|
| 175 | - id_ = self.connect('button-press-event', self._on_button_press)
|
|
| 176 | - self._handlers[id_] = self
|
|
| 177 | - id_ = self.connect('populate-popup', self._on_populate_popup)
|
|
| 178 | - self._handlers[id_] = self
|
|
| 179 | - |
|
| 180 | - self._account = account
|
|
| 181 | - |
|
| 182 | - # Used for changing the mouse pointer when hovering clickable URIs
|
|
| 183 | - self._cursor_changed: bool = False
|
|
| 184 | - |
|
| 185 | - # Keeps text selections for quoting and search actions
|
|
| 186 | - self._selected_text: str = ''
|
|
| 187 | - |
|
| 188 | - self.get_style_context().add_class('gajim-conversation-text')
|
|
| 189 | - |
|
| 190 | - # Create Tags
|
|
| 191 | - self._create_url_tags()
|
|
| 192 | - self.get_buffer().create_tag('strong', weight=Pango.Weight.BOLD)
|
|
| 193 | - self.get_buffer().create_tag('emphasis', style=Pango.Style.ITALIC)
|
|
| 194 | - self.get_buffer().create_tag('strike', strikethrough=True)
|
|
| 195 | - self.get_buffer().create_tag('pre', family='monospace')
|
|
| 196 | - |
|
| 197 | - self.connect('destroy', self._on_destroy)
|
|
| 198 | - |
|
| 199 | - def _on_destroy(self, *args: Any) -> None:
|
|
| 200 | - for id_, widget in self._handlers.items():
|
|
| 201 | - if widget.handler_is_connected(id_):
|
|
| 202 | - widget.disconnect(id_)
|
|
| 203 | - self._handlers.clear()
|
|
| 204 | - |
|
| 205 | - def _create_url_tags(self) -> None:
|
|
| 206 | - color = app.css_config.get_value('.gajim-url', StyleAttr.COLOR)
|
|
| 207 | - for name in URI_TAGS:
|
|
| 208 | - tag = self.get_buffer().create_tag(name,
|
|
| 209 | - foreground=color,
|
|
| 210 | - underline=Pango.Underline.SINGLE)
|
|
| 211 | - tag.connect('event', self._on_uri_clicked, tag)
|
|
| 212 | - |
|
| 213 | - def clear(self) -> None:
|
|
| 214 | - buffer_ = self.get_buffer()
|
|
| 215 | - start, end = buffer_.get_bounds()
|
|
| 216 | - buffer_.delete(start, end)
|
|
| 217 | - |
|
| 218 | - def get_text(self) -> str:
|
|
| 219 | - buffer_ = self.get_buffer()
|
|
| 220 | - start, end = buffer_.get_bounds()
|
|
| 221 | - return buffer_.get_text(start, end, False)
|
|
| 222 | - |
|
| 223 | - def print_text_with_styling(self, block: PlainBlock) -> None:
|
|
| 224 | - buffer_ = self.get_buffer()
|
|
| 225 | - buffer_.insert(buffer_.get_start_iter(), block.text.strip())
|
|
| 226 | - |
|
| 227 | - if len(self.get_text()) > 10000:
|
|
| 228 | - # Limit message styling processing
|
|
| 229 | - return
|
|
| 230 | - |
|
| 231 | - for span in block.spans:
|
|
| 232 | - start_iter = buffer_.get_iter_at_offset(span.start)
|
|
| 233 | - end_iter = buffer_.get_iter_at_offset(span.end)
|
|
| 234 | - buffer_.apply_tag_by_name(span.name, start_iter, end_iter)
|
|
| 235 | - |
|
| 236 | - for uri in block.uris:
|
|
| 237 | - start_iter = buffer_.get_iter_at_offset(uri.start)
|
|
| 238 | - end_iter = buffer_.get_iter_at_offset(uri.end)
|
|
| 239 | - buffer_.apply_tag_by_name(uri.name, start_iter, end_iter)
|
|
| 240 | - |
|
| 241 | - for emoji in block.emojis:
|
|
| 242 | - start_iter = buffer_.get_iter_at_offset(emoji.start)
|
|
| 243 | - end_iter = buffer_.get_iter_at_offset(emoji.end)
|
|
| 244 | - if emoji_pixbufs.complete:
|
|
| 245 | - # Only search for pixbuf if loading is completed
|
|
| 246 | - pixbuf = get_emoji_pixbuf(emoji.text)
|
|
| 247 | - if pixbuf is None:
|
|
| 248 | - buffer_.insert(end_iter, emoji.text)
|
|
| 249 | - else:
|
|
| 250 | - pixbuf = pixbuf.copy()
|
|
| 251 | - anchor = buffer_.create_child_anchor(end_iter)
|
|
| 252 | - anchor.plaintext = emoji.text # type: ignore
|
|
| 253 | - img = Gtk.Image.new_from_pixbuf(pixbuf)
|
|
| 254 | - img.show()
|
|
| 255 | - self.add_child_at_anchor(img, anchor)
|
|
| 256 | - buffer_.delete(
|
|
| 257 | - buffer_.get_iter_at_offset(emoji.start),
|
|
| 258 | - buffer_.get_iter_at_offset(emoji.end))
|
|
| 259 | - else:
|
|
| 260 | - # Set marks and save them so we can replace emojis
|
|
| 261 | - # once loading is complete
|
|
| 262 | - start_mark = buffer_.create_mark(None, end_iter, True)
|
|
| 263 | - buffer_.insert(end_iter, emoji.text)
|
|
| 264 | - end_mark = buffer_.create_mark(None, end_iter, True)
|
|
| 265 | - emoji_pixbufs.append_marks(
|
|
| 266 | - self, start_mark, end_mark, emoji.text)
|
|
| 267 | - |
|
| 268 | - def replace_emoji(self,
|
|
| 269 | - start_mark: Gtk.TextMark,
|
|
| 270 | - end_mark: Gtk.TextMark,
|
|
| 271 | - pixbuf: GdkPixbuf.Pixbuf,
|
|
| 272 | - codepoint: str
|
|
| 273 | - ) -> None:
|
|
| 274 | - buffer_ = self.get_buffer()
|
|
| 275 | - start_iter = buffer_.get_iter_at_mark(start_mark)
|
|
| 276 | - end_iter = buffer_.get_iter_at_mark(end_mark)
|
|
| 277 | - buffer_.delete(start_iter, end_iter)
|
|
| 278 | - |
|
| 279 | - anchor = buffer_.create_child_anchor(start_iter)
|
|
| 280 | - anchor.plaintext = codepoint # type: ignore
|
|
| 281 | - img = Gtk.Image.new_from_pixbuf(pixbuf)
|
|
| 282 | - img.show()
|
|
| 283 | - self.add_child_at_anchor(img, anchor)
|
|
| 284 | - buffer_.delete_mark(start_mark)
|
|
| 285 | - buffer_.delete_mark(end_mark)
|
|
| 286 | - |
|
| 287 | - def add_action_phrase(self, text: str, nickname: str) -> None:
|
|
| 288 | - text = text.replace('/me', f'* {nickname}', 1)
|
|
| 289 | - |
|
| 290 | - buffer_ = self.get_buffer()
|
|
| 291 | - buffer_.insert(buffer_.get_start_iter(), text.strip())
|
|
| 292 | - |
|
| 293 | - uris = process_uris(text)
|
|
| 294 | - for uri in uris:
|
|
| 295 | - start_iter = buffer_.get_iter_at_offset(uri.start)
|
|
| 296 | - end_iter = buffer_.get_iter_at_offset(uri.end)
|
|
| 297 | - buffer_.apply_tag_by_name(uri.name, start_iter, end_iter)
|
|
| 298 | - |
|
| 299 | - start_iter = buffer_.get_start_iter()
|
|
| 300 | - end_iter = buffer_.get_end_iter()
|
|
| 301 | - buffer_.apply_tag_by_name('emphasis', start_iter, end_iter)
|
|
| 302 | - |
|
| 303 | - def _query_tooltip(self,
|
|
| 304 | - widget: Gtk.TextView,
|
|
| 305 | - x_pos: int,
|
|
| 306 | - y_pos: int,
|
|
| 307 | - _keyboard_mode: bool,
|
|
| 308 | - _tooltip: Gtk.Tooltip
|
|
| 309 | - ) -> bool:
|
|
| 310 | - |
|
| 311 | - window = widget.get_window(Gtk.TextWindowType.TEXT)
|
|
| 312 | - assert window is not None
|
|
| 313 | - x_pos, y_pos = self.window_to_buffer_coords(
|
|
| 314 | - Gtk.TextWindowType.TEXT, x_pos, y_pos)
|
|
| 315 | - |
|
| 316 | - iter_ = self.get_iter_at_position(x_pos, y_pos)[1]
|
|
| 317 | - for tag in iter_.get_tags():
|
|
| 318 | - tag_name = tag.get_property('name')
|
|
| 319 | - if tag_name in URI_TAGS:
|
|
| 320 | - window.set_cursor(get_cursor('pointer'))
|
|
| 321 | - self._cursor_changed = True
|
|
| 322 | - return False
|
|
| 323 | - |
|
| 324 | - if self._cursor_changed:
|
|
| 325 | - window.set_cursor(get_cursor('text'))
|
|
| 326 | - self._cursor_changed = False
|
|
| 327 | - return False
|
|
| 328 | - |
|
| 329 | - def _on_button_press(self, _widget: Any, event: Gdk.EventButton) -> bool:
|
|
| 330 | - '''
|
|
| 331 | - We don’t open the standard context menu when receiving
|
|
| 332 | - a click on tagged text.
|
|
| 333 | - If it’s untagged text, check if something is selected
|
|
| 334 | - '''
|
|
| 335 | - self._selected_text = ''
|
|
| 336 | - |
|
| 337 | - if event.button != 3:
|
|
| 338 | - # If it’s not a right click
|
|
| 339 | - return False
|
|
| 340 | - |
|
| 341 | - x_pos, y_pos = self.window_to_buffer_coords(
|
|
| 342 | - Gtk.TextWindowType.TEXT,
|
|
| 343 | - int(event.x),
|
|
| 344 | - int(event.y))
|
|
| 345 | - _, iter_ = self.get_iter_at_location(x_pos, y_pos)
|
|
| 346 | - tags = iter_.get_tags()
|
|
| 347 | - |
|
| 348 | - if tags:
|
|
| 349 | - # A tagged text fragment has been clicked
|
|
| 350 | - for tag in tags:
|
|
| 351 | - if tag.get_property('name') in URI_TAGS:
|
|
| 352 | - # Block regular context menu
|
|
| 353 | - return True
|
|
| 354 | - |
|
| 355 | - # Check if there is a selection and make it available for
|
|
| 356 | - # _on_populate_popup
|
|
| 357 | - buffer_ = self.get_buffer()
|
|
| 358 | - return_val = buffer_.get_selection_bounds()
|
|
| 359 | - if return_val:
|
|
| 360 | - # Something has been selected, get the text
|
|
| 361 | - start_sel, finish_sel = return_val[0], return_val[1]
|
|
| 362 | - self._selected_text = buffer_.get_text(
|
|
| 363 | - start_sel, finish_sel, True)
|
|
| 364 | - elif iter_.get_char() and ord(iter_.get_char()) > 31:
|
|
| 365 | - # Clicked on a word, take whole word for selection
|
|
| 366 | - start_sel = iter_.copy()
|
|
| 367 | - if not start_sel.starts_word():
|
|
| 368 | - start_sel.backward_word_start()
|
|
| 369 | - finish_sel = iter_.copy()
|
|
| 370 | - if not finish_sel.ends_word():
|
|
| 371 | - finish_sel.forward_word_end()
|
|
| 372 | - self._selected_text = buffer_.get_text(
|
|
| 373 | - start_sel, finish_sel, True)
|
|
| 374 | - return False
|
|
| 375 | - |
|
| 376 | - def _on_populate_popup(self,
|
|
| 377 | - _textview: Gtk.TextView,
|
|
| 378 | - menu: Gtk.Menu
|
|
| 379 | - ) -> None:
|
|
| 380 | - '''
|
|
| 381 | - Overrides the default context menu.
|
|
| 382 | - If text is selected, a submenu with actions on the selection is added.
|
|
| 383 | - (see _on_button_press)
|
|
| 384 | - '''
|
|
| 385 | - if not self._selected_text:
|
|
| 386 | - menu.show_all()
|
|
| 387 | - return
|
|
| 388 | - |
|
| 389 | - action_menu_item = get_conv_action_context_menu(
|
|
| 390 | - self._account, self._selected_text)
|
|
| 391 | - menu.prepend(action_menu_item)
|
|
| 392 | - menu.show_all()
|
|
| 393 | - |
|
| 394 | - def _on_uri_clicked(self,
|
|
| 395 | - texttag: Gtk.TextTag,
|
|
| 396 | - _widget: Any,
|
|
| 397 | - event: Gdk.Event,
|
|
| 398 | - iter_: Gtk.TextIter,
|
|
| 399 | - _kind: Gtk.TextTag
|
|
| 400 | - ) -> int:
|
|
| 401 | - if event.type != Gdk.EventType.BUTTON_PRESS:
|
|
| 402 | - return Gdk.EVENT_PROPAGATE
|
|
| 403 | - |
|
| 404 | - begin_iter = iter_.copy()
|
|
| 405 | - # we get the beginning of the tag
|
|
| 406 | - while not begin_iter.starts_tag(texttag):
|
|
| 407 | - begin_iter.backward_char()
|
|
| 408 | - end_iter = iter_.copy()
|
|
| 409 | - # we get the end of the tag
|
|
| 410 | - while not end_iter.ends_tag(texttag):
|
|
| 411 | - end_iter.forward_char()
|
|
| 412 | - |
|
| 413 | - # Detect XHTML-IM link
|
|
| 414 | - word = getattr(texttag, 'href', None)
|
|
| 415 | - if not word:
|
|
| 416 | - word = self.get_buffer().get_text(begin_iter, end_iter, True)
|
|
| 417 | - |
|
| 418 | - uri = parse_uri(word)
|
|
| 419 | - if event.button.button == 3: # right click
|
|
| 420 | - self._show_uri_context_menu(uri)
|
|
| 421 | - return Gdk.EVENT_STOP
|
|
| 422 | - |
|
| 423 | - # TODO:
|
|
| 424 | - # self.plugin_modified = False
|
|
| 425 | - # app.plugin_manager.extension_point(
|
|
| 426 | - # 'hyperlink_handler', uri, self, self.get_toplevel())
|
|
| 427 | - # if self.plugin_modified:
|
|
| 428 | - # return Gdk.EVENT_STOP
|
|
| 429 | - |
|
| 430 | - open_uri(uri, account=self._account)
|
|
| 431 | - return Gdk.EVENT_STOP
|
|
| 432 | - |
|
| 433 | - def _show_uri_context_menu(self, uri: URI) -> None:
|
|
| 434 | - menu = get_conv_uri_context_menu(self._account, uri)
|
|
| 435 | - if menu is None:
|
|
| 436 | - log.warning('No handler for URI type: %s', uri)
|
|
| 437 | - return
|
|
| 438 | - |
|
| 439 | - def _destroy(menu: Gtk.Menu, _pspec: GObject.ParamSpec) -> None:
|
|
| 440 | - visible = menu.get_property('visible')
|
|
| 441 | - if not visible:
|
|
| 442 | - GLib.idle_add(menu.destroy)
|
|
| 443 | - |
|
| 444 | - menu.attach_to_widget(self, None)
|
|
| 445 | - menu.connect('notify::visible', _destroy)
|
|
| 446 | - menu.popup_at_pointer() |
| 1 | -# This file is part of Gajim.
|
|
| 2 | -#
|
|
| 3 | -# Gajim is free software; you can redistribute it and/or modify
|
|
| 4 | -# it under the terms of the GNU General Public License as published
|
|
| 5 | -# by the Free Software Foundation; version 3 only.
|
|
| 6 | -#
|
|
| 7 | -# Gajim is distributed in the hope that it will be useful,
|
|
| 8 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 9 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 10 | -# GNU General Public License for more details.
|
|
| 11 | -#
|
|
| 12 | -# You should have received a copy of the GNU General Public License
|
|
| 13 | -# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
| 14 | - |
|
| 15 | -import logging
|
|
| 16 | -import weakref
|
|
| 17 | -from collections import OrderedDict
|
|
| 18 | - |
|
| 19 | -from gi.repository import Gtk
|
|
| 20 | -from gi.repository import GLib
|
|
| 21 | -from gi.repository import GdkPixbuf
|
|
| 22 | -from gi.repository import Pango
|
|
| 23 | - |
|
| 24 | -from gajim.common import app
|
|
| 25 | -from gajim.common import helpers
|
|
| 26 | -from gajim.common import configpaths
|
|
| 27 | - |
|
| 28 | -from .builder import get_builder
|
|
| 29 | -from .emoji_data import emoji_data
|
|
| 30 | -from .emoji_data import emoji_pixbufs
|
|
| 31 | -from .emoji_data import Emoji
|
|
| 32 | - |
|
| 33 | -MODIFIER_MAX_CHILDREN_PER_LINE = 6
|
|
| 34 | -MAX_CHILDREN_PER_LINE = 10
|
|
| 35 | - |
|
| 36 | -log = logging.getLogger('gajim.emoji')
|
|
| 37 | - |
|
| 38 | - |
|
| 39 | -class Section(Gtk.Box):
|
|
| 40 | - def __init__(self, name, search_entry, press_cb, chooser):
|
|
| 41 | - Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL)
|
|
| 42 | - self._chooser = chooser
|
|
| 43 | - self._press_cb = press_cb
|
|
| 44 | - self.pixbuf_generator = None
|
|
| 45 | - self.heading = Gtk.Label(label=name)
|
|
| 46 | - self.heading.set_halign(Gtk.Align.START)
|
|
| 47 | - self.heading.get_style_context().add_class('emoji-chooser-heading')
|
|
| 48 | - self.add(self.heading)
|
|
| 49 | - |
|
| 50 | - self.flowbox = Gtk.FlowBox()
|
|
| 51 | - self.flowbox.get_style_context().add_class('emoji-chooser-flowbox')
|
|
| 52 | - self.flowbox.set_max_children_per_line(MAX_CHILDREN_PER_LINE)
|
|
| 53 | - self.flowbox.set_filter_func(self._filter_func, search_entry)
|
|
| 54 | - self.flowbox.connect('child-activated', press_cb)
|
|
| 55 | - |
|
| 56 | - self.add(self.flowbox)
|
|
| 57 | - self.show_all()
|
|
| 58 | - |
|
| 59 | - def _filter_func(self, child, search_entry):
|
|
| 60 | - name = search_entry.get_text()
|
|
| 61 | - if not name:
|
|
| 62 | - self.show()
|
|
| 63 | - return True
|
|
| 64 | - |
|
| 65 | - if name in child.desc:
|
|
| 66 | - self.show()
|
|
| 67 | - return True
|
|
| 68 | - return False
|
|
| 69 | - |
|
| 70 | - def add_emoji(self, codepoint, attrs):
|
|
| 71 | - # Return always True, this method is used for the emoji factory
|
|
| 72 | - # called by GLib.idle_add()
|
|
| 73 | - pixbuf = self._get_next_pixbuf()
|
|
| 74 | - |
|
| 75 | - variations = attrs.get('variations', None)
|
|
| 76 | - if variations is None:
|
|
| 77 | - if pixbuf is None:
|
|
| 78 | - return True
|
|
| 79 | - self.flowbox.add(EmojiChild(codepoint, pixbuf, attrs['desc']))
|
|
| 80 | - if pixbuf != 'font':
|
|
| 81 | - # We save the pixbuf for fast access if we need to
|
|
| 82 | - # replace a codepoint in the textview
|
|
| 83 | - emoji_pixbufs[codepoint] = pixbuf
|
|
| 84 | - else:
|
|
| 85 | - if pixbuf is not None:
|
|
| 86 | - chooser = ModifierChooser()
|
|
| 87 | - |
|
| 88 | - # Iterate over the variations and add the codepoints
|
|
| 89 | - for codepoint_ in variations.keys():
|
|
| 90 | - pixbuf_ = self._get_next_pixbuf()
|
|
| 91 | - if pixbuf_ is None:
|
|
| 92 | - continue
|
|
| 93 | - |
|
| 94 | - if pixbuf_ == 'font':
|
|
| 95 | - if not self._chooser.font_supports_codepoint(
|
|
| 96 | - codepoint_):
|
|
| 97 | - continue
|
|
| 98 | - else:
|
|
| 99 | - emoji_pixbufs[codepoint_] = pixbuf_
|
|
| 100 | - |
|
| 101 | - # Only codepoints are added which the
|
|
| 102 | - # font or theme supports
|
|
| 103 | - chooser.add_emoji(codepoint_, pixbuf_)
|
|
| 104 | - |
|
| 105 | - # Check if we successfully added codepoints with modifiers
|
|
| 106 | - if chooser.has_child:
|
|
| 107 | - # If we have children then add a button
|
|
| 108 | - # and set the popover
|
|
| 109 | - child = EmojiModifierChild(
|
|
| 110 | - codepoint, pixbuf, attrs['desc'])
|
|
| 111 | - child.button.set_popover(chooser)
|
|
| 112 | - chooser.flowbox.connect(
|
|
| 113 | - 'child-activated', self._press_cb)
|
|
| 114 | - else:
|
|
| 115 | - # If no children were added, destroy the chooser
|
|
| 116 | - # and add a EmojiChild instead of a EmojiModifierChild
|
|
| 117 | - chooser.destroy()
|
|
| 118 | - child = EmojiChild(codepoint, pixbuf, attrs['desc'])
|
|
| 119 | - |
|
| 120 | - if pixbuf != 'font':
|
|
| 121 | - emoji_pixbufs[codepoint] = pixbuf
|
|
| 122 | - |
|
| 123 | - self.flowbox.add(child)
|
|
| 124 | - else:
|
|
| 125 | - # We don’t have a image for the base codepoint
|
|
| 126 | - # so skip all modifiers of it
|
|
| 127 | - for _entry in variations:
|
|
| 128 | - pixbuf = self._get_next_pixbuf()
|
|
| 129 | - return True
|
|
| 130 | - |
|
| 131 | - def clear_emojis(self):
|
|
| 132 | - def _remove_emoji(emoji):
|
|
| 133 | - self.flowbox.remove(emoji)
|
|
| 134 | - emoji.destroy()
|
|
| 135 | - self.flowbox.foreach(_remove_emoji)
|
|
| 136 | - |
|
| 137 | - def _get_next_pixbuf(self):
|
|
| 138 | - if self.pixbuf_generator is None:
|
|
| 139 | - return 'font'
|
|
| 140 | - return next(self.pixbuf_generator, False)
|
|
| 141 | - |
|
| 142 | - |
|
| 143 | -class EmojiChild(Gtk.FlowBoxChild):
|
|
| 144 | - def __init__(self, codepoint, pixbuf, desc):
|
|
| 145 | - Gtk.FlowBoxChild.__init__(self)
|
|
| 146 | - self.desc = desc
|
|
| 147 | - self.codepoint = codepoint
|
|
| 148 | - self.pixbuf = pixbuf
|
|
| 149 | - if pixbuf == 'font':
|
|
| 150 | - self.add(Gtk.Label(label=codepoint))
|
|
| 151 | - else:
|
|
| 152 | - self.add(Gtk.Image.new_from_pixbuf(pixbuf))
|
|
| 153 | - self.set_tooltip_text(desc)
|
|
| 154 | - self.show_all()
|
|
| 155 | - |
|
| 156 | - def get_emoji(self):
|
|
| 157 | - if self.pixbuf != 'font':
|
|
| 158 | - pixbuf = self.get_child().get_pixbuf()
|
|
| 159 | - pixbuf = pixbuf.scale_simple(Emoji.INPUT_SIZE,
|
|
| 160 | - Emoji.INPUT_SIZE,
|
|
| 161 | - GdkPixbuf.InterpType.HYPER)
|
|
| 162 | - return self.codepoint, pixbuf
|
|
| 163 | - return self.codepoint, None
|
|
| 164 | - |
|
| 165 | - |
|
| 166 | -class EmojiModifierChild(Gtk.FlowBoxChild):
|
|
| 167 | - def __init__(self, codepoint, pixbuf, desc):
|
|
| 168 | - Gtk.FlowBoxChild.__init__(self)
|
|
| 169 | - self.desc = desc
|
|
| 170 | - self.codepoint = codepoint
|
|
| 171 | - self.pixbuf = pixbuf
|
|
| 172 | - |
|
| 173 | - self.button = Gtk.MenuButton()
|
|
| 174 | - self.button.set_relief(Gtk.ReliefStyle.NONE)
|
|
| 175 | - self.button.connect('button-press-event', self._button_press)
|
|
| 176 | - |
|
| 177 | - if pixbuf == 'font':
|
|
| 178 | - self.button.remove(self.button.get_child())
|
|
| 179 | - label = Gtk.Label(label=codepoint)
|
|
| 180 | - self.button.add(label)
|
|
| 181 | - else:
|
|
| 182 | - self.button.get_child().set_from_pixbuf(pixbuf)
|
|
| 183 | - |
|
| 184 | - self.set_tooltip_text(desc)
|
|
| 185 | - self.add(self.button)
|
|
| 186 | - self.show_all()
|
|
| 187 | - |
|
| 188 | - def _button_press(self, button, event):
|
|
| 189 | - if event.button == 3:
|
|
| 190 | - button.get_popover().show()
|
|
| 191 | - button.get_popover().get_child().unselect_all()
|
|
| 192 | - return True
|
|
| 193 | - if event.button == 1:
|
|
| 194 | - self.get_parent().emit('child-activated', self)
|
|
| 195 | - return True
|
|
| 196 | - return False
|
|
| 197 | - |
|
| 198 | - def get_emoji(self):
|
|
| 199 | - if self.pixbuf != 'font':
|
|
| 200 | - pixbuf = self.button.get_child().get_pixbuf()
|
|
| 201 | - pixbuf = pixbuf.scale_simple(Emoji.INPUT_SIZE,
|
|
| 202 | - Emoji.INPUT_SIZE,
|
|
| 203 | - GdkPixbuf.InterpType.HYPER)
|
|
| 204 | - return self.codepoint, pixbuf
|
|
| 205 | - return self.codepoint, None
|
|
| 206 | - |
|
| 207 | - |
|
| 208 | -class ModifierChooser(Gtk.Popover):
|
|
| 209 | - def __init__(self):
|
|
| 210 | - Gtk.Popover.__init__(self)
|
|
| 211 | - self.set_name('EmoticonPopover')
|
|
| 212 | - self._has_child = False
|
|
| 213 | - |
|
| 214 | - self.flowbox = Gtk.FlowBox()
|
|
| 215 | - self.flowbox.get_style_context().add_class(
|
|
| 216 | - 'emoji-modifier-chooser-flowbox')
|
|
| 217 | - self.flowbox.set_size_request(200, -1)
|
|
| 218 | - self.flowbox.set_max_children_per_line(MODIFIER_MAX_CHILDREN_PER_LINE)
|
|
| 219 | - self.flowbox.show()
|
|
| 220 | - self.add(self.flowbox)
|
|
| 221 | - |
|
| 222 | - @property
|
|
| 223 | - def has_child(self):
|
|
| 224 | - return self._has_child
|
|
| 225 | - |
|
| 226 | - def add_emoji(self, codepoint, pixbuf):
|
|
| 227 | - self.flowbox.add(EmojiChild(codepoint, pixbuf, None))
|
|
| 228 | - self._has_child = True
|
|
| 229 | - |
|
| 230 | - |
|
| 231 | -class EmojiChooser(Gtk.Popover):
|
|
| 232 | - |
|
| 233 | - _section_names = [
|
|
| 234 | - 'Smileys & People',
|
|
| 235 | - 'Animals & Nature',
|
|
| 236 | - 'Food & Drink',
|
|
| 237 | - 'Travel & Places',
|
|
| 238 | - 'Activities',
|
|
| 239 | - 'Objects',
|
|
| 240 | - 'Symbols',
|
|
| 241 | - 'Flags'
|
|
| 242 | - ]
|
|
| 243 | - |
|
| 244 | - def __init__(self):
|
|
| 245 | - super().__init__()
|
|
| 246 | - self.set_name('EmoticonPopover')
|
|
| 247 | - self._text_widget = None
|
|
| 248 | - self._load_source_id = None
|
|
| 249 | - self._pango_layout = Pango.Layout(self.get_pango_context())
|
|
| 250 | - |
|
| 251 | - self._builder = get_builder('emoji_chooser.ui')
|
|
| 252 | - self._search = self._builder.get_object('search')
|
|
| 253 | - self._stack = self._builder.get_object('stack')
|
|
| 254 | - |
|
| 255 | - self._sections = OrderedDict()
|
|
| 256 | - for name in self._section_names:
|
|
| 257 | - self._sections[name] = Section(
|
|
| 258 | - name, self._search, self._on_emoticon_press, self)
|
|
| 259 | - |
|
| 260 | - section_box = self._builder.get_object('section_box')
|
|
| 261 | - for section in self._sections.values():
|
|
| 262 | - section_box.add(section)
|
|
| 263 | - |
|
| 264 | - self.add(self._builder.get_object('box'))
|
|
| 265 | - |
|
| 266 | - self.connect('key-press-event', self._key_press)
|
|
| 267 | - self._builder.connect_signals(self)
|
|
| 268 | - |
|
| 269 | - @property
|
|
| 270 | - def text_widget(self):
|
|
| 271 | - return self._text_widget
|
|
| 272 | - |
|
| 273 | - @text_widget.setter
|
|
| 274 | - def text_widget(self, value):
|
|
| 275 | - # Hold only a weak reference so if we can destroy
|
|
| 276 | - # the ChatControl
|
|
| 277 | - self._text_widget = weakref.ref(value)
|
|
| 278 | - |
|
| 279 | - def _key_press(self, _widget, event):
|
|
| 280 | - return self._search.handle_event(event)
|
|
| 281 | - |
|
| 282 | - def _search_changed(self, _entry):
|
|
| 283 | - for section in self._sections.values():
|
|
| 284 | - section.hide()
|
|
| 285 | - section.flowbox.invalidate_filter()
|
|
| 286 | - self._switch_stack()
|
|
| 287 | - |
|
| 288 | - def _clear_sections(self):
|
|
| 289 | - for section in self._sections.values():
|
|
| 290 | - section.clear_emojis()
|
|
| 291 | - |
|
| 292 | - def _switch_stack(self):
|
|
| 293 | - for section in self._sections.values():
|
|
| 294 | - if section.is_visible():
|
|
| 295 | - self._stack.set_visible_child_name('list')
|
|
| 296 | - return
|
|
| 297 | - self._stack.set_visible_child_name('not-found')
|
|
| 298 | - |
|
| 299 | - @staticmethod
|
|
| 300 | - def _get_current_theme():
|
|
| 301 | - theme = app.settings.get('emoticons_theme')
|
|
| 302 | - themes = helpers.get_available_emoticon_themes()
|
|
| 303 | - if theme not in themes:
|
|
| 304 | - app.settings.set('emoticons_theme', 'noto')
|
|
| 305 | - theme = 'noto'
|
|
| 306 | - return theme
|
|
| 307 | - |
|
| 308 | - @staticmethod
|
|
| 309 | - def _get_emoji_theme_path(theme):
|
|
| 310 | - if theme == 'font':
|
|
| 311 | - return 'font'
|
|
| 312 | - |
|
| 313 | - base_path = configpaths.get('EMOTICONS')
|
|
| 314 | - emoticons_data_path = base_path / theme / f'{theme}.png'
|
|
| 315 | - if emoticons_data_path.exists():
|
|
| 316 | - return emoticons_data_path
|
|
| 317 | - |
|
| 318 | - emoticons_user_path = configpaths.get('MY_EMOTS') / f'{theme}.png'
|
|
| 319 | - if emoticons_user_path.exists():
|
|
| 320 | - return emoticons_user_path
|
|
| 321 | - |
|
| 322 | - log.warning('Could not find emoji theme: %s', theme)
|
|
| 323 | - return None
|
|
| 324 | - |
|
| 325 | - def load(self):
|
|
| 326 | - theme = self._get_current_theme()
|
|
| 327 | - path = self._get_emoji_theme_path(theme)
|
|
| 328 | - if not theme or path is None:
|
|
| 329 | - self._clear_sections()
|
|
| 330 | - emoji_pixbufs.clear()
|
|
| 331 | - return
|
|
| 332 | - |
|
| 333 | - # Attach pixbuf generator
|
|
| 334 | - pixbuf_generator = None
|
|
| 335 | - if theme != 'font':
|
|
| 336 | - pixbuf_generator = self._get_next_pixbuf(path)
|
|
| 337 | - for section in self._sections.values():
|
|
| 338 | - section.pixbuf_generator = pixbuf_generator
|
|
| 339 | - |
|
| 340 | - if self._load_source_id is not None:
|
|
| 341 | - GLib.source_remove(self._load_source_id)
|
|
| 342 | - |
|
| 343 | - # When we change emoji theme
|
|
| 344 | - self._clear_sections()
|
|
| 345 | - emoji_pixbufs.clear()
|
|
| 346 | - |
|
| 347 | - factory = self._emoji_factory(theme == 'font')
|
|
| 348 | - self._load_source_id = GLib.idle_add(lambda: next(factory, False),
|
|
| 349 | - priority=GLib.PRIORITY_LOW)
|
|
| 350 | - |
|
| 351 | - def _emoji_factory(self, font):
|
|
| 352 | - for codepoint, attrs in emoji_data.items():
|
|
| 353 | - if not attrs['fully-qualified']:
|
|
| 354 | - # We don’t add these to the UI
|
|
| 355 | - continue
|
|
| 356 | - |
|
| 357 | - if font and not self.font_supports_codepoint(codepoint):
|
|
| 358 | - continue
|
|
| 359 | - |
|
| 360 | - section = self._sections[attrs['group']]
|
|
| 361 | - yield section.add_emoji(codepoint, attrs)
|
|
| 362 | - self._load_source_id = None
|
|
| 363 | - emoji_pixbufs.complete = True
|
|
| 364 | - |
|
| 365 | - def font_supports_codepoint(self, codepoint):
|
|
| 366 | - self._pango_layout.set_text(codepoint, -1)
|
|
| 367 | - if self._pango_layout.get_unknown_glyphs_count():
|
|
| 368 | - return False
|
|
| 369 | - if len(codepoint) > 1:
|
|
| 370 | - # The font supports each of the codepoints
|
|
| 371 | - # Check if the rendered glyph is more than one char
|
|
| 372 | - if self._pango_layout.get_size()[0] > 19000:
|
|
| 373 | - return False
|
|
| 374 | - return True
|
|
| 375 | - |
|
| 376 | - @staticmethod
|
|
| 377 | - def _get_next_pixbuf(path):
|
|
| 378 | - src_x = src_y = cur_column = 0
|
|
| 379 | - atlas = GdkPixbuf.Pixbuf.new_from_file(str(path))
|
|
| 380 | - |
|
| 381 | - while True:
|
|
| 382 | - src_x = cur_column * Emoji.PARSE_WIDTH
|
|
| 383 | - subpixbuf = atlas.new_subpixbuf(src_x, src_y,
|
|
| 384 | - Emoji.PARSE_WIDTH,
|
|
| 385 | - Emoji.PARSE_HEIGHT)
|
|
| 386 | - |
|
| 387 | - if list(subpixbuf.get_pixels())[0:4] == [0, 0, 0, 255]:
|
|
| 388 | - # top left corner is a black pixel means image is missing
|
|
| 389 | - subpixbuf = None
|
|
| 390 | - |
|
| 391 | - if cur_column == Emoji.PARSE_COLUMNS - 1:
|
|
| 392 | - src_y += Emoji.PARSE_WIDTH
|
|
| 393 | - cur_column = 0
|
|
| 394 | - else:
|
|
| 395 | - cur_column += 1
|
|
| 396 | - |
|
| 397 | - yield subpixbuf
|
|
| 398 | - |
|
| 399 | - def _on_emoticon_press(self, flowbox, child):
|
|
| 400 | - GLib.timeout_add(100, flowbox.unselect_child, child)
|
|
| 401 | - codepoint, pixbuf = child.get_emoji()
|
|
| 402 | - self._text_widget().insert_emoji(codepoint, pixbuf)
|
|
| 403 | - self._text_widget().grab_focus()
|
|
| 404 | - |
|
| 405 | - def do_destroy(self):
|
|
| 406 | - # Remove the references we hold to other objects
|
|
| 407 | - self._text_widget = None
|
|
| 408 | - # Never destroy, creating a new EmoticonPopover is expensive
|
|
| 409 | - return True
|
|
| 410 | - |
|
| 411 | - |
|
| 412 | -emoji_chooser = EmojiChooser() |
| 1 | -# This file is part of Gajim.
|
|
| 2 | -#
|
|
| 3 | -# Gajim is free software; you can redistribute it and/or modify
|
|
| 4 | -# it under the terms of the GNU General Public License as published
|
|
| 5 | -# by the Free Software Foundation; version 3 only.
|
|
| 6 | -#
|
|
| 7 | -# Gajim is distributed in the hope that it will be useful,
|
|
| 8 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 9 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 10 | -# GNU General Public License for more details.
|
|
| 11 | -#
|
|
| 12 | -# You should have received a copy of the GNU General Public License
|
|
| 13 | -# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
| 14 | - |
|
| 15 | -from enum import IntEnum
|
|
| 16 | -from collections import OrderedDict
|
|
| 17 | - |
|
| 18 | -from gi.repository import GdkPixbuf
|
|
| 19 | -from gi.repository import Gtk
|
|
| 20 | - |
|
| 21 | -class Emoji(IntEnum):
|
|
| 22 | - ...
|
|
| 23 | - |
|
| 24 | -def is_emoji(codepoints: str) -> bool: ...
|
|
| 25 | -def get_emoji_pixbuf(codepoints: str) -> GdkPixbuf.Pixbuf: ...
|
|
| 26 | - |
|
| 27 | -class EmojiData(OrderedDict):
|
|
| 28 | - def get_regex(self) -> str: ...
|
|
| 29 | - |
|
| 30 | -class EmojiPixbufs(dict):
|
|
| 31 | - @property
|
|
| 32 | - def complete(self) -> bool: ...
|
|
| 33 | - @complete.setter
|
|
| 34 | - def complete(self, value: bool) -> None: ...
|
|
| 35 | - def clear(self) -> None: ...
|
|
| 36 | - def append_marks(self,
|
|
| 37 | - textview: Gtk.TextView,
|
|
| 38 | - start: Gtk.TextMark,
|
|
| 39 | - end: Gtk.TextMark,
|
|
| 40 | - codepoint: str) -> None: ...
|
|
| 41 | - |
|
| 42 | -emoji_pixbufs: EmojiPixbufs
|
|
| 43 | -emoji_ascii_data: dict[str, str]
|
|
| 44 | -emoji_data: EmojiData |
| ... | ... | @@ -18,7 +18,6 @@ |
| 18 | 18 | from typing import Optional
|
| 19 | 19 | |
| 20 | 20 | import os
|
| 21 | -import sys
|
|
| 22 | 21 | import tempfile
|
| 23 | 22 | import logging
|
| 24 | 23 | import uuid
|
| ... | ... | @@ -45,7 +44,6 @@ |
| 45 | 44 | |
| 46 | 45 | from .builder import get_builder
|
| 47 | 46 | from .dialogs import ErrorDialog
|
| 48 | -from .emoji_chooser import emoji_chooser
|
|
| 49 | 47 | from .menus import get_encryption_menu
|
| 50 | 48 | from .menus import get_format_menu
|
| 51 | 49 | from .menus import get_groupchat_menu
|
| ... | ... | @@ -98,8 +96,6 @@ def __init__(self) -> None: |
| 98 | 96 | |
| 99 | 97 | self._ui.quick_invite_button.set_action_name('win.muc-invite')
|
| 100 | 98 | |
| 101 | - self._init_emoticon_popover()
|
|
| 102 | - |
|
| 103 | 99 | self.show_all()
|
| 104 | 100 | self._ui.connect_signals(self)
|
| 105 | 101 | |
| ... | ... | @@ -157,7 +153,7 @@ def _on_action(self, |
| 157 | 153 | self._on_format(action_name)
|
| 158 | 154 | |
| 159 | 155 | elif action_name == 'show-emoji-chooser':
|
| 160 | - self._on_show_emoji_chooser()
|
|
| 156 | + self.msg_textview.emit('insert-emoji')
|
|
| 161 | 157 | |
| 162 | 158 | elif action_name == 'quote':
|
| 163 | 159 | assert param
|
| ... | ... | @@ -389,31 +385,6 @@ def _set_settings_menu(self, contact: ChatContactT) -> None: |
| 389 | 385 | menu = get_singlechat_menu(contact)
|
| 390 | 386 | self._ui.settings_menu.set_menu_model(menu)
|
| 391 | 387 | |
| 392 | - def _init_emoticon_popover(self) -> None:
|
|
| 393 | - if not app.settings.get('emoticons_theme'):
|
|
| 394 | - return
|
|
| 395 | - |
|
| 396 | - if sys.platform == 'darwin':
|
|
| 397 | - emoji_chooser.text_widget = self.msg_textview
|
|
| 398 | - self._ui.emoticons_button.set_popover(emoji_chooser)
|
|
| 399 | - return
|
|
| 400 | - |
|
| 401 | - self._ui.emoticons_button.set_sensitive(True)
|
|
| 402 | - self._ui.emoticons_button.connect('clicked',
|
|
| 403 | - self._on_emoticon_button_clicked)
|
|
| 404 | - |
|
| 405 | - def toggle_emoticons(self) -> None:
|
|
| 406 | - if app.settings.get('emoticons_theme'):
|
|
| 407 | - self._ui.emoticons_button.set_no_show_all(False)
|
|
| 408 | - self._ui.emoticons_button.show()
|
|
| 409 | - else:
|
|
| 410 | - self._ui.emoticons_button.set_no_show_all(True)
|
|
| 411 | - self._ui.emoticons_button.hide()
|
|
| 412 | - |
|
| 413 | - def _on_emoticon_button_clicked(self, _widget: Gtk.Button) -> None:
|
|
| 414 | - self.msg_textview.emit('insert-emoji')
|
|
| 415 | - self._ui.emoticons_button.set_active(False)
|
|
| 416 | - |
|
| 417 | 388 | def _on_format(self, name: str) -> None:
|
| 418 | 389 | name = name.removeprefix('input-')
|
| 419 | 390 | self.msg_textview.apply_formatting(name)
|
| ... | ... | @@ -421,14 +392,6 @@ def _on_format(self, name: str) -> None: |
| 421 | 392 | def _on_clear(self) -> None:
|
| 422 | 393 | self.msg_textview.clear()
|
| 423 | 394 | |
| 424 | - def _on_show_emoji_chooser(self) -> None:
|
|
| 425 | - if sys.platform == 'darwin':
|
|
| 426 | - popover = self._ui.emoticons_button.get_popover()
|
|
| 427 | - assert popover
|
|
| 428 | - popover.show()
|
|
| 429 | - else:
|
|
| 430 | - self.msg_textview.emit('insert-emoji')
|
|
| 431 | - |
|
| 432 | 395 | def _on_send_file_enabled_changed(self,
|
| 433 | 396 | action: Gio.SimpleAction,
|
| 434 | 397 | _param: GObject.ParamSpec) -> None:
|
| ... | ... | @@ -25,11 +25,9 @@ |
| 25 | 25 | from collections import defaultdict
|
| 26 | 26 | |
| 27 | 27 | import logging
|
| 28 | -import sys
|
|
| 29 | 28 | |
| 30 | 29 | from gi.repository import Gtk
|
| 31 | 30 | from gi.repository import Gdk
|
| 32 | -from gi.repository import GdkPixbuf
|
|
| 33 | 31 | from gi.repository import GLib
|
| 34 | 32 | from gi.repository import Pango
|
| 35 | 33 | |
| ... | ... | @@ -420,60 +418,6 @@ def apply_formatting(self, formatting: str) -> None: |
| 420 | 418 | buf.get_iter_at_offset(start_offset),
|
| 421 | 419 | buf.get_iter_at_offset(end_offset + 2))
|
| 422 | 420 | |
| 423 | - def replace_emojis(self) -> None:
|
|
| 424 | - if sys.platform != 'darwin':
|
|
| 425 | - # TODO: Remove if colored emoji rendering works well on MacOS
|
|
| 426 | - return
|
|
| 427 | - |
|
| 428 | - theme = app.settings.get('emoticons_theme')
|
|
| 429 | - if not theme or theme == 'font':
|
|
| 430 | - return
|
|
| 431 | - |
|
| 432 | - def _replace(anchor: Gtk.TextChildAnchor) -> None:
|
|
| 433 | - if anchor is None:
|
|
| 434 | - return
|
|
| 435 | - image = anchor.get_widgets()[0]
|
|
| 436 | - if hasattr(image, 'codepoint'):
|
|
| 437 | - # found emoji
|
|
| 438 | - codepoint = getattr(image, 'codepoint')
|
|
| 439 | - self._replace_char_at_iter(iter_, codepoint)
|
|
| 440 | - image.destroy()
|
|
| 441 | - |
|
| 442 | - iter_ = self.get_buffer().get_start_iter()
|
|
| 443 | - _replace(iter_.get_child_anchor())
|
|
| 444 | - |
|
| 445 | - while iter_.forward_char():
|
|
| 446 | - _replace(iter_.get_child_anchor())
|
|
| 447 | - |
|
| 448 | - def _replace_char_at_iter(self,
|
|
| 449 | - iter_: Gtk.TextIter,
|
|
| 450 | - new_char: str
|
|
| 451 | - ) -> None:
|
|
| 452 | - buf = self.get_buffer()
|
|
| 453 | - iter_2 = iter_.copy()
|
|
| 454 | - iter_2.forward_char()
|
|
| 455 | - buf.delete(iter_, iter_2)
|
|
| 456 | - buf.insert(iter_, new_char)
|
|
| 457 | - |
|
| 458 | - def insert_emoji(self, codepoint: str, pixbuf: GdkPixbuf.Pixbuf) -> None:
|
|
| 459 | - buf = self.get_buffer()
|
|
| 460 | - if buf.get_char_count():
|
|
| 461 | - # buffer contains text
|
|
| 462 | - buf.insert_at_cursor(' ')
|
|
| 463 | - |
|
| 464 | - insert_mark = buf.get_insert()
|
|
| 465 | - insert_iter = buf.get_iter_at_mark(insert_mark)
|
|
| 466 | - |
|
| 467 | - if pixbuf is None:
|
|
| 468 | - buf.insert(insert_iter, codepoint)
|
|
| 469 | - else:
|
|
| 470 | - anchor = buf.create_child_anchor(insert_iter)
|
|
| 471 | - image = Gtk.Image.new_from_pixbuf(pixbuf)
|
|
| 472 | - setattr(image, 'codepoint', codepoint)
|
|
| 473 | - image.show()
|
|
| 474 | - self.add_child_at_anchor(image, anchor)
|
|
| 475 | - buf.insert_at_cursor(' ')
|
|
| 476 | - |
|
| 477 | 421 | def clear(self, *args: Any) -> None:
|
| 478 | 422 | buf = self.get_buffer()
|
| 479 | 423 | start, end = buf.get_bounds()
|
| ... | ... | @@ -26,7 +26,6 @@ |
| 26 | 26 | |
| 27 | 27 | from gajim.common import app
|
| 28 | 28 | from gajim.common import configpaths
|
| 29 | -from gajim.common import helpers
|
|
| 30 | 29 | from gajim.common.const import THRESHOLD_OPTIONS
|
| 31 | 30 | from gajim.common.i18n import _
|
| 32 | 31 | from gajim.common.helpers import open_file
|
| ... | ... | @@ -42,7 +41,6 @@ |
| 42 | 41 | from .const import SettingType
|
| 43 | 42 | from .dialogs import DialogButton
|
| 44 | 43 | from .dialogs import ConfirmationDialog
|
| 45 | -from .emoji_chooser import emoji_chooser
|
|
| 46 | 44 | from .settings import PopoverSetting
|
| 47 | 45 | from .settings import SettingsBox
|
| 48 | 46 | from .settings import SettingsDialog
|
| ... | ... | @@ -81,8 +79,6 @@ def __init__(self) -> None: |
| 81 | 79 | |
| 82 | 80 | self.add(self._ui.grid)
|
| 83 | 81 | |
| 84 | - self._check_emoji_theme()
|
|
| 85 | - |
|
| 86 | 82 | prefs: list[tuple[str, Type[PreferenceBox]]] = [
|
| 87 | 83 | ('window_behaviour', WindowBehaviour),
|
| 88 | 84 | ('plugins', Plugins),
|
| ... | ... | @@ -95,7 +91,6 @@ def __init__(self) -> None: |
| 95 | 91 | ('status_message', StatusMessage),
|
| 96 | 92 | ('automatic_status', AutomaticStatus),
|
| 97 | 93 | ('themes', Themes),
|
| 98 | - ('emoji', Emoji),
|
|
| 99 | 94 | ('server', Server),
|
| 100 | 95 | ('audio', Audio),
|
| 101 | 96 | ('video', Video),
|
| ... | ... | @@ -117,10 +112,6 @@ def __init__(self) -> None: |
| 117 | 112 | self._ui.connect_signals(self)
|
| 118 | 113 | |
| 119 | 114 | self.show_all()
|
| 120 | - if sys.platform != 'darwin':
|
|
| 121 | - # TODO: Remove if colored emoji rendering works well on
|
|
| 122 | - # Windows and MacOS
|
|
| 123 | - self._ui.emoji.hide()
|
|
| 124 | 115 | |
| 125 | 116 | def get_ui(self):
|
| 126 | 117 | return self._ui
|
| ... | ... | @@ -157,14 +148,6 @@ def update_proxy_list(self) -> None: |
| 157 | 148 | miscellaneous = cast(Miscellaneous, self._prefs['miscellaneous'])
|
| 158 | 149 | miscellaneous.update_proxy_list()
|
| 159 | 150 | |
| 160 | - @staticmethod
|
|
| 161 | - def _check_emoji_theme() -> None:
|
|
| 162 | - # Ensure selected emoji theme is valid
|
|
| 163 | - emoji_themes = helpers.get_available_emoticon_themes()
|
|
| 164 | - settings_theme = app.settings.get('emoticons_theme')
|
|
| 165 | - if settings_theme not in emoji_themes:
|
|
| 166 | - app.settings.set('emoticons_theme', 'font')
|
|
| 167 | - |
|
| 168 | 151 | def _on_destroy(self, _widget: Gtk.Widget) -> None:
|
| 169 | 152 | self._prefs.clear()
|
| 170 | 153 | app.check_finalize(self)
|
| ... | ... | @@ -704,42 +687,6 @@ def _on_dark_theme(value: str, *args: Any) -> None: |
| 704 | 687 | app.ged.raise_event(StyleChanged())
|
| 705 | 688 | |
| 706 | 689 | |
| 707 | -class Emoji(PreferenceBox):
|
|
| 708 | - def __init__(self, *args: Any) -> None:
|
|
| 709 | - if sys.platform != 'darwin':
|
|
| 710 | - # TODO: Remove if colored emoji rendering works well on
|
|
| 711 | - # Windows and MacOS
|
|
| 712 | - PreferenceBox.__init__(self, [])
|
|
| 713 | - return
|
|
| 714 | - |
|
| 715 | - emoji_themes_items: list[str] = []
|
|
| 716 | - for theme in helpers.get_available_emoticon_themes():
|
|
| 717 | - emoji_themes_items.append(theme)
|
|
| 718 | - |
|
| 719 | - settings = [
|
|
| 720 | - Setting(SettingKind.POPOVER,
|
|
| 721 | - _('Emoji Theme'),
|
|
| 722 | - SettingType.CONFIG,
|
|
| 723 | - 'emoticons_theme',
|
|
| 724 | - desc=_('Choose from various emoji styles'),
|
|
| 725 | - props={'entries': emoji_themes_items},
|
|
| 726 | - callback=self._on_emoticons_theme)
|
|
| 727 | - ]
|
|
| 728 | - |
|
| 729 | - PreferenceBox.__init__(self, settings)
|
|
| 730 | - |
|
| 731 | - def _on_emoticons_theme(self, *args: Any) -> None:
|
|
| 732 | - emoji_chooser.load()
|
|
| 733 | - self._toggle_emoticons()
|
|
| 734 | - |
|
| 735 | - @staticmethod
|
|
| 736 | - def _toggle_emoticons() -> None:
|
|
| 737 | - # TODO
|
|
| 738 | - # for ctrl in app.window.get_controls():
|
|
| 739 | - # ctrl.toggle_emoticons()
|
|
| 740 | - pass
|
|
| 741 | - |
|
| 742 | - |
|
| 743 | 690 | class Server(PreferenceBox):
|
| 744 | 691 | def __init__(self, *args: Any) -> None:
|
| 745 | 692 |
| ... | ... | @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" |
| 6 | 6 | |
| 7 | 7 | |
| 8 | 8 | [tool.codespell]
|
| 9 | -skip = "*__pycache__*,gajim/gui,gajim/data/icons,gajim/data/sounds,gajim/data/emoticons"
|
|
| 9 | +skip = "*__pycache__*,gajim/gui,gajim/data/icons,gajim/data/sounds"
|
|
| 10 | 10 | ignore-words-list = "claus,pres,ser,trough"
|
| 11 | 11 | |
| 12 | 12 |
| ... | ... | @@ -49,8 +49,6 @@ install_requires = |
| 49 | 49 | |
| 50 | 50 | [options.package_data]
|
| 51 | 51 | gajim =
|
| 52 | - data/emoticons/*/*.png
|
|
| 53 | - data/emoticons/*/LICENSE
|
|
| 54 | 52 | data/gui/*.ui
|
| 55 | 53 | data/icons/hicolor/*/*/*.png
|
| 56 | 54 | data/icons/hicolor/*/*/*.svg
|