gajim.org
Sign In
Sign Up
Sign In
Sign Up
Manage this list
×
Keyboard Shortcuts
Thread View
j
: Next unread message
k
: Previous unread message
j a
: Jump to all threads
j l
: Jump to MailingList overview
2026
October
September
August
July
June
May
April
March
February
January
2025
December
November
October
September
August
July
June
May
April
March
February
January
2024
December
November
October
September
August
July
June
May
April
March
February
January
2023
December
November
October
September
August
July
June
May
April
March
February
January
2022
December
November
October
September
August
July
June
May
April
March
February
January
2021
December
November
October
September
August
July
June
May
April
March
February
January
2020
December
November
October
September
August
July
June
May
April
March
February
January
2019
December
November
October
September
August
July
June
May
April
March
February
January
2018
December
November
October
September
August
July
June
May
April
March
February
January
2017
December
November
October
September
August
July
June
May
April
March
February
January
2016
December
November
October
September
August
July
June
May
April
March
February
January
2015
December
November
October
September
August
July
June
May
April
March
February
January
2014
December
November
October
September
August
July
June
May
April
March
February
January
2013
December
November
October
September
August
July
June
May
April
March
February
January
2012
December
November
October
September
August
July
June
May
April
March
February
January
2011
December
November
October
September
August
July
June
May
April
March
February
January
2010
December
November
October
September
August
July
June
May
April
March
February
January
2009
December
November
October
September
August
July
June
May
April
March
February
January
2008
December
November
October
September
August
July
June
May
April
March
February
January
2007
December
November
October
September
August
July
June
May
April
March
February
January
2006
December
November
October
September
August
July
June
May
April
List overview
Download
Commits
November 2021
----- 2026 -----
October 2026
September 2026
August 2026
July 2026
June 2026
May 2026
April 2026
March 2026
February 2026
January 2026
----- 2025 -----
December 2025
November 2025
October 2025
September 2025
August 2025
July 2025
June 2025
May 2025
April 2025
March 2025
February 2025
January 2025
----- 2024 -----
December 2024
November 2024
October 2024
September 2024
August 2024
July 2024
June 2024
May 2024
April 2024
March 2024
February 2024
January 2024
----- 2023 -----
December 2023
November 2023
October 2023
September 2023
August 2023
July 2023
June 2023
May 2023
April 2023
March 2023
February 2023
January 2023
----- 2022 -----
December 2022
November 2022
October 2022
September 2022
August 2022
July 2022
June 2022
May 2022
April 2022
March 2022
February 2022
January 2022
----- 2021 -----
December 2021
November 2021
October 2021
September 2021
August 2021
July 2021
June 2021
May 2021
April 2021
March 2021
February 2021
January 2021
----- 2020 -----
December 2020
November 2020
October 2020
September 2020
August 2020
July 2020
June 2020
May 2020
April 2020
March 2020
February 2020
January 2020
----- 2019 -----
December 2019
November 2019
October 2019
September 2019
August 2019
July 2019
June 2019
May 2019
April 2019
March 2019
February 2019
January 2019
----- 2018 -----
December 2018
November 2018
October 2018
September 2018
August 2018
July 2018
June 2018
May 2018
April 2018
March 2018
February 2018
January 2018
----- 2017 -----
December 2017
November 2017
October 2017
September 2017
August 2017
July 2017
June 2017
May 2017
April 2017
March 2017
February 2017
January 2017
----- 2016 -----
December 2016
November 2016
October 2016
September 2016
August 2016
July 2016
June 2016
May 2016
April 2016
March 2016
February 2016
January 2016
----- 2015 -----
December 2015
November 2015
October 2015
September 2015
August 2015
July 2015
June 2015
May 2015
April 2015
March 2015
February 2015
January 2015
----- 2014 -----
December 2014
November 2014
October 2014
September 2014
August 2014
July 2014
June 2014
May 2014
April 2014
March 2014
February 2014
January 2014
----- 2013 -----
December 2013
November 2013
October 2013
September 2013
August 2013
July 2013
June 2013
May 2013
April 2013
March 2013
February 2013
January 2013
----- 2012 -----
December 2012
November 2012
October 2012
September 2012
August 2012
July 2012
June 2012
May 2012
April 2012
March 2012
February 2012
January 2012
----- 2011 -----
December 2011
November 2011
October 2011
September 2011
August 2011
July 2011
June 2011
May 2011
April 2011
March 2011
February 2011
January 2011
----- 2010 -----
December 2010
November 2010
October 2010
September 2010
August 2010
July 2010
June 2010
May 2010
April 2010
March 2010
February 2010
January 2010
----- 2009 -----
December 2009
November 2009
October 2009
September 2009
August 2009
July 2009
June 2009
May 2009
April 2009
March 2009
February 2009
January 2009
----- 2008 -----
December 2008
November 2008
October 2008
September 2008
August 2008
July 2008
June 2008
May 2008
April 2008
March 2008
February 2008
January 2008
----- 2007 -----
December 2007
November 2007
October 2007
September 2007
August 2007
July 2007
June 2007
May 2007
April 2007
March 2007
February 2007
January 2007
----- 2006 -----
December 2006
November 2006
October 2006
September 2006
August 2006
July 2006
June 2006
May 2006
April 2006
commits@gajim.org
1 participants
101 discussions
Start a n
N
ew thread
[Git][gajim/gajim][master] Docker: Install nbxmpp
by Philipp Hörist (@lovetox)
07 Nov '21
07 Nov '21
Philipp Hörist pushed to branch master at gajim / gajim Commits: 8e01ad49 by lovetox at 2021-11-07T08:10:32+01:00 Docker: Install nbxmpp - - - - - 1 changed file: - docker/requirements.txt Changes: ===================================== docker/requirements.txt ===================================== @@ -1,3 +1,4 @@ +git+https://dev.gajim.org/gajim/python-nbxmpp.git@master PyGObject mypy pylint View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/commit/8e01ad4980ab78e00bca5e14b09227cb…
-- View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/commit/8e01ad4980ab78e00bca5e14b09227cb…
You're receiving this email because of your account on
dev.gajim.org
.
1
0
0
0
[Git][gajim/gajim][master] Add docker files
by Philipp Hörist (@lovetox)
07 Nov '21
07 Nov '21
Philipp Hörist pushed to branch master at gajim / gajim Commits: a216b54a by lovetox at 2021-11-07T08:05:06+01:00 Add docker files - - - - - 2 changed files: - + docker/Dockerfile - + docker/requirements.txt Changes: ===================================== docker/Dockerfile ===================================== @@ -0,0 +1,23 @@ +FROM python:3.9 + +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt-get update +RUN apt-get install -y apt-utils +RUN apt-get install -y appstream-util +RUN apt-get install -y libgirepository1.0-dev +RUN apt-get install -y gir1.2-gtk-3.0 +RUN apt-get install -y gir1.2-soup-2.4 +RUN apt-get install -y gettext + +RUN adduser --disabled-password worker +WORKDIR /home/worker +ENV PATH="/home/worker/.local/bin:${PATH}" +USER worker + +RUN pip install --upgrade pip + +COPY --chown=worker:worker requirements.txt requirements.txt +RUN pip install --no-cache-dir --user -r requirements.txt + +CMD bash ===================================== docker/requirements.txt ===================================== @@ -0,0 +1,12 @@ +PyGObject +mypy +pylint +codespell +coverage +css-parser +keyring +packaging +precis-i18n +pyOpenSSL +types-pkg_resources +requests \ No newline at end of file View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/commit/a216b54aed82e84ebfd3bb6a8f5c0fe7…
-- View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/commit/a216b54aed82e84ebfd3bb6a8f5c0fe7…
You're receiving this email because of your account on
dev.gajim.org
.
1
0
0
0
[Git][gajim/python-nbxmpp][master] CI: Ignore some pylint errors
by Philipp Hörist (@lovetox)
06 Nov '21
06 Nov '21
Philipp Hörist pushed to branch master at gajim / python-nbxmpp Commits: 14f22522 by lovetox at 2021-11-07T00:27:27+01:00 CI: Ignore some pylint errors - - - - - 1 changed file: - .gitlab-ci.yml Changes: ===================================== .gitlab-ci.yml ===================================== @@ -15,7 +15,7 @@ run-test: - pip3 install -I coverage - pip3 install -I idna - pip3 install -I precis-i18n - - python3 -m pylint nbxmpp --disable=C0103,C0201,C0301,C0326,C0330,W0201,W0212,W0221,W0231,W0233,W0621,W0622,R0201,E1101,E1135 + - python3 -m pylint nbxmpp --disable=C0103,C0201,C0209,C0301,C0326,C0330,W0201,W0212,W0221,W0231,W0233,W0621,W0622,W0707,R0201,R0801,E1101,E1135 - coverage run --source=nbxmpp -m unittest discover -v - coverage report -mi - coverage xml -i @@ -28,6 +28,7 @@ run-test: # C0103 invalid-name # C0201 consider-iterating-dictionary +# C0209 consider-using-f-string # C0301 line-too-long # C0326 bad-whitespace # C0330 bad-continuation @@ -38,7 +39,9 @@ run-test: # W0233 non-parent-init-called # W0621 redefined-outer-name # W0622 redefined-builtin +# W0707 raise-missing-from # R0201 no-self-use +# R0801 duplicate-code # E1101 no-member # E1135 unsupported-membership-test View it on GitLab:
https://dev.gajim.org/gajim/python-nbxmpp/-/commit/14f225223ca2d47fedb92516…
-- View it on GitLab:
https://dev.gajim.org/gajim/python-nbxmpp/-/commit/14f225223ca2d47fedb92516…
You're receiving this email because of your account on
dev.gajim.org
.
1
0
0
0
[Git][gajim/gajim][mainwindow] 3 commits: Preview Helpers: Typing
by Daniel Brötzmann (@wurstsalat)
06 Nov '21
06 Nov '21
Daniel Brötzmann pushed to branch mainwindow at gajim / gajim Commits: 7770cc9c by wurstsalat at 2021-11-06T22:34:46+01:00 Preview Helpers: Typing - - - - - b4fc69f1 by wurstsalat at 2021-11-07T00:21:20+01:00 Preview Helpers: Typing - - - - - 06d18602 by wurstsalat at 2021-11-07T00:21:28+01:00 Preview: Typing - - - - - 2 changed files: - gajim/common/preview.py - gajim/common/preview_helpers.py Changes: ===================================== gajim/common/preview.py ===================================== @@ -12,17 +12,26 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see <
http://www.gnu.org/licenses/
>. +from typing import Any +from typing import Dict +from typing import Optional +from typing import Tuple + import logging import os from pathlib import Path from urllib.parse import urlparse +from urllib.parse import ParseResult +from gi.repository import GdkPixbuf +from gi.repository import Gio from gi.repository import GLib from gi.repository import Soup from gajim.common import app from gajim.common import configpaths from gajim.common.const import MIME_TYPES +from gajim.common.helpers import AdditionalDataDict from gajim.common.helpers import load_file_async from gajim.common.helpers import write_file_async from gajim.common.helpers import get_tls_error_phrase @@ -46,7 +55,13 @@ class Preview: - def __init__(self, uri, urlparts, orig_path, thumb_path, size, widget): + def __init__(self, + uri: str, + urlparts: Optional[ParseResult], + orig_path: Optional[Path], + thumb_path: Optional[Path], + size: int, + widget: Any) -> None: self._uri = uri self._urlparts = urlparts self._filename = filename_from_uri(uri) @@ -57,41 +72,44 @@ def __init__(self, uri, urlparts, orig_path, thumb_path, size, widget): self.thumb_path = thumb_path self.size = size - self.thumbnail = None - self.mime_type = None - self.file_size = 0 + self.thumbnail: Optional[bytes] = None + self.mime_type: str = '' + self.file_size: int = 0 - self.key, self.iv = None, None - if self.is_aes_encrypted: + self.key: Optional[bytes] = None + self.iv: Optional[bytes] = None + if self.is_aes_encrypted and urlparts is not None: self.key, self.iv = parse_fragment(urlparts.fragment) @property - def is_geo_uri(self): + def is_geo_uri(self) -> bool: return self._uri.startswith('geo:') @property - def is_web_uri(self): + def is_web_uri(self) -> bool: return not self.is_geo_uri @property - def is_previewable(self): + def is_previewable(self) -> bool: return self.mime_type in PREVIEWABLE_MIME_TYPES @property - def is_audio(self): + def is_audio(self) -> bool: is_allowed = bool(self.mime_type in ALLOWED_MIME_TYPES) return is_allowed and self.mime_type.startswith('audio/') @property - def uri(self): + def uri(self) -> str: return self._uri @property - def filename(self): + def filename(self) -> str: return self._filename @property - def request_uri(self): + def request_uri(self) -> Optional[str]: + if self._urlparts is None: + return '' if self.is_aes_encrypted: # Remove fragments so we dont transmit it to the server urlparts = self._urlparts._replace(scheme='https', fragment='') @@ -99,31 +117,37 @@ def request_uri(self): return self._urlparts.geturl() @property - def is_aes_encrypted(self): + def is_aes_encrypted(self) -> bool: if self._urlparts is None: return False return self._urlparts.scheme == 'aesgcm' - def thumb_exists(self): + def thumb_exists(self) -> bool: + if self.thumb_path is None: + return False return self.thumb_path.exists() - def orig_exists(self): + def orig_exists(self) -> bool: + if self.orig_path is None: + return False return self.orig_path.exists() - def create_thumbnail(self, data): + def create_thumbnail(self, data: bytes) -> bool: self.thumbnail = create_thumbnail(data, self.size) if self.thumbnail is None: log.warning('Creating thumbnail failed for: %s', self.orig_path) return False return True - def update_widget(self, data=None): + def update_widget(self, data: Optional[GdkPixbuf.Pixbuf] = None) -> None: self._widget.update(self, data) class PreviewManager: - def __init__(self): - self._sessions = {} + def __init__(self) -> None: + self._sessions: Dict[ + str, + Tuple[Soup.Session, Optional[Gio.SimpleProxyResolver]]] = {} self._orig_dir = Path(configpaths.get('MY_DATA')) / 'downloads' self._thumb_dir = Path(configpaths.get('MY_CACHE')) / 'downloads.thumb' @@ -134,13 +158,14 @@ def __init__(self): if GLib.mkdir_with_parents(str(self._thumb_dir), 0o700) != 0: log.error('Failed to create: %s', self._thumb_dir) - def _get_session(self, account): + def _get_session(self, account: str) -> Soup.Session: if account not in self._sessions: self._sessions[account] = self._create_session(account) return self._sessions[account][0] @staticmethod - def _create_session(account): + def _create_session(account: str) -> Tuple[ + Soup.Session, Optional[Gio.SimpleProxyResolver]]: session = Soup.Session() session.add_feature_by_type(Soup.ContentSniffer) session.props.https_aliases = ['aesgcm'] @@ -156,7 +181,9 @@ def _create_session(account): return session, resolver @staticmethod - def _accept_uri(urlparts, uri, additional_data): + def _accept_uri(urlparts: ParseResult, + uri: str, + additional_data: AdditionalDataDict) -> bool: try: oob_url = additional_data['gajim']['oob_url'] except (KeyError, AttributeError): @@ -190,7 +217,9 @@ def _accept_uri(urlparts, uri, additional_data): log.info('Unsupported URI scheme: %s', uri) return False - def get_previewable(self, text, additional_data): + def get_previewable(self, + text: str, + additional_data: AdditionalDataDict) -> bool: if len(text.split(' ')) > 1: # urlparse doesn't recognise spaces as URL delimiter log.debug('Text is not an uri: %s...', text[:15]) @@ -211,7 +240,7 @@ def get_previewable(self, text, additional_data): return True - def create_preview(self, uri, widget, context): + def create_preview(self, uri: str, widget: Any, context: str) -> None: if uri.startswith('geo:'): preview = Preview(uri, None, None, None, 96, widget) preview.update_widget() @@ -238,7 +267,7 @@ def create_preview(self, uri, widget, context): self._on_thumb_load_finished, preview) - def _process_web_uri(self, uri, widget): + def _process_web_uri(self, uri: str, widget: Any) -> Preview: urlparts = urlparse(uri) size = app.settings.get('preview_size') orig_path, thumb_path = get_image_paths(uri, @@ -253,7 +282,13 @@ def _process_web_uri(self, uri, widget): size, widget) - def _on_orig_load_finished(self, data, error, preview): + def _on_orig_load_finished(self, + data: Optional[bytes], + error: Gio.AsyncResult, + preview: Preview) -> None: + if preview.thumb_path is None or preview.orig_path is None: + return + if data is None: log.error('%s: %s', preview.orig_path.name, error) return @@ -270,7 +305,12 @@ def _on_orig_load_finished(self, data, error, preview): preview.update_widget() @staticmethod - def _on_thumb_load_finished(data, error, preview): + def _on_thumb_load_finished(data: Optional[bytes], + error: Gio.AsyncResult, + preview: Preview) -> None: + if preview.thumb_path is None or preview.orig_path is None: + return + if data is None: log.error('%s: %s', preview.thumb_path.name, error) return @@ -288,7 +328,9 @@ def _on_thumb_load_finished(data, error, preview): return preview.update_widget(data=pixbuf) - def download_content(self, preview, force=False): + def download_content(self, + preview: Preview, + force: bool = False) -> None: if preview.account is None: # History Window can be opened without account context # This means we can not apply proxy settings @@ -302,7 +344,9 @@ def download_content(self, preview, force=False): session = self._get_session(preview.account) session.queue_message(message, self._on_finished, preview) - def _check_certificate(self, message, preview): + def _check_certificate(self, + message: Soup.Message, + preview: Preview) -> None: _https_used, _tls_certificate, tls_errors = message.get_https_status() if not app.settings.get('preview_verify_https'): @@ -315,7 +359,12 @@ def _check_certificate(self, message, preview): session.cancel_message(message, Soup.Status.CANCELLED) return - def _on_content_sniffed(self, message, type_, _params, preview, force): + def _on_content_sniffed(self, + message: Soup.Message, + type_: str, + _params: GLib.HashTable, + preview: Preview, + force: bool) -> None: file_size = message.props.response_headers.get_content_length() uri = message.props.uri.to_string(False) session = self._get_session(preview.account) @@ -337,7 +386,10 @@ def _on_content_sniffed(self, message, type_, _params, preview, force): preview.update_widget() - def _on_finished(self, _session, message, preview): + def _on_finished(self, + _session: Soup.Session, + message: Soup.Message, + preview: Preview) -> None: if message.status_code != Soup.Status.OK: log.warning('Download failed: %s', preview.request_uri) log.warning(Soup.Status.get_phrase(message.status_code)) @@ -349,10 +401,12 @@ def _on_finished(self, _session, message, preview): return if preview.is_aes_encrypted: - data = aes_decrypt(preview, data) + if preview.key is not None and preview.iv is not None: + data = aes_decrypt(preview.key, preview.iv, data) if preview.mime_type == 'application/octet-stream': - preview.mime_type = guess_mime_type(preview.orig_path, data) + if preview.orig_path is not None: + preview.mime_type = guess_mime_type(preview.orig_path, data) write_file_async(preview.orig_path, data, @@ -367,7 +421,12 @@ def _on_finished(self, _session, message, preview): preview) @staticmethod - def _on_orig_write_finished(_result, error, preview): + def _on_orig_write_finished(_result: bool, + error: GLib.Error, + preview: Preview) -> None: + if preview.orig_path is None: + return + if error is not None: log.error('%s: %s', preview.orig_path.name, error) return @@ -380,13 +439,21 @@ def _on_orig_write_finished(_result, error, preview): preview.update_widget() @staticmethod - def _on_thumb_write_finished(_result, error, preview): + def _on_thumb_write_finished(_result: bool, + error: GLib.Error, + preview: Preview) -> None: + if preview.thumb_path is None: + return + if error is not None: log.error('%s: %s', preview.thumb_path.name, error) return log.info('Thumbnail stored: %s ', preview.thumb_path.name) + if preview.thumbnail is None: + return + try: pixbuf = pixbuf_from_data(preview.thumbnail) except Exception as err: ===================================== gajim/common/preview_helpers.py ===================================== @@ -12,9 +12,13 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see <
http://www.gnu.org/licenses/
>. +from typing import Any +from typing import Dict from typing import List from typing import Tuple from typing import Optional +from typing import Union + import math import logging import binascii @@ -25,6 +29,7 @@ from pathlib import Path from urllib.parse import urlparse from urllib.parse import unquote +from urllib.parse import ParseResult from gi.repository import GdkPixbuf from gi.repository import GLib @@ -45,7 +50,8 @@ Coords = namedtuple('Coords', 'location lat lon') -def resize_gif(image, output_file, resize_to): +def resize_gif(image: Image.Image, output_file: BytesIO, + resize_to: Tuple[int, int]) -> None: frames, result = extract_and_resize_frames(image, resize_to) frames[0].save(output_file, @@ -57,7 +63,7 @@ def resize_gif(image, output_file, resize_to): loop=1000) -def analyse_image(image): +def analyse_image(image: Image.Image) -> Tuple[Image.Image, Dict[str, Any]]: ''' Pre-process pass over the image to determine the mode (full or additive). Necessary as assessing single frames isn't reliable. Need to know the mode @@ -85,7 +91,8 @@ def analyse_image(image): return image, result -def extract_and_resize_frames(image, resize_to): +def extract_and_resize_frames(image: Image.Image, resize_to: Tuple[int, int] + ) -> Tuple[List[Image.Image], Dict[str, Any]]: image, result = analyse_image(image) i = 0 @@ -242,7 +249,7 @@ def pixbuf_from_data(data: bytes) -> GdkPixbuf.Pixbuf: return loader.get_pixbuf().apply_embedded_orientation() -def parse_fragment(fragment): +def parse_fragment(fragment: str) -> Tuple[bytes, bytes]: if not fragment: raise ValueError('Invalid fragment') @@ -263,7 +270,8 @@ def parse_fragment(fragment): return key, iv -def get_image_paths(uri, urlparts, size, orig_dir, thumb_dir): +def get_image_paths(uri: str, urlparts: ParseResult, size: int, orig_dir: Path, + thumb_dir: Path) -> Tuple[Path, Path]: path = Path(unquote(urlparts.path)) web_stem = path.stem extension = path.suffix @@ -327,18 +335,17 @@ def filename_from_uri(uri: str) -> str: return path.name -def aes_decrypt(preview, payload): +def aes_decrypt(key: bytes, iv: bytes, payload: bytes) -> bytes: # Use AES128 GCM with the given key and iv to decrypt the payload data = payload[:-16] tag = payload[-16:] - decryptor = Cipher( - algorithms.AES(preview.key), - GCM(preview.iv, tag=tag), - backend=default_backend()).decryptor() + decryptor = Cipher(algorithms.AES(key), + GCM(iv, tag=tag), + backend=default_backend()).decryptor() return decryptor.update(data) + decryptor.finalize() -def contains_audio_streams(file_path): +def contains_audio_streams(file_path: Path) -> bool: # Check if it is really an audio file has_audio = False discoverer = GstPbutils.Discoverer() @@ -368,14 +375,15 @@ def get_previewable_mime_types(): )) -def guess_mime_type(file_path: str, +def guess_mime_type(file_path: Union[Path, str], data: Optional[bytes] = None ) -> str: - mime_type, _ = mimetypes.MimeTypes().guess_type(str(file_path)) + file_path = str(file_path) + mime_type, _ = mimetypes.MimeTypes().guess_type(file_path) if mime_type is None: # Try to guess MIME type by file name - mime_type, _ = Gio.content_type_guess(str(file_path), data) - log.debug('Guessed MIME type: %s', str(mime_type)) + mime_type, _ = Gio.content_type_guess(file_path, data) + log.debug('Guessed MIME type: %s', mime_type) return mime_type View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/compare/f4fde9cefb69a2f5033276449216533…
-- View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/compare/f4fde9cefb69a2f5033276449216533…
You're receiving this email because of your account on
dev.gajim.org
.
1
0
0
0
[Git][gajim/python-nbxmpp][master] CI: Use current pylint version
by Philipp Hörist (@lovetox)
06 Nov '21
06 Nov '21
Philipp Hörist pushed to branch master at gajim / python-nbxmpp Commits: 15e39258 by lovetox at 2021-11-07T00:22:24+01:00 CI: Use current pylint version - - - - - 1 changed file: - .gitlab-ci.yml Changes: ===================================== .gitlab-ci.yml ===================================== @@ -11,7 +11,7 @@ run-test: - rm -rf civenv-nbxmpp - virtualenv -p python3 --system-site-packages civenv-nbxmpp - . ./civenv-nbxmpp/bin/activate - - pip3 install -I pylint==2.4.4 + - pip3 install -I pylint - pip3 install -I coverage - pip3 install -I idna - pip3 install -I precis-i18n View it on GitLab:
https://dev.gajim.org/gajim/python-nbxmpp/-/commit/15e39258e584fcc701b751af…
-- View it on GitLab:
https://dev.gajim.org/gajim/python-nbxmpp/-/commit/15e39258e584fcc701b751af…
You're receiving this email because of your account on
dev.gajim.org
.
1
0
0
0
[Git][gajim/gajim][mainwindow] 4 commits: URI: Use NamedTuple
by Daniel Brötzmann (@wurstsalat)
06 Nov '21
06 Nov '21
Daniel Brötzmann pushed to branch mainwindow at gajim / gajim Commits: a01f9cc5 by wurstsalat at 2021-11-06T21:24:54+01:00 URI: Use NamedTuple - - - - - 4843bf14 by wurstsalat at 2021-11-06T21:25:04+01:00 Helpers: Typing - - - - - 45d775ff by wurstsalat at 2021-11-06T21:54:57+01:00 Util: Typing - - - - - f4fde9ce by wurstsalat at 2021-11-06T22:19:30+01:00 Menu Builder: Typing - - - - - 4 changed files: - gajim/common/helpers.py - gajim/common/structs.py - gajim/gtk/util.py - gajim/gui_menu_builder.py Changes: ===================================== gajim/common/helpers.py ===================================== @@ -27,6 +27,10 @@ from typing import Any # pylint: disable=unused-import from typing import Dict # pylint: disable=unused-import +from typing import Optional +from typing import List +from typing import Tuple +from typing import Union import sys import re @@ -62,6 +66,8 @@ from nbxmpp.const import Role from nbxmpp.const import ConnectionProtocol from nbxmpp.const import ConnectionType +from nbxmpp.const import Affiliation +from nbxmpp.const import Role from nbxmpp.structs import ProxyData from nbxmpp.protocol import JID from nbxmpp.protocol import InvalidJid @@ -163,12 +169,13 @@ def parse_resource(resource): except UnicodeError: raise InvalidFormat('Invalid character in resource.') -def windowsify(word): +def windowsify(word: str) -> str: if os.name == 'nt': return word.capitalize() return word -def get_uf_show(show, use_mnemonic=False): +def get_uf_show(show: Union[ShowConstant, str], + use_mnemonic: bool = False) -> str: """ Return a userfriendly string for dnd/xa/chat and make all strings translatable @@ -219,7 +226,7 @@ def get_uf_show(show, use_mnemonic=False): uf_show = Q_('?contact has status:Has errors') return uf_show -def get_uf_sub(sub): +def get_uf_sub(sub: str) -> str: if sub == 'none': uf_sub = Q_('?Subscription we already have:None') elif sub == 'to': @@ -233,7 +240,7 @@ def get_uf_sub(sub): return uf_sub -def get_uf_ask(ask): +def get_uf_ask(ask: Union[str, None]) -> str: if ask is None: uf_ask = Q_('?Ask (for Subscription):None') elif ask == 'subscribe': @@ -243,7 +250,7 @@ def get_uf_ask(ask): return uf_ask -def get_uf_role(role, plural=False): +def get_uf_role(role: Union[Role, str], plural: bool = False) -> str: ''' plural determines if you get Moderators or Moderator''' if not isinstance(role, str): role = role.value @@ -267,7 +274,8 @@ def get_uf_role(role, plural=False): role_name = _('Visitor') return role_name -def get_uf_affiliation(affiliation, plural=False): +def get_uf_affiliation(affiliation: Union[Affiliation, str], + plural: bool = False) -> str: '''Get a nice and translated affilition for muc''' if not isinstance(affiliation, str): affiliation = affiliation.value @@ -319,7 +327,7 @@ def get_sorted_keys(adict): keys = sorted(adict.keys()) return keys -def to_one_line(msg): +def to_one_line(msg: str) -> str: msg = msg.replace('\\', '\\\\') msg = msg.replace('\n', '\\n') # s1 = 'test\ntest\\ntest' @@ -329,7 +337,7 @@ def to_one_line(msg): # 'test\\ntest\\\\ntest' return msg -def from_one_line(msg): +def from_one_line(msg: str) -> str: # (?<!\\) is a lookbehind assertion which asks anything but '\' # to match the regexp that follows it @@ -344,7 +352,7 @@ def from_one_line(msg): # 'test\ntest\\ntest' return msg -def get_uf_chatstate(chatstate): +def get_uf_chatstate(chatstate: str) -> str: """ Remove chatstate jargon and returns user friendly messages """ @@ -393,7 +401,7 @@ def get_file_path_from_dnd_dropped_uri(uri): path = path[5:] # 5 is len('file:') return path -def sanitize_filename(filename): +def sanitize_filename(filename: str) -> str: """ Make sure the filename we will write does contain only acceptable and latin characters, and is not too long (in that case hash it) @@ -413,7 +421,8 @@ def sanitize_filename(filename): return filename -def reduce_chars_newlines(text, max_chars=0, max_lines=0): +def reduce_chars_newlines(text: str, max_chars: int = 0, + max_lines: int = 0) -> str: """ Cut the chars after 'max_chars' on each line and show only the first 'max_lines' @@ -454,7 +463,8 @@ def get_contact_dict_for_account(account): return contacts_dict -def play_sound(sound_event, account, force=False): +def play_sound(sound_event: str, account: str, + force: bool = False) -> None: if sound_event is None: return if force or allow_sound_notification(account, sound_event): @@ -547,7 +557,7 @@ def play_sound_file(path_to_soundfile): except GLib.Error as error: log.error('Could not play sound: %s', error.message) -def get_connection_status(account): +def get_connection_status(account: str) -> str: con = app.connections[account] if con.state.is_reconnect_scheduled: return 'error' @@ -559,7 +569,7 @@ def get_connection_status(account): return 'offline' return con.status -def get_global_show(): +def get_global_show() -> str: maxi = 0 for account in app.connections: if not app.settings.get_account_setting(account, @@ -571,7 +581,7 @@ def get_global_show(): maxi = index return SHOW_LIST[maxi] -def get_global_status_message(): +def get_global_status_message() -> str: maxi = 0 for account in app.connections: if not app.settings.get_account_setting(account, @@ -584,7 +594,7 @@ def get_global_status_message(): status_message = app.connections[account].status_message return status_message -def statuses_unified(): +def statuses_unified() -> bool: """ Test if all statuses are the same """ @@ -628,7 +638,7 @@ def remove_invalid_xml_chars(string_): string_ = re.sub(INVALID_XML_CHARS_REGEX, '', string_) return string_ -def get_random_string(count=16): +def get_random_string(count: int = 16) -> str: """ Create random string of count length @@ -638,7 +648,7 @@ def get_random_string(count=16): return ''.join(random.choice(allowed) for char in range(count)) @functools.lru_cache(maxsize=1) -def get_os_info(): +def get_os_info() -> str: info = 'N/A' if sys.platform in ('win32', 'darwin'): info = f'{platform.system()} {platform.release()}' @@ -680,7 +690,7 @@ def message_needs_highlight(text, nickname, own_jid): return False -def allow_showing_notification(account): +def allow_showing_notification(account: str) -> bool: if not app.settings.get('show_notifications'): return False if app.settings.get('show_notifications_away'): @@ -690,7 +700,7 @@ def allow_showing_notification(account): return False -def allow_sound_notification(account, sound_event): +def allow_sound_notification(account: str, sound_event: str) -> bool: if not app.settings.get('sounds_on'): return False client = app.get_client(account) @@ -730,23 +740,25 @@ def jid_is_blocked(account, jid): con = app.connections[account] return jid in con.get_module('Blocking').blocked -def get_subscription_request_msg(account=None): - s = app.settings.get_account_setting(account, 'subscription_request_msg') - if s: - return s - s = _('I would like to add you to my contact list.') - if account: - s = _('Hello, I am $name.') + ' ' + s - s = Template(s).safe_substitute({'name': app.nicks[account]}) - return s - -def get_user_proxy(account): +def get_subscription_request_msg(account: Optional[str] = None) -> str: + message = _('I would like to add you to my contact list.') + if account is None: + return message + + message = app.settings.get_account_setting(account, 'subscription_request_msg') + if message: + return message + + message = _('Hello, I am $name. %s') % message + return Template(message).safe_substitute({'name': app.nicks[account]}) + +def get_user_proxy(account: str) -> Optional[ProxyData]: proxy_name = app.settings.get_account_setting(account, 'proxy') if not proxy_name: return None return get_proxy(proxy_name) -def get_proxy(proxy_name): +def get_proxy(proxy_name: str) -> Optional[ProxyData]: try: settings = app.settings.get_proxy_settings(proxy_name) except ValueError: @@ -926,7 +938,7 @@ def func_wrapper(self, *args, **kwargs): return func_wrapper -def parse_uri_actions(uri): +def parse_uri_actions(uri: str) -> Tuple[str, Dict[str, str]]: uri = uri[5:] if '?' not in uri: return 'message', {'jid': uri} @@ -948,7 +960,7 @@ def parse_uri_actions(uri): return action, data -def parse_uri(uri): +def parse_uri(uri: str) -> URI: if uri.startswith('xmpp:'): action, data = parse_uri_actions(uri) try: @@ -990,7 +1002,7 @@ def parse_uri(uri): @catch_exceptions -def open_uri(uri, account=None): +def open_uri(uri: Union[URI, str], account: Optional[str] = None) -> None: if not isinstance(uri, URI): uri = parse_uri(uri) @@ -1023,13 +1035,17 @@ def open_uri(uri, account=None): log.warning('Account must be specified to open XMPP uri') return + if isinstance(uri.data, dict): + jid = uri.data['jid'] + message = uri.data.get('body') + else: + log.warning('Cant open URI: %s', uri) if uri.action == URIAction.JOIN: app.app.activate_action( 'groupchat-join', - GLib.Variant('as', [account, uri.data['jid']])) + GLib.Variant('as', [account, jid])) elif uri.action == URIAction.MESSAGE: - app.interface.start_chat_from_jid(account, uri.data['jid'], - message=uri.data.get('body')) + app.interface.start_chat_from_jid(account, jid, message=message) else: log.warning('Cant open URI: %s', uri) @@ -1080,9 +1096,8 @@ def file_is_locked(path_to_file): return False -def geo_provider_from_location(lat, lon): - return ('
https://www.openstreetmap.org/?
' - 'mlat=%s&mlon=%s&zoom=16') % (lat, lon) +def geo_provider_from_location(lat: str, lon: str) -> str: + return f'
https://www.openstreetmap.org/?mlat={lat}&mlon={lon}&zoom=16
' def get_resource(account): @@ -1097,7 +1112,7 @@ def get_resource(account): return resource -def get_default_muc_config(): +def get_default_muc_config() -> Dict[str, Union[bool, str]]: return { # XEP-0045 options 'muc#roomconfig_allowinvites': True, @@ -1182,10 +1197,10 @@ def is_role_change_allowed(self_contact, contact): return self_contact.affiliation >= contact.affiliation -def get_tls_error_phrase(tls_error): +def get_tls_error_phrase(tls_error: Gio.TlsCertificateFlags) -> str: phrase = GIO_TLS_ERRORS.get(tls_error) if phrase is None: - return GIO_TLS_ERRORS.get(Gio.TlsCertificateFlags.GENERIC_ERROR) + return GIO_TLS_ERRORS[Gio.TlsCertificateFlags.GENERIC_ERROR] return phrase @@ -1296,7 +1311,8 @@ def convert_gio_to_openssl_cert(cert): return cert -def get_custom_host(account): +def get_custom_host(account: str + ) -> Optional[Tuple[str, ConnectionProtocol, str]]: if not app.settings.get_account_setting(account, 'use_custom_host'): return None host = app.settings.get_account_setting(account, 'custom_host') @@ -1312,7 +1328,9 @@ def get_custom_host(account): return (host, protocol, ConnectionType(type_)) -def warn_about_plain_connection(account, connection_types): +def warn_about_plain_connection(account: str, + connection_types: List[ConnectionType] + ) -> bool: warn = app.settings.get_account_setting( account, 'confirm_unencrypted_connection') for type_ in connection_types: @@ -1321,7 +1339,7 @@ def warn_about_plain_connection(account, connection_types): return False -def get_idle_status_message(state, status_message): +def get_idle_status_message(state: str, status_message: str) -> str: message = app.settings.get(f'auto{state}_message') if not message: message = status_message @@ -1349,7 +1367,7 @@ def should_log(account, jid): return (account not in no_log_for) and (jid not in no_log_for) -def ask_for_status_message(status, signin=False): +def ask_for_status_message(status: str, signin: bool = False) -> bool: if status is None: # We try to change the message return True ===================================== gajim/common/structs.py ===================================== @@ -12,8 +12,14 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see <
http://www.gnu.org/licenses/
>. +from typing import Dict +from typing import NamedTuple +from typing import Optional +from typing import Tuple +from typing import Union +from typing import Any + import time -from collections import namedtuple from dataclasses import dataclass from nbxmpp.protocol import JID @@ -23,9 +29,14 @@ from gajim.common.const import MUCJoinedState from gajim.common.const import KindConstant from gajim.common.const import PresenceShowExt +from gajim.common.const import URIType +from gajim.common.const import URIAction + -URI = namedtuple('URI', 'type action data') -URI.__new__.__defaults__ = (None, None) # type: ignore +class URI(NamedTuple): + type: URIType + action: Optional[URIAction] = None + data: Optional[Union[Dict[str, str], str]] = None class MUCData: ===================================== gajim/gtk/util.py ===================================== @@ -44,6 +44,7 @@ from gi.repository import Pango from gi.repository import GdkPixbuf import nbxmpp +from nbxmpp.structs import LocationData import cairo from gajim.common import app @@ -263,7 +264,7 @@ def get_icon_name(name: str, return '%s-%s' % (iconset, name) -def load_user_iconsets(): +def load_user_iconsets() -> None: iconsets_path = configpaths.get('MY_ICONSETS') if not iconsets_path.exists(): return @@ -275,7 +276,7 @@ def load_user_iconsets(): _icon_theme.append_search_path(str(path)) -def get_available_iconsets(): +def get_available_iconsets() -> List[str]: iconsets = [] for iconset in GajimIconSet: iconsets.append(iconset.value) @@ -421,7 +422,7 @@ def at_the_end(widget: Gtk.ScrolledWindow) -> bool: def get_image_button(icon_name: str, tooltip: str, - toggle: Optional[bool] = False) -> Gtk.Button: + toggle: bool = False) -> Gtk.Button: if toggle: button = Gtk.ToggleButton() image = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.MENU) @@ -534,7 +535,7 @@ def func_wrapper(self, *args, **kwargs): return func_wrapper -def format_mood(mood, text): +def format_mood(mood: str, text: str) -> str: if mood is None: return '' mood = MOODS[mood] @@ -544,13 +545,13 @@ def format_mood(mood, text): return markuptext -def get_account_mood_icon_name(account): +def get_account_mood_icon_name(account: str) -> Optional[str]: client = app.get_client(account) mood = client.get_module('UserMood').get_current_mood() return f'mood-{mood.mood}' if mood is not None else mood -def format_activity(activity, subactivity, text): +def format_activity(activity: str, subactivity: str, text: str) -> str: if subactivity in ACTIVITIES[activity]: subactivity = ACTIVITIES[activity][subactivity] activity = ACTIVITIES[activity]['category'] @@ -564,14 +565,15 @@ def format_activity(activity, subactivity, text): return markuptext -def get_activity_icon_name(activity, subactivity=None): +def get_activity_icon_name(activity: str, + subactivity: Optional[str] = None) -> str: icon_name = 'activity-%s' % activity.replace('_', '-') if subactivity is not None: icon_name += '-%s' % subactivity.replace('_', '-') return icon_name -def get_account_activity_icon_name(account): +def get_account_activity_icon_name(account: str) -> Optional[str]: client = app.get_client(account) activity = client.get_module('UserActivity').get_current_activity() if activity is None: @@ -579,7 +581,8 @@ def get_account_activity_icon_name(account): return get_activity_icon_name(activity.activity, activity.subactivity) -def format_tune(artist, _length, _rating, source, title, _track, _uri): +def format_tune(artist: str, _length: str, _rating: str, source: str, + title: str, _track: str, _uri: str) -> str: artist = GLib.markup_escape_text(artist or _('Unknown Artist')) title = GLib.markup_escape_text(title or _('Unknown Title')) source = GLib.markup_escape_text(source or _('Unknown Source')) @@ -591,13 +594,13 @@ def format_tune(artist, _length, _rating, source, title, _track, _uri): return tune_string -def get_account_tune_icon_name(account): +def get_account_tune_icon_name(account: str) -> Optional[str]: client = app.get_client(account) tune = client.get_module('UserTune').get_current_tune() return None if tune is None else 'audio-x-generic' -def format_location(location): +def format_location(location: LocationData) -> str: location = location._asdict() location_string = '' for attr, value in location.items(): @@ -614,7 +617,7 @@ def format_location(location): return location_string.strip() -def get_account_location_icon_name(account): +def get_account_location_icon_name(account: str) -> Optional[str]: client = app.get_client(account) location = client.get_module('UserLocation').get_current_location() return None if location is None else 'applications-internet' @@ -625,7 +628,7 @@ def format_eta(time_: Union[int, float]) -> str: time_ = int(time_) times['seconds'] = time_ % 60 if time_ >= 60: - time_ /= 60 + time_ = int(time_ / 60) times['minutes'] = round(time_ % 60) return _('%(minutes)s min %(seconds)s s') % times return _('%s s') % times['seconds'] @@ -705,7 +708,7 @@ def get_css_show_class(show: str) -> str: return '.gajim-status-offline' -def add_css_to_widget(widget, css): +def add_css_to_widget(widget: Any, css: str) -> None: provider = Gtk.CssProvider() provider.load_from_data(bytes(css.encode())) context = widget.get_style_context() @@ -713,7 +716,7 @@ def add_css_to_widget(widget, css): Gtk.STYLE_PROVIDER_PRIORITY_USER) -def get_pixbuf_from_data(file_data): +def get_pixbuf_from_data(file_data: bytes) -> Optional[GdkPixbuf.Pixbuf]: # TODO: This already exists in preview_helpery pixbuf_from_data """ Get image data and returns GdkPixbuf.Pixbuf @@ -738,7 +741,7 @@ def get_pixbuf_from_data(file_data): except Exception: log.warning('Could not use pillow to convert avatar image, ' 'image cannot be displayed', exc_info=True) - return + return None return pixbuf @@ -763,7 +766,7 @@ def scale_pixbuf(pixbuf: GdkPixbuf.Pixbuf, GdkPixbuf.InterpType.BILINEAR) -def scale_pixbuf_from_data(data, size): +def scale_pixbuf_from_data(data: bytes, size: int) -> Optional[GdkPixbuf.Pixbuf]: pixbuf = get_pixbuf_from_data(data) return scale_pixbuf(pixbuf, size) @@ -821,7 +824,7 @@ def get_thumbnail_size(pixbuf: GdkPixbuf.Pixbuf, size: int) -> Tuple[int, int]: return image_width, image_height -def make_href_markup(string): +def make_href_markup(string: str) -> str: url_color = app.css_config.get_value('.gajim-url', StyleAttr.COLOR) color = convert_rgb_to_hex(url_color) ===================================== gajim/gui_menu_builder.py ===================================== @@ -14,13 +14,19 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see <
http://www.gnu.org/licenses/
>. -from typing import Optional +from typing import Tuple +from typing import List +from typing import Union +from typing import Any + from urllib.parse import quote from gi.repository import Gtk from gi.repository import Gio from gi.repository import GLib +from nbxmpp import JID + from gajim.common import app from gajim.common.helpers import is_affiliation_change_allowed from gajim.common.helpers import is_role_change_allowed @@ -35,11 +41,12 @@ from gajim.gui.const import ControlType -def get_singlechat_menu(control_id, account, jid, type_): +def get_singlechat_menu(control_id: str, account: str, jid: JID, + type_: ControlType) -> Gio.Menu: client = app.get_client(account) is_self_contact = jid.bare == client.get_own_jid().bare - singlechat_menu = [ + singlechat_menu: List[Any] = [ (_('Send File'), [ ('win.send-file-httpupload-', _('Upload File…')), ('win.send-file-jingle-', _('Send File Directly…')) @@ -117,7 +124,7 @@ def build_menu(preset): return build_menu(singlechat_menu) -def get_groupchat_menu(control_id, account, jid): +def get_groupchat_menu(control_id: str, account: str, jid: str) -> Gio.Menu: groupchat_menu = [ ('win.information-', _('Information')), ('win.groupchat-settings-', _('Settings…')), @@ -219,7 +226,7 @@ def build_menu(preset): return build_menu(account_menu) -def build_accounts_menu(): +def build_accounts_menu() -> None: menubar = app.app.get_menubar() # Accounts Submenu menu_position = 1 @@ -259,7 +266,7 @@ def build_accounts_menu(): def get_encryption_menu(control_id: str, control_type: ControlType, - zeroconf: Optional[bool] = False) -> Gio.Menu: + zeroconf: bool = False) -> Gio.Menu: menu = Gio.Menu() menu.append( _('Disabled'), f'win.set-encryption-{control_id}::disabled') @@ -405,7 +412,10 @@ def get_conv_uri_context_menu(account: str, uri: URI) -> Gtk.Menu: data = uri.data if uri.type == URIType.XMPP: - data = uri.data['jid'] + if isinstance(uri.data, dict): + data = uri.data['jid'] + else: + data = '' if action in ('app.open-mail', 'app.copy-text'): value = GLib.Variant.new_string(data) @@ -417,7 +427,7 @@ def get_conv_uri_context_menu(account: str, uri: URI) -> Gtk.Menu: return menu -def get_roster_menu(account, jid, gateway=False): +def get_roster_menu(account: str, jid: str, gateway: bool = False) -> Gio.Menu: if jid_is_blocked(account, jid): block_label = _('Unblock') else: @@ -436,14 +446,14 @@ def get_roster_menu(account, jid, gateway=False): action, label = item action = f'win.{action}-{account}' menuitem = Gio.MenuItem.new(label, action) - variant = GLib.Variant('s', str(jid)) + variant = GLib.Variant('s', jid) menuitem.set_action_and_target_value(action, variant) menu.append_item(menuitem) return menu -def get_subscription_menu(account, jid): +def get_subscription_menu(account: str, jid: str) -> Gio.Menu: menu = Gio.Menu() action_name = 'win.add-chat' @@ -475,13 +485,15 @@ def get_subscription_menu(account, jid): return menu -def get_chat_list_row_menu(workspace_id, account, jid, pinned): +def get_chat_list_row_menu(workspace_id: str, account: str, + jid: Union[JID, str], pinned: bool) -> Gio.Menu: + jid = str(jid) client = app.get_client(account) contact = client.get_module('Contacts').get_contact(jid) toggle_label = _('Unpin Chat') if pinned else _('Pin Chat') - menu_items = [ + menu_items: List[Any] = [ ('toggle-chat-pinned', toggle_label), ] @@ -502,20 +514,21 @@ def get_chat_list_row_menu(workspace_id, account, jid, pinned): action = f'win.{action}' menuitem = Gio.MenuItem.new(label, action) variant_list = GLib.Variant( - 'as', [workspace_id, account, str(jid)]) + 'as', [workspace_id, account, jid]) menuitem.set_action_and_target_value(action, variant_list) menu.append_item(menuitem) else: # This is a submenu if len(workspaces) > 1: submenu = build_workspaces_submenu( - workspace_id, account, str(jid)) + workspace_id, account, jid) menu.append_submenu(item[0], submenu) return menu -def build_workspaces_submenu(current_workspace_id, account, jid): +def build_workspaces_submenu(current_workspace_id: str, account: str, + jid: str) -> Gio.Menu: submenu = Gio.Menu() for workspace_id in app.settings.get_workspaces(): if workspace_id == current_workspace_id: View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/compare/e484fc87d0a11ec8bf83b31635b6267…
-- View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/compare/e484fc87d0a11ec8bf83b31635b6267…
You're receiving this email because of your account on
dev.gajim.org
.
1
0
0
0
[Git][gajim/gajim][mainwindow] 3 commits: Refactor 'Actions for' context menu
by Daniel Brötzmann (@wurstsalat)
06 Nov '21
06 Nov '21
Daniel Brötzmann pushed to branch mainwindow at gajim / gajim Commits: 54681a79 by wurstsalat at 2021-11-06T18:14:07+01:00 Refactor 'Actions for' context menu - - - - - 63aa18a9 by wurstsalat at 2021-11-06T18:25:58+01:00 Menu Builder: Use f-strings - - - - - e484fc87 by wurstsalat at 2021-11-06T18:34:26+01:00 Menu Builder: Typing - - - - - 2 changed files: - gajim/gtk/conversation/plain_widget.py - gajim/gui_menu_builder.py Changes: ===================================== gajim/gtk/conversation/plain_widget.py ===================================== @@ -13,7 +13,6 @@ # along with Gajim. If not, see <
http://www.gnu.org/licenses/
>. import logging -from urllib.parse import quote from gi.repository import Gtk from gi.repository import Pango @@ -21,14 +20,13 @@ from gi.repository import GLib from gajim.common import app -from gajim.common import i18n from gajim.common.const import StyleAttr from gajim.common.helpers import open_uri from gajim.common.helpers import reduce_chars_newlines from gajim.common.helpers import parse_uri -from gajim.common.i18n import _ -from gajim.gui_menu_builder import get_conv_context_menu +from gajim.gui_menu_builder import get_conv_action_context_menu +from gajim.gui_menu_builder import get_conv_uri_context_menu from ..util import get_cursor from ..util import make_pango_attribute @@ -72,6 +70,20 @@ def __init__(self, account, selectable): self.get_style_context().add_class('gajim-conversation-text') + self.connect('populate-popup', self._on_populate_popup) + + def _on_populate_popup(self, label, menu): + selected, start, end = label.get_selection_bounds() + if not selected: + menu.show_all() + return + + selected_text = label.get_text()[start:end] + action_menu_item = get_conv_action_context_menu( + self._account, selected_text) + menu.prepend(action_menu_item) + menu.show_all() + def print_text_with_styling(self, block): text = GLib.markup_escape_text(block.text.strip()) for uri in block.uris: @@ -261,69 +273,9 @@ def _on_populate_popup(self, _textview, menu): menu.show_all() return - selected_text_short = reduce_chars_newlines( - self._selected_text, 25, 2) - item = Gtk.MenuItem.new_with_mnemonic( - _('_Actions for "%s"') % selected_text_short) - menu.prepend(item) - submenu = Gtk.Menu() - item.set_submenu(submenu) - uri_text = quote(self._selected_text.encode('utf-8')) - - if app.settings.get('always_english_wikipedia'): - uri = (f'
https://en.wikipedia.org/wiki/
' - f'Special:Search?search={uri_text}') - else: - uri = (f'https://{i18n.get_short_lang_code()}.wikipedia.org/' - f'wiki/Special:Search?search={uri_text}') - item = Gtk.MenuItem.new_with_mnemonic(_('Read _Wikipedia Article')) - id_ = item.connect('activate', self._visit_uri, uri) - self._handlers[id_] = item - submenu.append(item) - - item = Gtk.MenuItem.new_with_mnemonic( - _('Look it up in _Dictionary')) - dict_link = app.settings.get('dictionary_url') - if dict_link == 'WIKTIONARY': - # Default is
wikitionary.org
- if app.settings.get('always_english_wiktionary'): - uri = (f'
https://en.wiktionary.org/wiki/
' - f'Special:Search?search={uri_text}') - else: - uri = (f'https://{i18n.get_short_lang_code()}.wiktionary.org/' - f'wiki/Special:Search?search={uri_text}') - id_ = item.connect('activate', self._visit_uri, uri) - self._handlers[id_] = item - else: - if dict_link.find('%s') == -1: - # There has to be a '%s' in the url if it’s not WIKTIONARY - item = Gtk.MenuItem.new_with_label( - _('Dictionary URL is missing a "%s"')) - item.set_sensitive(False) - else: - uri = dict_link % uri_text - id_ = item.connect('activate', self._visit_uri, uri) - self._handlers[id_] = item - submenu.append(item) - - search_link = app.settings.get('search_engine') - if search_link.find('%s') == -1: - # There has to be a '%s' in the url - item = Gtk.MenuItem.new_with_label( - _('Web Search URL is missing a "%s"')) - item.set_sensitive(False) - else: - item = Gtk.MenuItem.new_with_mnemonic(_('Web _Search for it')) - uri = search_link % uri_text - id_ = item.connect('activate', self._visit_uri, uri) - self._handlers[id_] = item - submenu.append(item) - - item = Gtk.MenuItem.new_with_mnemonic(_('Open as _Link')) - id_ = item.connect('activate', self._visit_uri, uri) - self._handlers[id_] = item - submenu.append(item) - + 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, _widget, event, iter_, _kind): @@ -346,7 +298,7 @@ def _on_uri_clicked(self, texttag, _widget, event, iter_, _kind): uri = parse_uri(word) if event.button.button == 3: # right click - self._show_context_menu(uri) + self._show_uri_context_menu(uri) return Gdk.EVENT_STOP # TODO: @@ -359,8 +311,8 @@ def _on_uri_clicked(self, texttag, _widget, event, iter_, _kind): open_uri(uri, account=self._account) return Gdk.EVENT_STOP - def _show_context_menu(self, uri): - menu = get_conv_context_menu(self._account, uri) + def _show_uri_context_menu(self, uri): + menu = get_conv_uri_context_menu(self._account, uri) if menu is None: log.warning('No handler for URI type: %s', uri) return @@ -373,7 +325,3 @@ def destroy(menu, _pspec): menu.attach_to_widget(self, None) menu.connect('notify::visible', destroy) menu.popup_at_pointer() - - @staticmethod - def _visit_uri(_widget, uri): - open_uri(uri) ===================================== gajim/gui_menu_builder.py ===================================== @@ -14,6 +14,9 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see <
http://www.gnu.org/licenses/
>. +from typing import Optional +from urllib.parse import quote + from gi.repository import Gtk from gi.repository import Gio from gi.repository import GLib @@ -22,9 +25,14 @@ from gajim.common.helpers import is_affiliation_change_allowed from gajim.common.helpers import is_role_change_allowed from gajim.common.helpers import jid_is_blocked +from gajim.common.helpers import reduce_chars_newlines from gajim.common.i18n import _ +from gajim.common.i18n import get_short_lang_code from gajim.common.const import URIType from gajim.common.const import URIAction +from gajim.common.structs import URI + +from gajim.gui.const import ControlType def get_singlechat_menu(control_id, account, jid, type_): @@ -65,7 +73,7 @@ def build_chatstate_menu(): for entry in entries: label, setting = entry - action = 'win.send-chatstate-%s::%s' % (control_id, setting) + action = f'win.send-chatstate-{control_id}::{setting}' menu.append(label, action) return menu @@ -162,7 +170,7 @@ def build_menu(preset): return build_menu(groupchat_menu) -def get_account_menu(account): +def get_account_menu(account: str) -> Gio.Menu: ''' [(action, label/sub_menu)] action: string @@ -196,7 +204,7 @@ def build_menu(preset): for item in preset: if isinstance(item[1], str): action, label = item - action = 'app.{}{}'.format(account, action) + action = f'app.{account}{action}' menuitem = Gio.MenuItem.new(label, action) variant = GLib.Variant('s', account) menuitem.set_action_and_target_value(action, variant) @@ -250,11 +258,11 @@ def build_accounts_menu(): menubar.insert_submenu(menu_position, _('Accounts'), acc_menu) -def get_encryption_menu(control_id, control_type, zeroconf=False): +def get_encryption_menu(control_id: str, control_type: ControlType, + zeroconf: Optional[bool] = False) -> Gio.Menu: menu = Gio.Menu() menu.append( - _('Disabled'), - 'win.set-encryption-{}::{}'.format(control_id, 'disabled')) + _('Disabled'), f'win.set-encryption-{control_id}::disabled') for name, plugin in app.plugin_manager.encryption_plugins.items(): if control_type.is_groupchat: if not hasattr(plugin, 'allow_groupchat'): @@ -265,15 +273,82 @@ def get_encryption_menu(control_id, control_type, zeroconf=False): if zeroconf: if not hasattr(plugin, 'allow_zeroconf'): continue - menu_action = 'win.set-encryption-{}::{}'.format( - control_id, name) + menu_action = f'win.set-encryption-{control_id}::{name}' menu.append(name, menu_action) if menu.get_n_items() == 1: return None return menu -def get_conv_context_menu(account, uri): +def get_conv_action_context_menu(account: str, selected_text: str) -> Gtk.Menu: + selected_text_short = reduce_chars_newlines(selected_text, 10, 1) + + action_menu_item = Gtk.MenuItem.new_with_mnemonic( + _('_Actions for "%s"') % selected_text_short) + submenu = Gtk.Menu() + action_menu_item.set_submenu(submenu) + + uri_text = quote(selected_text.encode('utf-8')) + + if app.settings.get('always_english_wikipedia'): + uri = (f'
https://en.wikipedia.org/wiki/
' + f'Special:Search?search={uri_text}') + else: + uri = (f'https://{get_short_lang_code()}.wikipedia.org/' + f'wiki/Special:Search?search={uri_text}') + item = Gtk.MenuItem.new_with_mnemonic(_('Read _Wikipedia Article')) + value = GLib.Variant.new_strv([account, uri]) + item.set_action_target_value(value) + submenu.append(item) + + item = Gtk.MenuItem.new_with_mnemonic( + _('Look it up in _Dictionary')) + dict_link = app.settings.get('dictionary_url') + if dict_link == 'WIKTIONARY': + # Default is
wikitionary.org
+ if app.settings.get('always_english_wiktionary'): + uri = (f'
https://en.wiktionary.org/wiki/
' + f'Special:Search?search={uri_text}') + else: + uri = (f'https://{get_short_lang_code()}.wiktionary.org/' + f'wiki/Special:Search?search={uri_text}') + else: + if dict_link.find('%s') == -1: + # There has to be a '%s' in the url if it’s not WIKTIONARY + item = Gtk.MenuItem.new_with_label( + _('Dictionary URL is missing a "%s"')) + item.set_sensitive(False) + else: + uri = dict_link % uri_text + value = GLib.Variant.new_strv([account, uri]) + item.set_action_target_value(value) + submenu.append(item) + + search_link = app.settings.get('search_engine') + if search_link.find('%s') == -1: + # There has to be a '%s' in the url + item = Gtk.MenuItem.new_with_label( + _('Web Search URL is missing a "%s"')) + item.set_sensitive(False) + else: + item = Gtk.MenuItem.new_with_mnemonic(_('Web _Search for it')) + uri = search_link % uri_text + value = GLib.Variant.new_strv([account, uri]) + item.set_action_target_value(value) + submenu.append(item) + + item = Gtk.MenuItem.new_with_mnemonic(_('Open as _Link')) + value = GLib.Variant.new_strv([account, uri_text]) + item.set_action_target_value(value) + submenu.append(item) + + for item in submenu: + item.set_action_name('app.open-link') + + return action_menu_item + + +def get_conv_uri_context_menu(account: str, uri: URI) -> Gtk.Menu: if uri.type == URIType.XMPP: if uri.action == URIAction.JOIN: context_menu = [ @@ -323,9 +398,9 @@ def get_conv_context_menu(account, uri): menuitem.set_label(label) if action.startswith('-'): - action = 'app.%s%s' % (account, action) + action = f'app.{account}{action}' else: - action = 'app.%s' % action + action = f'app.{action}' menuitem.set_action_name(action) data = uri.data @@ -456,15 +531,15 @@ def build_workspaces_submenu(current_workspace_id, account, jid): def get_groupchat_roster_menu(account, control_id, self_contact, contact): menu = Gtk.Menu() + real_jid = contact.real_jid or '' item = Gtk.MenuItem(label=_('Information')) - action = 'win.contact-information-%s::%s' % (control_id, contact.name) + action = f'win.contact-information-{control_id}::{contact.name}' item.set_detailed_action_name(action) menu.append(item) item = Gtk.MenuItem(label=_('Add to Contact List…')) - action = 'app.{account}-add-contact(["{account}", "{jid}"])'.format( - account=account, jid=contact.real_jid or '') + action = f'app.{account}-add-contact(["{account}", "{real_jid}"])' if contact.real_jid is None: item.set_sensitive(False) else: @@ -472,14 +547,14 @@ def get_groupchat_roster_menu(account, control_id, self_contact, contact): menu.append(item) item = Gtk.MenuItem(label=_('Execute Command…')) - action = 'win.execute-command-%s::%s' % (control_id, contact.name) + action = f'win.execute-command-{control_id}::{contact.name}' item.set_detailed_action_name(action) menu.append(item) menu.append(Gtk.SeparatorMenuItem()) item = Gtk.MenuItem(label=_('Kick')) - action = 'win.kick-%s::%s' % (control_id, contact.name) + action = f'win.kick-{control_id}::{contact.name}' if is_role_change_allowed(self_contact, contact): item.set_detailed_action_name(action) else: @@ -487,7 +562,7 @@ def get_groupchat_roster_menu(account, control_id, self_contact, contact): menu.append(item) item = Gtk.MenuItem(label=_('Ban')) - action = 'win.ban-%s::%s' % (control_id, contact.real_jid or '') + action = f'win.ban-{control_id}::{real_jid}' if is_affiliation_change_allowed(self_contact, contact, 'outcast'): item.set_detailed_action_name(action) else: @@ -497,8 +572,8 @@ def get_groupchat_roster_menu(account, control_id, self_contact, contact): menu.append(Gtk.SeparatorMenuItem()) item = Gtk.MenuItem(label=_('Make Owner')) - action = 'win.change-affiliation-%s(["%s", "owner"])' % (control_id, - contact.real_jid) + action = (f'win.change-affiliation-{control_id}' + f'(["{contact.real_jid}", "owner"])') if is_affiliation_change_allowed(self_contact, contact, 'owner'): item.set_detailed_action_name(action) else: @@ -506,8 +581,8 @@ def get_groupchat_roster_menu(account, control_id, self_contact, contact): menu.append(item) item = Gtk.MenuItem(label=_('Make Admin')) - action = 'win.change-affiliation-%s(["%s", "admin"])' % (control_id, - contact.real_jid) + action = (f'win.change-affiliation-{control_id}' + f'(["{contact.real_jid}", "admin"])') if is_affiliation_change_allowed(self_contact, contact, 'admin'): item.set_detailed_action_name(action) else: @@ -515,8 +590,8 @@ def get_groupchat_roster_menu(account, control_id, self_contact, contact): menu.append(item) item = Gtk.MenuItem(label=_('Make Member')) - action = 'win.change-affiliation-%s(["%s", "member"])' % (control_id, - contact.real_jid) + action = (f'win.change-affiliation-{control_id}' + f'(["{contact.real_jid}", "member"])') if is_affiliation_change_allowed(self_contact, contact, 'member'): item.set_detailed_action_name(action) else: @@ -524,8 +599,8 @@ def get_groupchat_roster_menu(account, control_id, self_contact, contact): menu.append(item) item = Gtk.MenuItem(label=_('Revoke Member')) - action = 'win.change-affiliation-%s(["%s", "none"])' % (control_id, - contact.real_jid) + action = (f'win.change-affiliation-{control_id}' + f'(["{contact.real_jid}", "none"])') if is_affiliation_change_allowed(self_contact, contact, 'none'): item.set_detailed_action_name(action) else: @@ -540,9 +615,8 @@ def get_groupchat_roster_menu(account, control_id, self_contact, contact): role = 'visitor' item = Gtk.MenuItem(label=label) - action = 'win.change-role-%s(["%s", "%s"])' % (control_id, - contact.name, - role) + action = (f'win.change-role-{control_id}' + f'(["{contact.name}", "{role}"])') if is_role_change_allowed(self_contact, contact): item.set_detailed_action_name(action) else: View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/compare/c21880ddfd988751f47bead35e73991…
-- View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/compare/c21880ddfd988751f47bead35e73991…
You're receiving this email because of your account on
dev.gajim.org
.
1
0
0
0
[Git][gajim/gajim][mainwindow] 9 commits: Gajim: Fix typo in _install_signal_handlers
by Daniel Brötzmann (@wurstsalat)
06 Nov '21
06 Nov '21
Daniel Brötzmann pushed to branch mainwindow at gajim / gajim Commits: 2f0432c7 by wurstsalat at 2021-11-06T12:58:12+01:00 Gajim: Fix typo in _install_signal_handlers - - - - - 6ebafbe3 by wurstsalat at 2021-11-06T16:15:33+01:00 Theming: Fix setting Conversation color - - - - - 5b235d5d by wurstsalat at 2021-11-06T16:17:21+01:00 Theming: Remove gajim-{incoming,outgoing}-message-text - - - - - ab2c4fdb by wurstsalat at 2021-11-06T16:42:41+01:00 Themes: Adapt roster styling - - - - - 70d22e52 by wurstsalat at 2021-11-06T17:02:06+01:00 Themes: Rename options - - - - - d4397d84 by wurstsalat at 2021-11-06T17:02:09+01:00 Themes: Remove chatstate colors - - - - - db1de367 by wurstsalat at 2021-11-06T17:02:09+01:00 Themes: Remove contact connected/disconnected color - - - - - 2f38e1d9 by wurstsalat at 2021-11-06T17:04:07+01:00 Themes: Remove tab-*muc-msg color - - - - - c21880dd by wurstsalat at 2021-11-06T17:06:55+01:00 Themes: Reorder style options - - - - - 10 changed files: - gajim/data/style/default-dark.css - gajim/data/style/default.css - gajim/gajim.py - gajim/gtk/controls/base.py - gajim/gtk/conversation/message_widget.py - gajim/gtk/conversation/plain_widget.py - gajim/gtk/conversation/rows/base.py - gajim/gtk/conversation/rows/message.py - gajim/gtk/message_input.py - gajim/gtk/themes.py Changes: ===================================== gajim/data/style/default-dark.css ===================================== @@ -5,8 +5,8 @@ .gajim-incoming-nickname { .gajim-outgoing-nickname { color: rgb(40, 140, 210); } -.gajim-outgoing-message-text { - color: rgb(200, 200, 200); +.gajim-conversation-text text { + color: inherit; } .gajim-status-message { color: rgb(140, 175, 210); @@ -25,36 +25,12 @@ .gajim-mention-highlight { .gajim-url { color: rgb(60, 135, 220); } -.gajim-state-tab-muc-directed-msg { - color: rgb(210, 70, 30); -} -.gajim-state-tab-muc-msg { - color: rgb(80, 95, 230); -} .gajim-user-disconnected { color: rgb(230, 170, 170); } .gajim-user-connected { color: rgb(160, 200, 170); } -.gajim-roster-disconnected { - background: rgb(80, 30, 30); -} -.gajim-roster-connected { - background: rgb(30, 80, 50); -} -.gajim-state-composing { - color: rgb(90, 180, 90); -} -.gajim-state-inactive { - color: rgb(158, 158, 158); -} -.gajim-state-gone { - color: rgb(128, 128, 128); -} -.gajim-state-paused { - color: rgb(20, 210, 160); -} .gajim-msg-correcting text { background: rgb(100, 100, 50); } ===================================== gajim/data/style/default.css ===================================== @@ -5,8 +5,8 @@ .gajim-incoming-nickname { .gajim-outgoing-nickname { color: rgb(50, 90, 140); } -.gajim-outgoing-message-text { - color: rgb(85, 85, 85); +.gajim-conversation-text text { + color: inherit; } .gajim-status-message { color: rgb(100, 130, 160); @@ -33,36 +33,12 @@ .gajim-mention-highlight { .gajim-url { color: rgb(30, 75, 135); } -.gajim-state-tab-muc-directed-msg { - color: rgb(210, 70, 30); -} -.gajim-state-tab-muc-msg { - color: rgb(40, 40, 200); -} .gajim-user-disconnected { color: rgb(230, 170, 170); } .gajim-user-connected { color: rgb(160, 200, 170); } -.gajim-roster-disconnected { - background: rgb(230, 170, 170); -} -.gajim-roster-connected { - background: rgb(160, 200, 170); -} -.gajim-state-composing { - color: rgb(40, 140, 40); -} -.gajim-state-inactive { - color: rgb(158, 158, 158); -} -.gajim-state-gone { - color: rgb(128, 128, 128); -} -.gajim-state-paused { - color: rgb(20, 160, 120); -} .gajim-msg-correcting text { background: rgb(240, 230, 170); } ===================================== gajim/gajim.py ===================================== @@ -115,7 +115,7 @@ def _init_gtk(): def _run_app(): from gajim.application import GajimApplication application = GajimApplication() - _install_sginal_handlers(application) + _install_signal_handlers(application) application.run(sys.argv) @@ -137,9 +137,9 @@ def _set_proc_title(): libc.setproctitle('gajim') -def _install_sginal_handlers(application): +def _install_signal_handlers(application): def sigint_cb(num, stack): - print('SIGINT/SIGTERM received') + print(' SIGINT/SIGTERM received') application.quit() # ^C exits the application normally signal.signal(signal.SIGINT, sigint_cb) ===================================== gajim/gtk/controls/base.py ===================================== @@ -1250,9 +1250,9 @@ def on_clear_formatting_menuitem_activate(self, _widget): self.msg_textview.clear_tags() def _style_changed(self, *args): - self.update_tags() + self.update_text_tags() - def update_tags(self): + def update_text_tags(self): self.conversation_view.update_text_tags() @staticmethod ===================================== gajim/gtk/conversation/message_widget.py ===================================== @@ -61,3 +61,8 @@ def add_content(self, content): def clear(self): self.foreach(self.remove) + + def update_text_tags(self, *args): + for widget in self.get_children(): + if not isinstance(widget, CodeWidget): + widget.update_text_tags() ===================================== gajim/gtk/conversation/plain_widget.py ===================================== @@ -15,7 +15,6 @@ import logging from urllib.parse import quote -from gi.repository import GObject from gi.repository import Gtk from gi.repository import Pango from gi.repository import Gdk @@ -55,6 +54,9 @@ def __init__(self, account, selectable): def add_content(self, block): self._text_widget.print_text_with_styling(block) + def update_text_tags(self): + self._text_widget.update_text_tags() + class MessageLabel(Gtk.Label): def __init__(self, account, selectable): @@ -68,6 +70,8 @@ def __init__(self, account, selectable): self._account = account + self.get_style_context().add_class('gajim-conversation-text') + def print_text_with_styling(self, block): text = GLib.markup_escape_text(block.text.strip()) for uri in block.uris: @@ -81,17 +85,11 @@ def print_text_with_styling(self, block): self.set_markup(text) self.set_attributes(attr_list) + def update_text_tags(self): + pass -class MessageTextview(Gtk.TextView): - - __gsignals__ = { - 'quote': ( - GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION, - None, - (str, ) - ), - } +class MessageTextview(Gtk.TextView): def __init__(self, account): Gtk.TextView.__init__(self) self.set_hexpand(True) @@ -122,7 +120,7 @@ def __init__(self, account): # Keeps text selections for quoting and search actions self._selected_text = '' - self.get_style_context().add_class('gajim-conversation-font') + self.get_style_context().add_class('gajim-conversation-text') # Create Tags self._create_url_tags() @@ -147,12 +145,11 @@ def _create_url_tags(self): underline=Pango.Underline.SINGLE) tag.connect('event', self._on_uri_clicked, tag) - def update_tags(self): + def update_text_tags(self): tag_table = self.get_buffer().get_tag_table() - color = app.css_config.get_value('.gajim-url', StyleAttr.COLOR) - + url_color = app.css_config.get_value('.gajim-url', StyleAttr.COLOR) for tag in URI_TAGS: - tag_table.lookup(tag).set_property('foreground', color) + tag_table.lookup(tag).set_property('foreground', url_color) def clear(self): buffer_ = self.get_buffer() @@ -229,8 +226,7 @@ def _on_button_press(self, _widget, event): if tags: # A tagged text fragment has been clicked for tag in tags: - tag_name = tag.get_property('name') - if tag_name in URI_TAGS: + if tag.get_property('name') in URI_TAGS: # Block regular context menu return True @@ -353,6 +349,7 @@ def _on_uri_clicked(self, texttag, _widget, event, iter_, _kind): self._show_context_menu(uri) return Gdk.EVENT_STOP + # TODO: # self.plugin_modified = False # app.plugin_manager.extension_point( # 'hyperlink_handler', uri, self, self.get_toplevel()) ===================================== gajim/gtk/conversation/rows/base.py ===================================== @@ -14,7 +14,6 @@ from datetime import datetime -from gi.repository import Gdk from gi.repository import GLib from gi.repository import Gtk from gi.repository import Pango @@ -23,8 +22,6 @@ from gajim.common.i18n import _ from gajim.common.helpers import from_one_line -from ...util import convert_rgba_to_hex -from ...util import text_to_color from ...util import wrap_with_event_box @@ -59,10 +56,7 @@ def is_merged(self): return self._merged def update_text_tags(self): - return - # TODO - if self.textview is not None: - self.textview.update_text_tags() + pass @staticmethod def create_timestamp_widget(timestamp: datetime) -> Gtk.Label: ===================================== gajim/gtk/conversation/rows/message.py ===================================== @@ -171,13 +171,15 @@ def __init__(self, self.show_all() + def update_text_tags(self): + self._message_widget.update_text_tags() + def _check_for_highlight(self, content): needs_highlight = message_needs_highlight( content.text, self._contact.nickname, self._client.get_own_jid().bare) if needs_highlight: - # TODO: add class to textview text (if message widget is TextView) self.get_style_context().add_class( 'gajim-mention-highlight') @@ -258,10 +260,10 @@ def _get_encryption_image(self, additional_data, encryption_enabled=None): color = 'encrypted-color' else: icon, trust_tooltip, color = TRUST_SYMBOL_DATA[trust] - tooltip = '%s\n%s' % (tooltip, trust_tooltip) + tooltip = f'{tooltip}\n{trust_tooltip}' if fingerprint is not None: fingerprint = format_fingerprint(fingerprint) - tooltip = '%s\n<tt>%s</tt>' % (tooltip, fingerprint) + tooltip = f'{tooltip}\n<tt>{fingerprint}</tt>' image = Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.MENU) image.set_tooltip_markup(tooltip) @@ -279,9 +281,6 @@ def _get_encryption_details(additional_data): trust = additional_data.get_value('encrypted', 'trust') return name, fingerprint, trust - def _on_quote_selection(self, _widget, text): - self.get_parent().on_quote(text) - @property def has_receipt(self): return self._has_receipt ===================================== gajim/gtk/message_input.py ===================================== @@ -53,7 +53,7 @@ def __init__(self): self.set_right_margin(2) self.set_pixels_above_lines(2) self.set_pixels_below_lines(2) - self.get_style_context().add_class('gajim-conversation-font') + self.get_style_context().add_class('gajim-conversation-text') self.drag_dest_unset() ===================================== gajim/gtk/themes.py ===================================== @@ -34,143 +34,91 @@ StyleOption = namedtuple('StyleOption', 'label selector attr') CSS_STYLE_OPTIONS = [ - StyleOption(_('Chatstate Composing'), - '.gajim-state-composing', - StyleAttr.COLOR), + StyleOption(_('Conversation: Text Font'), + '.gajim-conversation-text', + StyleAttr.FONT), - StyleOption(_('Chatstate Inactive'), - '.gajim-state-inactive', + StyleOption(_('Conversation: Text Color'), + '.gajim-conversation-text', StyleAttr.COLOR), - StyleOption(_('Chatstate Gone'), - '.gajim-state-gone', + StyleOption(_('Conversation: URL Color'), + '.gajim-url', StyleAttr.COLOR), - StyleOption(_('Chatstate Paused'), - '.gajim-state-paused', + StyleOption(_('Conversation: Nickname Color (Incoming)'), + '.gajim-incoming-nickname', StyleAttr.COLOR), - StyleOption(_('Group Chat Tab New Directed Message'), - '.gajim-state-tab-muc-directed-msg', + StyleOption(_('Conversation: Nickname Color (Outgoing)'), + '.gajim-outgoing-nickname', StyleAttr.COLOR), - StyleOption(_('Group Chat Tab New Message'), - '.gajim-state-tab-muc-msg', - StyleAttr.COLOR), + StyleOption(_('Mention: Message Background Color'), + '.gajim-mention-highlight', + StyleAttr.BACKGROUND), - StyleOption(_('Banner Foreground Color'), - '.gajim-banner', + StyleOption(_('Status Message: Text Color'), + '.gajim-status-message', StyleAttr.COLOR), - StyleOption(_('Banner Background Color'), - '.gajim-banner', + StyleOption(_('Status Message: Text Font'), + '.gajim-status-message', + StyleAttr.FONT), + + StyleOption(_('Message Correction: Background Color'), + '.gajim-msg-correcting text', StyleAttr.BACKGROUND), - StyleOption(_('Banner Font'), + StyleOption(_('Chat Banner: Foreground Color'), '.gajim-banner', - StyleAttr.FONT), - - StyleOption(_('Account Row Foreground Color'), - '.gajim-account-row', StyleAttr.COLOR), - StyleOption(_('Account Row Background Color'), - '.gajim-account-row', + StyleOption(_('Chat Banner: Background Color'), + '.gajim-banner', StyleAttr.BACKGROUND), - StyleOption(_('Account Row Font'), - '.gajim-account-row', + StyleOption(_('Chat Banner: Text Font'), + '.gajim-banner', StyleAttr.FONT), - StyleOption(_('Group Row Foreground Color'), + StyleOption(_('Contact List: Group Foreground Color'), '.gajim-group-row', StyleAttr.COLOR), - StyleOption(_('Group Row Background Color'), + StyleOption(_('Contact List: Group Background Color'), '.gajim-group-row', StyleAttr.BACKGROUND), - StyleOption(_('Group Row Font'), + StyleOption(_('Contact List: Group Font'), '.gajim-group-row', StyleAttr.FONT), - StyleOption(_('Contact Row Foreground Color'), + StyleOption(_('Contact List: Contact Foreground Color'), '.gajim-contact-row', StyleAttr.COLOR), - StyleOption(_('Contact Row Background Color'), + StyleOption(_('Contact List: Contact Background Color'), '.gajim-contact-row', StyleAttr.BACKGROUND), - StyleOption(_('Contact Row Font'), + StyleOption(_('Contact List: Contact Font'), '.gajim-contact-row', StyleAttr.FONT), - StyleOption(_('Conversation Font'), - '.gajim-conversation-font', - StyleAttr.FONT), - - StyleOption(_('Incoming Nickname Color'), - '.gajim-incoming-nickname', - StyleAttr.COLOR), - - StyleOption(_('Outgoing Nickname Color'), - '.gajim-outgoing-nickname', - StyleAttr.COLOR), - - StyleOption(_('Incoming Message Text Color'), - '.gajim-incoming-message-text', - StyleAttr.COLOR), - - StyleOption(_('Incoming Message Text Font'), - '.gajim-incoming-message-text', - StyleAttr.FONT), - - StyleOption(_('Outgoing Message Text Color'), - '.gajim-outgoing-message-text', - StyleAttr.COLOR), - - StyleOption(_('Outgoing Message Text Font'), - '.gajim-outgoing-message-text', - StyleAttr.FONT), - - StyleOption(_('Status Message Color'), - '.gajim-status-message', - StyleAttr.COLOR), - - StyleOption(_('Status Message Font'), - '.gajim-status-message', - StyleAttr.FONT), - - StyleOption(_('URL Color'), - '.gajim-url', - StyleAttr.COLOR), - - StyleOption(_('Highlight Message Background'), - '.gajim-mention-highlight', - StyleAttr.BACKGROUND), - - StyleOption(_('Message Correcting'), - '.gajim-msg-correcting text', - StyleAttr.BACKGROUND), - - StyleOption(_('Contact Disconnected Background'), - '.gajim-roster-disconnected', - StyleAttr.BACKGROUND), - - StyleOption(_('Contact Connected Background '), - '.gajim-roster-connected', - StyleAttr.BACKGROUND), - StyleOption(_('Status Online Color'), + StyleOption(_('Status: Online Color'), '.gajim-status-online', StyleAttr.COLOR), - StyleOption(_('Status Away Color'), + + StyleOption(_('Status: Away Color'), '.gajim-status-away', StyleAttr.COLOR), - StyleOption(_('Status DND Color'), + + StyleOption(_('Status: DND Color'), '.gajim-status-dnd', StyleAttr.COLOR), - StyleOption(_('Status Offline Color'), + + StyleOption(_('Status: Offline Color'), '.gajim-status-offline', StyleAttr.COLOR), ] @@ -317,6 +265,14 @@ def _on_add_new_theme(self, *args): self._select_theme_row(iter_) self._apply_theme(name) + def reload_roster_theme(self): + tree_selection = self._ui.theme_treeview.get_selection() + store, iter_ = tree_selection.get_selected() + if iter_ is None: + return + theme = store[iter_][Column.THEME] + self._apply_theme(theme) + @staticmethod def _apply_theme(theme): app.settings.set('roster_theme', theme) @@ -423,11 +379,13 @@ def _on_color_set(self, color_button): app.css_config.set_value( self.option.selector, self.option.attr, color_string, pre=True) app.nec.push_incoming_event(NetworkEvent('style-changed')) + self.get_toplevel().reload_roster_theme() def _on_font_set(self, font_button): desc = font_button.get_font_desc() app.css_config.set_font(self.option.selector, desc, pre=True) app.nec.push_incoming_event(NetworkEvent('style-changed')) + self.get_toplevel().reload_roster_theme() def _on_remove(self, *args): self.get_parent().remove(self) View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/compare/d324e5f8c3b85d00adbbadc1772e1ae…
-- View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/compare/d324e5f8c3b85d00adbbadc1772e1ae…
You're receiving this email because of your account on
dev.gajim.org
.
1
0
0
0
[Git][gajim/gajim][mainwindow] 2 commits: Util: Typing
by Daniel Brötzmann (@wurstsalat)
04 Nov '21
04 Nov '21
Daniel Brötzmann pushed to branch mainwindow at gajim / gajim Commits: 1aa91768 by wurstsalat at 2021-11-05T00:14:53+01:00 Util: Typing - - - - - d324e5f8 by wurstsalat at 2021-11-05T00:14:58+01:00 PreviewHelpers: Typing - - - - - 2 changed files: - gajim/common/preview_helpers.py - gajim/gtk/util.py Changes: ===================================== gajim/common/preview_helpers.py ===================================== @@ -12,6 +12,9 @@ # You should have received a copy of the GNU General Public License # along with Gajim. If not, see <
http://www.gnu.org/licenses/
>. +from typing import List +from typing import Tuple +from typing import Optional import math import logging import binascii @@ -127,14 +130,14 @@ def extract_and_resize_frames(image, resize_to): return frames, result -def create_thumbnail(data, size): +def create_thumbnail(data: bytes, size:int) -> Optional[bytes]: thumbnail = create_thumbnail_with_pil(data, size) if thumbnail is not None: return thumbnail return create_thumbnail_with_pixbuf(data, size) -def create_thumbnail_with_pixbuf(data, size): +def create_thumbnail_with_pixbuf(data: bytes, size: int) -> Optional[bytes]: loader = GdkPixbuf.PixbufLoader() try: loader.write(data) @@ -160,7 +163,7 @@ def create_thumbnail_with_pixbuf(data, size): return bytes_ -def create_thumbnail_with_pil(data, size): +def create_thumbnail_with_pil(data: bytes, size: int) -> Optional[bytes]: input_file = BytesIO(data) output_file = BytesIO() try: @@ -170,7 +173,7 @@ def create_thumbnail_with_pil(data, size): log.warning('fallback to pixbuf') input_file.close() output_file.close() - return + return None image_width, image_height = image.size if size > image_width and size > image_height: @@ -197,7 +200,7 @@ def create_thumbnail_with_pil(data, size): return bytes_ -def get_thumbnail_size(pixbuf, size): +def get_thumbnail_size(pixbuf: GdkPixbuf.Pixbuf, size: int) -> Tuple[int, int]: # Calculates the new thumbnail size while preserving the aspect ratio image_width = pixbuf.get_width() image_height = pixbuf.get_height() @@ -214,7 +217,7 @@ def get_thumbnail_size(pixbuf, size): return image_width, image_height -def pixbuf_from_data(data): +def pixbuf_from_data(data: bytes) -> GdkPixbuf.Pixbuf: loader = GdkPixbuf.PixbufLoader() try: loader.write(data) @@ -286,7 +289,7 @@ def get_image_paths(uri, urlparts, size, orig_dir, thumb_dir): return orig_path, thumb_path -def split_geo_uri(uri): +def split_geo_uri(uri: str) -> Coords: # Example: # geo:37.786971,-122.399677,122.3;CRS=epsg:32718;U=20;mapcolors=abc # Assumption is all coordinates are CRS=WGS-84 @@ -318,7 +321,7 @@ def split_geo_uri(uri): return Coords(location=location, lat=lat, lon=lon) -def filename_from_uri(uri): +def filename_from_uri(uri: str) -> str: urlparts = urlparse(unquote(uri)) path = Path(urlparts.path) return path.name @@ -365,7 +368,9 @@ def get_previewable_mime_types(): )) -def guess_mime_type(file_path, data=None): +def guess_mime_type(file_path: str, + data: Optional[bytes] = None + ) -> str: mime_type, _ = mimetypes.MimeTypes().guess_type(str(file_path)) if mime_type is None: # Try to guess MIME type by file name @@ -374,7 +379,9 @@ def guess_mime_type(file_path, data=None): return mime_type -def guess_simple_file_type(file_path, data=None): +def guess_simple_file_type(file_path: str, + data: Optional[bytes] = None + ) -> Tuple[Gio.Icon, str]: mime_type = guess_mime_type(file_path, data) icon = get_icon_for_mime_type(mime_type) if file_path.startswith('geo:'): @@ -390,7 +397,7 @@ def guess_simple_file_type(file_path, data=None): return icon, _('File') -def get_icon_for_mime_type(mime_type): +def get_icon_for_mime_type(mime_type: str) -> Gio.Icon: if mime_type is None: return Gio.Icon.new_for_string('mail-attachment') return Gio.content_type_get_icon(mime_type) ===================================== gajim/gtk/util.py ===================================== @@ -19,6 +19,7 @@ from typing import List from typing import Tuple from typing import Optional +from typing import Union import sys import logging @@ -70,8 +71,8 @@ class NickCompletionGenerator: def __init__(self, self_nick: str) -> None: self.nick = self_nick - self.sender_list = [] # type: List[str] - self.attention_list = [] # type: List[str] + self.sender_list: List[str] = [] + self.attention_list: List[str] = [] def change_nick(self, new_nick: str) -> None: self.nick = new_nick @@ -419,7 +420,8 @@ def at_the_end(widget: Gtk.ScrolledWindow) -> bool: return adj_v.get_value() == max_scroll_pos -def get_image_button(icon_name, tooltip, toggle=False): +def get_image_button(icon_name: str, tooltip: str, + toggle: Optional[bool] = False) -> Gtk.Button: if toggle: button = Gtk.ToggleButton() image = Gtk.Image.new_from_icon_name(icon_name, Gtk.IconSize.MENU) @@ -465,11 +467,11 @@ def convert_rgb_string_to_float(rgb_string: str) -> Tuple[float, float, float]: return (rgba.red, rgba.green, rgba.blue) -def rgba_to_float(rgba): +def rgba_to_float(rgba: Gdk.RGBA) -> Tuple[float, float, float]: return (rgba.red, rgba.green, rgba.blue) -def make_rgba(color_string): +def make_rgba(color_string: str) -> Gdk.RGBA: rgba = Gdk.RGBA() rgba.parse(color_string) return rgba @@ -506,7 +508,7 @@ def get_metacontact_surface(icon_name, expanded, scale): return state_surface -def get_primary_accel_mod(): +def get_primary_accel_mod() -> Optional[Gdk.ModifierType]: """ Returns the primary Gdk.ModifierType modifier. cmd on osx, ctrl everywhere else. @@ -514,7 +516,7 @@ def get_primary_accel_mod(): return Gtk.accelerator_parse("<Primary>")[1] -def get_hardware_key_codes(keyval): +def get_hardware_key_codes(keyval: int) -> List[int]: keymap = Gdk.Keymap.get_for_display(Gdk.Display.get_default()) valid, key_map_keys = keymap.get_entries_for_keyval(keyval) @@ -618,7 +620,7 @@ def get_account_location_icon_name(account): return None if location is None else 'applications-internet' -def format_eta(time_): +def format_eta(time_: Union[int, float]) -> str: times = {'minutes': 0, 'seconds': 0} time_ = int(time_) times['seconds'] = time_ % 60 @@ -629,7 +631,7 @@ def format_eta(time_): return _('%s s') % times['seconds'] -def format_fingerprint(fingerprint): +def format_fingerprint(fingerprint: str) -> str: fplen = len(fingerprint) wordsize = fplen // 8 buf = '' @@ -677,7 +679,7 @@ def do_get_preferred_width(self): return minimum_width, natural_width -def text_to_color(text): +def text_to_color(text: str) -> Tuple[float, float, float]: if app.css_config.prefer_dark: background = (0, 0, 0) # RGB (0, 0, 0) black else: @@ -692,7 +694,7 @@ def get_color_for_account(account: str) -> str: @lru_cache(maxsize=16) -def get_css_show_class(show): +def get_css_show_class(show: str) -> str: if show in ('online', 'chat'): return '.gajim-status-online' if show == 'away': @@ -741,7 +743,7 @@ def get_pixbuf_from_data(file_data): return pixbuf -def scale_with_ratio(size, width, height): +def scale_with_ratio(size: int, width: int, height: int) -> Tuple[int, int]: if height == width: return size, size if height > width: @@ -752,7 +754,8 @@ def scale_with_ratio(size, width, height): return size, int(size / ratio) -def scale_pixbuf(pixbuf, size): +def scale_pixbuf(pixbuf: GdkPixbuf.Pixbuf, + size: int) -> Optional[GdkPixbuf.Pixbuf]: width, height = scale_with_ratio(size, pixbuf.get_width(), pixbuf.get_height()) @@ -799,7 +802,7 @@ def load_pixbuf(path, size=None): return None -def get_thumbnail_size(pixbuf, size): +def get_thumbnail_size(pixbuf: GdkPixbuf.Pixbuf, size: int) -> Tuple[int, int]: # Calculates the new thumbnail size while preserving the aspect ratio image_width = pixbuf.get_width() image_height = pixbuf.get_height() @@ -832,7 +835,7 @@ def _to_href(match): return URL_REGEX.sub(_to_href, string) -def get_app_windows(account): +def get_app_windows(account: str) -> List[Gtk.Window]: windows = [] for win in app.app.get_windows(): if hasattr(win, 'account'): @@ -934,7 +937,7 @@ def _on_realize(*args): class AccountBadge(Gtk.Label): - def __init__(self, account): + def __init__(self, account: str) -> None: Gtk.Label.__init__(self) self.set_ellipsize(Pango.EllipsizeMode.END) self.set_max_width_chars(12) @@ -945,7 +948,7 @@ def __init__(self, account): self.refresh() self.show() - def refresh(self): + def refresh(self) -> None: label = app.get_account_label(self._account) self.set_text(label) account_class = app.css_config.get_dynamic_class(self._account) @@ -953,7 +956,7 @@ def refresh(self): self.set_tooltip_text(_('Account: %s') % label) -def make_pango_attribute(name, start, end): +def make_pango_attribute(name: str, start: int, end: int) -> Pango.Attribute: if name == 'strong': attr = Pango.attr_weight_new(Pango.Weight.BOLD) if name == 'strike': View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/compare/b3d0169d64e49a87c4f76dc5d6b325a…
-- View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/compare/b3d0169d64e49a87c4f76dc5d6b325a…
You're receiving this email because of your account on
dev.gajim.org
.
1
0
0
0
[Git][gajim/gajim][mainwindow] ChatList: Properly split newlines
by Daniel Brötzmann (@wurstsalat)
04 Nov '21
04 Nov '21
Daniel Brötzmann pushed to branch mainwindow at gajim / gajim Commits: b3d0169d by wurstsalat at 2021-11-04T22:39:19+01:00 ChatList: Properly split newlines - - - - - 1 changed file: - gajim/gtk/chat_list.py Changes: ===================================== gajim/gtk/chat_list.py ===================================== @@ -14,6 +14,7 @@ import logging import time +from typing import Optional from gi.repository import Gio from gi.repository import Gdk @@ -29,6 +30,7 @@ from gajim.common.helpers import get_groupchat_name from gajim.common.helpers import get_group_chat_nick from gajim.common.helpers import get_uf_relative_time +from gajim.common.helpers import AdditionalDataDict from gajim.common.preview_helpers import filename_from_uri from gajim.common.preview_helpers import guess_simple_file_type @@ -667,7 +669,9 @@ def set_nick(self, nickname): self._ui.nick_label.set_visible(bool(nickname)) self._ui.nick_label.set_text(nickname) - def set_message_text(self, text, icon_name=None, additional_data=None): + def set_message_text(self, text: str, icon_name: Optional[str] = None, + additional_data: Optional[AdditionalDataDict] = None + ) -> None: icon = None if icon_name is not None: icon = Gio.Icon.new_for_string(icon_name) @@ -676,12 +680,13 @@ def set_message_text(self, text, icon_name=None, additional_data=None): text, additional_data): file_name = filename_from_uri(text) icon, file_type = guess_simple_file_type(text) - text = '%(file_type)s (%(file_name)s)' % { - 'file_type': file_type, - 'file_name': file_name} - else: - text = text.replace('\n', ' ') + text = f'{file_type} ({file_name})' + + # Split by newline and display last line + lines = text.split('\n') + text = lines[-1] self._ui.message_label.set_text(text) + if icon is None: self._ui.message_icon.hide() else: View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/commit/b3d0169d64e49a87c4f76dc5d6b325af…
-- View it on GitLab:
https://dev.gajim.org/gajim/gajim/-/commit/b3d0169d64e49a87c4f76dc5d6b325af…
You're receiving this email because of your account on
dev.gajim.org
.
1
0
0
0
← Newer
1
...
4
5
6
7
8
9
10
11
Older →
Jump to page:
1
2
3
4
5
6
7
8
9
10
11
Results per page:
10
25
50
100
200