Daniel Brötzmann pushed to branch pinned-chat-dnd at gajim / gajim
Commits:
-
e4245494
by lovetox at 2022-08-20T14:07:50+02:00
-
a0d69074
by lovetox at 2022-08-20T14:10:53+02:00
-
801bec06
by lovetox at 2022-08-20T14:11:20+02:00
-
923a07f4
by lovetox at 2022-08-20T14:19:10+02:00
-
90c796c5
by lovetox at 2022-08-20T15:41:21+02:00
-
287ef4f7
by lovetox at 2022-08-20T20:06:32+02:00
-
b14c590d
by lovetox at 2022-08-20T20:23:07+02:00
-
51ab5fe8
by lovetox at 2022-08-20T21:03:52+02:00
-
18ac2084
by lovetox at 2022-08-20T23:36:28+02:00
-
12669c51
by lovetox at 2022-08-21T00:28:36+02:00
-
ae699c18
by lovetox at 2022-08-21T11:29:53+02:00
-
32c0d3e3
by lovetox at 2022-08-21T11:42:29+02:00
-
ca5f740c
by lovetox at 2022-08-21T11:57:39+02:00
-
afdee60e
by lovetox at 2022-08-21T15:55:46+02:00
-
585107ce
by wurstsalat at 2022-08-21T15:55:46+02:00
27 changed files:
- data/gajim-remote.1
- gajim/common/app.py
- gajim/common/commands.py
- gajim/common/modules/chatstates.py
- gajim/common/setting_values.py
- gajim/common/settings.py
- gajim/common/types.py
- gajim/data/other/shortcuts.json
- − gajim/dev/__init__.py
- − gajim/dev/ipython_view.py
- gajim/gtk/application.py
- gajim/gtk/avatar.py
- gajim/gtk/avatar_selector.py
- gajim/gtk/chat_banner.py
- gajim/gtk/chat_list.py
- gajim/gtk/chat_list_stack.py
- gajim/gtk/chat_page.py
- gajim/gtk/chat_stack.py
- gajim/gtk/const.py
- gajim/gtk/main.py
- gajim/gtk/menus.py
- gajim/gtk/message_actions_box.py
- gajim/gtk/profile.py
- gajim/gtk/util.py
- gajim/gtk/workspace_side_bar.py
- gajim/gui_interface.py
- pyrightconfig.json
Changes:
| ... | ... | @@ -53,8 +53,6 @@ Sends custom XML |
| 53 | 53 | Changes the priority of account or accounts
|
| 54 | 54 | .Ss show_next_pending_event
|
| 55 | 55 | Pops up a window with the next pending event
|
| 56 | -.Ss toggle_ipython
|
|
| 57 | -Shows or hides the ipython window
|
|
| 58 | 56 | .Ss toggle_roster_appearance
|
| 59 | 57 | Shows or hides the roster window
|
| 60 | 58 | .Sh EXAMPLES
|
| ... | ... | @@ -75,7 +75,6 @@ |
| 75 | 75 | connections: dict[str, types.Client] = {}
|
| 76 | 76 | avatar_cache: dict[str, dict[str, Any]] = {}
|
| 77 | 77 | bob_cache: dict[str, bytes] = {}
|
| 78 | -ipython_window = None
|
|
| 79 | 78 | app = None # type: GajimApplication
|
| 80 | 79 | window = None # type: MainWindow
|
| 81 | 80 | commands = None # type: ChatCommands
|
| ... | ... | @@ -52,12 +52,15 @@ def split_argument_string(string: str) -> list[str]: |
| 52 | 52 | return result
|
| 53 | 53 | |
| 54 | 54 | |
| 55 | -def get_usage_from_command(cmd: argparse.ArgumentParser) -> str:
|
|
| 55 | +def get_usage_from_command(cmd: argparse.ArgumentParser,
|
|
| 56 | + command_name: str
|
|
| 57 | + ) -> str:
|
|
| 58 | + |
|
| 56 | 59 | with io.StringIO() as output:
|
| 57 | 60 | cmd.print_usage(file=output)
|
| 58 | 61 | usage = output.getvalue()
|
| 59 | 62 | |
| 60 | - usage = usage.split('[-h] ')[1]
|
|
| 63 | + usage = usage.split(f'ChatCommands {command_name}')[1]
|
|
| 61 | 64 | return usage.strip()
|
| 62 | 65 | |
| 63 | 66 | |
| ... | ... | @@ -69,7 +72,7 @@ class CommandError(Exception): |
| 69 | 72 | pass
|
| 70 | 73 | |
| 71 | 74 | |
| 72 | -class CommandNotFoundError(Exception):
|
|
| 75 | +class CommandFailed(Exception):
|
|
| 73 | 76 | pass
|
| 74 | 77 | |
| 75 | 78 | |
| ... | ... | @@ -82,7 +85,7 @@ class ChatCommands(Observable): |
| 82 | 85 | def __init__(self) -> None:
|
| 83 | 86 | Observable.__init__(self)
|
| 84 | 87 | self._parser = ArgumentParser(prog='ChatCommands')
|
| 85 | - self._sub_parser = self._parser.add_subparsers()
|
|
| 88 | + self._sub_parser = self._parser.add_subparsers(title='Commands')
|
|
| 86 | 89 | self._commands: dict[str, tuple[list[str], str]] = {}
|
| 87 | 90 | |
| 88 | 91 | self._create_commands()
|
| ... | ... | @@ -95,6 +98,16 @@ def get_commands(self, used_in: str) -> list[tuple[str, str]]: |
| 95 | 98 | commands.sort(key=operator.itemgetter(0))
|
| 96 | 99 | return commands
|
| 97 | 100 | |
| 101 | + def _generate_help(self, used_in: str) -> str:
|
|
| 102 | + help_text = 'Commands:\n\n'
|
|
| 103 | + commands = self.get_commands(used_in)
|
|
| 104 | + for command in commands:
|
|
| 105 | + name, usage = command
|
|
| 106 | + if name == 'help':
|
|
| 107 | + continue
|
|
| 108 | + help_text += f' {name:<15} {usage}\n'
|
|
| 109 | + return help_text
|
|
| 110 | + |
|
| 98 | 111 | def make_parser(self,
|
| 99 | 112 | command_name: str,
|
| 100 | 113 | callback: Callable[..., Any],
|
| ... | ... | @@ -104,9 +117,11 @@ def make_parser(self, |
| 104 | 117 | it with the command name.
|
| 105 | 118 | '''
|
| 106 | 119 | |
| 107 | - parser = self._sub_parser.add_parser(command_name, **kwargs)
|
|
| 108 | - parser.set_defaults(command_name=command_name)
|
|
| 109 | - self.connect(command_name, callback)
|
|
| 120 | + parser = self._sub_parser.add_parser(command_name,
|
|
| 121 | + add_help=False,
|
|
| 122 | + **kwargs)
|
|
| 123 | + parser.set_defaults(command_name=command_name,
|
|
| 124 | + exec=callback)
|
|
| 110 | 125 | return parser
|
| 111 | 126 | |
| 112 | 127 | def add_command(self,
|
| ... | ... | @@ -115,7 +130,7 @@ def add_command(self, |
| 115 | 130 | cmd: argparse.ArgumentParser
|
| 116 | 131 | ) -> None:
|
| 117 | 132 | |
| 118 | - usage = get_usage_from_command(cmd)
|
|
| 133 | + usage = get_usage_from_command(cmd, command_name)
|
|
| 119 | 134 | self._commands[command_name] = (used_in, usage)
|
| 120 | 135 | |
| 121 | 136 | def parse(self, type_: str, arg_string: str) -> None:
|
| ... | ... | @@ -124,13 +139,33 @@ def parse(self, type_: str, arg_string: str) -> None: |
| 124 | 139 | command_name = arg_list[0]
|
| 125 | 140 | command = self._commands.get(command_name)
|
| 126 | 141 | if command is None or type_ not in command[0]:
|
| 127 | - raise CommandNotFoundError(_('Unknown command: %s' % command_name))
|
|
| 142 | + self.notify('command-not-found',
|
|
| 143 | + _('Unknown command: %s' % command_name))
|
|
| 144 | + raise CommandFailed
|
|
| 145 | + |
|
| 146 | + try:
|
|
| 147 | + args = self._parser.parse_args(arg_list)
|
|
| 148 | + except ArgumentParserError as error:
|
|
| 149 | + self.notify('command-error', str(error))
|
|
| 150 | + raise CommandFailed
|
|
| 151 | + |
|
| 152 | + args.used_in = type_
|
|
| 153 | + |
|
| 154 | + try:
|
|
| 155 | + result = args.exec(args)
|
|
| 156 | + except CommandError as error:
|
|
| 157 | + self.notify('command-error', str(error))
|
|
| 158 | + raise CommandFailed
|
|
| 128 | 159 | |
| 129 | - args = self._parser.parse_args(arg_list)
|
|
| 160 | + if result is None:
|
|
| 161 | + result = _('Command executed successfully')
|
|
| 130 | 162 | |
| 131 | - self.notify(args.command_name, args)
|
|
| 163 | + self.notify('command-result', result)
|
|
| 132 | 164 | |
| 133 | 165 | def _create_commands(self) -> None:
|
| 166 | + parser = self.make_parser('help', self._help_command)
|
|
| 167 | + self.add_command('help', ['chat', 'groupchat', 'pm'], parser)
|
|
| 168 | + |
|
| 134 | 169 | parser = self.make_parser('status', self._status_command)
|
| 135 | 170 | parser.add_argument('status',
|
| 136 | 171 | choices=['online', 'away', 'xa', 'dnd'])
|
| ... | ... | @@ -164,12 +199,10 @@ def _create_commands(self) -> None: |
| 164 | 199 | choices=['moderator', 'participant', 'visitor'])
|
| 165 | 200 | self.add_command('role', ['groupchat'], parser)
|
| 166 | 201 | |
| 167 | - def _status_command(self,
|
|
| 168 | - chat_commands: Any,
|
|
| 169 | - signal_name: str,
|
|
| 170 | - args: Any
|
|
| 171 | - ) -> None:
|
|
| 202 | + def _help_command(self, args: Any) -> str:
|
|
| 203 | + return self._generate_help(args.used_in)
|
|
| 172 | 204 | |
| 205 | + def _status_command(self, args: Any) -> None:
|
|
| 173 | 206 | for client in app.get_clients():
|
| 174 | 207 | if not app.settings.get_account_setting(client.account,
|
| 175 | 208 | 'sync_with_global_status'):
|
| ... | ... | @@ -189,12 +222,7 @@ def _check_if_joined(self) -> GroupchatContact: |
| 189 | 222 | 'joined this group chat'))
|
| 190 | 223 | return contact
|
| 191 | 224 | |
| 192 | - def _invite_command(self,
|
|
| 193 | - chat_commands: Any,
|
|
| 194 | - signal_name: str,
|
|
| 195 | - args: Any
|
|
| 196 | - ) -> None:
|
|
| 197 | - |
|
| 225 | + def _invite_command(self, args: Any) -> None:
|
|
| 198 | 226 | contact = self._check_if_joined()
|
| 199 | 227 | |
| 200 | 228 | try:
|
| ... | ... | @@ -242,20 +270,10 @@ def _change_affiliation(self, |
| 242 | 270 | {jid: {'affiliation': affiliation,
|
| 243 | 271 | 'reason': reason}})
|
| 244 | 272 | |
| 245 | - def _ban_command(self,
|
|
| 246 | - chat_commands: Any,
|
|
| 247 | - signal_name: str,
|
|
| 248 | - args: Any
|
|
| 249 | - ) -> None:
|
|
| 250 | - |
|
| 273 | + def _ban_command(self, args: Any) -> None:
|
|
| 251 | 274 | self._change_affiliation(args.who, 'outcast', args.reason)
|
| 252 | 275 | |
| 253 | - def _affiliate_command(self,
|
|
| 254 | - chat_commands: Any,
|
|
| 255 | - signal_name: str,
|
|
| 256 | - args: Any
|
|
| 257 | - ) -> None:
|
|
| 258 | - |
|
| 276 | + def _affiliate_command(self, args: Any) -> None:
|
|
| 259 | 277 | self._change_affiliation(args.who, args.affiliation, None)
|
| 260 | 278 | |
| 261 | 279 | def _change_role(self, nick: str, role: str, reason: Optional[str]) -> None:
|
| ... | ... | @@ -278,18 +296,8 @@ def _change_role(self, nick: str, role: str, reason: Optional[str]) -> None: |
| 278 | 296 | role,
|
| 279 | 297 | reason)
|
| 280 | 298 | |
| 281 | - def _kick_command(self,
|
|
| 282 | - chat_commands: Any,
|
|
| 283 | - signal_name: str,
|
|
| 284 | - args: Any
|
|
| 285 | - ) -> None:
|
|
| 286 | - |
|
| 299 | + def _kick_command(self, args: Any) -> None:
|
|
| 287 | 300 | self._change_role(args.who, 'none', args.reason)
|
| 288 | 301 | |
| 289 | - def _role_command(self,
|
|
| 290 | - chat_commands: Any,
|
|
| 291 | - signal_name: str,
|
|
| 292 | - args: Any
|
|
| 293 | - ) -> None:
|
|
| 294 | - |
|
| 302 | + def _role_command(self, args: Any) -> None:
|
|
| 295 | 303 | self._change_role(args.who, args.role, None) |
| ... | ... | @@ -36,6 +36,8 @@ |
| 36 | 36 | from gajim.common.const import ClientState
|
| 37 | 37 | from gajim.common.structs import OutgoingMessage
|
| 38 | 38 | from gajim.common.modules.base import BaseModule
|
| 39 | +from gajim.common.modules.contacts import GroupchatParticipant
|
|
| 40 | +from gajim.common.modules.contacts import BareContact
|
|
| 39 | 41 | |
| 40 | 42 | INACTIVE_AFTER = 60
|
| 41 | 43 | PAUSED_AFTER = 10
|
| ... | ... | @@ -270,9 +272,9 @@ def set_chatstate(self, contact: types.ChatContactT, state: State) -> None: |
| 270 | 272 | if setting == 'disabled':
|
| 271 | 273 | # Send a last 'active' state after user disabled chatstates
|
| 272 | 274 | if current_state is not None:
|
| 273 | - self._log.info('Disabled for %s', contact.jid)
|
|
| 275 | + self._log.info('Disabled for %s', contact)
|
|
| 274 | 276 | self._log.info('Send last state: %-10s - %s',
|
| 275 | - State.ACTIVE, contact.jid)
|
|
| 277 | + State.ACTIVE, contact)
|
|
| 276 | 278 | |
| 277 | 279 | self._send_chatstate(contact, State.ACTIVE)
|
| 278 | 280 | |
| ... | ... | @@ -281,22 +283,23 @@ def set_chatstate(self, contact: types.ChatContactT, state: State) -> None: |
| 281 | 283 | self._last_keyboard_activity.pop(contact.jid, None)
|
| 282 | 284 | return
|
| 283 | 285 | |
| 284 | - if not contact.is_groupchat:
|
|
| 285 | - # Don’t leak presence to contacts
|
|
| 286 | - # which are not allowed to see our status
|
|
| 287 | - if not contact.is_pm_contact:
|
|
| 288 | - # TODO
|
|
| 289 | - # if contact and contact.sub in ('to', 'none'):
|
|
| 290 | - # self._log.info('Contact not subscribed: %s', contact.jid)
|
|
| 291 | - # return
|
|
| 292 | - pass
|
|
| 286 | + if isinstance(contact, BareContact):
|
|
| 287 | + if not contact.is_subscribed:
|
|
| 288 | + self._log.debug('Contact not subscribed: %s', contact)
|
|
| 289 | + return
|
|
| 293 | 290 | |
| 294 | 291 | if not contact.is_available:
|
| 295 | - self._log.info('Contact offline: %s', contact.jid)
|
|
| 292 | + self._log.debug('Contact offline: %s', contact)
|
|
| 296 | 293 | return
|
| 297 | 294 | |
| 298 | - if not contact.supports(Namespace.CHATSTATES):
|
|
| 299 | - self._log.info('Chatstates not supported: %s', contact.jid)
|
|
| 295 | + elif isinstance(contact, GroupchatParticipant):
|
|
| 296 | + if not contact.is_available:
|
|
| 297 | + self._log.debug('Contact offline: %s', contact)
|
|
| 298 | + return
|
|
| 299 | + |
|
| 300 | + else:
|
|
| 301 | + if not contact.is_joined:
|
|
| 302 | + self._log.debug('Groupchat not joined: %s', contact)
|
|
| 300 | 303 | return
|
| 301 | 304 | |
| 302 | 305 | if state in (State.ACTIVE, State.COMPOSING):
|
| ... | ... | @@ -309,7 +312,7 @@ def set_chatstate(self, contact: types.ChatContactT, state: State) -> None: |
| 309 | 312 | if current_state == state:
|
| 310 | 313 | return
|
| 311 | 314 | |
| 312 | - self._log.info('Send: %-10s - %s', state, contact.jid)
|
|
| 315 | + self._log.info('Send: %-10s - %s', state, contact)
|
|
| 313 | 316 | |
| 314 | 317 | self._send_chatstate(contact, state)
|
| 315 | 318 |
| ... | ... | @@ -164,7 +164,7 @@ class _ACCOUNT_DEFAULT: |
| 164 | 164 | ]
|
| 165 | 165 | |
| 166 | 166 | AllSettings = Literal[BoolSettings, IntSettings, StringSettings]
|
| 167 | -AllSettingsT = Union[str, int, bool]
|
|
| 167 | +AllSettingsT = Union[str, int, bool, list[str]]
|
|
| 168 | 168 | |
| 169 | 169 | APP_SETTINGS = {
|
| 170 | 170 | 'always_english_wikipedia': False,
|
| ... | ... | @@ -508,15 +508,24 @@ class _ACCOUNT_DEFAULT: |
| 508 | 508 | |
| 509 | 509 | AllWorkspaceSettings = Literal[StringWorkspaceSettings, 'open_chats']
|
| 510 | 510 | |
| 511 | -OpenChatSettingT = list[tuple[str, JID, str, bool]]
|
|
| 512 | -AllWorkspaceSettingsT = Union[str, OpenChatSettingT]
|
|
| 511 | + |
|
| 512 | +class OpenChatSettingDetails(TypedDict):
|
|
| 513 | + account: str
|
|
| 514 | + jid: JID
|
|
| 515 | + type: str
|
|
| 516 | + pinned: bool
|
|
| 517 | + position: int
|
|
| 518 | + |
|
| 519 | + |
|
| 520 | +OpenChatsSettingT = list[OpenChatSettingDetails]
|
|
| 521 | +AllWorkspaceSettingsT = Union[str, OpenChatsSettingT]
|
|
| 513 | 522 | |
| 514 | 523 | |
| 515 | 524 | class WorkspaceSettings(TypedDict):
|
| 516 | 525 | name: str
|
| 517 | 526 | color: str
|
| 518 | 527 | avatar_sha: str
|
| 519 | - open_chats: OpenChatSettingT
|
|
| 528 | + open_chats: OpenChatsSettingT
|
|
| 520 | 529 | |
| 521 | 530 | |
| 522 | 531 | WORKSPACE_SETTINGS: WorkspaceSettings = {
|
| ... | ... | @@ -56,7 +56,7 @@ |
| 56 | 56 | from gajim.common.setting_values import StringGroupChatSettings
|
| 57 | 57 | from gajim.common.setting_values import AllWorkspaceSettings
|
| 58 | 58 | from gajim.common.setting_values import AllWorkspaceSettingsT
|
| 59 | -from gajim.common.setting_values import OpenChatSettingT
|
|
| 59 | +from gajim.common.setting_values import OpenChatsSettingT
|
|
| 60 | 60 | from gajim.common.setting_values import StringWorkspaceSettings
|
| 61 | 61 | from gajim.common.setting_values import ACCOUNT_SETTINGS
|
| 62 | 62 | from gajim.common.setting_values import PROXY_SETTINGS
|
| ... | ... | @@ -82,6 +82,8 @@ |
| 82 | 82 | |
| 83 | 83 | log = logging.getLogger('gajim.c.settings')
|
| 84 | 84 | |
| 85 | +CURRENT_USER_VERSION = 2
|
|
| 86 | + |
|
| 85 | 87 | CREATE_SQL = '''
|
| 86 | 88 | CREATE TABLE settings (
|
| 87 | 89 | name TEXT UNIQUE,
|
| ... | ... | @@ -100,10 +102,11 @@ |
| 100 | 102 | INSERT INTO settings(name, settings) VALUES ('plugins', '{}');
|
| 101 | 103 | INSERT INTO settings(name, settings) VALUES ('workspaces', '%s');
|
| 102 | 104 | |
| 103 | - PRAGMA user_version=1;
|
|
| 105 | + PRAGMA user_version=%s;
|
|
| 104 | 106 | ''' % (json.dumps(STATUS_PRESET_EXAMPLES),
|
| 105 | 107 | json.dumps(PROXY_EXAMPLES),
|
| 106 | - json.dumps(INITAL_WORKSPACE))
|
|
| 108 | + json.dumps(INITAL_WORKSPACE),
|
|
| 109 | + CURRENT_USER_VERSION)
|
|
| 107 | 110 | |
| 108 | 111 | |
| 109 | 112 | _SignalCallable = Callable[[Any, str, Optional[str], Optional[JID]], Any]
|
| ... | ... | @@ -217,6 +220,7 @@ def init(self) -> None: |
| 217 | 220 | self._commit()
|
| 218 | 221 | self._migrate_database()
|
| 219 | 222 | self._load_app_overrides()
|
| 223 | + self._commit()
|
|
| 220 | 224 | |
| 221 | 225 | @staticmethod
|
| 222 | 226 | def _setup_installation_defaults() -> None:
|
| ... | ... | @@ -310,6 +314,8 @@ def _migrate_database(self) -> None: |
| 310 | 314 | self._con.close()
|
| 311 | 315 | log.exception('Error')
|
| 312 | 316 | sys.exit()
|
| 317 | + else:
|
|
| 318 | + self._commit_settings('workspaces')
|
|
| 313 | 319 | |
| 314 | 320 | def _migrate(self) -> None:
|
| 315 | 321 | version = self._get_user_version()
|
| ... | ... | @@ -320,6 +326,21 @@ def _migrate(self) -> None: |
| 320 | 326 | self._settings['workspaces'] = INITAL_WORKSPACE
|
| 321 | 327 | self._set_user_version(1)
|
| 322 | 328 | |
| 329 | + if version < 2:
|
|
| 330 | + # Migrate open chats tuple to dict
|
|
| 331 | + for workspace in self._settings['workspaces'].values():
|
|
| 332 | + open_chats = []
|
|
| 333 | + for open_chat in workspace.get('open_chats', []):
|
|
| 334 | + account, jid, type_, pinned = open_chat
|
|
| 335 | + open_chats.append({'account': account,
|
|
| 336 | + 'jid': jid,
|
|
| 337 | + 'type': type_,
|
|
| 338 | + 'pinned': pinned,
|
|
| 339 | + 'position': -1})
|
|
| 340 | + |
|
| 341 | + workspace['open_chats'] = open_chats
|
|
| 342 | + self._set_user_version(2)
|
|
| 343 | + |
|
| 323 | 344 | def _migrate_old_config(self) -> None:
|
| 324 | 345 | config_file = configpaths.get('CONFIG_FILE')
|
| 325 | 346 | if not config_file.exists():
|
| ... | ... | @@ -585,6 +606,12 @@ def set_app_setting(self, |
| 585 | 606 | value: Optional[int]) -> None:
|
| 586 | 607 | ...
|
| 587 | 608 | |
| 609 | + @overload
|
|
| 610 | + def set_app_setting(self,
|
|
| 611 | + setting: Literal['workspace_order'],
|
|
| 612 | + value: list[str]) -> None:
|
|
| 613 | + ...
|
|
| 614 | + |
|
| 588 | 615 | def set_app_setting(self,
|
| 589 | 616 | setting: str,
|
| 590 | 617 | value: Optional[AllSettingsT]) -> None:
|
| ... | ... | @@ -1175,7 +1202,7 @@ def set_workspace_setting(self, |
| 1175 | 1202 | def set_workspace_setting(self,
|
| 1176 | 1203 | workspace_id: str,
|
| 1177 | 1204 | setting: Literal['open_chats'],
|
| 1178 | - value: list[tuple[str, JID, str, bool]]
|
|
| 1205 | + value: OpenChatsSettingT
|
|
| 1179 | 1206 | ) -> None:
|
| 1180 | 1207 | ...
|
| 1181 | 1208 | |
| ... | ... | @@ -1202,7 +1229,7 @@ def set_workspace_setting(self, |
| 1202 | 1229 | def get_workspace_setting(self,
|
| 1203 | 1230 | workspace_id: str,
|
| 1204 | 1231 | setting: Literal['open_chats']
|
| 1205 | - ) -> OpenChatSettingT:
|
|
| 1232 | + ) -> OpenChatsSettingT:
|
|
| 1206 | 1233 | ...
|
| 1207 | 1234 | |
| 1208 | 1235 | @overload
|
| ... | ... | @@ -25,9 +25,12 @@ |
| 25 | 25 | from gi.repository import GdkPixbuf
|
| 26 | 26 | |
| 27 | 27 | import nbxmpp
|
| 28 | +from nbxmpp.const import PresenceShow
|
|
| 28 | 29 | from nbxmpp.protocol import JID
|
| 29 | 30 | from nbxmpp.structs import BookmarkData
|
| 30 | 31 | |
| 32 | +from gajim.common.const import PresenceShowExt
|
|
| 33 | + |
|
| 31 | 34 | if TYPE_CHECKING:
|
| 32 | 35 | from gajim.common.client import Client
|
| 33 | 36 | from nbxmpp.client import Client as xmppClient # noqa: F401
|
| ... | ... | @@ -49,7 +52,6 @@ |
| 49 | 52 | GroupchatContact,
|
| 50 | 53 | GroupchatParticipant]
|
| 51 | 54 | |
| 52 | - |
|
| 53 | 55 | InterfaceT = Union['Interface']
|
| 54 | 56 | PluginManagerT = Union['PluginManager']
|
| 55 | 57 | PluginRepositoryT = Union['PluginRepository']
|
| ... | ... | @@ -78,3 +80,5 @@ |
| 78 | 80 | ChatContactT = Union['BareContact', 'GroupchatContact', 'GroupchatParticipant']
|
| 79 | 81 | OneOnOneContactT = Union['BareContact', 'GroupchatParticipant']
|
| 80 | 82 | GroupchatContactT = Union['GroupchatContact']
|
| 83 | + |
|
| 84 | +PresenceShowT = Union[PresenceShowExt, PresenceShow] |
| ... | ... | @@ -5,7 +5,6 @@ |
| 5 | 5 | "app.plugins": ["<Primary>E"],
|
| 6 | 6 | "app.xml-console": ["<Primary><Shift>X"],
|
| 7 | 7 | "app.file-transfer": ["<Primary>T"],
|
| 8 | - "app.ipython": ["<Primary><Alt>I"],
|
|
| 9 | 8 | "app.start-chat::": ["<Primary>N"],
|
| 10 | 9 | "app.create-groupchat::": ["<Primary>G"],
|
| 11 | 10 | "win.change-nickname": ["<Primary><Shift>N"],
|
| 1 | -#!/usr/bin/python
|
|
| 2 | - |
|
| 3 | -# Copyright (C) 2008-2014 Yann Leboulanger <asterix AT lagaule.org>
|
|
| 4 | -#
|
|
| 5 | -# This file is part of Gajim.
|
|
| 6 | -#
|
|
| 7 | -# Gajim is free software; you can redistribute it and/or modify
|
|
| 8 | -# it under the terms of the GNU General Public License as published
|
|
| 9 | -# by the Free Software Foundation; version 3 only.
|
|
| 10 | -#
|
|
| 11 | -# Gajim is distributed in the hope that it will be useful,
|
|
| 12 | -# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 13 | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 14 | -# GNU General Public License for more details.
|
|
| 15 | -#
|
|
| 16 | -# You should have received a copy of the GNU General Public License
|
|
| 17 | -# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
| 18 | -#
|
|
| 19 | -# Copyright (c) 2007, IBM Corporation
|
|
| 20 | -# All rights reserved.
|
|
| 21 | - |
|
| 22 | -# Redistribution and use in source and binary forms, with or without
|
|
| 23 | -# modification, are permitted provided that the following conditions are met:
|
|
| 24 | - |
|
| 25 | -# * Redistributions of source code must retain the above copyright notice,
|
|
| 26 | -# this list of conditions and the following disclaimer.
|
|
| 27 | -# * Redistributions in binary form must reproduce the above copyright notice,
|
|
| 28 | -# this list of conditions and the following disclaimer in the documentation
|
|
| 29 | -# and/or other materials provided with the distribution.
|
|
| 30 | -# * Neither the name of the IBM Corporation nor the names of its contributors
|
|
| 31 | -# may be used to endorse or promote products derived from this software
|
|
| 32 | -# without specific prior written permission.
|
|
| 33 | - |
|
| 34 | -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
| 35 | -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
| 36 | -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
| 37 | -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
| 38 | -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
| 39 | -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
| 40 | -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
| 41 | -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
| 42 | -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
| 43 | -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
| 44 | -# POSSIBILITY OF SUCH DAMAGE.
|
|
| 45 | - |
|
| 46 | -'''
|
|
| 47 | -Provides IPython console widget
|
|
| 48 | - |
|
| 49 | -@author: Eitan Isaacson
|
|
| 50 | -@organization: IBM Corporation
|
|
| 51 | -@copyright: Copyright (c) 2007 IBM Corporation
|
|
| 52 | -@license: BSD
|
|
| 53 | - |
|
| 54 | -All rights reserved. This program and the accompanying materials are made
|
|
| 55 | -available under the terms of the BSD which accompanies this distribution, and
|
|
| 56 | -is available at U{http://www.opensource.org/licenses/bsd-license.php}
|
|
| 57 | -'''
|
|
| 58 | - |
|
| 59 | -import re
|
|
| 60 | -import sys
|
|
| 61 | -import os
|
|
| 62 | -from io import StringIO
|
|
| 63 | -from functools import reduce
|
|
| 64 | - |
|
| 65 | -from gi.repository import Gtk
|
|
| 66 | -from gi.repository import Gdk
|
|
| 67 | -from gi.repository import GObject
|
|
| 68 | -from gi.repository import GLib
|
|
| 69 | -from gi.repository import Pango
|
|
| 70 | - |
|
| 71 | -from pygments.token import Token
|
|
| 72 | - |
|
| 73 | -import IPython
|
|
| 74 | -from IPython.core.displayhook import DisplayHook
|
|
| 75 | -from IPython.core.display_trap import DisplayTrap
|
|
| 76 | - |
|
| 77 | - |
|
| 78 | -class MyPromptDisplayHook(DisplayHook):
|
|
| 79 | - def __init__(self, shell, view):
|
|
| 80 | - DisplayHook.__init__(self, shell=shell)
|
|
| 81 | - self.view = view
|
|
| 82 | - |
|
| 83 | - def write_output_prompt(self):
|
|
| 84 | - tokens = self.shell.prompts.out_prompt_tokens()
|
|
| 85 | - self.view.write('\n')
|
|
| 86 | - self.view.write(tokens)
|
|
| 87 | - |
|
| 88 | - |
|
| 89 | -class IterableIPShell:
|
|
| 90 | - '''
|
|
| 91 | - Create an IPython instance. Does not start a blocking event loop,
|
|
| 92 | - instead allow single iterations. This allows embedding in GTK
|
|
| 93 | - without blockage
|
|
| 94 | - |
|
| 95 | - @ivar IP: IPython instance.
|
|
| 96 | - @type IP: IPython.iplib.InteractiveShell
|
|
| 97 | - @ivar iter_more: Indicates if the line executed was a complete command,
|
|
| 98 | - or we should wait for more.
|
|
| 99 | - @type iter_more: integer
|
|
| 100 | - @ivar history_level: The place in history where we currently are
|
|
| 101 | - when pressing up/down.
|
|
| 102 | - @type history_level: integer
|
|
| 103 | - @ivar complete_sep: Separation delimiters for completion function.
|
|
| 104 | - @type complete_sep: _sre.SRE_Pattern
|
|
| 105 | - '''
|
|
| 106 | - def __init__(self, argv=None, user_ns=None, user_global_ns=None, cin=None,
|
|
| 107 | - cout=None, cerr=None, input_func=None):
|
|
| 108 | - '''
|
|
| 109 | - @param argv: Command line options for IPython
|
|
| 110 | - @type argv: list
|
|
| 111 | - @param user_ns: User namespace.
|
|
| 112 | - @type user_ns: dictionary
|
|
| 113 | - @param user_global_ns: User global namespace.
|
|
| 114 | - @type user_global_ns: dictionary.
|
|
| 115 | - @param cin: Console standard input.
|
|
| 116 | - @type cin: IO stream
|
|
| 117 | - @param cout: Console standard output.
|
|
| 118 | - @type cout: IO stream
|
|
| 119 | - @param cerr: Console standard error.
|
|
| 120 | - @type cerr: IO stream
|
|
| 121 | - @param input_func: Replacement for builtin raw_input()
|
|
| 122 | - @type input_func: function
|
|
| 123 | - '''
|
|
| 124 | - if argv is None:
|
|
| 125 | - argv = []
|
|
| 126 | - |
|
| 127 | - io = IPython.utils.io
|
|
| 128 | - if input_func:
|
|
| 129 | - IPython.terminal.interactiveshell.raw_input_original = input_func
|
|
| 130 | - |
|
| 131 | - # This is to get rid of the blockage that occurs during
|
|
| 132 | - # IPython.Shell.InteractiveShell.user_setup()
|
|
| 133 | - io.raw_input = lambda x: None
|
|
| 134 | - |
|
| 135 | - os.environ['TERM'] = 'dumb'
|
|
| 136 | - excepthook = sys.excepthook
|
|
| 137 | - |
|
| 138 | - from traitlets.config.loader import Config
|
|
| 139 | - |
|
| 140 | - cfg = Config()
|
|
| 141 | - cfg.InteractiveShell.colors = 'Linux'
|
|
| 142 | - |
|
| 143 | - # InteractiveShell's __init__ gets a reference of stdout and stderr
|
|
| 144 | - # so we save the standard here to revert it after init
|
|
| 145 | - old_stdout, old_stderr = sys.stdout, sys.stderr
|
|
| 146 | - sys.stdout, sys.stderr = cout, cerr
|
|
| 147 | - |
|
| 148 | - # InteractiveShell inherits from SingletonConfigurable so use instance()
|
|
| 149 | - self.IP = IPython.terminal.embed.InteractiveShellEmbed.instance(
|
|
| 150 | - config=cfg, user_ns=user_ns, user_module=user_global_ns)
|
|
| 151 | - |
|
| 152 | - # Set back stdout and stderr to what it was before
|
|
| 153 | - sys.stdout, sys.stderr = old_stdout, old_stderr
|
|
| 154 | - |
|
| 155 | - self.IP.system = lambda cmd: self.shell(self.IP.var_expand(cmd),
|
|
| 156 | - header='IPython system call: ',
|
|
| 157 | - local_ns=user_ns)
|
|
| 158 | - |
|
| 159 | - self.IP.raw_input = input_func
|
|
| 160 | - sys.excepthook = excepthook
|
|
| 161 | - self.iter_more = 0
|
|
| 162 | - self.history_level = 0
|
|
| 163 | - self.complete_sep = re.compile(r'[\s\{\}\[\]\(\)]')
|
|
| 164 | - self.updateNamespace({'exit': lambda: None})
|
|
| 165 | - self.updateNamespace({'quit': lambda: None})
|
|
| 166 | - |
|
| 167 | - # Workaround for updating namespace with sys.modules
|
|
| 168 | - #
|
|
| 169 | - self.__update_namespace()
|
|
| 170 | - self.lines = []
|
|
| 171 | - |
|
| 172 | - def __update_namespace(self):
|
|
| 173 | - '''
|
|
| 174 | - Update self.IP namespace for autocompletion with sys.modules
|
|
| 175 | - '''
|
|
| 176 | - for k, v in sys.modules.items():
|
|
| 177 | - if '.' not in k:
|
|
| 178 | - self.IP.user_ns.update({k: v})
|
|
| 179 | - |
|
| 180 | - def execute(self):
|
|
| 181 | - '''
|
|
| 182 | - Execute the current line provided by the shell object
|
|
| 183 | - '''
|
|
| 184 | - self.history_level = 0
|
|
| 185 | - orig_stdout = sys.stdout
|
|
| 186 | - sys.stdout = IPython.utils.io.stdout
|
|
| 187 | - |
|
| 188 | - orig_stdin = sys.stdin
|
|
| 189 | - sys.stdin = IPython.utils.io.stdin
|
|
| 190 | - self.prompt = self.generatePrompt(self.iter_more)
|
|
| 191 | - |
|
| 192 | - self.IP.hooks.pre_prompt_hook()
|
|
| 193 | - if self.iter_more:
|
|
| 194 | - try:
|
|
| 195 | - self.prompt = self.generatePrompt(True)
|
|
| 196 | - except Exception:
|
|
| 197 | - self.IP.showtraceback()
|
|
| 198 | - if self.IP.autoindent:
|
|
| 199 | - self.IP.rl_do_indent = True
|
|
| 200 | - |
|
| 201 | - try:
|
|
| 202 | - self.IP.raw_input(self.prompt)
|
|
| 203 | - except KeyboardInterrupt:
|
|
| 204 | - self.IP.write('\nKeyboardInterrupt\n')
|
|
| 205 | - self.IP.input_splitter.reset()
|
|
| 206 | - except Exception:
|
|
| 207 | - self.IP.showtraceback()
|
|
| 208 | - else:
|
|
| 209 | - self.lines.append(self.IP.raw_input(self.prompt))
|
|
| 210 | - self.iter_more = self.IP.check_complete(
|
|
| 211 | - '\n'.join(self.lines))[0] == 'incomplete'
|
|
| 212 | - |
|
| 213 | - self.prompt = self.generatePrompt(self.iter_more)
|
|
| 214 | - if not self.iter_more:
|
|
| 215 | - source_raw = '\n'.join(self.lines)
|
|
| 216 | - self.lines = []
|
|
| 217 | - |
|
| 218 | - self.IP.run_cell(source_raw, store_history=True)
|
|
| 219 | - self.IP.rl_do_indent = False
|
|
| 220 | - else:
|
|
| 221 | - # TODO: Auto-indent
|
|
| 222 | - #
|
|
| 223 | - self.IP.rl_do_indent = True
|
|
| 224 | - |
|
| 225 | - sys.stdout = orig_stdout
|
|
| 226 | - sys.stdin = orig_stdin
|
|
| 227 | - |
|
| 228 | - def generatePrompt(self, is_continuation):
|
|
| 229 | - '''
|
|
| 230 | - Generate prompt depending on is_continuation value
|
|
| 231 | - |
|
| 232 | - @param is_continuation
|
|
| 233 | - @type is_continuation: boolean
|
|
| 234 | - |
|
| 235 | - @return: The prompt string representation
|
|
| 236 | - @rtype: string
|
|
| 237 | - |
|
| 238 | - '''
|
|
| 239 | - # TODO: Update to IPython 5.x and later
|
|
| 240 | - prompt = 'In [%d]: ' % self.IP.execution_count
|
|
| 241 | - |
|
| 242 | - return prompt
|
|
| 243 | - |
|
| 244 | - def historyBack(self):
|
|
| 245 | - '''
|
|
| 246 | - Provide one history command back
|
|
| 247 | - |
|
| 248 | - @return: The command string.
|
|
| 249 | - @rtype: string
|
|
| 250 | - '''
|
|
| 251 | - self.history_level -= 1
|
|
| 252 | - return self._getHistory()
|
|
| 253 | - |
|
| 254 | - def historyForward(self):
|
|
| 255 | - '''
|
|
| 256 | - Provide one history command forward
|
|
| 257 | - |
|
| 258 | - @return: The command string.
|
|
| 259 | - @rtype: string
|
|
| 260 | - '''
|
|
| 261 | - self.history_level += 1
|
|
| 262 | - return self._getHistory()
|
|
| 263 | - |
|
| 264 | - def _getHistory(self):
|
|
| 265 | - '''
|
|
| 266 | - Get the command string of the current history level
|
|
| 267 | - |
|
| 268 | - @return: Historic command string.
|
|
| 269 | - @rtype: string
|
|
| 270 | - '''
|
|
| 271 | - try:
|
|
| 272 | - rv = self.IP.user_ns['In'][self.history_level].strip('\n')
|
|
| 273 | - except IndexError:
|
|
| 274 | - self.history_level = 0
|
|
| 275 | - rv = ''
|
|
| 276 | - return rv
|
|
| 277 | - |
|
| 278 | - def updateNamespace(self, ns_dict):
|
|
| 279 | - '''
|
|
| 280 | - Add the current dictionary to the shell namespace
|
|
| 281 | - |
|
| 282 | - @param ns_dict: A dictionary of symbol-values.
|
|
| 283 | - @type ns_dict: dictionary
|
|
| 284 | - '''
|
|
| 285 | - self.IP.user_ns.update(ns_dict)
|
|
| 286 | - |
|
| 287 | - def complete(self, line):
|
|
| 288 | - '''
|
|
| 289 | - Returns an auto completed line and/or possibilities for completion
|
|
| 290 | - |
|
| 291 | - @param line: Given line so far.
|
|
| 292 | - @type line: string
|
|
| 293 | - |
|
| 294 | - @return: Line completed as for as possible,
|
|
| 295 | - and possible further completions.
|
|
| 296 | - @rtype: tuple
|
|
| 297 | - '''
|
|
| 298 | - split_line = self.complete_sep.split(line)
|
|
| 299 | - if split_line[-1]:
|
|
| 300 | - possibilities = self.IP.complete(split_line[-1])
|
|
| 301 | - else:
|
|
| 302 | - completed = line
|
|
| 303 | - possibilities = ['', []]
|
|
| 304 | - if possibilities:
|
|
| 305 | - def _commonPrefix(str1, str2):
|
|
| 306 | - '''
|
|
| 307 | - Reduction function. returns common prefix of two given strings.
|
|
| 308 | - |
|
| 309 | - @param str1: First string.
|
|
| 310 | - @type str1: string
|
|
| 311 | - @param str2: Second string
|
|
| 312 | - @type str2: string
|
|
| 313 | - |
|
| 314 | - @return: Common prefix to both strings.
|
|
| 315 | - @rtype: string
|
|
| 316 | - '''
|
|
| 317 | - for i in range(len(str1)):
|
|
| 318 | - if not str2.startswith(str1[:i + 1]):
|
|
| 319 | - return str1[:i]
|
|
| 320 | - return str1
|
|
| 321 | - |
|
| 322 | - if possibilities[1]:
|
|
| 323 | - common_prefix = \
|
|
| 324 | - reduce(_commonPrefix, possibilities[1]) or line[-1]
|
|
| 325 | - completed = line[:-len(split_line[-1])] + common_prefix
|
|
| 326 | - else:
|
|
| 327 | - completed = line
|
|
| 328 | - else:
|
|
| 329 | - completed = line
|
|
| 330 | - return completed, possibilities[1]
|
|
| 331 | - |
|
| 332 | - |
|
| 333 | -class ConsoleView(Gtk.TextView):
|
|
| 334 | - '''
|
|
| 335 | - Specialized text view for console-like workflow
|
|
| 336 | - |
|
| 337 | - @cvar ANSI_COLORS: Mapping of terminal colors to X11 names.
|
|
| 338 | - @type ANSI_COLORS: dictionary
|
|
| 339 | - |
|
| 340 | - @ivar text_buffer: Widget's text buffer.
|
|
| 341 | - @type text_buffer: Gtk.TextBuffer
|
|
| 342 | - @ivar color_pat: Regex of terminal color pattern
|
|
| 343 | - @type color_pat: _sre.SRE_Pattern
|
|
| 344 | - @ivar mark: Scroll mark for automatic scrolling on input.
|
|
| 345 | - @type mark: Gtk.TextMark
|
|
| 346 | - @ivar line_start: Start of command line mark.
|
|
| 347 | - @type line_start: Gtk.TextMark
|
|
| 348 | - '''
|
|
| 349 | - |
|
| 350 | - ANSI_COLORS = {'0;30': 'Black', '0;31': 'Red',
|
|
| 351 | - '0;32': 'Green', '0;33': 'Brown',
|
|
| 352 | - '0;34': 'Blue', '0;35': 'Purple',
|
|
| 353 | - '0;36': 'Cyan', '0;37': 'LightGray',
|
|
| 354 | - '1;30': 'DarkGray', '1;31': 'DarkRed',
|
|
| 355 | - '1;32': 'SeaGreen', '1;33': 'Yellow',
|
|
| 356 | - '1;34': 'LightBlue', '1;35': 'MediumPurple',
|
|
| 357 | - '1;36': 'LightCyan', '1;37': 'White'}
|
|
| 358 | - |
|
| 359 | - def __init__(self):
|
|
| 360 | - '''
|
|
| 361 | - Initialize console view
|
|
| 362 | - '''
|
|
| 363 | - GObject.GObject.__init__(self)
|
|
| 364 | - self.override_font(Pango.FontDescription('Mono'))
|
|
| 365 | - self.set_cursor_visible(True)
|
|
| 366 | - self.text_buffer = self.get_buffer()
|
|
| 367 | - self.mark = self.text_buffer.create_mark(
|
|
| 368 | - 'scroll_mark',
|
|
| 369 | - self.text_buffer.get_end_iter(),
|
|
| 370 | - False)
|
|
| 371 | - for code, color in self.ANSI_COLORS.items():
|
|
| 372 | - self.text_buffer.create_tag(code,
|
|
| 373 | - foreground=color,
|
|
| 374 | - weight=700)
|
|
| 375 | - self.text_buffer.create_tag('0')
|
|
| 376 | - self.text_buffer.create_tag('notouch', editable=False)
|
|
| 377 | - self.color_pat = re.compile(r'\x01?\x1b\[(.*?)m\x02?')
|
|
| 378 | - |
|
| 379 | - self.style_dict = {
|
|
| 380 | - Token.Prompt: '0;32',
|
|
| 381 | - Token.PromptNum: '1;32',
|
|
| 382 | - Token.OutPrompt: '0;31',
|
|
| 383 | - Token.OutPromptNum: '1;31',
|
|
| 384 | - }
|
|
| 385 | - |
|
| 386 | - self.line_start = \
|
|
| 387 | - self.text_buffer.create_mark('line_start',
|
|
| 388 | - self.text_buffer.get_end_iter(),
|
|
| 389 | - True)
|
|
| 390 | - self.connect('key-press-event', self.onKeyPress)
|
|
| 391 | - |
|
| 392 | - def write(self, text, editable=False):
|
|
| 393 | - if isinstance(text, str):
|
|
| 394 | - GLib.idle_add(self._write, text, editable)
|
|
| 395 | - else:
|
|
| 396 | - GLib.idle_add(self._write5, text, editable)
|
|
| 397 | - |
|
| 398 | - def _write5(self, text, editable=False):
|
|
| 399 | - '''
|
|
| 400 | - Write given text to buffer
|
|
| 401 | - |
|
| 402 | - @param text: Text to append.
|
|
| 403 | - @type text: list of (token: string)
|
|
| 404 | - @param editable: If true, added text is editable.
|
|
| 405 | - @type editable: boolean
|
|
| 406 | - '''
|
|
| 407 | - start_mark = self.text_buffer.create_mark(
|
|
| 408 | - None, self.text_buffer.get_end_iter(), True)
|
|
| 409 | - |
|
| 410 | - for token, segment in text:
|
|
| 411 | - tag = self.style_dict[token]
|
|
| 412 | - self.text_buffer.insert_with_tags_by_name(
|
|
| 413 | - self.text_buffer.get_end_iter(), segment, tag)
|
|
| 414 | - if not editable:
|
|
| 415 | - self.text_buffer.apply_tag_by_name(
|
|
| 416 | - 'notouch',
|
|
| 417 | - self.text_buffer.get_iter_at_mark(start_mark),
|
|
| 418 | - self.text_buffer.get_end_iter())
|
|
| 419 | - self.text_buffer.delete_mark(start_mark)
|
|
| 420 | - self.scroll_mark_onscreen(self.mark)
|
|
| 421 | - |
|
| 422 | - def _write(self, text, editable=False):
|
|
| 423 | - '''
|
|
| 424 | - Write given text to buffer
|
|
| 425 | - |
|
| 426 | - @param text: Text to append.
|
|
| 427 | - @type text: string
|
|
| 428 | - @param editable: If true, added text is editable.
|
|
| 429 | - @type editable: boolean
|
|
| 430 | - '''
|
|
| 431 | - if isinstance(text, list):
|
|
| 432 | - self._write5(text, editable)
|
|
| 433 | - return
|
|
| 434 | - segments = self.color_pat.split(text)
|
|
| 435 | - segment = segments.pop(0)
|
|
| 436 | - start_mark = self.text_buffer.create_mark(
|
|
| 437 | - None, self.text_buffer.get_end_iter(), True)
|
|
| 438 | - self.text_buffer.insert(self.text_buffer.get_end_iter(), segment)
|
|
| 439 | - |
|
| 440 | - if segments:
|
|
| 441 | - ansi_tags = self.color_pat.findall(text)
|
|
| 442 | - for tag in ansi_tags:
|
|
| 443 | - i = segments.index(tag)
|
|
| 444 | - self.text_buffer.insert_with_tags_by_name(
|
|
| 445 | - self.text_buffer.get_end_iter(), segments[i + 1], str(tag))
|
|
| 446 | - segments.pop(i)
|
|
| 447 | - if not editable:
|
|
| 448 | - self.text_buffer.apply_tag_by_name(
|
|
| 449 | - 'notouch',
|
|
| 450 | - self.text_buffer.get_iter_at_mark(start_mark),
|
|
| 451 | - self.text_buffer.get_end_iter())
|
|
| 452 | - |
|
| 453 | - self.text_buffer.delete_mark(start_mark)
|
|
| 454 | - self.scroll_mark_onscreen(self.mark)
|
|
| 455 | - |
|
| 456 | - def showPrompt(self, prompt):
|
|
| 457 | - GLib.idle_add(self._showPrompt, prompt)
|
|
| 458 | - |
|
| 459 | - def _showPrompt(self, prompt):
|
|
| 460 | - '''
|
|
| 461 | - Print prompt at start of line
|
|
| 462 | - |
|
| 463 | - @param prompt: Prompt to print.
|
|
| 464 | - @type prompt: string
|
|
| 465 | - '''
|
|
| 466 | - self._write(prompt)
|
|
| 467 | - self.text_buffer.move_mark(self.line_start,
|
|
| 468 | - self.text_buffer.get_end_iter())
|
|
| 469 | - |
|
| 470 | - def changeLine(self, text):
|
|
| 471 | - GLib.idle_add(self._changeLine, text)
|
|
| 472 | - |
|
| 473 | - def _changeLine(self, text):
|
|
| 474 | - '''
|
|
| 475 | - Replace currently entered command line with given text
|
|
| 476 | - |
|
| 477 | - @param text: Text to use as replacement.
|
|
| 478 | - @type text: string
|
|
| 479 | - '''
|
|
| 480 | - iter_ = self.text_buffer.get_iter_at_mark(self.line_start)
|
|
| 481 | - iter_.forward_to_line_end()
|
|
| 482 | - self.text_buffer.delete(
|
|
| 483 | - self.text_buffer.get_iter_at_mark(self.line_start), iter_)
|
|
| 484 | - self._write(text, True)
|
|
| 485 | - |
|
| 486 | - def getCurrentLine(self):
|
|
| 487 | - '''
|
|
| 488 | - Get text in current command line
|
|
| 489 | - |
|
| 490 | - @return: Text of current command line.
|
|
| 491 | - @rtype: string
|
|
| 492 | - '''
|
|
| 493 | - rv = self.text_buffer.get_slice(
|
|
| 494 | - self.text_buffer.get_iter_at_mark(self.line_start),
|
|
| 495 | - self.text_buffer.get_end_iter(), False)
|
|
| 496 | - return rv
|
|
| 497 | - |
|
| 498 | - def showReturned(self, text):
|
|
| 499 | - GLib.idle_add(self._showReturned, text)
|
|
| 500 | - |
|
| 501 | - def _showReturned(self, text):
|
|
| 502 | - '''
|
|
| 503 | - Show returned text from last command and print new prompt
|
|
| 504 | - |
|
| 505 | - @param text: Text to show.
|
|
| 506 | - @type text: string
|
|
| 507 | - '''
|
|
| 508 | - iter_ = self.text_buffer.get_iter_at_mark(self.line_start)
|
|
| 509 | - iter_.forward_to_line_end()
|
|
| 510 | - self.text_buffer.apply_tag_by_name(
|
|
| 511 | - 'notouch',
|
|
| 512 | - self.text_buffer.get_iter_at_mark(self.line_start),
|
|
| 513 | - iter_)
|
|
| 514 | - self._write('\n' + text)
|
|
| 515 | - if text:
|
|
| 516 | - self._write('\n')
|
|
| 517 | - self._showPrompt(self.prompt)
|
|
| 518 | - self.text_buffer.move_mark(
|
|
| 519 | - self.line_start, self.text_buffer.get_end_iter())
|
|
| 520 | - self.text_buffer.place_cursor(self.text_buffer.get_end_iter())
|
|
| 521 | - |
|
| 522 | - if self.IP.rl_do_indent:
|
|
| 523 | - indentation = self.IP.input_splitter.indent_spaces * ' '
|
|
| 524 | - self.text_buffer.insert_at_cursor(indentation)
|
|
| 525 | - |
|
| 526 | - def onKeyPress(self, _widget, event):
|
|
| 527 | - '''
|
|
| 528 | - Key press callback used for correcting behavior for console-like
|
|
| 529 | - interfaces. For example 'home' should go to prompt, not to beginning of
|
|
| 530 | - line
|
|
| 531 | - |
|
| 532 | - @param widget: Widget that key press occurred in.
|
|
| 533 | - @type widget: Gtk.Widget
|
|
| 534 | - @param event: Event object
|
|
| 535 | - @type event: Gdk.Event
|
|
| 536 | - |
|
| 537 | - @return: Return True if event should not trickle.
|
|
| 538 | - @rtype: boolean
|
|
| 539 | - '''
|
|
| 540 | - insert_mark = self.text_buffer.get_insert()
|
|
| 541 | - insert_iter = self.text_buffer.get_iter_at_mark(insert_mark)
|
|
| 542 | - selection_mark = self.text_buffer.get_selection_bound()
|
|
| 543 | - selection_iter = self.text_buffer.get_iter_at_mark(selection_mark)
|
|
| 544 | - start_iter = self.text_buffer.get_iter_at_mark(self.line_start)
|
|
| 545 | - if event.keyval == Gdk.KEY_Home:
|
|
| 546 | - if event.get_state() == 0:
|
|
| 547 | - self.text_buffer.place_cursor(start_iter)
|
|
| 548 | - return True
|
|
| 549 | - if event.get_state() == Gdk.ModifierType.SHIFT_MASK:
|
|
| 550 | - self.text_buffer.move_mark(insert_mark, start_iter)
|
|
| 551 | - return True
|
|
| 552 | - |
|
| 553 | - if event.keyval == Gdk.KEY_Left:
|
|
| 554 | - insert_iter.backward_cursor_position()
|
|
| 555 | - if not insert_iter.editable(True):
|
|
| 556 | - return True
|
|
| 557 | - elif not event.string:
|
|
| 558 | - pass
|
|
| 559 | - elif (start_iter.compare(insert_iter) <= 0 and
|
|
| 560 | - start_iter.compare(selection_iter) <= 0):
|
|
| 561 | - pass
|
|
| 562 | - elif (start_iter.compare(insert_iter) > 0 and
|
|
| 563 | - start_iter.compare(selection_iter) > 0):
|
|
| 564 | - self.text_buffer.place_cursor(start_iter)
|
|
| 565 | - elif insert_iter.compare(selection_iter) < 0:
|
|
| 566 | - self.text_buffer.move_mark(insert_mark, start_iter)
|
|
| 567 | - elif insert_iter.compare(selection_iter) > 0:
|
|
| 568 | - self.text_buffer.move_mark(selection_mark, start_iter)
|
|
| 569 | - |
|
| 570 | - return self.onKeyPressExtend(event)
|
|
| 571 | - |
|
| 572 | - def onKeyPressExtend(self, event):
|
|
| 573 | - '''
|
|
| 574 | - For some reason we can't extend onKeyPress directly (bug #500900)
|
|
| 575 | - '''
|
|
| 576 | - |
|
| 577 | - |
|
| 578 | -class IPythonView(ConsoleView, IterableIPShell):
|
|
| 579 | - '''
|
|
| 580 | - Sub-class of both modified IPython shell and L{ConsoleView} this makes
|
|
| 581 | - a GTK IPython console.
|
|
| 582 | - '''
|
|
| 583 | - def __init__(self):
|
|
| 584 | - '''
|
|
| 585 | - Initialize. Redirect I/O to console
|
|
| 586 | - '''
|
|
| 587 | - ConsoleView.__init__(self)
|
|
| 588 | - self.cout = StringIO()
|
|
| 589 | - IterableIPShell.__init__(self, cout=self.cout, cerr=self.cout,
|
|
| 590 | - input_func=self.raw_input)
|
|
| 591 | - |
|
| 592 | - displayhook = MyPromptDisplayHook(shell=self.IP, view=self)
|
|
| 593 | - self.IP.displayhook = displayhook
|
|
| 594 | - self.IP.display_trap = DisplayTrap(hook=displayhook)
|
|
| 595 | - |
|
| 596 | - self.interrupt = False
|
|
| 597 | - self.execute()
|
|
| 598 | - self.prompt = self.generatePrompt(False)
|
|
| 599 | - self.cout.truncate(0)
|
|
| 600 | - self.showPrompt(self.prompt)
|
|
| 601 | - |
|
| 602 | - def prompt_for_code(self):
|
|
| 603 | - # IPython 5.0 calls prompt_for_code instead of raw_input
|
|
| 604 | - return self.raw_input(self)
|
|
| 605 | - |
|
| 606 | - def raw_input(self, _prompt=''):
|
|
| 607 | - '''
|
|
| 608 | - Custom raw_input() replacement. Gets current line from console buffer
|
|
| 609 | - |
|
| 610 | - @param prompt: Prompt to print. Here for compatibility as replacement.
|
|
| 611 | - @type prompt: string
|
|
| 612 | - |
|
| 613 | - @return: The current command line text.
|
|
| 614 | - @rtype: string
|
|
| 615 | - '''
|
|
| 616 | - if self.interrupt:
|
|
| 617 | - self.interrupt = False
|
|
| 618 | - raise KeyboardInterrupt
|
|
| 619 | - return self.getCurrentLine()
|
|
| 620 | - |
|
| 621 | - def onKeyPressExtend(self, event):
|
|
| 622 | - '''
|
|
| 623 | - Key press callback with plenty of shell goodness, like history,
|
|
| 624 | - autocompletions, etc
|
|
| 625 | - |
|
| 626 | - @param widget: Widget that key press occurred in.
|
|
| 627 | - @type widget: Gtk.Widget
|
|
| 628 | - @param event: Event object.
|
|
| 629 | - @type event: Gdk.Event
|
|
| 630 | - |
|
| 631 | - @return: True if event should not trickle.
|
|
| 632 | - @rtype: boolean
|
|
| 633 | - '''
|
|
| 634 | - if (event.get_state() & Gdk.ModifierType.CONTROL_MASK and
|
|
| 635 | - event.keyval == 99):
|
|
| 636 | - self.interrupt = True
|
|
| 637 | - self._processLine()
|
|
| 638 | - return True
|
|
| 639 | - if event.keyval == Gdk.KEY_Return:
|
|
| 640 | - self._processLine()
|
|
| 641 | - return True
|
|
| 642 | - if event.keyval == Gdk.KEY_Up:
|
|
| 643 | - self.changeLine(self.historyBack())
|
|
| 644 | - return True
|
|
| 645 | - if event.keyval == Gdk.KEY_Down:
|
|
| 646 | - self.changeLine(self.historyForward())
|
|
| 647 | - return True
|
|
| 648 | - if event.keyval == Gdk.KEY_Tab:
|
|
| 649 | - if not self.getCurrentLine().strip():
|
|
| 650 | - return False
|
|
| 651 | - completed, possibilities = self.complete(self.getCurrentLine())
|
|
| 652 | - if len(possibilities) > 1:
|
|
| 653 | - slice_ = self.getCurrentLine()
|
|
| 654 | - self.write('\n')
|
|
| 655 | - for symbol in possibilities:
|
|
| 656 | - self.write(symbol + '\n')
|
|
| 657 | - self.showPrompt(self.prompt)
|
|
| 658 | - self.changeLine(completed or slice_)
|
|
| 659 | - return True
|
|
| 660 | - |
|
| 661 | - def _processLine(self):
|
|
| 662 | - '''
|
|
| 663 | - Process current command line
|
|
| 664 | - '''
|
|
| 665 | - self.history_pos = 0
|
|
| 666 | - self.execute()
|
|
| 667 | - rv = self.cout.getvalue()
|
|
| 668 | - if rv:
|
|
| 669 | - rv = rv.strip('\n')
|
|
| 670 | - self.showReturned(rv)
|
|
| 671 | - self.cout.truncate(0)
|
|
| 672 | - self.cout.seek(0) |
| ... | ... | @@ -165,13 +165,6 @@ def __init__(self): |
| 165 | 165 | GLib.OptionArg.NONE,
|
| 166 | 166 | _('Show all warnings'))
|
| 167 | 167 | |
| 168 | - self.add_main_option(
|
|
| 169 | - 'ipython',
|
|
| 170 | - ord('i'),
|
|
| 171 | - GLib.OptionFlags.NONE,
|
|
| 172 | - GLib.OptionArg.NONE,
|
|
| 173 | - _('Open IPython shell'))
|
|
| 174 | - |
|
| 175 | 168 | self.add_main_option(
|
| 176 | 169 | 'gdebug',
|
| 177 | 170 | 0,
|
| ... | ... | @@ -345,7 +338,6 @@ def _command_line(self, |
| 345 | 338 | options = command_line.get_options_dict()
|
| 346 | 339 | |
| 347 | 340 | remote_commands = [
|
| 348 | - ('ipython', None),
|
|
| 349 | 341 | ('start-chat', GLib.Variant('s', '')),
|
| 350 | 342 | ('show', None)
|
| 351 | 343 | ]
|
| ... | ... | @@ -417,7 +409,6 @@ def _connect_app_actions(self) -> None: |
| 417 | 409 | ('join-support-chat', self._on_join_support_chat),
|
| 418 | 410 | ('about', self._on_about_action),
|
| 419 | 411 | ('faq', self._on_faq_action),
|
| 420 | - ('ipython', self._on_ipython_action),
|
|
| 421 | 412 | ('start-chat', self._on_new_chat_action),
|
| 422 | 413 | ('accounts', self._on_accounts_action),
|
| 423 | 414 | ('add-contact', self._on_add_contact_action),
|
| ... | ... | @@ -742,18 +733,6 @@ def _on_mark_as_read_action(self, |
| 742 | 733 | |
| 743 | 734 | app.window.mark_as_read(params.account, params.jid)
|
| 744 | 735 | |
| 745 | - @staticmethod
|
|
| 746 | - def _on_ipython_action(_action: Gio.SimpleAction,
|
|
| 747 | - _param: Optional[GLib.Variant]) -> None:
|
|
| 748 | - '''
|
|
| 749 | - Show/hide the ipython window
|
|
| 750 | - '''
|
|
| 751 | - win = cast(Gtk.Window, app.ipython_window)
|
|
| 752 | - if win and win.is_visible():
|
|
| 753 | - win.present()
|
|
| 754 | - else:
|
|
| 755 | - app.interface.create_ipython_window()
|
|
| 756 | - |
|
| 757 | 736 | @staticmethod
|
| 758 | 737 | def _on_open_mail_action(_action: Gio.SimpleAction,
|
| 759 | 738 | param: GLib.Variant) -> None:
|
| ... | ... | @@ -52,8 +52,8 @@ |
| 52 | 52 | log = logging.getLogger('gajim.gui.avatar')
|
| 53 | 53 | |
| 54 | 54 | |
| 55 | -AvatarCacheT = dict[JID, dict[tuple[int, int, Optional[str]],
|
|
| 56 | - cairo.ImageSurface]]
|
|
| 55 | +AvatarCacheT = dict[Union[JID, str], dict[tuple[int, int, Optional[str]],
|
|
| 56 | + cairo.ImageSurface]]
|
|
| 57 | 57 | |
| 58 | 58 | |
| 59 | 59 | def generate_avatar(letters: str,
|
| ... | ... | @@ -102,7 +102,7 @@ def generate_avatar(letters: str, |
| 102 | 102 | context.set_operator(cairo.Operator.OVER)
|
| 103 | 103 | context.show_text(letters)
|
| 104 | 104 | |
| 105 | - return context.get_target()
|
|
| 105 | + return context.get_target() # pyright: ignore
|
|
| 106 | 106 | |
| 107 | 107 | |
| 108 | 108 | @lru_cache(maxsize=None)
|
| ... | ... | @@ -132,7 +132,8 @@ def make_workspace_avatar(letter: str, |
| 132 | 132 | |
| 133 | 133 | |
| 134 | 134 | def add_status_to_avatar(surface: cairo.ImageSurface,
|
| 135 | - show: str) -> cairo.ImageSurface:
|
|
| 135 | + show: str
|
|
| 136 | + ) -> cairo.ImageSurface:
|
|
| 136 | 137 | |
| 137 | 138 | width = surface.get_width()
|
| 138 | 139 | height = surface.get_height()
|
| ... | ... | @@ -179,11 +180,15 @@ def add_status_to_avatar(surface: cairo.ImageSurface, |
| 179 | 180 | context.set_line_width(clip_radius / 4)
|
| 180 | 181 | context.stroke()
|
| 181 | 182 | |
| 182 | - return context.get_target()
|
|
| 183 | + return context.get_target() # pyright: ignore
|
|
| 183 | 184 | |
| 184 | 185 | |
| 185 | 186 | @lru_cache(maxsize=128)
|
| 186 | -def get_show_circle(show, size: int, scale: int) -> cairo.ImageSurface:
|
|
| 187 | +def get_show_circle(show: Union[str, types.PresenceShowT],
|
|
| 188 | + size: int,
|
|
| 189 | + scale: int
|
|
| 190 | + ) -> cairo.ImageSurface:
|
|
| 191 | + |
|
| 187 | 192 | if not isinstance(show, str):
|
| 188 | 193 | show = show.value
|
| 189 | 194 | |
| ... | ... | @@ -212,7 +217,7 @@ def get_show_circle(show, size: int, scale: int) -> cairo.ImageSurface: |
| 212 | 217 | context.set_line_width(size / 10)
|
| 213 | 218 | context.stroke()
|
| 214 | 219 | |
| 215 | - return context.get_target()
|
|
| 220 | + return context.get_target() # pyright: ignore
|
|
| 216 | 221 | |
| 217 | 222 | |
| 218 | 223 | def fit(surface: cairo.ImageSurface, size: int) -> cairo.ImageSurface:
|
| ... | ... | @@ -235,7 +240,7 @@ def fit(surface: cairo.ImageSurface, size: int) -> cairo.ImageSurface: |
| 235 | 240 | context.scale(factor, factor)
|
| 236 | 241 | context.set_source_surface(square_surface, 0, 0)
|
| 237 | 242 | context.paint()
|
| 238 | - return context.get_target()
|
|
| 243 | + return context.get_target() # pyright: ignore
|
|
| 239 | 244 | |
| 240 | 245 | |
| 241 | 246 | def square(surface: cairo.ImageSurface, size: int) -> cairo.ImageSurface:
|
| ... | ... | @@ -259,7 +264,7 @@ def square(surface: cairo.ImageSurface, size: int) -> cairo.ImageSurface: |
| 259 | 264 | |
| 260 | 265 | context.set_source_surface(surface, x_pos, y_pos)
|
| 261 | 266 | context.paint()
|
| 262 | - return context.get_target()
|
|
| 267 | + return context.get_target() # pyright: ignore
|
|
| 263 | 268 | |
| 264 | 269 | |
| 265 | 270 | def clip(surface: cairo.ImageSurface, mode: str) -> cairo.ImageSurface:
|
| ... | ... | @@ -289,7 +294,7 @@ def clip_circle(surface: cairo.ImageSurface) -> cairo.ImageSurface: |
| 289 | 294 | context.clip()
|
| 290 | 295 | context.paint()
|
| 291 | 296 | |
| 292 | - return context.get_target()
|
|
| 297 | + return context.get_target() # pyright: ignore
|
|
| 293 | 298 | |
| 294 | 299 | |
| 295 | 300 | def round_corners(surface: cairo.ImageSurface) -> cairo.ImageSurface:
|
| ... | ... | @@ -320,7 +325,7 @@ def round_corners(surface: cairo.ImageSurface) -> cairo.ImageSurface: |
| 320 | 325 | |
| 321 | 326 | context.paint()
|
| 322 | 327 | |
| 323 | - return context.get_target()
|
|
| 328 | + return context.get_target() # pyright: ignore
|
|
| 324 | 329 | |
| 325 | 330 | |
| 326 | 331 | def convert_to_greyscale(surface: cairo.ImageSurface) -> cairo.ImageSurface:
|
| ... | ... | @@ -333,14 +338,14 @@ def convert_to_greyscale(surface: cairo.ImageSurface) -> cairo.ImageSurface: |
| 333 | 338 | context.set_source_rgba(1, 1, 1, 0.5)
|
| 334 | 339 | context.rectangle(0, 0, surface.get_width(), surface.get_height())
|
| 335 | 340 | context.fill()
|
| 336 | - return context.get_target()
|
|
| 341 | + return context.get_target() # pyright: ignore
|
|
| 337 | 342 | |
| 338 | 343 | |
| 339 | 344 | class AvatarStorage(metaclass=Singleton):
|
| 340 | 345 | def __init__(self):
|
| 341 | 346 | self._cache: AvatarCacheT = defaultdict(dict)
|
| 342 | 347 | |
| 343 | - def invalidate_cache(self, jid: JID) -> None:
|
|
| 348 | + def invalidate_cache(self, jid: Union[JID, str]) -> None:
|
|
| 344 | 349 | self._cache.pop(jid, None)
|
| 345 | 350 | |
| 346 | 351 | def get_pixbuf(self,
|
| ... | ... | @@ -373,6 +378,7 @@ def get_surface(self, |
| 373 | 378 | |
| 374 | 379 | if transport_icon is not None:
|
| 375 | 380 | surface = load_icon_surface(transport_icon, size, scale)
|
| 381 | + assert surface is not None
|
|
| 376 | 382 | if show is not None:
|
| 377 | 383 | surface = add_status_to_avatar(surface, show)
|
| 378 | 384 | self._cache[jid][(size, scale, show)] = surface
|
| ... | ... | @@ -412,11 +418,12 @@ def get_muc_surface(self, |
| 412 | 418 | |
| 413 | 419 | if transport_icon is not None:
|
| 414 | 420 | surface = load_icon_surface(transport_icon, size, scale)
|
| 415 | - self._cache[jid][(size, scale)] = surface
|
|
| 421 | + assert surface is not None
|
|
| 422 | + self._cache[jid][(size, scale, None)] = surface
|
|
| 416 | 423 | return surface
|
| 417 | 424 | |
| 418 | 425 | if not default:
|
| 419 | - surface = self._cache[jid].get((size, scale))
|
|
| 426 | + surface = self._cache[jid].get((size, scale, None))
|
|
| 420 | 427 | if surface is not None:
|
| 421 | 428 | return surface
|
| 422 | 429 | |
| ... | ... | @@ -425,7 +432,7 @@ def get_muc_surface(self, |
| 425 | 432 | surface = self.surface_from_filename(avatar_sha, size, scale)
|
| 426 | 433 | if surface is not None:
|
| 427 | 434 | surface = clip(surface, style)
|
| 428 | - self._cache[jid][(size, scale)] = surface
|
|
| 435 | + self._cache[jid][(size, scale, None)] = surface
|
|
| 429 | 436 | return surface
|
| 430 | 437 | |
| 431 | 438 | # avatar_sha set, but image is missing
|
| ... | ... | @@ -436,7 +443,7 @@ def get_muc_surface(self, |
| 436 | 443 | name = get_groupchat_name(con, jid)
|
| 437 | 444 | letter = self._generate_letter(name)
|
| 438 | 445 | surface = generate_default_avatar(letter, str(jid), size, scale, style)
|
| 439 | - self._cache[jid][(size, scale)] = surface
|
|
| 446 | + self._cache[jid][(size, scale, None)] = surface
|
|
| 440 | 447 | return surface
|
| 441 | 448 | |
| 442 | 449 | def get_workspace_surface(self,
|
| ... | ... | @@ -444,7 +451,7 @@ def get_workspace_surface(self, |
| 444 | 451 | size: int,
|
| 445 | 452 | scale: int) -> Optional[cairo.ImageSurface]:
|
| 446 | 453 | |
| 447 | - surface = self._cache[workspace_id].get((size, scale))
|
|
| 454 | + surface = self._cache[workspace_id].get((size, scale, None))
|
|
| 448 | 455 | if surface is not None:
|
| 449 | 456 | return surface
|
| 450 | 457 | |
| ... | ... | @@ -465,7 +472,7 @@ def get_workspace_surface(self, |
| 465 | 472 | letter = name[:1].upper()
|
| 466 | 473 | surface = make_workspace_avatar(
|
| 467 | 474 | letter, rgba_to_float(rgba), size, scale)
|
| 468 | - self._cache[workspace_id][(size, scale)] = surface
|
|
| 475 | + self._cache[workspace_id][(size, scale, None)] = surface
|
|
| 469 | 476 | return surface
|
| 470 | 477 | |
| 471 | 478 | @staticmethod
|
| ... | ... | @@ -483,6 +490,7 @@ def _load_for_publish(path: str) -> Optional[tuple[bool, bytes]]: |
| 483 | 490 | height,
|
| 484 | 491 | GdkPixbuf.InterpType.BILINEAR)
|
| 485 | 492 | |
| 493 | + assert pixbuf is not None
|
|
| 486 | 494 | return pixbuf.save_to_bufferv('png', [], [])
|
| 487 | 495 | |
| 488 | 496 | @staticmethod
|
| ... | ... | @@ -178,7 +178,9 @@ def _scale_for_publish(pixbuf: GdkPixbuf.Pixbuf |
| 178 | 178 | return scaled_pixbuf, width, height
|
| 179 | 179 | return pixbuf, width, height
|
| 180 | 180 | |
| 181 | - def get_avatar_surface(self) -> Optional[tuple[cairo.Surface, int, int]]:
|
|
| 181 | + def get_avatar_surface(self) -> Optional[tuple[cairo.ImageSurface,
|
|
| 182 | + int,
|
|
| 183 | + int]]:
|
|
| 182 | 184 | pixbuf = self._crop_area.get_pixbuf()
|
| 183 | 185 | if pixbuf is None:
|
| 184 | 186 | return None
|
| ... | ... | @@ -236,12 +236,12 @@ def _update_avatar(self) -> None: |
| 236 | 236 | scale = app.window.get_scale_factor()
|
| 237 | 237 | assert self._contact
|
| 238 | 238 | surface = self._contact.get_avatar(AvatarSize.CHAT, scale)
|
| 239 | - assert isinstance(surface, cairo.Surface)
|
|
| 239 | + assert isinstance(surface, cairo.ImageSurface)
|
|
| 240 | 240 | self._ui.avatar_image.set_from_surface(surface)
|
| 241 | 241 | |
| 242 | 242 | def _update_content(self) -> None:
|
| 243 | - if self._client is None or self._contact is None:
|
|
| 244 | - return
|
|
| 243 | + assert self._contact is not None
|
|
| 244 | + assert self._client is not None
|
|
| 245 | 245 | |
| 246 | 246 | name = self._contact.name
|
| 247 | 247 |
| ... | ... | @@ -26,6 +26,7 @@ |
| 26 | 26 | from gi.repository import Gio
|
| 27 | 27 | from gi.repository import Gdk
|
| 28 | 28 | from gi.repository import GLib
|
| 29 | +from gi.repository import GObject
|
|
| 29 | 30 | from gi.repository import Gtk
|
| 30 | 31 | import cairo
|
| 31 | 32 | |
| ... | ... | @@ -47,6 +48,7 @@ |
| 47 | 48 | from gajim.common.helpers import AdditionalDataDict
|
| 48 | 49 | from gajim.common.preview_helpers import filename_from_uri
|
| 49 | 50 | from gajim.common.preview_helpers import guess_simple_file_type
|
| 51 | +from gajim.common.setting_values import OpenChatsSettingT
|
|
| 50 | 52 | from gajim.common.types import ChatContactT
|
| 51 | 53 | from gajim.common.types import OneOnOneContactT
|
| 52 | 54 | from gajim.common.modules.contacts import BareContact
|
| ... | ... | @@ -65,6 +67,13 @@ |
| 65 | 67 | |
| 66 | 68 | |
| 67 | 69 | class ChatList(Gtk.ListBox, EventHelper):
|
| 70 | + |
|
| 71 | + __gsignals__ = {
|
|
| 72 | + 'chat-order-changed': (GObject.SignalFlags.RUN_LAST,
|
|
| 73 | + None,
|
|
| 74 | + ()),
|
|
| 75 | + }
|
|
| 76 | + |
|
| 68 | 77 | def __init__(self, workspace_id: str) -> None:
|
| 69 | 78 | Gtk.ListBox.__init__(self)
|
| 70 | 79 | EventHelper.__init__(self)
|
| ... | ... | @@ -94,12 +103,19 @@ def __init__(self, workspace_id: str) -> None: |
| 94 | 103 | entries,
|
| 95 | 104 | Gdk.DragAction.MOVE)
|
| 96 | 105 | |
| 106 | + self._drag_row: Optional[ChatRow] = None
|
|
| 107 | + self._row_before: Optional[ChatRow] = None
|
|
| 108 | + self._row_after: Optional[ChatRow] = None
|
|
| 109 | + self._chat_order: list[ChatRow] = []
|
|
| 110 | + |
|
| 97 | 111 | self.register_events([
|
| 98 | 112 | ('account-enabled', ged.GUI2, self._on_account_changed),
|
| 99 | 113 | ('account-disabled', ged.GUI2, self._on_account_changed),
|
| 100 | 114 | ('bookmarks-received', ged.GUI1, self._on_bookmarks_received),
|
| 101 | 115 | ])
|
| 102 | 116 | |
| 117 | + self.connect('drag-motion', self._on_drag_motion)
|
|
| 118 | + self.connect('drag-data-received', self._on_drag_data_received)
|
|
| 103 | 119 | self.connect('destroy', self._on_destroy)
|
| 104 | 120 | |
| 105 | 121 | self._timer_id = GLib.timeout_add_seconds(60, self._update_timer)
|
| ... | ... | @@ -146,6 +162,103 @@ def emit_unread_changed(self) -> None: |
| 146 | 162 | self._workspace_id,
|
| 147 | 163 | count)
|
| 148 | 164 | |
| 165 | + def _get_row_before(self, row: ChatRow) -> Optional[ChatRow]:
|
|
| 166 | + row = cast(ChatRow, self.get_row_at_index(row.get_index() - 1))
|
|
| 167 | + return row
|
|
| 168 | + |
|
| 169 | + def _get_row_after(self, row: ChatRow) -> Optional[ChatRow]:
|
|
| 170 | + row = cast(ChatRow, self.get_row_at_index(row.get_index() + 1))
|
|
| 171 | + return row
|
|
| 172 | + |
|
| 173 | + def _get_last_row(self) -> ChatRow:
|
|
| 174 | + row = cast(ChatRow, self.get_row_at_index(len(self.get_children())))
|
|
| 175 | + return row
|
|
| 176 | + |
|
| 177 | + def set_drag_row(self, row: ChatRow) -> None:
|
|
| 178 | + self._drag_row = row
|
|
| 179 | + |
|
| 180 | + def _on_drag_motion(self,
|
|
| 181 | + _widget: Gtk.Widget,
|
|
| 182 | + _drag_context: Gdk.DragContext,
|
|
| 183 | + _x_coord: int,
|
|
| 184 | + y_coord: int,
|
|
| 185 | + _time: int
|
|
| 186 | + ) -> bool:
|
|
| 187 | + |
|
| 188 | + row = cast(ChatRow, self.get_row_at_y(y_coord))
|
|
| 189 | + if row:
|
|
| 190 | + alloc = row.get_allocation()
|
|
| 191 | + hover_row_y = alloc.y
|
|
| 192 | + hover_row_height = alloc.height
|
|
| 193 | + |
|
| 194 | + if y_coord < hover_row_y + hover_row_height / 2:
|
|
| 195 | + self._row_before = self._get_row_before(row)
|
|
| 196 | + self._row_after = row
|
|
| 197 | + else:
|
|
| 198 | + self._row_before = row
|
|
| 199 | + self._row_after = self._get_row_after(row)
|
|
| 200 | + else:
|
|
| 201 | + self._row_before = self._get_last_row()
|
|
| 202 | + self._row_after = None
|
|
| 203 | + |
|
| 204 | + if self._drag_row in (self._row_before, self._row_after):
|
|
| 205 | + return False
|
|
| 206 | + |
|
| 207 | + return True
|
|
| 208 | + |
|
| 209 | + def _on_drag_data_received(self,
|
|
| 210 | + _widget: Gtk.Widget,
|
|
| 211 | + _drag_context: Gdk.DragContext,
|
|
| 212 | + _x_coord: int,
|
|
| 213 | + y_coord: int,
|
|
| 214 | + selection_data: Gtk.SelectionData,
|
|
| 215 | + _info: int,
|
|
| 216 | + _time: int
|
|
| 217 | + ) -> None:
|
|
| 218 | + |
|
| 219 | + item_type = selection_data.get_data_type().name()
|
|
| 220 | + if item_type == 'CHAT_LIST_ITEM':
|
|
| 221 | + self._change_pinned_order()
|
|
| 222 | + else:
|
|
| 223 | + log.debug('Unknown item type dropped')
|
|
| 224 | + |
|
| 225 | + def _change_pinned_order(self) -> None:
|
|
| 226 | + assert self._drag_row is not None
|
|
| 227 | + |
|
| 228 | + if not self._drag_row.is_pinned:
|
|
| 229 | + log.debug('Dragged row is not pinned')
|
|
| 230 | + return
|
|
| 231 | + |
|
| 232 | + if self._drag_row in (self._row_before, self._row_after):
|
|
| 233 | + log.debug('Dragged row dropped on self')
|
|
| 234 | + return
|
|
| 235 | + |
|
| 236 | + if self._row_after is None:
|
|
| 237 | + log.debug('Dragged row dropped in empty space')
|
|
| 238 | + return
|
|
| 239 | + |
|
| 240 | + self._chat_order.remove(self._drag_row)
|
|
| 241 | + |
|
| 242 | + if self._row_before is None:
|
|
| 243 | + self._chat_order.insert(0, self._drag_row)
|
|
| 244 | + else:
|
|
| 245 | + if self._row_before.position < self._drag_row.position:
|
|
| 246 | + offset = 1
|
|
| 247 | + else:
|
|
| 248 | + offset = 0
|
|
| 249 | + self._chat_order.insert(
|
|
| 250 | + self._row_before.position + offset, self._drag_row)
|
|
| 251 | + |
|
| 252 | + for row in self._chat_order:
|
|
| 253 | + row.position = self._chat_order.index(row)
|
|
| 254 | + |
|
| 255 | + self._row_before = None
|
|
| 256 | + self._row_after = None
|
|
| 257 | + self._drag_row = None
|
|
| 258 | + |
|
| 259 | + self.emit('chat-order-changed')
|
|
| 260 | + self.invalidate_sort()
|
|
| 261 | + |
|
| 149 | 262 | def _on_destroy(self, _widget: Gtk.Widget) -> None:
|
| 150 | 263 | GLib.source_remove(self._timer_id)
|
| 151 | 264 | |
| ... | ... | @@ -198,9 +311,11 @@ def _sort_func(self, row1: ChatRow, row2: ChatRow) -> int: |
| 198 | 311 | log.debug('Mouseover active, don’t sort rows')
|
| 199 | 312 | return 0
|
| 200 | 313 | |
| 201 | - # Don’t sort pinned rows themselves
|
|
| 314 | + # Sort pinned rows according to stored order
|
|
| 202 | 315 | if row1.is_pinned and row2.is_pinned:
|
| 203 | - return 0
|
|
| 316 | + if row1.position > row2.position:
|
|
| 317 | + return 1
|
|
| 318 | + return -1
|
|
| 204 | 319 | |
| 205 | 320 | # Sort pinned rows to top
|
| 206 | 321 | if row1.is_pinned > row2.is_pinned:
|
| ... | ... | @@ -250,14 +365,30 @@ def get_chat_type(self, account: str, jid: JID) -> Optional[str]: |
| 250 | 365 | return row.type
|
| 251 | 366 | return None
|
| 252 | 367 | |
| 253 | - def add_chat(self, account: str, jid: JID, type_: str,
|
|
| 254 | - pinned: bool = False) -> None:
|
|
| 255 | - if self._chats.get((account, jid)) is not None:
|
|
| 368 | + def add_chat(self,
|
|
| 369 | + account: str,
|
|
| 370 | + jid: JID,
|
|
| 371 | + type_: str,
|
|
| 372 | + pinned: bool,
|
|
| 373 | + position: int
|
|
| 374 | + ) -> None:
|
|
| 375 | + |
|
| 376 | + key = (account, jid)
|
|
| 377 | + if self._chats.get(key) is not None:
|
|
| 256 | 378 | # Chat is already in the List
|
| 257 | 379 | return
|
| 258 | 380 | |
| 259 | - row = ChatRow(self._workspace_id, account, jid, type_, pinned)
|
|
| 260 | - self._chats[(account, jid)] = row
|
|
| 381 | + row = ChatRow(self._workspace_id,
|
|
| 382 | + account,
|
|
| 383 | + jid,
|
|
| 384 | + type_,
|
|
| 385 | + pinned,
|
|
| 386 | + position)
|
|
| 387 | + |
|
| 388 | + self._chats[key] = row
|
|
| 389 | + if position != -1:
|
|
| 390 | + self._chat_order.insert(position, row)
|
|
| 391 | + |
|
| 261 | 392 | self.add(row)
|
| 262 | 393 | |
| 263 | 394 | def select_chat(self, account: str, jid: JID) -> None:
|
| ... | ... | @@ -333,11 +464,27 @@ def select_chat_number(self, number: int) -> None: |
| 333 | 464 | def toggle_chat_pinned(self, account: str, jid: JID) -> None:
|
| 334 | 465 | row = self._chats[(account, jid)]
|
| 335 | 466 | row.toggle_pinned()
|
| 467 | + |
|
| 468 | + self._chat_order.clear()
|
|
| 469 | + chat_rows = cast(list[ChatRow], self.get_children())
|
|
| 470 | + for row in chat_rows:
|
|
| 471 | + if row.is_pinned:
|
|
| 472 | + row.position = chat_rows.index(row)
|
|
| 473 | + self._chat_order.append(row)
|
|
| 474 | + else:
|
|
| 475 | + row.position = -1
|
|
| 476 | + |
|
| 336 | 477 | self.invalidate_sort()
|
| 337 | 478 | |
| 338 | - def remove_chat(self, account: str, jid: JID,
|
|
| 339 | - emit_unread: bool = True) -> None:
|
|
| 479 | + def remove_chat(self,
|
|
| 480 | + account: str,
|
|
| 481 | + jid: JID,
|
|
| 482 | + emit_unread: bool = True
|
|
| 483 | + ) -> None:
|
|
| 484 | + |
|
| 340 | 485 | row = self._chats.pop((account, jid))
|
| 486 | + if row.position != -1:
|
|
| 487 | + self._chat_order.remove(row)
|
|
| 341 | 488 | self.remove(row)
|
| 342 | 489 | row.destroy()
|
| 343 | 490 | if emit_unread:
|
| ... | ... | @@ -359,10 +506,15 @@ def get_selected_chat(self) -> Optional[ChatRow]: |
| 359 | 506 | def contains_chat(self, account: str, jid: JID) -> bool:
|
| 360 | 507 | return self._chats.get((account, jid)) is not None
|
| 361 | 508 | |
| 362 | - def get_open_chats(self) -> list[tuple[str, JID, str, bool]]:
|
|
| 363 | - open_chats: list[tuple[str, JID, str, bool]] = []
|
|
| 364 | - for key, value in self._chats.items():
|
|
| 365 | - open_chats.append(key + (value.type, value.is_pinned))
|
|
| 509 | + def get_open_chats(self) -> OpenChatsSettingT:
|
|
| 510 | + open_chats: OpenChatsSettingT = []
|
|
| 511 | + for key, row in self._chats.items():
|
|
| 512 | + account, jid = key
|
|
| 513 | + open_chats.append({'account': account,
|
|
| 514 | + 'jid': jid,
|
|
| 515 | + 'type': row.type,
|
|
| 516 | + 'pinned': row.is_pinned,
|
|
| 517 | + 'position': row.position})
|
|
| 366 | 518 | return open_chats
|
| 367 | 519 | |
| 368 | 520 | def update_time(self) -> None:
|
| ... | ... | @@ -536,14 +688,22 @@ def _on_bookmarks_received(self, _event: events.BookmarksReceived) -> None: |
| 536 | 688 | |
| 537 | 689 | |
| 538 | 690 | class ChatRow(Gtk.ListBoxRow):
|
| 539 | - def __init__(self, workspace_id: str, account: str, jid: JID, type_: str,
|
|
| 540 | - pinned: bool) -> None:
|
|
| 691 | + def __init__(self,
|
|
| 692 | + workspace_id: str,
|
|
| 693 | + account: str,
|
|
| 694 | + jid: JID,
|
|
| 695 | + type_: str,
|
|
| 696 | + pinned: bool,
|
|
| 697 | + position: int
|
|
| 698 | + ) -> None:
|
|
| 699 | + |
|
| 541 | 700 | Gtk.ListBoxRow.__init__(self)
|
| 542 | 701 | |
| 543 | 702 | self.account = account
|
| 544 | 703 | self.jid = jid
|
| 545 | 704 | self.workspace_id = workspace_id
|
| 546 | 705 | self.type = type_
|
| 706 | + self._position = position
|
|
| 547 | 707 | |
| 548 | 708 | self.active_label = ActiveHeader()
|
| 549 | 709 | self.conversations_label = ConversationsHeader()
|
| ... | ... | @@ -705,6 +865,14 @@ def header(self, type_: Optional[RowHeaderType]) -> None: |
| 705 | 865 | def is_pinned(self) -> bool:
|
| 706 | 866 | return self._pinned
|
| 707 | 867 | |
| 868 | + @property
|
|
| 869 | + def position(self) -> int:
|
|
| 870 | + return self._position
|
|
| 871 | + |
|
| 872 | + @position.setter
|
|
| 873 | + def position(self, position: int) -> None:
|
|
| 874 | + self._position = position
|
|
| 875 | + |
|
| 708 | 876 | def _on_button_press(self,
|
| 709 | 877 | _widget: Gtk.Widget,
|
| 710 | 878 | event: Gdk.EventButton
|
| ... | ... | @@ -735,6 +903,10 @@ def _on_drag_begin(self, |
| 735 | 903 | widget: Gtk.Widget,
|
| 736 | 904 | drag_context: Gdk.DragContext
|
| 737 | 905 | ) -> None:
|
| 906 | + |
|
| 907 | + chat_list = cast(ChatList, self.get_parent())
|
|
| 908 | + chat_list.set_drag_row(self)
|
|
| 909 | + |
|
| 738 | 910 | # Use rendered ChatListRow as drag icon
|
| 739 | 911 | alloc = self.get_allocation()
|
| 740 | 912 | surface = cairo.ImageSurface(
|
| ... | ... | @@ -14,7 +14,6 @@ |
| 14 | 14 | |
| 15 | 15 | from __future__ import annotations
|
| 16 | 16 | |
| 17 | -from typing import Dict
|
|
| 18 | 17 | from typing import Optional
|
| 19 | 18 | from typing import cast
|
| 20 | 19 | |
| ... | ... | @@ -66,7 +65,7 @@ def __init__(self, |
| 66 | 65 | self.set_vexpand(True)
|
| 67 | 66 | self.set_vhomogeneous(False)
|
| 68 | 67 | |
| 69 | - self._chat_lists: Dict[str, ChatList] = {}
|
|
| 68 | + self._chat_lists: dict[str, ChatList] = {}
|
|
| 70 | 69 | |
| 71 | 70 | self._last_visible_child_name: str = 'default'
|
| 72 | 71 | |
| ... | ... | @@ -153,6 +152,7 @@ def _on_search_changed(self, search_entry: Gtk.SearchEntry) -> None: |
| 153 | 152 | def add_chat_list(self, workspace_id: str) -> ChatList:
|
| 154 | 153 | chat_list = ChatList(workspace_id)
|
| 155 | 154 | chat_list.connect('row-selected', self._on_row_selected)
|
| 155 | + chat_list.connect('chat-order-changed', self._on_chat_order_changed)
|
|
| 156 | 156 | |
| 157 | 157 | self._chat_lists[workspace_id] = chat_list
|
| 158 | 158 | self.add_named(chat_list, workspace_id)
|
| ... | ... | @@ -161,8 +161,10 @@ def add_chat_list(self, workspace_id: str) -> ChatList: |
| 161 | 161 | def remove_chat_list(self, workspace_id: str) -> None:
|
| 162 | 162 | chat_list = self._chat_lists[workspace_id]
|
| 163 | 163 | self.remove(chat_list)
|
| 164 | - for account, jid, _type, _pinned in chat_list.get_open_chats():
|
|
| 165 | - self.remove_chat(workspace_id, account, jid)
|
|
| 164 | + for open_chat in chat_list.get_open_chats():
|
|
| 165 | + self.remove_chat(workspace_id,
|
|
| 166 | + open_chat['account'],
|
|
| 167 | + open_chat['jid'])
|
|
| 166 | 168 | |
| 167 | 169 | self._chat_lists.pop(workspace_id)
|
| 168 | 170 | chat_list.destroy()
|
| ... | ... | @@ -177,6 +179,12 @@ def _on_row_selected(self, |
| 177 | 179 | |
| 178 | 180 | self.emit('chat-selected', row.workspace_id, row.account, row.jid)
|
| 179 | 181 | |
| 182 | + def _on_chat_order_changed(self,
|
|
| 183 | + chat_list: ChatList
|
|
| 184 | + ) -> None:
|
|
| 185 | + |
|
| 186 | + self.store_open_chats(chat_list.workspace_id)
|
|
| 187 | + |
|
| 180 | 188 | def show_chat_list(self, workspace_id: str) -> None:
|
| 181 | 189 | cur_workspace_id = self.get_visible_child_name()
|
| 182 | 190 | if cur_workspace_id == workspace_id:
|
| ... | ... | @@ -187,12 +195,19 @@ def show_chat_list(self, workspace_id: str) -> None: |
| 187 | 195 | |
| 188 | 196 | self.set_visible_child_name(workspace_id)
|
| 189 | 197 | |
| 190 | - def add_chat(self, workspace_id: str, account: str, jid: JID, type_: str,
|
|
| 191 | - pinned: bool = False) -> None:
|
|
| 198 | + def add_chat(self,
|
|
| 199 | + workspace_id: str,
|
|
| 200 | + account: str,
|
|
| 201 | + jid: JID,
|
|
| 202 | + type_: str,
|
|
| 203 | + pinned: bool,
|
|
| 204 | + position: int
|
|
| 205 | + ) -> None:
|
|
| 206 | + |
|
| 192 | 207 | chat_list = self._chat_lists.get(workspace_id)
|
| 193 | 208 | if chat_list is None:
|
| 194 | 209 | chat_list = self.add_chat_list(workspace_id)
|
| 195 | - chat_list.add_chat(account, jid, type_, pinned)
|
|
| 210 | + chat_list.add_chat(account, jid, type_, pinned, position)
|
|
| 196 | 211 | |
| 197 | 212 | def select_chat(self, account: str, jid: JID) -> None:
|
| 198 | 213 | chat_list = self.find_chat(account, jid)
|
| ... | ... | @@ -235,7 +250,8 @@ def _move_chat_to_workspace(self, |
| 235 | 250 | current_chatlist.remove_chat(params.account, params.jid)
|
| 236 | 251 | |
| 237 | 252 | new_chatlist = self.get_chatlist(workspace_id)
|
| 238 | - new_chatlist.add_chat(params.account, params.jid, type_)
|
|
| 253 | + new_chatlist.add_chat(params.account, params.jid, type_, False, -1)
|
|
| 254 | + |
|
| 239 | 255 | self.store_open_chats(current_chatlist.workspace_id)
|
| 240 | 256 | self.store_open_chats(workspace_id)
|
| 241 | 257 | |
| ... | ... | @@ -251,31 +267,38 @@ def remove_chat(self, workspace_id: str, account: str, jid: JID) -> None: |
| 251 | 267 | chat_list = self._chat_lists[workspace_id]
|
| 252 | 268 | type_ = chat_list.get_chat_type(account, jid)
|
| 253 | 269 | |
| 254 | - def _leave(is_checked: bool) -> None:
|
|
| 255 | - if is_checked:
|
|
| 270 | + def _leave(not_ask_again: bool) -> None:
|
|
| 271 | + if not_ask_again:
|
|
| 256 | 272 | app.settings.set('confirm_close_muc', False)
|
| 273 | + _remove()
|
|
| 257 | 274 | |
| 275 | + def _remove() -> None:
|
|
| 258 | 276 | chat_list.remove_chat(account, jid, emit_unread=False)
|
| 259 | 277 | self.store_open_chats(workspace_id)
|
| 260 | 278 | self.emit('chat-removed', account, jid, type_)
|
| 261 | 279 | |
| 262 | - if type_ == 'groupchat' and app.settings.get('confirm_close_muc'):
|
|
| 263 | - client = app.get_client(account)
|
|
| 264 | - contact = client.get_module('Contacts').get_group_chat_contact(jid)
|
|
| 265 | - ConfirmationCheckDialog(
|
|
| 266 | - _('Leave Group Chat'),
|
|
| 267 | - _('Are you sure you want to leave this group chat?'),
|
|
| 268 | - _('If you close this chat, you will leave '
|
|
| 269 | - '"%s".') % contact.name,
|
|
| 270 | - _('_Do not ask me again'),
|
|
| 271 | - [DialogButton.make('Cancel'),
|
|
| 272 | - DialogButton.make('Accept',
|
|
| 273 | - text=_('_Leave'),
|
|
| 274 | - callback=_leave)],
|
|
| 275 | - transient_for=app.window).show()
|
|
| 280 | + if type_ != 'groupchat' or not app.settings.get('confirm_close_muc'):
|
|
| 281 | + _remove()
|
|
| 282 | + return
|
|
| 283 | + |
|
| 284 | + client = app.get_client(account)
|
|
| 285 | + contact = client.get_module('Contacts').get_group_chat_contact(jid)
|
|
| 286 | + |
|
| 287 | + if contact.is_not_joined:
|
|
| 288 | + _remove()
|
|
| 276 | 289 | return
|
| 277 | 290 | |
| 278 | - _leave(True)
|
|
| 291 | + ConfirmationCheckDialog(
|
|
| 292 | + _('Leave Group Chat'),
|
|
| 293 | + _('Are you sure you want to leave this group chat?'),
|
|
| 294 | + _('If you close this chat, you will leave '
|
|
| 295 | + '"%s".') % contact.name,
|
|
| 296 | + _('_Do not ask me again'),
|
|
| 297 | + [DialogButton.make('Cancel'),
|
|
| 298 | + DialogButton.make('Accept',
|
|
| 299 | + text=_('_Leave'),
|
|
| 300 | + callback=_leave)],
|
|
| 301 | + transient_for=app.window).show()
|
|
| 279 | 302 | |
| 280 | 303 | def remove_chats_for_account(self, account: str) -> None:
|
| 281 | 304 | for workspace_id, chat_list in self._chat_lists.items():
|
| ... | ... | @@ -221,6 +221,7 @@ def add_chat_for_workspace(self, |
| 221 | 221 | jid: JID,
|
| 222 | 222 | type_: str,
|
| 223 | 223 | pinned: bool = False,
|
| 224 | + position: int = -1,
|
|
| 224 | 225 | select: bool = False) -> None:
|
| 225 | 226 | |
| 226 | 227 | client = app.get_client(account)
|
| ... | ... | @@ -240,7 +241,7 @@ def add_chat_for_workspace(self, |
| 240 | 241 | return
|
| 241 | 242 | |
| 242 | 243 | self._chat_list_stack.add_chat(workspace_id, account, jid, type_,
|
| 243 | - pinned)
|
|
| 244 | + pinned, position)
|
|
| 244 | 245 | |
| 245 | 246 | if self._startup_finished:
|
| 246 | 247 | if select:
|
| ... | ... | @@ -252,15 +253,17 @@ def load_workspace_chats(self, workspace_id: str) -> None: |
| 252 | 253 | 'open_chats')
|
| 253 | 254 | |
| 254 | 255 | active_accounts = app.settings.get_active_accounts()
|
| 255 | - for account, jid, type_, pinned in open_chats:
|
|
| 256 | + for open_chat in open_chats:
|
|
| 257 | + account = open_chat['account']
|
|
| 256 | 258 | if account not in active_accounts:
|
| 257 | 259 | continue
|
| 258 | 260 | |
| 259 | 261 | self.add_chat_for_workspace(workspace_id,
|
| 260 | 262 | account,
|
| 261 | - jid,
|
|
| 262 | - type_,
|
|
| 263 | - pinned=pinned)
|
|
| 263 | + open_chat['jid'],
|
|
| 264 | + open_chat['type'],
|
|
| 265 | + pinned=open_chat['pinned'],
|
|
| 266 | + position=open_chat['position'])
|
|
| 264 | 267 | |
| 265 | 268 | def is_chat_active(self, account: str, jid: JID) -> bool:
|
| 266 | 269 | return self._chat_list_stack.is_chat_active(account, jid)
|
| ... | ... | @@ -34,6 +34,7 @@ |
| 34 | 34 | from gajim.common import events
|
| 35 | 35 | from gajim.common import helpers
|
| 36 | 36 | from gajim.common import preview_helpers
|
| 37 | +from gajim.common.commands import ChatCommands
|
|
| 37 | 38 | from gajim.common.i18n import _
|
| 38 | 39 | from gajim.common.const import CallType
|
| 39 | 40 | from gajim.common.modules.contacts import BareContact
|
| ... | ... | @@ -76,10 +77,10 @@ def __init__(self): |
| 76 | 77 | self._chat_banner = ChatBanner()
|
| 77 | 78 | self._chat_control = ChatControl()
|
| 78 | 79 | self._message_action_box = MessageActionsBox()
|
| 79 | - self._message_action_box.connect('command-error',
|
|
| 80 | - self._on_command_error)
|
|
| 81 | - self._message_action_box.connect('command-not-found',
|
|
| 82 | - self._on_command_not_found)
|
|
| 80 | + |
|
| 81 | + app.commands.connect('command-error', self._on_command_signal)
|
|
| 82 | + app.commands.connect('command-not-found', self._on_command_signal)
|
|
| 83 | + app.commands.connect('command-result', self._on_command_signal)
|
|
| 83 | 84 | |
| 84 | 85 | box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
| 85 | 86 | box.add(self._chat_banner)
|
| ... | ... | @@ -741,18 +742,14 @@ def clear(self) -> None: |
| 741 | 742 | self._message_action_box.clear()
|
| 742 | 743 | self._chat_control.clear()
|
| 743 | 744 | |
| 744 | - def _on_command_error(self,
|
|
| 745 | - message_actions_box: MessageActionsBox,
|
|
| 746 | - error: str) -> None:
|
|
| 747 | - |
|
| 748 | - self._chat_control.add_command_output(error, True)
|
|
| 749 | - |
|
| 750 | - def _on_command_not_found(self,
|
|
| 751 | - message_actions_box: MessageActionsBox,
|
|
| 752 | - error: str
|
|
| 753 | - ) -> None:
|
|
| 745 | + def _on_command_signal(self,
|
|
| 746 | + chat_commands: ChatCommands,
|
|
| 747 | + signal_name: str,
|
|
| 748 | + text: str
|
|
| 749 | + ) -> None:
|
|
| 754 | 750 | |
| 755 | - self._chat_control.add_command_output(error, True)
|
|
| 751 | + is_error = signal_name != 'command-result'
|
|
| 752 | + self._chat_control.add_command_output(text, is_error)
|
|
| 756 | 753 | |
| 757 | 754 | |
| 758 | 755 | class ChatPlaceholderBox(Gtk.Box):
|
| ... | ... | @@ -171,7 +171,6 @@ def __str__(self): |
| 171 | 171 | ('file-transfer', None),
|
| 172 | 172 | ('forget-groupchat', 'a{sv}'),
|
| 173 | 173 | ('groupchat-join', 'as'),
|
| 174 | - ('ipython', None),
|
|
| 175 | 174 | ('join-support-chat', None),
|
| 176 | 175 | ('manage-proxies', None),
|
| 177 | 176 | ('open-link', 'as'),
|
| ... | ... | @@ -305,7 +305,7 @@ def _add_actions(self) -> None: |
| 305 | 305 | actions = [
|
| 306 | 306 | ('add-workspace', 's', self._add_workspace),
|
| 307 | 307 | ('edit-workspace', 's', self._edit_workspace),
|
| 308 | - ('remove-workspace', None, self._remove_workspace),
|
|
| 308 | + ('remove-workspace', 's', self._remove_workspace),
|
|
| 309 | 309 | ('activate-workspace', 's', self._activate_workspace),
|
| 310 | 310 | ('add-chat', 'a{sv}', self._add_chat),
|
| 311 | 311 | ('add-group-chat', 'as', self._add_group_chat),
|
| ... | ... | @@ -616,9 +616,12 @@ def _edit_workspace(self, |
| 616 | 616 | |
| 617 | 617 | def _remove_workspace(self,
|
| 618 | 618 | _action: Gio.SimpleAction,
|
| 619 | - _param: Optional[GLib.Variant]) -> None:
|
|
| 619 | + param: GLib.Variant) -> None:
|
|
| 620 | + |
|
| 621 | + workspace_id = param.get_string() or None
|
|
| 622 | + if workspace_id is None:
|
|
| 623 | + workspace_id = self.get_active_workspace()
|
|
| 620 | 624 | |
| 621 | - workspace_id = self.get_active_workspace()
|
|
| 622 | 625 | if workspace_id is not None:
|
| 623 | 626 | self.remove_workspace(workspace_id)
|
| 624 | 627 |
| ... | ... | @@ -574,6 +574,15 @@ def get_format_menu() -> GajimMenu: |
| 574 | 574 | return GajimMenu.from_list(menuitems)
|
| 575 | 575 | |
| 576 | 576 | |
| 577 | +def get_workspace_menu(workspace_id: str) -> GajimMenu:
|
|
| 578 | + menuitems: MenuItemListT = [
|
|
| 579 | + (_('Edit…'), 'win.edit-workspace', f'"{workspace_id}"'),
|
|
| 580 | + (_('Remove'), 'win.remove-workspace', f'"{workspace_id}"'),
|
|
| 581 | + ]
|
|
| 582 | + |
|
| 583 | + return GajimMenu.from_list(menuitems)
|
|
| 584 | + |
|
| 585 | + |
|
| 577 | 586 | def escape_mnemonic(label: Optional[str]) -> Optional[str]:
|
| 578 | 587 | if label is None:
|
| 579 | 588 | return None
|
| ... | ... | @@ -37,9 +37,7 @@ |
| 37 | 37 | from gajim.common import app
|
| 38 | 38 | from gajim.common import events
|
| 39 | 39 | from gajim.common import ged
|
| 40 | -from gajim.common.commands import ArgumentParserError
|
|
| 41 | -from gajim.common.commands import CommandError
|
|
| 42 | -from gajim.common.commands import CommandNotFoundError
|
|
| 40 | +from gajim.common.commands import CommandFailed
|
|
| 43 | 41 | from gajim.common.i18n import _
|
| 44 | 42 | from gajim.common.client import Client
|
| 45 | 43 | from gajim.common.const import SimpleClientState
|
| ... | ... | @@ -67,12 +65,6 @@ |
| 67 | 65 | |
| 68 | 66 | |
| 69 | 67 | class MessageActionsBox(Gtk.Grid, ged.EventHelper):
|
| 70 | - |
|
| 71 | - __gsignals__ = {
|
|
| 72 | - 'command-error': (GObject.SignalFlags.RUN_FIRST, None, (str,)),
|
|
| 73 | - 'command-not-found': (GObject.SignalFlags.RUN_FIRST, None, (str,))
|
|
| 74 | - }
|
|
| 75 | - |
|
| 76 | 68 | def __init__(self) -> None:
|
| 77 | 69 | Gtk.Grid.__init__(self)
|
| 78 | 70 | ged.EventHelper.__init__(self)
|
| ... | ... | @@ -544,10 +536,8 @@ def _on_msg_textview_key_press_event(self, |
| 544 | 536 | not message.startswith('/me ')):
|
| 545 | 537 | try:
|
| 546 | 538 | app.commands.parse(self._contact.type_string, message)
|
| 547 | - except (ArgumentParserError, CommandError) as error:
|
|
| 548 | - self.emit('command-error', error)
|
|
| 549 | - except CommandNotFoundError as error:
|
|
| 550 | - self.emit('command-not-found', error)
|
|
| 539 | + except CommandFailed:
|
|
| 540 | + pass
|
|
| 551 | 541 | else:
|
| 552 | 542 | self.msg_textview.clear()
|
| 553 | 543 | return True
|
| ... | ... | @@ -94,7 +94,7 @@ def __init__(self, account: str) -> None: |
| 94 | 94 | self._add_actions()
|
| 95 | 95 | |
| 96 | 96 | self._avatar_selector: Optional[AvatarSelector] = None
|
| 97 | - self._current_avatar: Optional[cairo.Surface] = None
|
|
| 97 | + self._current_avatar: Optional[cairo.ImageSurface] = None
|
|
| 98 | 98 | self._current_vcard: Optional[VCard] = None
|
| 99 | 99 | self._avatar_nick_public: Optional[bool] = None
|
| 100 | 100 |
| ... | ... | @@ -106,7 +106,7 @@ def load_icon_surface( |
| 106 | 106 | size: int = 16,
|
| 107 | 107 | scale: Optional[int] = None,
|
| 108 | 108 | flags: Gtk.IconLookupFlags = Gtk.IconLookupFlags.FORCE_SIZE
|
| 109 | -) -> Optional[cairo.Surface]:
|
|
| 109 | +) -> Optional[cairo.ImageSurface]:
|
|
| 110 | 110 | |
| 111 | 111 | icon_info = load_icon_info(icon_name, size, scale, flags)
|
| 112 | 112 | if icon_info is None:
|
| ... | ... | @@ -15,7 +15,6 @@ |
| 15 | 15 | from __future__ import annotations
|
| 16 | 16 | |
| 17 | 17 | from typing import Any
|
| 18 | -from typing import List
|
|
| 19 | 18 | from typing import Optional
|
| 20 | 19 | from typing import cast
|
| 21 | 20 | |
| ... | ... | @@ -23,8 +22,6 @@ |
| 23 | 22 | import pickle
|
| 24 | 23 | |
| 25 | 24 | from gi.repository import Gdk
|
| 26 | -from gi.repository import Gio
|
|
| 27 | -from gi.repository import GLib
|
|
| 28 | 25 | from gi.repository import Gtk
|
| 29 | 26 | from nbxmpp.protocol import JID
|
| 30 | 27 | |
| ... | ... | @@ -34,6 +31,7 @@ |
| 34 | 31 | |
| 35 | 32 | from .chat_list_stack import ChatListStack
|
| 36 | 33 | from .chat_page import ChatPage
|
| 34 | +from .menus import get_workspace_menu
|
|
| 37 | 35 | from .structs import ChatListEntryParam
|
| 38 | 36 | from .util import open_window
|
| 39 | 37 | |
| ... | ... | @@ -323,7 +321,7 @@ def _popup_menu(self, _widget: Gtk.Widget, event: Gdk.EventButton) -> None: |
| 323 | 321 | if event.button != 3: # right click
|
| 324 | 322 | return
|
| 325 | 323 | |
| 326 | - menu = self._get_workspace_menu()
|
|
| 324 | + menu = get_workspace_menu(self.workspace_id)
|
|
| 327 | 325 | |
| 328 | 326 | rectangle = Gdk.Rectangle()
|
| 329 | 327 | rectangle.x = int(event.x)
|
| ... | ... | @@ -336,20 +334,6 @@ def _popup_menu(self, _widget: Gtk.Widget, event: Gdk.EventButton) -> None: |
| 336 | 334 | popover.set_pointing_to(rectangle)
|
| 337 | 335 | popover.popup()
|
| 338 | 336 | |
| 339 | - def _get_workspace_menu(self) -> Gio.Menu:
|
|
| 340 | - menu_items: List[Any] = [
|
|
| 341 | - ('edit-workspace', _('Edit…')),
|
|
| 342 | - ]
|
|
| 343 | - menu = Gio.Menu()
|
|
| 344 | - for item in menu_items:
|
|
| 345 | - action, label = item
|
|
| 346 | - action = f'win.{action}'
|
|
| 347 | - menuitem = Gio.MenuItem.new(label, action)
|
|
| 348 | - variant = GLib.Variant('s', self.workspace_id)
|
|
| 349 | - menuitem.set_action_and_target_value(action, variant)
|
|
| 350 | - menu.append_item(menuitem)
|
|
| 351 | - return menu
|
|
| 352 | - |
|
| 353 | 337 | def update_avatar(self) -> None:
|
| 354 | 338 | self._image.update()
|
| 355 | 339 |
| ... | ... | @@ -38,13 +38,11 @@ |
| 38 | 38 | from typing import Optional
|
| 39 | 39 | from typing import Union
|
| 40 | 40 | |
| 41 | -import os
|
|
| 42 | 41 | import sys
|
| 43 | 42 | import time
|
| 44 | 43 | import logging
|
| 45 | 44 | from functools import partial
|
| 46 | 45 | from threading import Thread
|
| 47 | -from importlib.util import find_spec
|
|
| 48 | 46 | |
| 49 | 47 | from gi.repository import Gtk
|
| 50 | 48 | from gi.repository import GLib
|
| ... | ... | @@ -678,47 +676,6 @@ def save_avatar(self, data: bytes) -> Optional[str]: |
| 678 | 676 | def avatar_exists(self, filename: str) -> bool:
|
| 679 | 677 | return app.app.avatar_storage.get_avatar_path(filename) is not None
|
| 680 | 678 | |
| 681 | - @staticmethod
|
|
| 682 | - def create_ipython_window() -> None:
|
|
| 683 | - # Check if IPython is installed
|
|
| 684 | - ipython = find_spec('IPython')
|
|
| 685 | - is_installed = ipython is not None
|
|
| 686 | - if not is_installed:
|
|
| 687 | - # Abort early to avoid tracebacks
|
|
| 688 | - print('IPython is not installed')
|
|
| 689 | - return
|
|
| 690 | - try:
|
|
| 691 | - from gajim.dev.ipython_view import IPythonView
|
|
| 692 | - except ImportError:
|
|
| 693 | - print('ipython_view not found')
|
|
| 694 | - return
|
|
| 695 | - from gi.repository import Pango
|
|
| 696 | - |
|
| 697 | - if os.name == 'nt':
|
|
| 698 | - font = 'Lucida Console 9'
|
|
| 699 | - else:
|
|
| 700 | - font = 'Luxi Mono 10'
|
|
| 701 | - |
|
| 702 | - window = Gtk.Window()
|
|
| 703 | - window.set_title(_('Gajim: IPython Console'))
|
|
| 704 | - window.set_size_request(750, 550)
|
|
| 705 | - window.set_resizable(True)
|
|
| 706 | - sw = Gtk.ScrolledWindow()
|
|
| 707 | - sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
|
| 708 | - view = IPythonView()
|
|
| 709 | - view.override_font(Pango.FontDescription(font))
|
|
| 710 | - view.set_wrap_mode(Gtk.WrapMode.CHAR)
|
|
| 711 | - sw.add(view)
|
|
| 712 | - window.add(sw)
|
|
| 713 | - window.show_all()
|
|
| 714 | - |
|
| 715 | - def _on_delete(win, _event):
|
|
| 716 | - win.hide()
|
|
| 717 | - return True
|
|
| 718 | - window.connect('delete_event', _on_delete)
|
|
| 719 | - view.updateNamespace({'gajim': app})
|
|
| 720 | - app.ipython_window = window
|
|
| 721 | - |
|
| 722 | 679 | def run(self, _application: Gtk.Application) -> None:
|
| 723 | 680 | # Creating plugin manager
|
| 724 | 681 | from gajim import plugins
|
| ... | ... | @@ -15,15 +15,13 @@ |
| 15 | 15 | ".ci/*",
|
| 16 | 16 | "scripts/*",
|
| 17 | 17 | "win/*",
|
| 18 | - "gajim/gajim.py",
|
|
| 19 | - "gajim/gajim_remote.py",
|
|
| 20 | - "gajim/common/dbus/*",
|
|
| 21 | 18 | "gajim/common/application.py",
|
| 22 | 19 | "gajim/common/call_manager.py",
|
| 23 | 20 | "gajim/common/cert_store.py",
|
| 24 | 21 | "gajim/common/commands.py",
|
| 25 | 22 | "gajim/common/configpaths.py",
|
| 26 | 23 | "gajim/common/const.py",
|
| 24 | + "gajim/common/dbus/*",
|
|
| 27 | 25 | "gajim/common/events.py",
|
| 28 | 26 | "gajim/common/exceptions.py",
|
| 29 | 27 | "gajim/common/filetransfer.py",
|
| ... | ... | @@ -34,15 +32,16 @@ |
| 34 | 32 | "gajim/common/modules/chat_markers.py",
|
| 35 | 33 | "gajim/common/modules/pep.py",
|
| 36 | 34 | "gajim/common/modules/register.py",
|
| 37 | - "gajim/common/modules/vcard_temp.py",
|
|
| 38 | 35 | "gajim/common/modules/vcard4.py",
|
| 36 | + "gajim/common/modules/vcard_temp.py",
|
|
| 39 | 37 | "gajim/common/passwords.py",
|
| 40 | 38 | "gajim/common/regex.py",
|
| 41 | 39 | "gajim/common/setting_values.py",
|
| 42 | 40 | "gajim/common/sound.py",
|
| 43 | 41 | "gajim/common/storage/",
|
| 44 | 42 | "gajim/common/task_manager.py",
|
| 45 | - "gajim/gtk/conversation",
|
|
| 43 | + "gajim/gajim.py",
|
|
| 44 | + "gajim/gajim_remote.py",
|
|
| 46 | 45 | "gajim/gtk/about.py",
|
| 47 | 46 | "gajim/gtk/account_page.py",
|
| 48 | 47 | "gajim/gtk/account_side_bar.py",
|
| ... | ... | @@ -51,6 +50,7 @@ |
| 51 | 50 | "gajim/gtk/app_side_bar.py",
|
| 52 | 51 | "gajim/gtk/application.py",
|
| 53 | 52 | "gajim/gtk/apply_button_box.py",
|
| 53 | + "gajim/gtk/avatar.py",
|
|
| 54 | 54 | "gajim/gtk/avatar_selector.py",
|
| 55 | 55 | "gajim/gtk/blocking.py",
|
| 56 | 56 | "gajim/gtk/bookmarks.py",
|
| ... | ... | @@ -62,8 +62,8 @@ |
| 62 | 62 | "gajim/gtk/chat_banner.py",
|
| 63 | 63 | "gajim/gtk/chat_filter.py",
|
| 64 | 64 | "gajim/gtk/chat_function_page.py",
|
| 65 | - "gajim/gtk/chat_list_stack.py",
|
|
| 66 | 65 | "gajim/gtk/chat_list.py",
|
| 66 | + "gajim/gtk/chat_list_stack.py",
|
|
| 67 | 67 | "gajim/gtk/chat_page.py",
|
| 68 | 68 | "gajim/gtk/chat_stack.py",
|
| 69 | 69 | "gajim/gtk/const.py",
|
| ... | ... | @@ -71,6 +71,7 @@ |
| 71 | 71 | "gajim/gtk/contact_settings.py",
|
| 72 | 72 | "gajim/gtk/contacts_flowbox.py",
|
| 73 | 73 | "gajim/gtk/control.py",
|
| 74 | + "gajim/gtk/conversation/*",
|
|
| 74 | 75 | "gajim/gtk/dialogs.py",
|
| 75 | 76 | "gajim/gtk/emoji_data_gtk.py",
|
| 76 | 77 | "gajim/gtk/exception.py",
|
| ... | ... | @@ -83,8 +84,8 @@ |
| 83 | 84 | "gajim/gtk/groupchat_invitation.py",
|
| 84 | 85 | "gajim/gtk/groupchat_inviter.py",
|
| 85 | 86 | "gajim/gtk/groupchat_join.py",
|
| 86 | - "gajim/gtk/groupchat_nick_completion.py",
|
|
| 87 | 87 | "gajim/gtk/groupchat_nick.py",
|
| 88 | + "gajim/gtk/groupchat_nick_completion.py",
|
|
| 88 | 89 | "gajim/gtk/groupchat_outcasts.py",
|
| 89 | 90 | "gajim/gtk/groupchat_roster.py",
|
| 90 | 91 | "gajim/gtk/groupchat_settings.py",
|
| ... | ... | @@ -92,20 +93,20 @@ |
| 92 | 93 | "gajim/gtk/gstreamer.py",
|
| 93 | 94 | "gajim/gtk/history_export.py",
|
| 94 | 95 | "gajim/gtk/history_sync.py",
|
| 95 | - "gajim/gtk/main_stack.py",
|
|
| 96 | 96 | "gajim/gtk/main.py",
|
| 97 | + "gajim/gtk/main_stack.py",
|
|
| 97 | 98 | "gajim/gtk/mam_preferences.py",
|
| 98 | 99 | "gajim/gtk/manage_sounds.py",
|
| 99 | 100 | "gajim/gtk/menus.py",
|
| 100 | 101 | "gajim/gtk/message_actions_box.py",
|
| 101 | 102 | "gajim/gtk/message_input.py",
|
| 102 | - "gajim/gtk/notification_manager.py",
|
|
| 103 | 103 | "gajim/gtk/notification.py",
|
| 104 | + "gajim/gtk/notification_manager.py",
|
|
| 104 | 105 | "gajim/gtk/password_dialog.py",
|
| 105 | 106 | "gajim/gtk/pep_config.py",
|
| 106 | 107 | "gajim/gtk/plugins.py",
|
| 107 | - "gajim/gtk/preview_audio.py",
|
|
| 108 | 108 | "gajim/gtk/preview.py",
|
| 109 | + "gajim/gtk/preview_audio.py",
|
|
| 109 | 110 | "gajim/gtk/proxies.py",
|
| 110 | 111 | "gajim/gtk/remove_account.py",
|
| 111 | 112 | "gajim/gtk/resource_selector.py",
|
| ... | ... | @@ -123,6 +124,7 @@ |
| 123 | 124 | "gajim/gtk/util.py",
|
| 124 | 125 | "gajim/gtk/video_preview.py",
|
| 125 | 126 | "gajim/gtk/workspace_dialog.py",
|
| 127 | + "gajim/gtk/workspace_side_bar.py",
|
|
| 126 | 128 | "gajim/gtk/xml_console.py",
|
| 127 | 129 | "gajim/plugins/gajimplugin.py",
|
| 128 | 130 | "gajim/plugins/helpers.py",
|