Philipp Hörist pushed to branch newcontrol at gajim / gajim
Commits:
-
74571a31
by lovetox at 2022-07-04T19:24:53+02:00
-
a5f77d71
by lovetox at 2022-07-04T19:24:53+02:00
-
ee623949
by lovetox at 2022-07-04T19:24:53+02:00
-
3228ed27
by wurstsalat at 2022-07-04T19:24:53+02:00
-
1a13e06d
by wurstsalat at 2022-07-04T19:24:53+02:00
-
aa24823a
by wurstsalat at 2022-07-04T19:24:53+02:00
-
d3349704
by André Apitzsch at 2022-07-04T19:24:53+02:00
-
b8550beb
by lovetox at 2022-07-04T19:24:53+02:00
-
00878b99
by Peter Shkenev at 2022-07-04T19:24:53+02:00
-
17ac9d48
by lovetox at 2022-07-04T19:24:53+02:00
-
2e97128a
by lovetox at 2022-07-04T19:24:53+02:00
17 changed files:
- README.md
- gajim/common/helpers.py
- gajim/gajim.py
- gajim/gtk/account_page.py
- gajim/gtk/application.py
- gajim/gtk/chat_action_processor.py
- gajim/gtk/const.py
- gajim/gtk/conversation/code_widget.py
- gajim/gtk/conversation/rows/message.py
- gajim/gtk/groupchat_roster.py
- gajim/gtk/main.py
- gajim/gtk/menus.py
- gajim/gtk/notification_manager.py
- gajim/gtk/roster.py
- gajim/gtk/status_icon.py
- gajim/gtk/tooltips.py
- pyrightconfig.json
Changes:
| ... | ... | @@ -2,46 +2,45 @@ # Welcome to Gajim |
| 2 | 2 | |
| 3 | 3 | ### Runtime Requirements
|
| 4 | 4 | |
| 5 | -- python3.9 or higher
|
|
| 6 | -- python3-gi
|
|
| 7 | -- python3-gi-cairo
|
|
| 8 | -- python3-nbxmpp (>=2.99.0)
|
|
| 9 | -- python3-openssl (>=16.2)
|
|
| 10 | -- python3-css-parser
|
|
| 11 | -- python3-keyring
|
|
| 12 | -- python3-precis-i18n
|
|
| 13 | -- python3-packaging
|
|
| 14 | -- python3-pil
|
|
| 15 | -- python3-setuptools
|
|
| 16 | -- Gtk3 (>=3.22)
|
|
| 17 | -- GLib (>=2.60.0)
|
|
| 18 | -- gir1.2-gtksource-4
|
|
| 19 | -- gir1.2-soup-2.4
|
|
| 5 | +- [Python](https://www.python.org/) (>=3.9)
|
|
| 6 | +- [PyGObject](https://pypi.org/project/PyGObject/)
|
|
| 7 | +- [pycairo](https://pypi.org/project/pycairo/)
|
|
| 8 | +- [nbxmpp](https://pypi.org/project/nbxmpp/) (>=3.0.0)
|
|
| 9 | +- [pyOpenSSL](https://pypi.org/project/pyOpenSSL/) (>=16.2)
|
|
| 10 | +- [css-parser](https://pypi.org/project/css-parser/)
|
|
| 11 | +- [keyring](https://pypi.org/project/keyring/)
|
|
| 12 | +- [precis_i18n](https://pypi.org/project/precis-i18n/)
|
|
| 13 | +- [packaging](https://pypi.org/project/packaging/)
|
|
| 14 | +- [Pillow](https://pypi.org/project/Pillow/)
|
|
| 15 | +- [setuptools](https://pypi.org/project/setuptools/)
|
|
| 16 | +- [Gtk3](https://gitlab.com/gnome/gtk) (>=3.22)
|
|
| 17 | +- [GLib](https://gitlab.com/gnome/glib) (>=2.60.0)
|
|
| 18 | +- [GtkSourceView](https://gitlab.gnome.org/GNOME/gtksourceview)
|
|
| 19 | +- [libsoup](https://gitlab.gnome.org/GNOME/libsoup/)
|
|
| 20 | 20 | |
| 21 | 21 | ### Optional Runtime Requirements
|
| 22 | 22 | |
| 23 | -- python3-sentry-sdk for Sentry error reporting to dev.gajim.org (users decide whether to send reports or not)
|
|
| 24 | -- gir1.2-gspell-1 and hunspell-LANG where lang is your locale eg. en, fr etc
|
|
| 25 | -- gir1.2-secret-1 for GNOME Keyring or KDE support as password storage
|
|
| 26 | 23 | - D-Bus running to have gajim-remote working
|
| 27 | -- gir1.2-gupnpigd-1.0 for better NAT traversing
|
|
| 28 | -- gir1.2-networkmanager-1.0 for network lose detection
|
|
| 29 | -- gir1.2-geoclue-2.0 for sharing your location
|
|
| 30 | -- gir1.2-gsound-1.0 for sound on Linux
|
|
| 24 | +- [sentry-sdk](https://pypi.org/project/sentry-sdk/) for Sentry error reporting to dev.gajim.org (users decide whether to send reports or not)
|
|
| 25 | +- [gspell](https://gitlab.gnome.org/GNOME/gspell) and hunspell-LANG where lang is your locale eg. en, fr etc
|
|
| 26 | +- [libsecret](https://gitlab.gnome.org/GNOME/libsecret/) for GNOME Keyring or KDE support as password storage
|
|
| 27 | +- [GUPnP-IGD](https://gitlab.gnome.org/GNOME/gupnp) for better NAT traversing
|
|
| 28 | +- [NetworkManager](https://gitlab.freedesktop.org/NetworkManager/NetworkManager) for network lose detection
|
|
| 29 | +- [GeoClue](https://gitlab.freedesktop.org/geoclue/geoclue) for sharing your location
|
|
| 30 | +- [GSound](https://gitlab.gnome.org/GNOME/gsound) for sound on Linux
|
|
| 31 | 31 | |
| 32 | 32 | #### For Video and Audio Calls
|
| 33 | 33 | |
| 34 | -- gir1.2-farstream-0.2 for video calls
|
|
| 35 | -- gir1.2-gstreamer-1.0 for video and audio calls
|
|
| 36 | -- gir1.2-gst-plugins-base-1.0 for video and audio calls
|
|
| 37 | -- gstreamer1.0-plugins-ugly for video and audio calls
|
|
| 38 | -- gstreamer1.0-libav for video and audio calls
|
|
| 39 | -- gstreamer1.0-gtk3
|
|
| 34 | +- [Farstream](https://gitlab.freedesktop.org/farstream/farstream)
|
|
| 35 | +- [GStreamer](https://gitlab.freedesktop.org/gstreamer/gstreamer)
|
|
| 36 | +- [gst-plugins-base](https://gitlab.freedesktop.org/gstreamer/gst-plugins-base)
|
|
| 37 | +- [gst-plugins-ugly](https://gitlab.freedesktop.org/gstreamer/gst-plugins-ugly)
|
|
| 38 | +- [gst-libav](https://gitlab.freedesktop.org/gstreamer/gst-libav)
|
|
| 40 | 39 | |
| 41 | 40 | ### Compile-time Requirements
|
| 42 | 41 | |
| 43 | -- python3-setuptools
|
|
| 44 | -- gettext
|
|
| 42 | +- [setuptools](https://pypi.org/project/setuptools/)
|
|
| 43 | +- [gettext](https://savannah.gnu.org/projects/gettext/)
|
|
| 45 | 44 | |
| 46 | 45 | ### Running Tests
|
| 47 | 46 |
| ... | ... | @@ -385,36 +385,6 @@ def sanitize_filename(filename: str) -> str: |
| 385 | 385 | return filename
|
| 386 | 386 | |
| 387 | 387 | |
| 388 | -def reduce_chars_newlines(text: str, max_chars: int = 0,
|
|
| 389 | - max_lines: int = 0) -> str:
|
|
| 390 | - """
|
|
| 391 | - Cut the chars after 'max_chars' on each line and show only the first
|
|
| 392 | - 'max_lines'
|
|
| 393 | - |
|
| 394 | - If any of the params is not present (None or 0) the action on it is not
|
|
| 395 | - performed
|
|
| 396 | - """
|
|
| 397 | - def _cut_if_long(string_: str) -> str:
|
|
| 398 | - if len(string_) > max_chars:
|
|
| 399 | - string_ = string_[:max_chars - 3] + '…'
|
|
| 400 | - return string_
|
|
| 401 | - |
|
| 402 | - if max_lines == 0:
|
|
| 403 | - lines = text.split('\n')
|
|
| 404 | - else:
|
|
| 405 | - lines = text.split('\n', max_lines)[:max_lines]
|
|
| 406 | - if max_chars > 0:
|
|
| 407 | - if lines:
|
|
| 408 | - lines = [_cut_if_long(e) for e in lines]
|
|
| 409 | - if lines:
|
|
| 410 | - reduced_text = '\n'.join(lines)
|
|
| 411 | - if reduced_text != text:
|
|
| 412 | - reduced_text += '…'
|
|
| 413 | - else:
|
|
| 414 | - reduced_text = ''
|
|
| 415 | - return reduced_text
|
|
| 416 | - |
|
| 417 | - |
|
| 418 | 388 | def get_contact_dict_for_account(account: str) -> dict[str, types.BareContact]:
|
| 419 | 389 | """
|
| 420 | 390 | Creates a dict of jid -> contact with all contacts of account
|
| ... | ... | @@ -30,7 +30,7 @@ |
| 30 | 30 | from gajim.common import i18n
|
| 31 | 31 | |
| 32 | 32 | |
| 33 | -_MIN_NBXMPP_VER = '3.0.0.dev3'
|
|
| 33 | +_MIN_NBXMPP_VER = '3.0.0'
|
|
| 34 | 34 | _MIN_GTK_VER = '3.22.27'
|
| 35 | 35 | _MIN_CAIRO_VER = '1.16.0'
|
| 36 | 36 | _MIN_PYGOBJECT_VER = '3.32.0'
|
| ... | ... | @@ -75,7 +75,7 @@ def __init__(self, account: str) -> None: |
| 75 | 75 | |
| 76 | 76 | roster_menu = Gio.Menu()
|
| 77 | 77 | for action, label in ROSTER_MENU_DICT.items():
|
| 78 | - roster_menu.append(label, f'win.{action}-{account}')
|
|
| 78 | + roster_menu.append(label, f'win.{action}')
|
|
| 79 | 79 | self._ui.roster_menu_button.set_menu_model(roster_menu)
|
| 80 | 80 | |
| 81 | 81 | self._ui.connect_signals(self)
|
| ... | ... | @@ -524,6 +524,8 @@ def _on_feature_discovered(self, event: events.FeatureDiscovered) -> None: |
| 524 | 524 | elif event.feature == Namespace.BLOCKING:
|
| 525 | 525 | action = '%s-blocking' % event.account
|
| 526 | 526 | self.set_action_state(action, True)
|
| 527 | + action = '%s-block-contact' % event.account
|
|
| 528 | + self.set_action_state(action, True)
|
|
| 527 | 529 | |
| 528 | 530 | # Action Callbacks
|
| 529 | 531 |
| ... | ... | @@ -210,7 +210,7 @@ def _check_for_emoji(self, start_iter: Gtk.TextIter) -> bool: |
| 210 | 210 | start, _end = search
|
| 211 | 211 | colon_offset = start.get_offset()
|
| 212 | 212 | before_colon = self._buf.get_iter_at_offset(colon_offset - 1)
|
| 213 | - if before_colon.get_char() != ' ':
|
|
| 213 | + if before_colon.get_char() not in (' ', '\n'):
|
|
| 214 | 214 | # We want to show the menu only if text begins with a colon,
|
| 215 | 215 | # or if a colon follows on a space. This avoids showing the
|
| 216 | 216 | # menu within normal sentences containing colons.
|
| ... | ... | @@ -212,11 +212,17 @@ def __str__(self): |
| 212 | 212 | ('set-encryption', 's', 'disabled', False),
|
| 213 | 213 | ('send-chatstate', 's', 'disabled', False),
|
| 214 | 214 | ('send-marker', 'b', False, False),
|
| 215 | + ('sort-by-show', 'b', 'app-setting:showoffline', True),
|
|
| 216 | + ('show-offline', 'b', 'app-setting:sort_by_show_in_roster', True),
|
|
| 215 | 217 | ]
|
| 216 | 218 | |
| 217 | 219 | |
| 218 | 220 | ACCOUNT_ACTIONS = [
|
| 219 | 221 | ('add-contact', 'as'),
|
| 222 | + ('block-contact', 's'),
|
|
| 223 | + ('remove-contact', 's'),
|
|
| 224 | + ('execute-command', 's'),
|
|
| 225 | + ('modify-gateway', 's'),
|
|
| 220 | 226 | ('archive', 's'),
|
| 221 | 227 | ('blocking', 's'),
|
| 222 | 228 | ('bookmarks', 's'),
|
| ... | ... | @@ -242,6 +248,9 @@ def __str__(self): |
| 242 | 248 | |
| 243 | 249 | ONLINE_ACCOUNT_ACTIONS = {
|
| 244 | 250 | 'add-contact',
|
| 251 | + 'remove-contact',
|
|
| 252 | + 'execute-command',
|
|
| 253 | + 'modify-gateway',
|
|
| 245 | 254 | 'bookmarks',
|
| 246 | 255 | 'import-contacts',
|
| 247 | 256 | 'open-chat',
|
| ... | ... | @@ -256,4 +265,5 @@ def __str__(self): |
| 256 | 265 | FEATURE_ACCOUNT_ACTIONS = {
|
| 257 | 266 | 'archive',
|
| 258 | 267 | 'blocking',
|
| 268 | + 'block-contact',
|
|
| 259 | 269 | } |
| ... | ... | @@ -100,6 +100,7 @@ def __init__(self) -> None: |
| 100 | 100 | self.set_top_margin(2)
|
| 101 | 101 | self.set_bottom_margin(2)
|
| 102 | 102 | self.set_monospace(True)
|
| 103 | + self.get_buffer().set_highlight_matching_brackets(False)
|
|
| 103 | 104 | |
| 104 | 105 | self._source_manager = GtkSource.LanguageManager.get_default()
|
| 105 | 106 | self._style_scheme_manager = GtkSource.StyleSchemeManager.get_default()
|
| ... | ... | @@ -18,6 +18,7 @@ |
| 18 | 18 | |
| 19 | 19 | from datetime import datetime
|
| 20 | 20 | from datetime import timedelta
|
| 21 | +import textwrap
|
|
| 21 | 22 | |
| 22 | 23 | from gi.repository import Gdk
|
| 23 | 24 | from gi.repository import GdkPixbuf
|
| ... | ... | @@ -40,7 +41,6 @@ |
| 40 | 41 | from gajim.common.helpers import get_group_chat_nick
|
| 41 | 42 | from gajim.common.helpers import get_muc_context
|
| 42 | 43 | from gajim.common.helpers import message_needs_highlight
|
| 43 | -from gajim.common.helpers import reduce_chars_newlines
|
|
| 44 | 44 | from gajim.common.helpers import to_user_string
|
| 45 | 45 | from gajim.common.i18n import _
|
| 46 | 46 | from gajim.common.i18n import Q_
|
| ... | ... | @@ -187,8 +187,10 @@ def __init__(self, |
| 187 | 187 | if correction_original is not None:
|
| 188 | 188 | self._original_text = correction_original
|
| 189 | 189 | self._message_icons.set_correction_icon_visible(True)
|
| 190 | - original_text = reduce_chars_newlines(
|
|
| 191 | - correction_original, max_chars=150, max_lines=10)
|
|
| 190 | + original_text = textwrap.fill(correction_original,
|
|
| 191 | + width=150,
|
|
| 192 | + max_lines=10,
|
|
| 193 | + placeholder='…')
|
|
| 192 | 194 | self._message_icons.set_correction_tooltip(
|
| 193 | 195 | _('Message corrected. Original message:'
|
| 194 | 196 | '\n%s') % original_text)
|
| ... | ... | @@ -425,8 +427,10 @@ def set_correction(self, text: str, nickname: Optional[str]) -> None: |
| 425 | 427 | self._message_icons.set_receipt_icon_visible(False)
|
| 426 | 428 | self._message_icons.set_correction_icon_visible(True)
|
| 427 | 429 | |
| 428 | - original_text = reduce_chars_newlines(
|
|
| 429 | - self._original_text, max_chars=150, max_lines=10)
|
|
| 430 | + original_text = textwrap.fill(self._original_text,
|
|
| 431 | + width=150,
|
|
| 432 | + max_lines=10,
|
|
| 433 | + placeholder='…')
|
|
| 430 | 434 | self._message_icons.set_correction_tooltip(
|
| 431 | 435 | _('Message corrected. Original message:\n%s') % original_text)
|
| 432 | 436 |
| ... | ... | @@ -378,7 +378,6 @@ def _show_contact_menu(self, nick: str, rect: Gdk.Rectangle) -> None: |
| 378 | 378 | assert self_contact is not None
|
| 379 | 379 | contact = self._group_chat_contact.get_resource(nick)
|
| 380 | 380 | menu = get_groupchat_roster_menu(self._account,
|
| 381 | - self._control_id,
|
|
| 382 | 381 | self_contact,
|
| 383 | 382 | contact)
|
| 384 | 383 |
| ... | ... | @@ -335,6 +335,11 @@ def _add_actions2(self) -> None: |
| 335 | 335 | self.add_action(act)
|
| 336 | 336 | |
| 337 | 337 | for action, variant_type, default, enabled in MAIN_WIN_STATEFUL_ACTIONS:
|
| 338 | + |
|
| 339 | + if isinstance(default, str) and default.startswith('app-setting:'):
|
|
| 340 | + setting = default.removeprefix('app-setting:')
|
|
| 341 | + default = app.settings.get(setting)
|
|
| 342 | + |
|
| 338 | 343 | action = Gio.SimpleAction.new_stateful(
|
| 339 | 344 | action,
|
| 340 | 345 | GLib.VariantType(variant_type),
|
| ... | ... | @@ -21,6 +21,7 @@ |
| 21 | 21 | from typing import Optional
|
| 22 | 22 | from typing import Union
|
| 23 | 23 | |
| 24 | +import textwrap
|
|
| 24 | 25 | from urllib.parse import quote
|
| 25 | 26 | |
| 26 | 27 | from gi.repository import Gtk
|
| ... | ... | @@ -34,7 +35,6 @@ |
| 34 | 35 | from gajim.common.helpers import is_affiliation_change_allowed
|
| 35 | 36 | from gajim.common.helpers import is_role_change_allowed
|
| 36 | 37 | from gajim.common.helpers import jid_is_blocked
|
| 37 | -from gajim.common.helpers import reduce_chars_newlines
|
|
| 38 | 38 | from gajim.common.i18n import _
|
| 39 | 39 | from gajim.common.i18n import get_short_lang_code
|
| 40 | 40 | from gajim.common.const import URIType
|
| ... | ... | @@ -77,7 +77,7 @@ def get_singlechat_menu(contact: types.BareContact) -> GajimMenu: |
| 77 | 77 | |
| 78 | 78 | menu = GajimMenu()
|
| 79 | 79 | menu.add_item(_('Details'), 'win.show-contact-info')
|
| 80 | - menu.add_item(_('Block Contact…'), f'win.block-contact-{account}')
|
|
| 80 | + menu.add_item(_('Block Contact…'), f'app.{account}-block-contact')
|
|
| 81 | 81 | |
| 82 | 82 | submenu = get_send_file_submenu()
|
| 83 | 83 | menu.append_submenu(_('Send File'), submenu)
|
| ... | ... | @@ -207,7 +207,9 @@ def get_encryption_menu() -> GajimMenu: |
| 207 | 207 | def get_conv_action_context_menu(account: str,
|
| 208 | 208 | selected_text: str
|
| 209 | 209 | ) -> Gtk.MenuItem:
|
| 210 | - selected_text_short = reduce_chars_newlines(selected_text, 10, 1)
|
|
| 210 | + selected_text_short = textwrap.shorten(selected_text,
|
|
| 211 | + width=10,
|
|
| 212 | + placeholder='…')
|
|
| 211 | 213 | |
| 212 | 214 | action_menu_item = Gtk.MenuItem.new_with_mnemonic(
|
| 213 | 215 | _('_Actions for "%s"') % selected_text_short)
|
| ... | ... | @@ -356,15 +358,15 @@ def get_roster_menu(account: str, jid: str, gateway: bool = False) -> GajimMenu: |
| 356 | 358 | value = f'"{jid}"'
|
| 357 | 359 | |
| 358 | 360 | menuitems: MenuItemListT = [
|
| 359 | - (_('Details'), f'win.contact-info-{account}', value),
|
|
| 360 | - (_('Execute Command…'), f'win.execute-command-{account}', value),
|
|
| 361 | - (block_label, f'win.block-contact-{account}', value),
|
|
| 362 | - (_('Remove…'), f'win.remove-contact-{account}', value),
|
|
| 361 | + (_('Details'), f'app.{account}-contact-info', value),
|
|
| 362 | + (_('Execute Command…'), f'app.{account}-execute-command', value),
|
|
| 363 | + (block_label, f'app.{account}-block-contact', value),
|
|
| 364 | + (_('Remove…'), f'app.{account}-remove-contact', value),
|
|
| 363 | 365 | ]
|
| 364 | 366 | |
| 365 | 367 | if gateway:
|
| 366 | 368 | menuitems.insert(
|
| 367 | - 1, (_('Modify Gateway…'), f'win.modify-gateway-{account}', value))
|
|
| 369 | + 1, (_('Modify Gateway…'), f'app.{account}-modify-gateway', value))
|
|
| 368 | 370 | |
| 369 | 371 | return GajimMenu.from_list(menuitems)
|
| 370 | 372 |
| ... | ... | @@ -378,7 +378,7 @@ def __init__(self, account: str, jid: str) -> None: |
| 378 | 378 | remove_button.set_valign(Gtk.Align.CENTER)
|
| 379 | 379 | remove_button.set_tooltip_text('Remove from contact list')
|
| 380 | 380 | remove_button.set_action_name(
|
| 381 | - f'win.remove-contact-{self._account}')
|
|
| 381 | + f'win.{self._account}-remove-contact')
|
|
| 382 | 382 | remove_button.set_action_target_value(GLib.Variant('s', str(self.jid)))
|
| 383 | 383 | self.grid.attach(remove_button, 3, 1, 1, 2)
|
| 384 | 384 |
| ... | ... | @@ -116,8 +116,6 @@ def __init__(self, account: str) -> None: |
| 116 | 116 | self._ui.connect_signals(self)
|
| 117 | 117 | |
| 118 | 118 | self.register_events([
|
| 119 | - ('account-connected', ged.CORE, self._on_account_state),
|
|
| 120 | - ('account-disconnected', ged.CORE, self._on_account_state),
|
|
| 121 | 119 | ('roster-received', ged.CORE, self._on_roster_received),
|
| 122 | 120 | ('theme-update', ged.GUI2, self._on_theme_update),
|
| 123 | 121 | ('roster-push', ged.GUI2, self._on_roster_push),
|
| ... | ... | @@ -134,73 +132,36 @@ def __init__(self, account: str) -> None: |
| 134 | 132 | self._filter_enabled = False
|
| 135 | 133 | self._filter_string = ''
|
| 136 | 134 | |
| 137 | - self._add_actions()
|
|
| 135 | + self._connect_actions()
|
|
| 138 | 136 | self._initial_draw()
|
| 139 | 137 | |
| 140 | - def _add_actions(self):
|
|
| 141 | - actions = [
|
|
| 142 | - ('contact-info', self._on_contact_info),
|
|
| 143 | - ('modify-gateway', self._on_modify_gateway),
|
|
| 144 | - ('execute-command', self._on_execute_command),
|
|
| 145 | - ('block-contact', self._on_block_contact),
|
|
| 146 | - ('remove-contact', self._on_remove_contact),
|
|
| 138 | + def _connect_actions(self):
|
|
| 139 | + app_actions = [
|
|
| 140 | + (f'{self._account}-modify-gateway', self._on_modify_gateway),
|
|
| 141 | + (f'{self._account}-execute-command', self._on_execute_command),
|
|
| 142 | + (f'{self._account}-block-contact', self._on_block_contact),
|
|
| 143 | + (f'{self._account}-remove-contact', self._on_remove_contact),
|
|
| 147 | 144 | ]
|
| 148 | - for action in actions:
|
|
| 149 | - action_name, func = action
|
|
| 150 | - act = Gio.SimpleAction.new(
|
|
| 151 | - f'{action_name}-{self._account}', GLib.VariantType.new('s'))
|
|
| 152 | - act.connect('activate', func)
|
|
| 153 | - app.window.add_action(act)
|
|
| 154 | - |
|
| 155 | - action = Gio.SimpleAction.new_stateful(
|
|
| 156 | - f'show-offline-{self._account}',
|
|
| 157 | - None,
|
|
| 158 | - GLib.Variant.new_boolean(app.settings.get('showoffline')))
|
|
| 159 | - action.connect('change-state', self._on_show_offline)
|
|
| 160 | - app.window.add_action(action)
|
|
| 161 | - |
|
| 162 | - action = Gio.SimpleAction.new_stateful(
|
|
| 163 | - f'sort-by-show-{self._account}',
|
|
| 164 | - None,
|
|
| 165 | - GLib.Variant.new_boolean(
|
|
| 166 | - app.settings.get('sort_by_show_in_roster')))
|
|
| 167 | - action.connect('change-state', self._on_sort_by_show)
|
|
| 168 | - app.window.add_action(action)
|
|
| 169 | - |
|
| 170 | - def update_actions(self):
|
|
| 171 | - online = app.account_is_connected(self._account)
|
|
| 172 | - blocking_support = self._client.get_module('Blocking').supported
|
|
| 173 | - |
|
| 174 | - actions = [('contact-info', online),
|
|
| 175 | - ('modify-gateway', online),
|
|
| 176 | - ('execute-command', online),
|
|
| 177 | - ('block-contact', online and blocking_support),
|
|
| 178 | - ('remove-contact', online)]
|
|
| 179 | - |
|
| 180 | - for action, enabled in actions:
|
|
| 181 | - act = app.window.lookup_action(f'{action}-{self._account}')
|
|
| 182 | - assert act is not None
|
|
| 183 | - act.set_enabled(enabled)
|
|
| 184 | - |
|
| 185 | - def _remove_actions(self):
|
|
| 186 | - actions = [
|
|
| 187 | - 'contact-info',
|
|
| 188 | - 'modify-gateway',
|
|
| 189 | - 'execute-command',
|
|
| 190 | - 'block-contact',
|
|
| 191 | - 'remove-contact',
|
|
| 192 | - 'show-offline',
|
|
| 193 | - 'sort-by-show',
|
|
| 145 | + |
|
| 146 | + win_actions = [
|
|
| 147 | + ('show-offline', self._on_show_offline),
|
|
| 148 | + ('sort-by-show', self._on_sort_by_show),
|
|
| 194 | 149 | ]
|
| 195 | - for action in actions:
|
|
| 196 | - app.window.remove_action(f'{action}-{self._account}')
|
|
| 150 | + |
|
| 151 | + for action in app_actions:
|
|
| 152 | + action_name, func = action
|
|
| 153 | + action = app.app.lookup_action(action_name)
|
|
| 154 | + assert action is not None
|
|
| 155 | + action.connect('activate', func)
|
|
| 156 | + |
|
| 157 | + for action in win_actions:
|
|
| 158 | + action_name, func = action
|
|
| 159 | + action = app.window.get_action(action_name)
|
|
| 160 | + action.connect('change-state', func)
|
|
| 197 | 161 | |
| 198 | 162 | def _get_contact(self, jid: str) -> types.BareContact:
|
| 199 | 163 | return self._client.get_module('Contacts').get_contact(jid)
|
| 200 | 164 | |
| 201 | - def _on_account_state(self, _event: ApplicationEvent) -> None:
|
|
| 202 | - self.update_actions()
|
|
| 203 | - |
|
| 204 | 165 | def _on_theme_update(self, _event: ApplicationEvent) -> None:
|
| 205 | 166 | self.redraw()
|
| 206 | 167 | |
| ... | ... | @@ -785,7 +746,6 @@ def process_event(self, event: ApplicationEvent) -> None: |
| 785 | 746 | return
|
| 786 | 747 | |
| 787 | 748 | def _on_destroy(self, _roster: Roster) -> None:
|
| 788 | - self._remove_actions()
|
|
| 789 | 749 | self._contact_refs.clear()
|
| 790 | 750 | self._group_refs.clear()
|
| 791 | 751 | self._unset_model()
|
| ... | ... | @@ -80,8 +80,7 @@ def _can_use_libindicator() -> bool: |
| 80 | 80 | app.is_installed('AYATANA_APPINDICATOR'))
|
| 81 | 81 | |
| 82 | 82 | def _on_setting_changed(self, *args: Any) -> None:
|
| 83 | - count = app.window.get_total_unread_count()
|
|
| 84 | - self._backend.update_state(count=count)
|
|
| 83 | + self._backend.update_state()
|
|
| 85 | 84 | |
| 86 | 85 | def connect_unread_widget(self, widget: Gtk.Widget, signal: str) -> None:
|
| 87 | 86 | widget.connect(signal, self._on_unread_count_changed)
|
| ... | ... | @@ -89,8 +88,7 @@ def connect_unread_widget(self, widget: Gtk.Widget, signal: str) -> None: |
| 89 | 88 | def _on_unread_count_changed(self, *args: Any) -> None:
|
| 90 | 89 | if not app.settings.get('trayicon_notification_on_events'):
|
| 91 | 90 | return
|
| 92 | - count = app.window.get_total_unread_count()
|
|
| 93 | - self._backend.update_state(count=count)
|
|
| 91 | + self._backend.update_state()
|
|
| 94 | 92 | |
| 95 | 93 | def is_visible(self) -> bool:
|
| 96 | 94 | return self._backend.is_visible()
|
| ... | ... | @@ -100,7 +98,7 @@ def shutdown(self) -> None: |
| 100 | 98 | |
| 101 | 99 | |
| 102 | 100 | class NoneBackend:
|
| 103 | - def update_state(self, count: int = 0) -> None:
|
|
| 101 | + def update_state(self, init: bool = False) -> None:
|
|
| 104 | 102 | pass
|
| 105 | 103 | |
| 106 | 104 | def is_visible(self) -> bool:
|
| ... | ... | @@ -133,7 +131,7 @@ def __init__(self) -> None: |
| 133 | 131 | client.connect_signal('state-changed',
|
| 134 | 132 | self._on_client_state_changed)
|
| 135 | 133 | |
| 136 | - def update_state(self, count: int = 0) -> None:
|
|
| 134 | + def update_state(self, init: bool = False) -> None:
|
|
| 137 | 135 | raise NotImplementedError
|
| 138 | 136 | |
| 139 | 137 | def is_visible(self) -> bool:
|
| ... | ... | @@ -188,9 +186,6 @@ def _on_sounds_mute(widget: Gtk.CheckMenuItem) -> None: |
| 188 | 186 | app.settings.set('sounds_on', not widget.get_active())
|
| 189 | 187 | |
| 190 | 188 | def _on_toggle_window(self, _widget: Gtk.MenuItem) -> None:
|
| 191 | - self._on_activate()
|
|
| 192 | - |
|
| 193 | - def _on_activate(self, *args: Any) -> None:
|
|
| 194 | 189 | if app.window.is_minimized():
|
| 195 | 190 | app.window.unminimize()
|
| 196 | 191 | elif app.window.is_withdrawn():
|
| ... | ... | @@ -223,9 +218,9 @@ def __init__(self) -> None: |
| 223 | 218 | self._status_icon.connect('popup-menu', self._on_popup_menu)
|
| 224 | 219 | self._status_icon.connect('size-changed', self._on_size_changed)
|
| 225 | 220 | |
| 226 | - self.update_state()
|
|
| 221 | + self.update_state(init=True)
|
|
| 227 | 222 | |
| 228 | - def update_state(self, count: int = 0) -> None:
|
|
| 223 | + def update_state(self, init: bool = False) -> None:
|
|
| 229 | 224 | if self._shutdown:
|
| 230 | 225 | # Shutdown in progress, don't update icon
|
| 231 | 226 | return
|
| ... | ... | @@ -236,7 +231,7 @@ def update_state(self, count: int = 0) -> None: |
| 236 | 231 | |
| 237 | 232 | self._status_icon.set_visible(True)
|
| 238 | 233 | |
| 239 | - if count > 0:
|
|
| 234 | + if not init and app.window.get_total_unread_count():
|
|
| 240 | 235 | self._status_icon.set_from_icon_name('mail-message-new')
|
| 241 | 236 | return
|
| 242 | 237 | |
| ... | ... | @@ -258,17 +253,22 @@ def _on_size_changed(self, |
| 258 | 253 | self.update_state()
|
| 259 | 254 | |
| 260 | 255 | def _on_popup_menu(self,
|
| 261 | - _status_icon: Gtk.StatusIcon,
|
|
| 256 | + status_icon: Gtk.StatusIcon,
|
|
| 262 | 257 | button: int,
|
| 263 | 258 | activate_time: int) -> None:
|
| 264 | - if button == 1:
|
|
| 265 | - self._on_activate()
|
|
| 266 | - elif button == 2:
|
|
| 267 | - self._on_activate()
|
|
| 259 | + |
|
| 260 | + if button in (1, 2):
|
|
| 261 | + self._on_activate(status_icon)
|
|
| 268 | 262 | elif button == 3:
|
| 269 | - self._build_menu(activate_time)
|
|
| 263 | + self._build_menu(button, activate_time)
|
|
| 264 | + |
|
| 265 | + def _on_activate(self, _status_icon: Gtk.StatusIcon) -> None:
|
|
| 266 | + if app.window.has_toplevel_focus():
|
|
| 267 | + app.window.hide()
|
|
| 268 | + else:
|
|
| 269 | + app.window.show()
|
|
| 270 | 270 | |
| 271 | - def _build_menu(self, event_time: int) -> None:
|
|
| 271 | + def _build_menu(self, button: int, event_time: int) -> None:
|
|
| 272 | 272 | for menu in self._popup_menus:
|
| 273 | 273 | menu.destroy()
|
| 274 | 274 | |
| ... | ... | @@ -289,7 +289,7 @@ def _build_menu(self, event_time: int) -> None: |
| 289 | 289 | |
| 290 | 290 | self._ui.systray_context_menu.show_all()
|
| 291 | 291 | self._ui.systray_context_menu.popup(
|
| 292 | - None, None, None, None, 0, event_time)
|
|
| 292 | + None, None, None, None, button, event_time)
|
|
| 293 | 293 | |
| 294 | 294 | |
| 295 | 295 | class AppIndicator(GtkMenuBackend):
|
| ... | ... | @@ -306,16 +306,16 @@ def __init__(self) -> None: |
| 306 | 306 | self._status_icon.set_secondary_activate_target(
|
| 307 | 307 | self._ui.toggle_window_menuitem)
|
| 308 | 308 | |
| 309 | - self.update_state()
|
|
| 309 | + self.update_state(init=True)
|
|
| 310 | 310 | |
| 311 | - def update_state(self, count: int = 0) -> None:
|
|
| 311 | + def update_state(self, init: bool = False) -> None:
|
|
| 312 | 312 | if not app.settings.get('show_trayicon'):
|
| 313 | 313 | self._status_icon.set_status(appindicator.IndicatorStatus.PASSIVE)
|
| 314 | 314 | return
|
| 315 | 315 | |
| 316 | 316 | self._status_icon.set_status(appindicator.IndicatorStatus.ACTIVE)
|
| 317 | 317 | |
| 318 | - if count > 0:
|
|
| 318 | + if not init and app.window.get_total_unread_count():
|
|
| 319 | 319 | icon_name = 'mail-message-new'
|
| 320 | 320 | self._status_icon.set_icon_full(icon_name, _('Pending Event'))
|
| 321 | 321 | return
|
| ... | ... | @@ -206,6 +206,8 @@ def _add_resources(self, contact: types.BareContact) -> None: |
| 206 | 206 | show_image.set_halign(Gtk.Align.START)
|
| 207 | 207 | show_image.set_valign(Gtk.Align.CENTER)
|
| 208 | 208 | |
| 209 | + show_string = helpers.get_uf_show(resource.show.value)
|
|
| 210 | + |
|
| 209 | 211 | assert resource.jid.resource is not None
|
| 210 | 212 | resource_string = GLib.markup_escape_text(resource.jid.resource)
|
| 211 | 213 | resource_label = Gtk.Label()
|
| ... | ... | @@ -213,7 +215,7 @@ def _add_resources(self, contact: types.BareContact) -> None: |
| 213 | 215 | resource_label.set_xalign(0)
|
| 214 | 216 | resource_label.set_ellipsize(Pango.EllipsizeMode.END)
|
| 215 | 217 | resource_label.set_max_width_chars(30)
|
| 216 | - resource_label.set_text(resource_string)
|
|
| 218 | + resource_label.set_text(f'{show_string} ({resource_string})')
|
|
| 217 | 219 | |
| 218 | 220 | base_box = Gtk.Box(spacing=6)
|
| 219 | 221 | base_box.add(show_image)
|
| ... | ... | @@ -41,6 +41,7 @@ |
| 41 | 41 | "gajim/common/sound.py",
|
| 42 | 42 | "gajim/common/storage/",
|
| 43 | 43 | "gajim/common/task_manager.py",
|
| 44 | + "gajim/gtk/conversation",
|
|
| 44 | 45 | "gajim/gtk/about.py",
|
| 45 | 46 | "gajim/gtk/account_page.py",
|
| 46 | 47 | "gajim/gtk/account_side_bar.py",
|
| ... | ... | @@ -65,9 +66,8 @@ |
| 65 | 66 | "gajim/gtk/control_stack.py",
|
| 66 | 67 | "gajim/gtk/const.py",
|
| 67 | 68 | "gajim/gtk/contact_info.py",
|
| 68 | - "gajim/gtk/contacts_flowbox.py",
|
|
| 69 | 69 | "gajim/gtk/contact_settings.py",
|
| 70 | - "gajim/gtk/conversation",
|
|
| 70 | + "gajim/gtk/contacts_flowbox.py",
|
|
| 71 | 71 | "gajim/gtk/dialogs.py",
|
| 72 | 72 | "gajim/gtk/emoji_data_gtk.py",
|
| 73 | 73 | "gajim/gtk/exception.py",
|
| ... | ... | @@ -79,11 +79,13 @@ |
| 79 | 79 | "gajim/gtk/groupchat_details.py",
|
| 80 | 80 | "gajim/gtk/groupchat_invitation.py",
|
| 81 | 81 | "gajim/gtk/groupchat_inviter.py",
|
| 82 | - "gajim/gtk/groupchat_nick.py",
|
|
| 82 | + "gajim/gtk/groupchat_join.py",
|
|
| 83 | 83 | "gajim/gtk/groupchat_nick_completion.py",
|
| 84 | + "gajim/gtk/groupchat_nick.py",
|
|
| 84 | 85 | "gajim/gtk/groupchat_outcasts.py",
|
| 85 | 86 | "gajim/gtk/groupchat_settings.py",
|
| 86 | 87 | "gajim/gtk/groupchat_state.py",
|
| 88 | + "gajim/gtk/gstreamer.py",
|
|
| 87 | 89 | "gajim/gtk/history_export.py",
|
| 88 | 90 | "gajim/gtk/history_sync.py",
|
| 89 | 91 | "gajim/gtk/main_stack.py",
|