Philipp Hörist pushed to branch master at gajim / gajim
Commits: eebbe3e8 by wurstsalat at 2022-11-30T21:34:12+00:00 refactor: Remove old emoji code
Fixes #10932
- - - - -
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:
===================================== gajim/common/configpaths.py ===================================== @@ -125,7 +125,6 @@ def __init__(self) -> None: source_paths = [ ('DATA', basedir / 'data'), ('STYLE', basedir / 'data' / 'style'), - ('EMOTICONS', basedir / 'data' / 'emoticons'), ('GUI', basedir / 'data' / 'gui'), ('ICONS', basedir / 'data' / 'icons'), ('HOME', Path.home()), @@ -216,8 +215,6 @@ def init(self): ('PLUGINS_IMAGES', 'plugins_images', PathLocation.CACHE, PathType.FOLDER), ('PLUGINS_USER', 'plugins', PathLocation.DATA, PathType.FOLDER), - ('MY_EMOTS', - 'emoticons', PathLocation.DATA, PathType.FOLDER_OPTIONAL), ('MY_ICONSETS', 'iconsets', PathLocation.DATA, PathType.FOLDER_OPTIONAL),
===================================== gajim/common/helpers.py ===================================== @@ -766,22 +766,6 @@ def version_condition(current_version: str, required_version: str) -> bool: return True
-def get_available_emoticon_themes() -> list[str]: - files: list[Path] = [] - for folder in configpaths.get('EMOTICONS').iterdir(): - if not folder.is_dir(): - continue - files += [theme for theme in folder.iterdir() if theme.is_file()] - - my_emots = configpaths.get('MY_EMOTS') - if my_emots.is_dir(): - files += list(my_emots.iterdir()) - - emoticons_themes = ['font'] - emoticons_themes += [file.stem for file in files if file.suffix == '.png'] - return sorted(emoticons_themes) - - def call_counter(func): def helper(self, restart=False): if restart:
===================================== gajim/common/setting_values.py ===================================== @@ -49,7 +49,6 @@ class _ACCOUNT_DEFAULT: 'confirm_close_muc', 'confirm_on_window_delete', 'dev_force_bookmark_2', - 'dev_use_message_label', 'developer_modus', 'enable_emoji_shortcodes', 'enable_negative_priority', @@ -137,7 +136,6 @@ class _ACCOUNT_DEFAULT: 'date_format', 'date_time_format', 'dictionary_url', - 'emoticons_theme', 'gc_refer_to_nick_char', 'global_proxy', 'last_save_dir', @@ -194,7 +192,6 @@ class _ACCOUNT_DEFAULT: 'dev_use_message_label': True, 'developer_modus': False, 'dictionary_url': 'WIKTIONARY', - 'emoticons_theme': 'noto-emoticons', 'enable_emoji_shortcodes': True, 'enable_negative_priority': False, 'enable_file_preview': True,
===================================== gajim/common/settings.py ===================================== @@ -237,11 +237,6 @@ def _setup_installation_defaults() -> None: if IS_PORTABLE: APP_SETTINGS['use_keyring'] = False
- if sys.platform in ('win32', 'darwin'): - # Cairo does not support color emojis fonts on - # Windows and MacOS - APP_SETTINGS['dev_use_message_label'] = False - def _load_app_overrides(self) -> None: if not OVERRIDES_PATH.exists(): return
===================================== gajim/common/styling.py ===================================== @@ -19,7 +19,6 @@ from typing import Optional
import string -import sys import re from dataclasses import dataclass from dataclasses import field @@ -31,7 +30,6 @@ from gajim.common.helpers import parse_uri as analyze_uri from gajim.common.helpers import validate_jid from gajim.common.text_helpers import jid_to_iri -from gajim.gui.emoji_data import emoji_data
PRE = '`' STRONG = '*' @@ -53,9 +51,6 @@ URI_OR_JID_RX = re.compile( fr'(?P<uri>(?<![\w+.-]){regex.IRI})|(?P<jid>{regex.XMPP.jid})')
-EMOJI_RX = emoji_data.get_regex() -EMOJI_RX = re.compile(EMOJI_RX) - SD = 0 SD_POS = 1 MAX_QUOTE_LEVEL = 20 @@ -97,11 +92,6 @@ class MailAddress(BaseHyperlink): name: str = field(default='mailadr', init=False)
-@dataclass -class Emoji(StyleObject): - name: str = field(default='emoji', init=False) - - @dataclass class Block(StyleObject):
@@ -117,7 +107,6 @@ class PlainBlock(Block): name: str = field(default='plain', init=False) spans: list[Span] = field(default_factory=list) uris: list[BaseHyperlink] = field(default_factory=list) - emojis: list[Emoji] = field(default_factory=list)
@classmethod def from_quote_match(cls, match: Match[str]) -> PlainBlock: @@ -208,10 +197,6 @@ def process(text: Union[str, bytes], level: int = 0) -> ParsingResult: for line in block.text.splitlines(keepends=True): block.spans += _parse_line(line, offset, offset_bytes) block.uris += _parse_uris(line, offset, offset_bytes) - if sys.platform == 'darwin': - # block.emojis is used for replacing emojis with Gtk.Images - # Necessary for MessageTextview (darwin) only - block.emojis += _parse_emojis(line, offset)
offset += len(line) offset_bytes += len(line.encode()) @@ -361,22 +346,6 @@ def make(start: int, end: int, is_jid: bool) -> Optional[BaseHyperlink]: return uris
-def _parse_emojis(line: str, offset: int) -> list[Emoji]: - emojis: list[Emoji] = [] - - def make(match: Match[str]) -> Emoji: - return _make_emoji(line, - match.start(), - match.end() - 1, - offset) - - for match in EMOJI_RX.finditer(line): - emoji = make(match) - emojis.append(emoji) - - return emojis - - def _handle_pre_span(line: str, index: int, offset: int, @@ -464,18 +433,6 @@ def _make_hyperlink(line: str, text=text)
-def _make_emoji(line: str, - start: int, - end: int, - offset: int) -> Emoji: - text = line[start:end + 1] - start += offset - end += offset + 1 - return Emoji(start=start, - end=end, - text=text) - - def _is_span_empty(sd: str, index: int, stack: list[tuple[str, int]]) -> bool: start_sd = stack[-1][SD] if start_sd != sd:
===================================== gajim/data/emoticons/noto/LICENSE deleted ===================================== @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License.
===================================== gajim/data/emoticons/noto/noto.png deleted ===================================== Binary files a/gajim/data/emoticons/noto/noto.png and /dev/null differ
===================================== gajim/data/emoticons/twemoji/LICENSE deleted ===================================== @@ -1,393 +0,0 @@ -Attribution 4.0 International - -======================================================================= - -Creative Commons Corporation ("Creative Commons") is not a law firm and -does not provide legal services or legal advice. Distribution of -Creative Commons public licenses does not create a lawyer-client or -other relationship. Creative Commons makes its licenses and related -information available on an "as-is" basis. Creative Commons gives no -warranties regarding its licenses, any material licensed under their -terms and conditions, or any related information. Creative Commons -disclaims all liability for damages resulting from their use to the -fullest extent possible. - -Using Creative Commons Public Licenses - -Creative Commons public licenses provide a standard set of terms and -conditions that creators and other rights holders may use to share -original works of authorship and other material subject to copyright -and certain other rights specified in the public license below. The -following considerations are for informational purposes only, are not -exhaustive, and do not form part of our licenses. - - Considerations for licensors: Our public licenses are - intended for use by those authorized to give the public - permission to use material in ways otherwise restricted by - copyright and certain other rights. Our licenses are - irrevocable. Licensors should read and understand the terms - and conditions of the license they choose before applying it. - Licensors should also secure all rights necessary before - applying our licenses so that the public can reuse the - material as expected. Licensors should clearly mark any - material not subject to the license. This includes other CC- - licensed material, or material used under an exception or - limitation to copyright. More considerations for licensors: - wiki.creativecommons.org/Considerations_for_licensors - - Considerations for the public: By using one of our public - licenses, a licensor grants the public permission to use the - licensed material under specified terms and conditions. If - the licensor's permission is not necessary for any reason--for - example, because of any applicable exception or limitation to - copyright--then that use is not regulated by the license. Our - licenses grant only permissions under copyright and certain - other rights that a licensor has authority to grant. Use of - the licensed material may still be restricted for other - reasons, including because others have copyright or other - rights in the material. A licensor may make special requests, - such as asking that all changes be marked or described. - Although not required by our licenses, you are encouraged to - respect those requests where reasonable. More_considerations - for the public: - wiki.creativecommons.org/Considerations_for_licensees - -======================================================================= - -Creative Commons Attribution 4.0 International Public License - -By exercising the Licensed Rights (defined below), You accept and agree -to be bound by the terms and conditions of this Creative Commons -Attribution 4.0 International Public License ("Public License"). To the -extent this Public License may be interpreted as a contract, You are -granted the Licensed Rights in consideration of Your acceptance of -these terms and conditions, and the Licensor grants You such rights in -consideration of benefits the Licensor receives from making the -Licensed Material available under these terms and conditions. - - -Section 1 -- Definitions. - - a. Adapted Material means material subject to Copyright and Similar - Rights that is derived from or based upon the Licensed Material - and in which the Licensed Material is translated, altered, - arranged, transformed, or otherwise modified in a manner requiring - permission under the Copyright and Similar Rights held by the - Licensor. For purposes of this Public License, where the Licensed - Material is a musical work, performance, or sound recording, - Adapted Material is always produced where the Licensed Material is - synched in timed relation with a moving image. - - b. Adapter's License means the license You apply to Your Copyright - and Similar Rights in Your contributions to Adapted Material in - accordance with the terms and conditions of this Public License. - - c. Copyright and Similar Rights means copyright and/or similar rights - closely related to copyright including, without limitation, - performance, broadcast, sound recording, and Sui Generis Database - Rights, without regard to how the rights are labeled or - categorized. For purposes of this Public License, the rights - specified in Section 2(b)(1)-(2) are not Copyright and Similar - Rights. - - d. Effective Technological Measures means those measures that, in the - absence of proper authority, may not be circumvented under laws - fulfilling obligations under Article 11 of the WIPO Copyright - Treaty adopted on December 20, 1996, and/or similar international - agreements. - - e. Exceptions and Limitations means fair use, fair dealing, and/or - any other exception or limitation to Copyright and Similar Rights - that applies to Your use of the Licensed Material. - - f. Licensed Material means the artistic or literary work, database, - or other material to which the Licensor applied this Public - License. - - g. Licensed Rights means the rights granted to You subject to the - terms and conditions of this Public License, which are limited to - all Copyright and Similar Rights that apply to Your use of the - Licensed Material and that the Licensor has authority to license. - - h. Licensor means the individual(s) or entity(ies) granting rights - under this Public License. - - i. Share means to provide material to the public by any means or - process that requires permission under the Licensed Rights, such - as reproduction, public display, public performance, distribution, - dissemination, communication, or importation, and to make material - available to the public including in ways that members of the - public may access the material from a place and at a time - individually chosen by them. - - j. Sui Generis Database Rights means rights other than copyright - resulting from Directive 96/9/EC of the European Parliament and of - the Council of 11 March 1996 on the legal protection of databases, - as amended and/or succeeded, as well as other essentially - equivalent rights anywhere in the world. - - k. You means the individual or entity exercising the Licensed Rights - under this Public License. Your has a corresponding meaning. - - -Section 2 -- Scope. - - a. License grant. - - 1. Subject to the terms and conditions of this Public License, - the Licensor hereby grants You a worldwide, royalty-free, - non-sublicensable, non-exclusive, irrevocable license to - exercise the Licensed Rights in the Licensed Material to: - - a. reproduce and Share the Licensed Material, in whole or - in part; and - - b. produce, reproduce, and Share Adapted Material. - - 2. Exceptions and Limitations. For the avoidance of doubt, where - Exceptions and Limitations apply to Your use, this Public - License does not apply, and You do not need to comply with - its terms and conditions. - - 3. Term. The term of this Public License is specified in Section - 6(a). - - 4. Media and formats; technical modifications allowed. The - Licensor authorizes You to exercise the Licensed Rights in - all media and formats whether now known or hereafter created, - and to make technical modifications necessary to do so. The - Licensor waives and/or agrees not to assert any right or - authority to forbid You from making technical modifications - necessary to exercise the Licensed Rights, including - technical modifications necessary to circumvent Effective - Technological Measures. For purposes of this Public License, - simply making modifications authorized by this Section 2(a) - (4) never produces Adapted Material. - - 5. Downstream recipients. - - a. Offer from the Licensor -- Licensed Material. Every - recipient of the Licensed Material automatically - receives an offer from the Licensor to exercise the - Licensed Rights under the terms and conditions of this - Public License. - - b. No downstream restrictions. You may not offer or impose - any additional or different terms or conditions on, or - apply any Effective Technological Measures to, the - Licensed Material if doing so restricts exercise of the - Licensed Rights by any recipient of the Licensed - Material. - - 6. No endorsement. Nothing in this Public License constitutes or - may be construed as permission to assert or imply that You - are, or that Your use of the Licensed Material is, connected - with, or sponsored, endorsed, or granted official status by, - the Licensor or others designated to receive attribution as - provided in Section 3(a)(1)(A)(i). - - b. Other rights. - - 1. Moral rights, such as the right of integrity, are not - licensed under this Public License, nor are publicity, - privacy, and/or other similar personality rights; however, to - the extent possible, the Licensor waives and/or agrees not to - assert any such rights held by the Licensor to the limited - extent necessary to allow You to exercise the Licensed - Rights, but not otherwise. - - 2. Patent and trademark rights are not licensed under this - Public License. - - 3. To the extent possible, the Licensor waives any right to - collect royalties from You for the exercise of the Licensed - Rights, whether directly or through a collecting society - under any voluntary or waivable statutory or compulsory - licensing scheme. In all other cases the Licensor expressly - reserves any right to collect such royalties. - - -Section 3 -- License Conditions. - -Your exercise of the Licensed Rights is expressly made subject to the -following conditions. - - a. Attribution. - - 1. If You Share the Licensed Material (including in modified - form), You must: - - a. retain the following if it is supplied by the Licensor - with the Licensed Material: - - i. identification of the creator(s) of the Licensed - Material and any others designated to receive - attribution, in any reasonable manner requested by - the Licensor (including by pseudonym if - designated); - - ii. a copyright notice; - - iii. a notice that refers to this Public License; - - iv. a notice that refers to the disclaimer of - warranties; - - v. a URI or hyperlink to the Licensed Material to the - extent reasonably practicable; - - b. indicate if You modified the Licensed Material and - retain an indication of any previous modifications; and - - c. indicate the Licensed Material is licensed under this - Public License, and include the text of, or the URI or - hyperlink to, this Public License. - - 2. You may satisfy the conditions in Section 3(a)(1) in any - reasonable manner based on the medium, means, and context in - which You Share the Licensed Material. For example, it may be - reasonable to satisfy the conditions by providing a URI or - hyperlink to a resource that includes the required - information. - - 3. If requested by the Licensor, You must remove any of the - information required by Section 3(a)(1)(A) to the extent - reasonably practicable. - - 4. If You Share Adapted Material You produce, the Adapter's - License You apply must not prevent recipients of the Adapted - Material from complying with this Public License. - - -Section 4 -- Sui Generis Database Rights. - -Where the Licensed Rights include Sui Generis Database Rights that -apply to Your use of the Licensed Material: - - a. for the avoidance of doubt, Section 2(a)(1) grants You the right - to extract, reuse, reproduce, and Share all or a substantial - portion of the contents of the database; - - b. if You include all or a substantial portion of the database - contents in a database in which You have Sui Generis Database - Rights, then the database in which You have Sui Generis Database - Rights (but not its individual contents) is Adapted Material; and - - c. You must comply with the conditions in Section 3(a) if You Share - all or a substantial portion of the contents of the database. - -For the avoidance of doubt, this Section 4 supplements and does not -replace Your obligations under this Public License where the Licensed -Rights include other Copyright and Similar Rights. - - -Section 5 -- Disclaimer of Warranties and Limitation of Liability. - - a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE - EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS - AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF - ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, - IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, - WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR - PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, - ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT - KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT - ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. - - b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE - TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, - NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, - INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, - COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR - USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN - ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR - DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR - IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. - - c. The disclaimer of warranties and limitation of liability provided - above shall be interpreted in a manner that, to the extent - possible, most closely approximates an absolute disclaimer and - waiver of all liability. - - -Section 6 -- Term and Termination. - - a. This Public License applies for the term of the Copyright and - Similar Rights licensed here. However, if You fail to comply with - this Public License, then Your rights under this Public License - terminate automatically. - - b. Where Your right to use the Licensed Material has terminated under - Section 6(a), it reinstates: - - 1. automatically as of the date the violation is cured, provided - it is cured within 30 days of Your discovery of the - violation; or - - 2. upon express reinstatement by the Licensor. - - For the avoidance of doubt, this Section 6(b) does not affect any - right the Licensor may have to seek remedies for Your violations - of this Public License. - - c. For the avoidance of doubt, the Licensor may also offer the - Licensed Material under separate terms or conditions or stop - distributing the Licensed Material at any time; however, doing so - will not terminate this Public License. - - d. Sections 1, 5, 6, 7, and 8 survive termination of this Public - License. - - -Section 7 -- Other Terms and Conditions. - - a. The Licensor shall not be bound by any additional or different - terms or conditions communicated by You unless expressly agreed. - - b. Any arrangements, understandings, or agreements regarding the - Licensed Material not stated herein are separate from and - independent of the terms and conditions of this Public License. - - -Section 8 -- Interpretation. - - a. For the avoidance of doubt, this Public License does not, and - shall not be interpreted to, reduce, limit, restrict, or impose - conditions on any use of the Licensed Material that could lawfully - be made without permission under this Public License. - - b. To the extent possible, if any provision of this Public License is - deemed unenforceable, it shall be automatically reformed to the - minimum extent necessary to make it enforceable. If the provision - cannot be reformed, it shall be severed from this Public License - without affecting the enforceability of the remaining terms and - conditions. - - c. No term or condition of this Public License will be waived and no - failure to comply consented to unless expressly agreed to by the - Licensor. - - d. Nothing in this Public License constitutes or may be interpreted - as a limitation upon, or waiver of, any privileges and immunities - that apply to the Licensor or You, including from the legal - processes of any jurisdiction or authority. - - -======================================================================= - -Creative Commons is not a party to its public licenses. -Notwithstanding, Creative Commons may elect to apply one of its public -licenses to material it publishes and in those instances will be -considered the "Licensor." Except for the limited purpose of indicating -that material is shared under a Creative Commons public license or as -otherwise permitted by the Creative Commons policies published at -creativecommons.org/policies, Creative Commons does not authorize the -use of the trademark "Creative Commons" or any other trademark or logo -of Creative Commons without its prior written consent including, -without limitation, in connection with any unauthorized modifications -to any of its public licenses or any other arrangements, -understandings, or agreements concerning use of licensed material. For -the avoidance of doubt, this paragraph does not form part of the public -licenses. - -Creative Commons may be contacted at creativecommons.org. \ No newline at end of file
===================================== gajim/data/emoticons/twemoji/twemoji.png deleted ===================================== Binary files a/gajim/data/emoticons/twemoji/twemoji.png and /dev/null differ
===================================== gajim/data/gui/message_actions_box.ui ===================================== @@ -143,6 +143,7 @@ <property name="can-focus">True</property> <property name="receives-default">True</property> <property name="tooltip-text" translatable="yes">Show a list of emojis</property> + <property name="action-name">win.show-emoji-chooser</property> <property name="relief">none</property> <child> <object class="GtkImage">
===================================== gajim/data/gui/preferences.ui ===================================== @@ -688,57 +688,7 @@ </packing> </child> <child> - <!-- n-columns=3 n-rows=3 --> - <object class="GtkGrid" id="emoji"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <property name="row-spacing">12</property> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Emoji</property> - <property name="xalign">0</property> - <style> - <class name="bold"/> - </style> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">0</property> - </packing> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> + <placeholder/> </child> </object> </child>
===================================== gajim/data/style/gajim.css ===================================== @@ -748,18 +748,6 @@ #NotificationPopupTitle { font-weight: bold; }
-/* Emoticon Popover (ModifierChooser) */ -popover#EmoticonPopover button { background: none; border: none; box-shadow:none; padding: 0px;} -popover#EmoticonPopover button > label { font-size: 2.0rem; } -popover#EmoticonPopover flowboxchild > label { font-size: 2.0rem; } -popover#EmoticonPopover flowboxchild { padding-top: 5px; padding-bottom: 5px; } -popover#EmoticonPopover scrolledwindow { border: none; } -popover#EmoticonPopover { padding: 5px; background-color: @theme_unfocused_base_color; } - -.emoji-chooser-heading { font-size: 1.15rem; font-weight: bold; padding: 5px;} -.emoji-chooser-flowbox { padding-left: 5px; padding-right: 11px; } -.emoji-modifier-chooser-flowbox { padding-left: 5px; } - /* Settings widgets ======================================================== */
/* SettingsBox */
===================================== gajim/gtk/application.py ===================================== @@ -244,12 +244,6 @@ def _startup(self) -> None: idle.Monitor.set_interval(app.settings.get('autoawaytime') * 60, app.settings.get('autoxatime') * 60)
- if sys.platform == 'darwin': - # TODO: Remove if colored emoji rendering works well on - # Windows and MacOS - from gajim.gui.emoji_chooser import emoji_chooser - emoji_chooser.load() - from gajim.gui_interface import Interface
self.interface = Interface()
===================================== gajim/gtk/chat_stack.py ===================================== @@ -712,7 +712,6 @@ def _on_function_message(self, self._chat_control.add_info_message(message)
def _on_send_message(self) -> None: - self._message_action_box.msg_textview.replace_emojis() message = self._message_action_box.msg_textview.get_text() if message.startswith('//'): # Escape sequence for chat commands
===================================== gajim/gtk/conversation/plain_widget.py ===================================== @@ -14,32 +14,19 @@
from __future__ import annotations
-from typing import Any - import logging -import sys
from gi.repository import Gtk from gi.repository import Pango from gi.repository import Gdk -from gi.repository import GdkPixbuf from gi.repository import GLib -from gi.repository import GObject
-from gajim.common import app -from gajim.common.const import StyleAttr from gajim.common.helpers import open_uri -from gajim.common.helpers import parse_uri -from gajim.common.structs import URI from gajim.common.styling import BaseHyperlink from gajim.common.styling import PlainBlock from gajim.common.styling import process_uris
from ..menus import get_conv_action_context_menu -from ..menus import get_conv_uri_context_menu -from ..emoji_data import emoji_pixbufs -from ..emoji_data import get_emoji_pixbuf -from ..util import get_cursor from ..util import make_pango_attributes
log = logging.getLogger('gajim.gui.conversaion.plain_widget') @@ -55,22 +42,11 @@ def __init__(self, account: str, selectable: bool) -> None:
self._account = account
- # We use a Gtk.Textview on MacOS, since there is no support - # for rendering color fonts (Emojis) on MacOS yet, see: - # https://gitlab.freedesktop.org/cairo/cairo/-/merge_requests/289 - # https://gitlab.freedesktop.org/cairo/cairo/-/merge_requests/9 - if sys.platform == 'darwin': - if app.settings.get('dev_use_message_label'): - self._text_widget = MessageLabel(self._account, selectable) - else: - self._text_widget = MessageTextview(self._account) - else: - self._text_widget = MessageLabel(self._account, selectable) + self._text_widget = MessageLabel(self._account, selectable) self.add(self._text_widget)
def set_selectable(self, selectable: bool) -> None: - if isinstance(self._text_widget, MessageLabel): - self._text_widget.set_selectable(selectable) + self._text_widget.set_selectable(selectable)
def add_content(self, block: PlainBlock) -> None: self._text_widget.print_text_with_styling(block) @@ -152,295 +128,3 @@ def _on_focus_out(widget: MessageLabel, _event: Gdk.EventFocus ) -> None: widget.get_style_context().add_class('transparent-selection') - - -class MessageTextview(Gtk.TextView): - def __init__(self, account: str) -> None: - Gtk.TextView.__init__(self) - self.set_hexpand(True) - self.set_margin_start(0) - self.set_margin_end(0) - self.set_border_width(0) - self.set_left_margin(0) - self.set_right_margin(0) - self.set_has_tooltip(True) - self.set_editable(False) - self.set_cursor_visible(False) - self.set_wrap_mode(Gtk.WrapMode.WORD_CHAR) - - self._handlers: dict[int, MessageTextview] = {} - - id_ = self.connect('query-tooltip', self._query_tooltip) - self._handlers[id_] = self - id_ = self.connect('button-press-event', self._on_button_press) - self._handlers[id_] = self - id_ = self.connect('populate-popup', self._on_populate_popup) - self._handlers[id_] = self - - self._account = account - - # Used for changing the mouse pointer when hovering clickable URIs - self._cursor_changed: bool = False - - # Keeps text selections for quoting and search actions - self._selected_text: str = '' - - self.get_style_context().add_class('gajim-conversation-text') - - # Create Tags - self._create_url_tags() - self.get_buffer().create_tag('strong', weight=Pango.Weight.BOLD) - self.get_buffer().create_tag('emphasis', style=Pango.Style.ITALIC) - self.get_buffer().create_tag('strike', strikethrough=True) - self.get_buffer().create_tag('pre', family='monospace') - - self.connect('destroy', self._on_destroy) - - def _on_destroy(self, *args: Any) -> None: - for id_, widget in self._handlers.items(): - if widget.handler_is_connected(id_): - widget.disconnect(id_) - self._handlers.clear() - - def _create_url_tags(self) -> None: - color = app.css_config.get_value('.gajim-url', StyleAttr.COLOR) - for name in URI_TAGS: - tag = self.get_buffer().create_tag(name, - foreground=color, - underline=Pango.Underline.SINGLE) - tag.connect('event', self._on_uri_clicked, tag) - - def clear(self) -> None: - buffer_ = self.get_buffer() - start, end = buffer_.get_bounds() - buffer_.delete(start, end) - - def get_text(self) -> str: - buffer_ = self.get_buffer() - start, end = buffer_.get_bounds() - return buffer_.get_text(start, end, False) - - def print_text_with_styling(self, block: PlainBlock) -> None: - buffer_ = self.get_buffer() - buffer_.insert(buffer_.get_start_iter(), block.text.strip()) - - if len(self.get_text()) > 10000: - # Limit message styling processing - return - - for span in block.spans: - start_iter = buffer_.get_iter_at_offset(span.start) - end_iter = buffer_.get_iter_at_offset(span.end) - buffer_.apply_tag_by_name(span.name, start_iter, end_iter) - - for uri in block.uris: - start_iter = buffer_.get_iter_at_offset(uri.start) - end_iter = buffer_.get_iter_at_offset(uri.end) - buffer_.apply_tag_by_name(uri.name, start_iter, end_iter) - - for emoji in block.emojis: - start_iter = buffer_.get_iter_at_offset(emoji.start) - end_iter = buffer_.get_iter_at_offset(emoji.end) - if emoji_pixbufs.complete: - # Only search for pixbuf if loading is completed - pixbuf = get_emoji_pixbuf(emoji.text) - if pixbuf is None: - buffer_.insert(end_iter, emoji.text) - else: - pixbuf = pixbuf.copy() - anchor = buffer_.create_child_anchor(end_iter) - anchor.plaintext = emoji.text # type: ignore - img = Gtk.Image.new_from_pixbuf(pixbuf) - img.show() - self.add_child_at_anchor(img, anchor) - buffer_.delete( - buffer_.get_iter_at_offset(emoji.start), - buffer_.get_iter_at_offset(emoji.end)) - else: - # Set marks and save them so we can replace emojis - # once loading is complete - start_mark = buffer_.create_mark(None, end_iter, True) - buffer_.insert(end_iter, emoji.text) - end_mark = buffer_.create_mark(None, end_iter, True) - emoji_pixbufs.append_marks( - self, start_mark, end_mark, emoji.text) - - def replace_emoji(self, - start_mark: Gtk.TextMark, - end_mark: Gtk.TextMark, - pixbuf: GdkPixbuf.Pixbuf, - codepoint: str - ) -> None: - buffer_ = self.get_buffer() - start_iter = buffer_.get_iter_at_mark(start_mark) - end_iter = buffer_.get_iter_at_mark(end_mark) - buffer_.delete(start_iter, end_iter) - - anchor = buffer_.create_child_anchor(start_iter) - anchor.plaintext = codepoint # type: ignore - img = Gtk.Image.new_from_pixbuf(pixbuf) - img.show() - self.add_child_at_anchor(img, anchor) - buffer_.delete_mark(start_mark) - buffer_.delete_mark(end_mark) - - def add_action_phrase(self, text: str, nickname: str) -> None: - text = text.replace('/me', f'* {nickname}', 1) - - buffer_ = self.get_buffer() - buffer_.insert(buffer_.get_start_iter(), text.strip()) - - uris = process_uris(text) - for uri in uris: - start_iter = buffer_.get_iter_at_offset(uri.start) - end_iter = buffer_.get_iter_at_offset(uri.end) - buffer_.apply_tag_by_name(uri.name, start_iter, end_iter) - - start_iter = buffer_.get_start_iter() - end_iter = buffer_.get_end_iter() - buffer_.apply_tag_by_name('emphasis', start_iter, end_iter) - - def _query_tooltip(self, - widget: Gtk.TextView, - x_pos: int, - y_pos: int, - _keyboard_mode: bool, - _tooltip: Gtk.Tooltip - ) -> bool: - - window = widget.get_window(Gtk.TextWindowType.TEXT) - assert window is not None - x_pos, y_pos = self.window_to_buffer_coords( - Gtk.TextWindowType.TEXT, x_pos, y_pos) - - iter_ = self.get_iter_at_position(x_pos, y_pos)[1] - for tag in iter_.get_tags(): - tag_name = tag.get_property('name') - if tag_name in URI_TAGS: - window.set_cursor(get_cursor('pointer')) - self._cursor_changed = True - return False - - if self._cursor_changed: - window.set_cursor(get_cursor('text')) - self._cursor_changed = False - return False - - def _on_button_press(self, _widget: Any, event: Gdk.EventButton) -> bool: - ''' - We don’t open the standard context menu when receiving - a click on tagged text. - If it’s untagged text, check if something is selected - ''' - self._selected_text = '' - - if event.button != 3: - # If it’s not a right click - return False - - x_pos, y_pos = self.window_to_buffer_coords( - Gtk.TextWindowType.TEXT, - int(event.x), - int(event.y)) - _, iter_ = self.get_iter_at_location(x_pos, y_pos) - tags = iter_.get_tags() - - if tags: - # A tagged text fragment has been clicked - for tag in tags: - if tag.get_property('name') in URI_TAGS: - # Block regular context menu - return True - - # Check if there is a selection and make it available for - # _on_populate_popup - buffer_ = self.get_buffer() - return_val = buffer_.get_selection_bounds() - if return_val: - # Something has been selected, get the text - start_sel, finish_sel = return_val[0], return_val[1] - self._selected_text = buffer_.get_text( - start_sel, finish_sel, True) - elif iter_.get_char() and ord(iter_.get_char()) > 31: - # Clicked on a word, take whole word for selection - start_sel = iter_.copy() - if not start_sel.starts_word(): - start_sel.backward_word_start() - finish_sel = iter_.copy() - if not finish_sel.ends_word(): - finish_sel.forward_word_end() - self._selected_text = buffer_.get_text( - start_sel, finish_sel, True) - return False - - def _on_populate_popup(self, - _textview: Gtk.TextView, - menu: Gtk.Menu - ) -> None: - ''' - Overrides the default context menu. - If text is selected, a submenu with actions on the selection is added. - (see _on_button_press) - ''' - if not self._selected_text: - menu.show_all() - return - - action_menu_item = get_conv_action_context_menu( - self._account, self._selected_text) - menu.prepend(action_menu_item) - menu.show_all() - - def _on_uri_clicked(self, - texttag: Gtk.TextTag, - _widget: Any, - event: Gdk.Event, - iter_: Gtk.TextIter, - _kind: Gtk.TextTag - ) -> int: - if event.type != Gdk.EventType.BUTTON_PRESS: - return Gdk.EVENT_PROPAGATE - - begin_iter = iter_.copy() - # we get the beginning of the tag - while not begin_iter.starts_tag(texttag): - begin_iter.backward_char() - end_iter = iter_.copy() - # we get the end of the tag - while not end_iter.ends_tag(texttag): - end_iter.forward_char() - - # Detect XHTML-IM link - word = getattr(texttag, 'href', None) - if not word: - word = self.get_buffer().get_text(begin_iter, end_iter, True) - - uri = parse_uri(word) - if event.button.button == 3: # right click - self._show_uri_context_menu(uri) - return Gdk.EVENT_STOP - - # TODO: - # self.plugin_modified = False - # app.plugin_manager.extension_point( - # 'hyperlink_handler', uri, self, self.get_toplevel()) - # if self.plugin_modified: - # return Gdk.EVENT_STOP - - open_uri(uri, account=self._account) - return Gdk.EVENT_STOP - - def _show_uri_context_menu(self, uri: URI) -> None: - menu = get_conv_uri_context_menu(self._account, uri) - if menu is None: - log.warning('No handler for URI type: %s', uri) - return - - def _destroy(menu: Gtk.Menu, _pspec: GObject.ParamSpec) -> None: - visible = menu.get_property('visible') - if not visible: - GLib.idle_add(menu.destroy) - - menu.attach_to_widget(self, None) - menu.connect('notify::visible', _destroy) - menu.popup_at_pointer()
===================================== gajim/gtk/emoji_chooser.py deleted ===================================== @@ -1,412 +0,0 @@ -# This file is part of Gajim. -# -# Gajim is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation; version 3 only. -# -# Gajim is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Gajim. If not, see http://www.gnu.org/licenses/. - -import logging -import weakref -from collections import OrderedDict - -from gi.repository import Gtk -from gi.repository import GLib -from gi.repository import GdkPixbuf -from gi.repository import Pango - -from gajim.common import app -from gajim.common import helpers -from gajim.common import configpaths - -from .builder import get_builder -from .emoji_data import emoji_data -from .emoji_data import emoji_pixbufs -from .emoji_data import Emoji - -MODIFIER_MAX_CHILDREN_PER_LINE = 6 -MAX_CHILDREN_PER_LINE = 10 - -log = logging.getLogger('gajim.emoji') - - -class Section(Gtk.Box): - def __init__(self, name, search_entry, press_cb, chooser): - Gtk.Box.__init__(self, orientation=Gtk.Orientation.VERTICAL) - self._chooser = chooser - self._press_cb = press_cb - self.pixbuf_generator = None - self.heading = Gtk.Label(label=name) - self.heading.set_halign(Gtk.Align.START) - self.heading.get_style_context().add_class('emoji-chooser-heading') - self.add(self.heading) - - self.flowbox = Gtk.FlowBox() - self.flowbox.get_style_context().add_class('emoji-chooser-flowbox') - self.flowbox.set_max_children_per_line(MAX_CHILDREN_PER_LINE) - self.flowbox.set_filter_func(self._filter_func, search_entry) - self.flowbox.connect('child-activated', press_cb) - - self.add(self.flowbox) - self.show_all() - - def _filter_func(self, child, search_entry): - name = search_entry.get_text() - if not name: - self.show() - return True - - if name in child.desc: - self.show() - return True - return False - - def add_emoji(self, codepoint, attrs): - # Return always True, this method is used for the emoji factory - # called by GLib.idle_add() - pixbuf = self._get_next_pixbuf() - - variations = attrs.get('variations', None) - if variations is None: - if pixbuf is None: - return True - self.flowbox.add(EmojiChild(codepoint, pixbuf, attrs['desc'])) - if pixbuf != 'font': - # We save the pixbuf for fast access if we need to - # replace a codepoint in the textview - emoji_pixbufs[codepoint] = pixbuf - else: - if pixbuf is not None: - chooser = ModifierChooser() - - # Iterate over the variations and add the codepoints - for codepoint_ in variations.keys(): - pixbuf_ = self._get_next_pixbuf() - if pixbuf_ is None: - continue - - if pixbuf_ == 'font': - if not self._chooser.font_supports_codepoint( - codepoint_): - continue - else: - emoji_pixbufs[codepoint_] = pixbuf_ - - # Only codepoints are added which the - # font or theme supports - chooser.add_emoji(codepoint_, pixbuf_) - - # Check if we successfully added codepoints with modifiers - if chooser.has_child: - # If we have children then add a button - # and set the popover - child = EmojiModifierChild( - codepoint, pixbuf, attrs['desc']) - child.button.set_popover(chooser) - chooser.flowbox.connect( - 'child-activated', self._press_cb) - else: - # If no children were added, destroy the chooser - # and add a EmojiChild instead of a EmojiModifierChild - chooser.destroy() - child = EmojiChild(codepoint, pixbuf, attrs['desc']) - - if pixbuf != 'font': - emoji_pixbufs[codepoint] = pixbuf - - self.flowbox.add(child) - else: - # We don’t have a image for the base codepoint - # so skip all modifiers of it - for _entry in variations: - pixbuf = self._get_next_pixbuf() - return True - - def clear_emojis(self): - def _remove_emoji(emoji): - self.flowbox.remove(emoji) - emoji.destroy() - self.flowbox.foreach(_remove_emoji) - - def _get_next_pixbuf(self): - if self.pixbuf_generator is None: - return 'font' - return next(self.pixbuf_generator, False) - - -class EmojiChild(Gtk.FlowBoxChild): - def __init__(self, codepoint, pixbuf, desc): - Gtk.FlowBoxChild.__init__(self) - self.desc = desc - self.codepoint = codepoint - self.pixbuf = pixbuf - if pixbuf == 'font': - self.add(Gtk.Label(label=codepoint)) - else: - self.add(Gtk.Image.new_from_pixbuf(pixbuf)) - self.set_tooltip_text(desc) - self.show_all() - - def get_emoji(self): - if self.pixbuf != 'font': - pixbuf = self.get_child().get_pixbuf() - pixbuf = pixbuf.scale_simple(Emoji.INPUT_SIZE, - Emoji.INPUT_SIZE, - GdkPixbuf.InterpType.HYPER) - return self.codepoint, pixbuf - return self.codepoint, None - - -class EmojiModifierChild(Gtk.FlowBoxChild): - def __init__(self, codepoint, pixbuf, desc): - Gtk.FlowBoxChild.__init__(self) - self.desc = desc - self.codepoint = codepoint - self.pixbuf = pixbuf - - self.button = Gtk.MenuButton() - self.button.set_relief(Gtk.ReliefStyle.NONE) - self.button.connect('button-press-event', self._button_press) - - if pixbuf == 'font': - self.button.remove(self.button.get_child()) - label = Gtk.Label(label=codepoint) - self.button.add(label) - else: - self.button.get_child().set_from_pixbuf(pixbuf) - - self.set_tooltip_text(desc) - self.add(self.button) - self.show_all() - - def _button_press(self, button, event): - if event.button == 3: - button.get_popover().show() - button.get_popover().get_child().unselect_all() - return True - if event.button == 1: - self.get_parent().emit('child-activated', self) - return True - return False - - def get_emoji(self): - if self.pixbuf != 'font': - pixbuf = self.button.get_child().get_pixbuf() - pixbuf = pixbuf.scale_simple(Emoji.INPUT_SIZE, - Emoji.INPUT_SIZE, - GdkPixbuf.InterpType.HYPER) - return self.codepoint, pixbuf - return self.codepoint, None - - -class ModifierChooser(Gtk.Popover): - def __init__(self): - Gtk.Popover.__init__(self) - self.set_name('EmoticonPopover') - self._has_child = False - - self.flowbox = Gtk.FlowBox() - self.flowbox.get_style_context().add_class( - 'emoji-modifier-chooser-flowbox') - self.flowbox.set_size_request(200, -1) - self.flowbox.set_max_children_per_line(MODIFIER_MAX_CHILDREN_PER_LINE) - self.flowbox.show() - self.add(self.flowbox) - - @property - def has_child(self): - return self._has_child - - def add_emoji(self, codepoint, pixbuf): - self.flowbox.add(EmojiChild(codepoint, pixbuf, None)) - self._has_child = True - - -class EmojiChooser(Gtk.Popover): - - _section_names = [ - 'Smileys & People', - 'Animals & Nature', - 'Food & Drink', - 'Travel & Places', - 'Activities', - 'Objects', - 'Symbols', - 'Flags' - ] - - def __init__(self): - super().__init__() - self.set_name('EmoticonPopover') - self._text_widget = None - self._load_source_id = None - self._pango_layout = Pango.Layout(self.get_pango_context()) - - self._builder = get_builder('emoji_chooser.ui') - self._search = self._builder.get_object('search') - self._stack = self._builder.get_object('stack') - - self._sections = OrderedDict() - for name in self._section_names: - self._sections[name] = Section( - name, self._search, self._on_emoticon_press, self) - - section_box = self._builder.get_object('section_box') - for section in self._sections.values(): - section_box.add(section) - - self.add(self._builder.get_object('box')) - - self.connect('key-press-event', self._key_press) - self._builder.connect_signals(self) - - @property - def text_widget(self): - return self._text_widget - - @text_widget.setter - def text_widget(self, value): - # Hold only a weak reference so if we can destroy - # the ChatControl - self._text_widget = weakref.ref(value) - - def _key_press(self, _widget, event): - return self._search.handle_event(event) - - def _search_changed(self, _entry): - for section in self._sections.values(): - section.hide() - section.flowbox.invalidate_filter() - self._switch_stack() - - def _clear_sections(self): - for section in self._sections.values(): - section.clear_emojis() - - def _switch_stack(self): - for section in self._sections.values(): - if section.is_visible(): - self._stack.set_visible_child_name('list') - return - self._stack.set_visible_child_name('not-found') - - @staticmethod - def _get_current_theme(): - theme = app.settings.get('emoticons_theme') - themes = helpers.get_available_emoticon_themes() - if theme not in themes: - app.settings.set('emoticons_theme', 'noto') - theme = 'noto' - return theme - - @staticmethod - def _get_emoji_theme_path(theme): - if theme == 'font': - return 'font' - - base_path = configpaths.get('EMOTICONS') - emoticons_data_path = base_path / theme / f'{theme}.png' - if emoticons_data_path.exists(): - return emoticons_data_path - - emoticons_user_path = configpaths.get('MY_EMOTS') / f'{theme}.png' - if emoticons_user_path.exists(): - return emoticons_user_path - - log.warning('Could not find emoji theme: %s', theme) - return None - - def load(self): - theme = self._get_current_theme() - path = self._get_emoji_theme_path(theme) - if not theme or path is None: - self._clear_sections() - emoji_pixbufs.clear() - return - - # Attach pixbuf generator - pixbuf_generator = None - if theme != 'font': - pixbuf_generator = self._get_next_pixbuf(path) - for section in self._sections.values(): - section.pixbuf_generator = pixbuf_generator - - if self._load_source_id is not None: - GLib.source_remove(self._load_source_id) - - # When we change emoji theme - self._clear_sections() - emoji_pixbufs.clear() - - factory = self._emoji_factory(theme == 'font') - self._load_source_id = GLib.idle_add(lambda: next(factory, False), - priority=GLib.PRIORITY_LOW) - - def _emoji_factory(self, font): - for codepoint, attrs in emoji_data.items(): - if not attrs['fully-qualified']: - # We don’t add these to the UI - continue - - if font and not self.font_supports_codepoint(codepoint): - continue - - section = self._sections[attrs['group']] - yield section.add_emoji(codepoint, attrs) - self._load_source_id = None - emoji_pixbufs.complete = True - - def font_supports_codepoint(self, codepoint): - self._pango_layout.set_text(codepoint, -1) - if self._pango_layout.get_unknown_glyphs_count(): - return False - if len(codepoint) > 1: - # The font supports each of the codepoints - # Check if the rendered glyph is more than one char - if self._pango_layout.get_size()[0] > 19000: - return False - return True - - @staticmethod - def _get_next_pixbuf(path): - src_x = src_y = cur_column = 0 - atlas = GdkPixbuf.Pixbuf.new_from_file(str(path)) - - while True: - src_x = cur_column * Emoji.PARSE_WIDTH - subpixbuf = atlas.new_subpixbuf(src_x, src_y, - Emoji.PARSE_WIDTH, - Emoji.PARSE_HEIGHT) - - if list(subpixbuf.get_pixels())[0:4] == [0, 0, 0, 255]: - # top left corner is a black pixel means image is missing - subpixbuf = None - - if cur_column == Emoji.PARSE_COLUMNS - 1: - src_y += Emoji.PARSE_WIDTH - cur_column = 0 - else: - cur_column += 1 - - yield subpixbuf - - def _on_emoticon_press(self, flowbox, child): - GLib.timeout_add(100, flowbox.unselect_child, child) - codepoint, pixbuf = child.get_emoji() - self._text_widget().insert_emoji(codepoint, pixbuf) - self._text_widget().grab_focus() - - def do_destroy(self): - # Remove the references we hold to other objects - self._text_widget = None - # Never destroy, creating a new EmoticonPopover is expensive - return True - - -emoji_chooser = EmojiChooser()
===================================== gajim/gtk/emoji_data.py deleted ===================================== The diff for this file was not included because it is too large.
===================================== gajim/gtk/emoji_data.pyi deleted ===================================== @@ -1,44 +0,0 @@ -# This file is part of Gajim. -# -# Gajim is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published -# by the Free Software Foundation; version 3 only. -# -# Gajim is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Gajim. If not, see http://www.gnu.org/licenses/. - -from enum import IntEnum -from collections import OrderedDict - -from gi.repository import GdkPixbuf -from gi.repository import Gtk - -class Emoji(IntEnum): - ... - -def is_emoji(codepoints: str) -> bool: ... -def get_emoji_pixbuf(codepoints: str) -> GdkPixbuf.Pixbuf: ... - -class EmojiData(OrderedDict): - def get_regex(self) -> str: ... - -class EmojiPixbufs(dict): - @property - def complete(self) -> bool: ... - @complete.setter - def complete(self, value: bool) -> None: ... - def clear(self) -> None: ... - def append_marks(self, - textview: Gtk.TextView, - start: Gtk.TextMark, - end: Gtk.TextMark, - codepoint: str) -> None: ... - -emoji_pixbufs: EmojiPixbufs -emoji_ascii_data: dict[str, str] -emoji_data: EmojiData
===================================== gajim/gtk/message_actions_box.py ===================================== @@ -18,7 +18,6 @@ from typing import Optional
import os -import sys import tempfile import logging import uuid @@ -45,7 +44,6 @@
from .builder import get_builder from .dialogs import ErrorDialog -from .emoji_chooser import emoji_chooser from .menus import get_encryption_menu from .menus import get_format_menu from .menus import get_groupchat_menu @@ -98,8 +96,6 @@ def __init__(self) -> None:
self._ui.quick_invite_button.set_action_name('win.muc-invite')
- self._init_emoticon_popover() - self.show_all() self._ui.connect_signals(self)
@@ -157,7 +153,7 @@ def _on_action(self, self._on_format(action_name)
elif action_name == 'show-emoji-chooser': - self._on_show_emoji_chooser() + self.msg_textview.emit('insert-emoji')
elif action_name == 'quote': assert param @@ -389,31 +385,6 @@ def _set_settings_menu(self, contact: ChatContactT) -> None: menu = get_singlechat_menu(contact) self._ui.settings_menu.set_menu_model(menu)
- def _init_emoticon_popover(self) -> None: - if not app.settings.get('emoticons_theme'): - return - - if sys.platform == 'darwin': - emoji_chooser.text_widget = self.msg_textview - self._ui.emoticons_button.set_popover(emoji_chooser) - return - - self._ui.emoticons_button.set_sensitive(True) - self._ui.emoticons_button.connect('clicked', - self._on_emoticon_button_clicked) - - def toggle_emoticons(self) -> None: - if app.settings.get('emoticons_theme'): - self._ui.emoticons_button.set_no_show_all(False) - self._ui.emoticons_button.show() - else: - self._ui.emoticons_button.set_no_show_all(True) - self._ui.emoticons_button.hide() - - def _on_emoticon_button_clicked(self, _widget: Gtk.Button) -> None: - self.msg_textview.emit('insert-emoji') - self._ui.emoticons_button.set_active(False) - def _on_format(self, name: str) -> None: name = name.removeprefix('input-') self.msg_textview.apply_formatting(name) @@ -421,14 +392,6 @@ def _on_format(self, name: str) -> None: def _on_clear(self) -> None: self.msg_textview.clear()
- def _on_show_emoji_chooser(self) -> None: - if sys.platform == 'darwin': - popover = self._ui.emoticons_button.get_popover() - assert popover - popover.show() - else: - self.msg_textview.emit('insert-emoji') - def _on_send_file_enabled_changed(self, action: Gio.SimpleAction, _param: GObject.ParamSpec) -> None:
===================================== gajim/gtk/message_input.py ===================================== @@ -25,11 +25,9 @@ from collections import defaultdict
import logging -import sys
from gi.repository import Gtk from gi.repository import Gdk -from gi.repository import GdkPixbuf from gi.repository import GLib from gi.repository import Pango
@@ -420,60 +418,6 @@ def apply_formatting(self, formatting: str) -> None: buf.get_iter_at_offset(start_offset), buf.get_iter_at_offset(end_offset + 2))
- def replace_emojis(self) -> None: - if sys.platform != 'darwin': - # TODO: Remove if colored emoji rendering works well on MacOS - return - - theme = app.settings.get('emoticons_theme') - if not theme or theme == 'font': - return - - def _replace(anchor: Gtk.TextChildAnchor) -> None: - if anchor is None: - return - image = anchor.get_widgets()[0] - if hasattr(image, 'codepoint'): - # found emoji - codepoint = getattr(image, 'codepoint') - self._replace_char_at_iter(iter_, codepoint) - image.destroy() - - iter_ = self.get_buffer().get_start_iter() - _replace(iter_.get_child_anchor()) - - while iter_.forward_char(): - _replace(iter_.get_child_anchor()) - - def _replace_char_at_iter(self, - iter_: Gtk.TextIter, - new_char: str - ) -> None: - buf = self.get_buffer() - iter_2 = iter_.copy() - iter_2.forward_char() - buf.delete(iter_, iter_2) - buf.insert(iter_, new_char) - - def insert_emoji(self, codepoint: str, pixbuf: GdkPixbuf.Pixbuf) -> None: - buf = self.get_buffer() - if buf.get_char_count(): - # buffer contains text - buf.insert_at_cursor(' ') - - insert_mark = buf.get_insert() - insert_iter = buf.get_iter_at_mark(insert_mark) - - if pixbuf is None: - buf.insert(insert_iter, codepoint) - else: - anchor = buf.create_child_anchor(insert_iter) - image = Gtk.Image.new_from_pixbuf(pixbuf) - setattr(image, 'codepoint', codepoint) - image.show() - self.add_child_at_anchor(image, anchor) - buf.insert_at_cursor(' ') - def clear(self, *args: Any) -> None: buf = self.get_buffer() start, end = buf.get_bounds()
===================================== gajim/gtk/preferences.py ===================================== @@ -26,7 +26,6 @@
from gajim.common import app from gajim.common import configpaths -from gajim.common import helpers from gajim.common.const import THRESHOLD_OPTIONS from gajim.common.i18n import _ from gajim.common.helpers import open_file @@ -42,7 +41,6 @@ from .const import SettingType from .dialogs import DialogButton from .dialogs import ConfirmationDialog -from .emoji_chooser import emoji_chooser from .settings import PopoverSetting from .settings import SettingsBox from .settings import SettingsDialog @@ -81,8 +79,6 @@ def __init__(self) -> None:
self.add(self._ui.grid)
- self._check_emoji_theme() - prefs: list[tuple[str, Type[PreferenceBox]]] = [ ('window_behaviour', WindowBehaviour), ('plugins', Plugins), @@ -95,7 +91,6 @@ def __init__(self) -> None: ('status_message', StatusMessage), ('automatic_status', AutomaticStatus), ('themes', Themes), - ('emoji', Emoji), ('server', Server), ('audio', Audio), ('video', Video), @@ -117,10 +112,6 @@ def __init__(self) -> None: self._ui.connect_signals(self)
self.show_all() - if sys.platform != 'darwin': - # TODO: Remove if colored emoji rendering works well on - # Windows and MacOS - self._ui.emoji.hide()
def get_ui(self): return self._ui @@ -157,14 +148,6 @@ def update_proxy_list(self) -> None: miscellaneous = cast(Miscellaneous, self._prefs['miscellaneous']) miscellaneous.update_proxy_list()
- @staticmethod - def _check_emoji_theme() -> None: - # Ensure selected emoji theme is valid - emoji_themes = helpers.get_available_emoticon_themes() - settings_theme = app.settings.get('emoticons_theme') - if settings_theme not in emoji_themes: - app.settings.set('emoticons_theme', 'font') - def _on_destroy(self, _widget: Gtk.Widget) -> None: self._prefs.clear() app.check_finalize(self) @@ -704,42 +687,6 @@ def _on_dark_theme(value: str, *args: Any) -> None: app.ged.raise_event(StyleChanged())
-class Emoji(PreferenceBox): - def __init__(self, *args: Any) -> None: - if sys.platform != 'darwin': - # TODO: Remove if colored emoji rendering works well on - # Windows and MacOS - PreferenceBox.__init__(self, []) - return - - emoji_themes_items: list[str] = [] - for theme in helpers.get_available_emoticon_themes(): - emoji_themes_items.append(theme) - - settings = [ - Setting(SettingKind.POPOVER, - _('Emoji Theme'), - SettingType.CONFIG, - 'emoticons_theme', - desc=_('Choose from various emoji styles'), - props={'entries': emoji_themes_items}, - callback=self._on_emoticons_theme) - ] - - PreferenceBox.__init__(self, settings) - - def _on_emoticons_theme(self, *args: Any) -> None: - emoji_chooser.load() - self._toggle_emoticons() - - @staticmethod - def _toggle_emoticons() -> None: - # TODO - # for ctrl in app.window.get_controls(): - # ctrl.toggle_emoticons() - pass - - class Server(PreferenceBox): def __init__(self, *args: Any) -> None:
===================================== pyproject.toml ===================================== @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
[tool.codespell] -skip = "*__pycache__*,gajim/gui,gajim/data/icons,gajim/data/sounds,gajim/data/emoticons" +skip = "*__pycache__*,gajim/gui,gajim/data/icons,gajim/data/sounds" ignore-words-list = "claus,pres,ser,trough"
===================================== setup.cfg ===================================== @@ -49,8 +49,6 @@ install_requires =
[options.package_data] gajim = - data/emoticons/*/*.png - data/emoticons/*/LICENSE data/gui/*.ui data/icons/hicolor/*/*/*.png data/icons/hicolor/*/*/*.svg
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/commit/eebbe3e87fa5904f94951af0161262c6c...