diff --git a/gajim/data/gui/chat_paned.ui b/gajim/data/gui/chat_paned.ui new file mode 100644 index 0000000000000000000000000000000000000000..278d75fbda834dbfcbf44ffe321cb9c2eaec8bdf --- /dev/null +++ b/gajim/data/gui/chat_paned.ui @@ -0,0 +1,197 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- Generated with glade 3.38.2 --> +<interface> + <requires lib="gtk+" version="3.24"/> + <object class="GtkPaned" id="paned"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="position">250</property> + <property name="position-set">True</property> + <child> + <!-- n-columns=1 n-rows=2 --> + <object class="GtkGrid" id="middle_grid"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="vexpand">True</property> + <property name="orientation">vertical</property> + <child> + <!-- n-columns=1 n-rows=3 --> + <object class="GtkGrid"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="row-spacing">3</property> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkLabel" id="workspace_label"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label">Workspace Label</property> + <property name="ellipsize">end</property> + <style> + <class name="bold16"/> + </style> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkMenuButton" id="workspace_menu_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="focus-on-click">False</property> + <property name="receives-default">True</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">open-menu-symbolic</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="pack-type">end</property> + <property name="position">2</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkSeparator"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="margin-left">6</property> + <property name="margin-right">6</property> + <property name="margin-start">6</property> + <property name="margin-end">6</property> + <property name="margin-top">12</property> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">2</property> + </packing> + </child> + <child> + <object class="GtkBox"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="spacing">12</property> + <child> + <object class="GtkSearchEntry" id="search_entry"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="hexpand">True</property> + <property name="primary-icon-name">edit-find-symbolic</property> + <property name="primary-icon-activatable">False</property> + <property name="primary-icon-sensitive">False</property> + <property name="placeholder-text" translatable="yes">Search…</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkButton" id="start_chat_button"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">True</property> + <property name="tooltip-text" translatable="yes">Start Chat</property> + <child> + <object class="GtkImage"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="icon-name">list-add-symbolic</property> + </object> + </child> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">1</property> + </packing> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">1</property> + </packing> + </child> + <style> + <class name="chatlist-top-bar"/> + </style> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + <child> + <object class="GtkScrolledWindow" id="chat_list_scrolled"> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="shadow-type">in</property> + <child> + <placeholder/> + </child> + <style> + <class name="no-border"/> + </style> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">1</property> + </packing> + </child> + <style> + <class name="middle-grid"/> + </style> + </object> + <packing> + <property name="resize">False</property> + <property name="shrink">False</property> + </packing> + </child> + <child> + <!-- n-columns=1 n-rows=1 --> + <object class="GtkGrid" id="right_grid"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <child> + <object class="GtkOverlay" id="right_grid_overlay"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="hexpand">True</property> + <property name="vexpand">True</property> + <child> + <placeholder/> + </child> + </object> + <packing> + <property name="left-attach">0</property> + <property name="top-attach">0</property> + </packing> + </child> + </object> + <packing> + <property name="resize">True</property> + <property name="shrink">True</property> + </packing> + </child> + </object> +</interface> diff --git a/gajim/data/gui/main.ui b/gajim/data/gui/main.ui index 32891fb471a521f2b9c41db9523deae166f124b5..b7f668f94a4efc5b3582bca0b49b90635c27d54c 100644 --- a/gajim/data/gui/main.ui +++ b/gajim/data/gui/main.ui @@ -2,7 +2,7 @@ <!-- Generated with glade 3.38.2 --> <interface> <requires lib="gtk+" version="3.22"/> - <!-- n-columns=3 n-rows=3 --> + <!-- n-columns=2 n-rows=1 --> <object class="GtkGrid" id="main_grid"> <property name="visible">True</property> <property name="can-focus">False</property> @@ -130,229 +130,6 @@ <property name="top-attach">0</property> </packing> </child> - <child> - <object class="GtkStack" id="main_stack"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <child> - <object class="GtkPaned" id="paned"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="position">250</property> - <property name="position-set">True</property> - <child> - <!-- n-columns=1 n-rows=2 --> - <object class="GtkGrid" id="middle_grid"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="vexpand">True</property> - <property name="orientation">vertical</property> - <child> - <!-- n-columns=1 n-rows=3 --> - <object class="GtkGrid"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="row-spacing">3</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="spacing">12</property> - <child> - <object class="GtkLabel" id="workspace_label"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label">Workspace Label</property> - <property name="ellipsize">end</property> - <style> - <class name="bold16"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkMenuButton" id="workspace_menu_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="focus-on-click">False</property> - <property name="receives-default">True</property> - <child> - <object class="GtkImage"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="icon-name">open-menu-symbolic</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="pack-type">end</property> - <property name="position">2</property> - </packing> - </child> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">0</property> - </packing> - </child> - <child> - <object class="GtkSeparator"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="margin-start">6</property> - <property name="margin-end">6</property> - <property name="margin-top">12</property> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">2</property> - </packing> - </child> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="spacing">12</property> - <child> - <object class="GtkSearchEntry" id="search_entry"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="hexpand">True</property> - <property name="primary-icon-name">edit-find-symbolic</property> - <property name="primary-icon-activatable">False</property> - <property name="primary-icon-sensitive">False</property> - <property name="placeholder-text" translatable="yes">Search…</property> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkButton" id="start_chat_button"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">True</property> - <property name="tooltip-text" translatable="yes">Start Chat</property> - <child> - <object class="GtkImage"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="icon-name">list-add-symbolic</property> - </object> - </child> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">1</property> - </packing> - </child> - <style> - <class name="chatlist-top-bar"/> - </style> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">0</property> - </packing> - </child> - <child> - <object class="GtkScrolledWindow" id="chat_list_scrolled"> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="shadow-type">in</property> - <child> - <placeholder/> - </child> - <style> - <class name="no-border"/> - </style> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">1</property> - </packing> - </child> - <style> - <class name="middle-grid"/> - </style> - </object> - <packing> - <property name="resize">False</property> - <property name="shrink">False</property> - </packing> - </child> - <child> - <!-- n-columns=1 n-rows=1 --> - <object class="GtkGrid" id="right_grid"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="hexpand">True</property> - <property name="vexpand">True</property> - <child> - <object class="GtkOverlay" id="right_grid_overlay"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="hexpand">True</property> - <property name="vexpand">True</property> - <child> - <placeholder/> - </child> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">0</property> - </packing> - </child> - </object> - <packing> - <property name="resize">True</property> - <property name="shrink">True</property> - </packing> - </child> - </object> - <packing> - <property name="name">chats</property> - </packing> - </child> - </object> - <packing> - <property name="left-attach">1</property> - <property name="top-attach">0</property> - </packing> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> - <child> - <placeholder/> - </child> <child> <placeholder/> </child> diff --git a/gajim/gtk/account_page.py b/gajim/gtk/account_page.py index d621ba102b025724c664f30adb4cfce15b9b6d24..c60adc5d5965ecda842b791348b8eab78b9011f3 100644 --- a/gajim/gtk/account_page.py +++ b/gajim/gtk/account_page.py @@ -64,7 +64,6 @@ def __init__(self, account): self._ui.roster_menu_button.set_menu_model(roster_menu) self._ui.connect_signals(self) - self.show_all() # pylint: disable=line-too-long self.register_events([ @@ -76,6 +75,7 @@ def __init__(self, account): # pylint: enable=line-too-long self.update() + self.show_all() def _on_edit_profile(self, _button): open_window('ProfileWindow', account=self._account) diff --git a/gajim/gtk/account_side_bar.py b/gajim/gtk/account_side_bar.py index 9d97b566c7eda868672e6a1cc9f81f286403eff4..949bb6d7553e8cc67424189dd623b8e31f571ec8 100644 --- a/gajim/gtk/account_side_bar.py +++ b/gajim/gtk/account_side_bar.py @@ -1,5 +1,17 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see <http://www.gnu.org/licenses/>. -from gi.repository import GLib from gi.repository import Gtk from gajim.common.const import AvatarSize diff --git a/gajim/gtk/chat_list.py b/gajim/gtk/chat_list.py index 0ab7578e2997dcc82321afe478a458de04e42985..f10d19124195b25a762537717336080efd06d767 100644 --- a/gajim/gtk/chat_list.py +++ b/gajim/gtk/chat_list.py @@ -1,3 +1,17 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see <http://www.gnu.org/licenses/>. + import logging from gi.repository import Gdk diff --git a/gajim/gtk/chat_list_stack.py b/gajim/gtk/chat_list_stack.py index c579a214906f8b2ce6403703003bb76350a0d3c5..61719e1d23d2b868286da10bbb71444f8c862339 100644 --- a/gajim/gtk/chat_list_stack.py +++ b/gajim/gtk/chat_list_stack.py @@ -1,6 +1,21 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see <http://www.gnu.org/licenses/>. from gi.repository import Gtk from gi.repository import GObject +from gi.repository import Gio +from gi.repository import GLib from gajim.common import app @@ -26,6 +41,9 @@ class ChatListStack(Gtk.Stack): 'chat-selected': (GObject.SignalFlags.RUN_LAST, None, (str, str, str)), + 'chat-unselected': (GObject.SignalFlags.RUN_LAST, + None, + ()), } def __init__(self, main_window, ui, chat_stack): @@ -42,13 +60,29 @@ def __init__(self, main_window, ui, chat_stack): self.add_named(Gtk.Box(), 'default') - self.show_all() self.connect('notify::visible-child-name', self._on_visible_child_name) self._ui.search_entry.connect( 'search-changed', self._on_search_changed) main_window.connect('notify::is-active', self._on_window_active) + self._add_actions() + self.show_all() + + def _add_actions(self): + actions = [ + ('toggle-chat-pinned', 'as', self._toggle_chat_pinned), + ('move-chat-to-workspace', 'as', self._move_chat_to_workspace), + ] + + for action in actions: + action_name, variant, func = action + if variant is not None: + variant = GLib.VariantType.new(variant) + act = Gio.SimpleAction.new(action_name, variant) + act.connect('activate', func) + app.window.add_action(act) + def _on_window_active(self, window, _param): is_active = window.get_property('is-active') if is_active: @@ -108,10 +142,9 @@ def remove_chat_list(self, workspace_id): def _on_row_selected(self, _chat_list, row): if row is None: - self._chat_stack.clear() + self.emit('chat-unselected') return - self._chat_stack.show_chat(row.account, row.jid) if row.is_active: row.reset_unread() self.emit('chat-selected', row.workspace_id, row.account, row.jid) @@ -148,11 +181,24 @@ def store_open_chats(self, workspace_id): app.settings.set_workspace_setting( workspace_id, 'open_chats', open_chats) - def toggle_chat_pinned(self, workspace_id, account, jid): + def _toggle_chat_pinned(self, _action, param): + workspace_id, account, jid = param.unpack() chat_list = self._chat_lists[workspace_id] chat_list.toggle_chat_pinned(account, jid) self.store_open_chats(workspace_id) + def _move_chat_to_workspace(self, _action, param): + new_workspace_id, account, jid = param.unpack() + + current_chatlist = self.get_visible_child() + type_ = current_chatlist.get_chat_type(account, jid) + current_chatlist.remove_chat(account, jid) + + new_chatlist = self.get_chatlist(new_workspace_id) + new_chatlist.add_chat(account, jid, type_) + self.store_open_chats(current_chatlist.workspace_id) + self.store_open_chats(new_workspace_id) + def remove_chat(self, workspace_id, account, jid): chat_list = self._chat_lists[workspace_id] type_ = chat_list.get_chat_type(account, jid) diff --git a/gajim/gtk/chat_page.py b/gajim/gtk/chat_page.py new file mode 100644 index 0000000000000000000000000000000000000000..866d97351226f303595d07db8bcae74cc89f1a15 --- /dev/null +++ b/gajim/gtk/chat_page.py @@ -0,0 +1,232 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see <http://www.gnu.org/licenses/>. + + +from gi.repository import Gtk +from gi.repository import GLib +from gi.repository import Gio +from gi.repository import GObject + +from gajim.common import app +from gajim.common.i18n import _ + +from .util import get_builder +from .chat_list_stack import ChatListStack +from .chat_stack import ChatStack +from .search_view import SearchView + + +WORKSPACE_MENU_DICT = { + 'edit': _('Edit…'), + 'remove': _('Remove'), +} + + +class ChatPage(Gtk.Box): + + __gsignals__ = { + 'chat-selected': (GObject.SignalFlags.RUN_LAST, + None, + (str, str, str)), + } + + def __init__(self): + Gtk.Box.__init__(self) + self._ui = get_builder('chat_paned.ui') + self.add(self._ui.paned) + + self._chat_stack = ChatStack() + self._ui.right_grid_overlay.add(self._chat_stack) + + self._search_view = SearchView() + self._search_view.connect('hide-search', self._on_search_hide) + + self._search_revealer = Gtk.Revealer() + self._search_revealer.set_reveal_child(True) + self._search_revealer.set_halign(Gtk.Align.END) + self._search_revealer.set_no_show_all(True) + self._search_revealer.add(self._search_view) + self._ui.right_grid_overlay.add_overlay(self._search_revealer) + + self._chat_list_stack = ChatListStack(self, self._ui, self._chat_stack) + self._chat_list_stack.connect('chat-selected', self._on_chat_selected) + self._chat_list_stack.connect('chat-unselected', + self._on_chat_unselected) + self._ui.chat_list_scrolled.add(self._chat_list_stack) + + self._ui.start_chat_button.connect('clicked', + self._on_start_chat_clicked) + + self._ui.paned.set_position(app.settings.get('chat_handle_position')) + self._ui.paned.connect('button-release-event', self._on_button_release) + + workspace_menu = Gio.Menu() + for action, label in WORKSPACE_MENU_DICT.items(): + workspace_menu.append(label, f'win.{action.lower()}-workspace') + + self._ui.workspace_menu_button.set_menu_model(workspace_menu) + + self._startup_finished = False + + self._add_actions() + + self.show_all() + + def _add_actions(self): + actions = [ + ('remove-chat', 'as', self._remove_chat), + ('search-history', None, self._on_search_history), + ] + + for action in actions: + action_name, variant, func = action + if variant is not None: + variant = GLib.VariantType.new(variant) + act = Gio.SimpleAction.new(action_name, variant) + act.connect('activate', func) + app.window.add_action(act) + + def set_startup_finished(self): + self._startup_finished = True + + def get_chat_list_stack(self): + return self._chat_list_stack + + def get_chat_stack(self): + return self._chat_stack + + @staticmethod + def _on_start_chat_clicked(_button): + app.app.activate_action('start-chat', GLib.Variant('s', '')) + + @staticmethod + def _on_button_release(paned, event): + if event.window != paned.get_handle_window(): + return + position = paned.get_position() + app.settings.set('chat_handle_position', position) + + def _on_chat_selected(self, _chat_list_stack, workspace_id, account, jid): + self._chat_stack.show_chat(account, jid) + self._search_view.set_context(account, jid) + self.emit('chat-selected', workspace_id, account, jid) + + def _on_chat_unselected(self, _chat_list_stack): + self._chat_stack.clear() + + def _on_search_history(self, _action, _param): + control = self.get_active_control() + if control is not None: + self._search_view.set_context(control.account, control.contact.jid) + self._search_view.clear() + self._search_revealer.show() + self._search_view.set_focus() + + def _on_search_hide(self, *args): + self._search_revealer.hide() + + def process_event(self, event): + self._chat_stack.process_event(event) + self._chat_list_stack.process_event(event) + + def add_chat_list(self, workspace_id): + self._chat_list_stack.add_chat_list(workspace_id) + + def show_workspace_chats(self, workspace_id): + self._chat_list_stack.show_chat_list(workspace_id) + + def update_workspace(self, workspace_id): + name = app.settings.get_workspace_setting(workspace_id, 'name') + self._ui.workspace_label.set_text(name) + + def remove_chat_list(self, workspace_id): + self._chat_list_stack.remove_chat_list(workspace_id) + + def chat_exists(self, account, jid): + return self._chat_list_stack.contains_chat(account, jid) + + def chat_exists_for_workspace(self, workspace_id, account, jid): + return self._chat_list_stack.contains_chat( + account, jid, workspace_id=workspace_id) + + def add_chat_for_workspace(self, + workspace_id, + account, + jid, + type_, + pinned=False, + select=False): + + if self.chat_exists(account, jid): + if select: + self._chat_list_stack.select_chat(account, jid) + return + + if type_ == 'groupchat': + self._chat_stack.add_group_chat(account, jid) + elif type_ == 'pm': + if not self._startup_finished: + # TODO: Currently we cant load private chats at start + # because the Contacts dont exist yet + return + self._chat_stack.add_private_chat(account, jid) + else: + self._chat_stack.add_chat(account, jid) + self._chat_list_stack.add_chat(workspace_id, account, jid, type_, + pinned) + + if self._startup_finished: + if select: + self._chat_list_stack.select_chat(account, jid) + self._chat_list_stack.store_open_chats(workspace_id) + + def load_workspace_chats(self, workspace_id): + open_chats = app.settings.get_workspace_setting(workspace_id, + 'open_chats') + + active_accounts = app.settings.get_active_accounts() + for account, jid, type_, pinned in open_chats: + if account not in active_accounts: + continue + + self.add_chat_for_workspace(workspace_id, + account, + jid, + type_, + pinned=pinned) + + def is_chat_active(self, account, jid): + return self._chat_list_stack.is_chat_active(account, jid) + + def _remove_chat(self, _action, param): + account, jid = param.unpack() + self.remove_chat(account, jid) + + def remove_chat(self, account, jid): + for workspace_id in app.settings.get_workspaces(): + if self.chat_exists_for_workspace(workspace_id, account, jid): + self._chat_list_stack.remove_chat(workspace_id, account, jid) + return + + def get_control(self, account, jid): + return self._chat_stack.get_control(account, jid) + + def get_active_control(self): + chat = self._chat_list_stack.get_selected_chat() + if chat is None: + return None + return self.get_control(chat.account, chat.jid) + + def get_controls(self, account=None): + return self._chat_stack.get_controls(account) diff --git a/gajim/gtk/chat_stack.py b/gajim/gtk/chat_stack.py index 5ea020612ac53d88e0a76c5c07ebd5e841d8c37b..3214c8043972236f4aee9ebdd25f0854b29c31c4 100644 --- a/gajim/gtk/chat_stack.py +++ b/gajim/gtk/chat_stack.py @@ -1,15 +1,25 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see <http://www.gnu.org/licenses/>. import logging from gi.repository import Gtk -from gajim.common import app from gajim.chat_control import ChatControl from gajim.groupchat_control import GroupchatControl from gajim.privatechat_control import PrivateChatControl -from gajim.common.i18n import _ - log = logging.getLogger('gajim.gui.chatstack') diff --git a/gajim/gtk/main.py b/gajim/gtk/main.py index 300eb0dc5649e0e6d5643c5e5765af5c7cc32156..06328f8ca923feed70ede5e66f63e822ab8636b2 100644 --- a/gajim/gtk/main.py +++ b/gajim/gtk/main.py @@ -11,13 +11,10 @@ from gajim.common.i18n import _ from gajim.common.nec import EventHelper -from gajim.gui.account_page import AccountPage from gajim.gui.adhoc import AdHocCommand -from gajim.gui.search_view import SearchView -from gajim.gui.chat_list_stack import ChatListStack -from gajim.gui.chat_stack import ChatStack from gajim.gui.account_side_bar import AccountSideBar from gajim.gui.workspace_side_bar import WorkspaceSideBar +from gajim.gui.main_stack import MainStack from gajim.gui.dialogs import DialogButton from gajim.gui.dialogs import ConfirmationDialog from gajim.gui.util import get_builder @@ -27,11 +24,6 @@ log = logging.getLogger('gajim.gui.main') -WORKSPACE_MENU_DICT = { - 'edit': _('Edit…'), - 'remove': _('Remove'), -} - class MainWindow(Gtk.ApplicationWindow, EventHelper): def __init__(self): @@ -46,8 +38,6 @@ def __init__(self): self.window = self app.window = self - self._active_workspace = None - self._startup_finished = False self._ui = get_builder('main.ui') @@ -57,44 +47,19 @@ def __init__(self): surface = load_icon('org.gajim.Gajim', self, 40) self._ui.app_image.set_from_surface(surface) - self._chat_stack = ChatStack() - self._ui.right_grid_overlay.add(self._chat_stack) - - self._search_view = SearchView() - self._search_view.connect('hide-search', self._on_search_hide) - - self._search_revealer = Gtk.Revealer() - self._search_revealer.set_reveal_child(True) - self._search_revealer.set_halign(Gtk.Align.END) - self._search_revealer.set_no_show_all(True) - self._search_revealer.add(self._search_view) - self._ui.right_grid_overlay.add_overlay(self._search_revealer) + self._main_stack = MainStack() + self._ui.main_grid.add(self._main_stack) - self._chat_list_stack = ChatListStack(self, self._ui, self._chat_stack) - self._chat_list_stack.connect('chat-selected', self._on_chat_selected) - self._ui.chat_list_scrolled.add(self._chat_list_stack) + self._chat_page = self._main_stack.get_chat_page() - self._workspace_side_bar = WorkspaceSideBar(self._chat_list_stack) + self._workspace_side_bar = WorkspaceSideBar(self._chat_page) self._ui.workspace_scrolled.add(self._workspace_side_bar) self._account_side_bar = AccountSideBar() self._ui.account_box.add(self._account_side_bar) - self._account_pages = {} - - workspace_menu = Gio.Menu() - for action, label in WORKSPACE_MENU_DICT.items(): - workspace_menu.append(label, f'win.{action.lower()}-workspace') - - self._ui.workspace_menu_button.set_menu_model(workspace_menu) - - self._ui.start_chat_button.connect( - 'clicked', self._on_start_chat_clicked) self._ui.connect_signals(self) - self._ui.paned.set_position(app.settings.get('chat_handle_position')) - self._ui.paned.connect('button-release-event', self._on_button_release) - self.register_events([ ('presence-received', ged.GUI1, self._on_event), ('caps-update', ged.GUI1, self._on_event), @@ -113,12 +78,10 @@ def __init__(self): ('signed-in', ged.GUI1, self._on_signed_in), ]) - self.show_all() - self._load_chats() - self._add_accounts() self._add_actions() self._add_actions2() + self.show_all() @staticmethod def _on_our_show(event): @@ -131,13 +94,6 @@ def _on_signed_in(event): app.app.set_account_actions_state(event.account, True) app.app.update_app_actions_state() - @staticmethod - def _on_button_release(paned, event): - if event.window != paned.get_handle_window(): - return - position = paned.get_position() - app.settings.set('chat_handle_position', position) - def _add_actions(self): actions = [ ('add-workspace', 's', self._add_workspace), @@ -146,11 +102,7 @@ def _add_actions(self): ('activate-workspace', 's', self._activate_workspace), ('add-chat', 'a{sv}', self._add_chat), ('add-group-chat', 'as', self._add_group_chat), - ('remove-chat', 'as', self._remove_chat), - ('toggle-chat-pinned', 'as', self._toggle_chat_pinned), - ('move-chat-to-workspace', 'as', self._move_chat_to_workspace), ('add-to-roster', 'as', self._add_to_roster), - ('search-history', None, self._on_search_history), ] for action in actions: @@ -275,6 +227,10 @@ def _on_action(self, action, _param): # self.notebook.set_current_page(number - 1) # return + def _set_startup_finished(self): + self._startup_finished = True + self._chat_page.set_startup_finished() + def get_widget(self, name): return getattr(self._ui, name) @@ -290,15 +246,9 @@ def show_title(self, *args): def get_active_jid(self, *args): pass - def get_account_page(self, account): - return self._account_pages[account] - def show_account_page(self, account): self._account_side_bar.activate_account_page(account) - self._ui.main_stack.set_visible_child_name(account) - - def get_workspace_bar(self): - return self._workspace_side_bar + self._main_stack.show_account(account) def get_active_workspace(self): return self._workspace_side_bar.get_active_workspace() @@ -306,10 +256,7 @@ def get_active_workspace(self): def is_chat_active(self, account, jid): if not self.get_property('has-toplevel-focus'): return False - return self._chat_list_stack.is_chat_active(account, jid) - - def show_chats(self): - self._ui.main_stack.set_visible_child_name('chats') + return self._chat_page.is_chat_active(account, jid) def _add_workspace(self, _action, param): workspace_id = param.get_string() @@ -317,20 +264,16 @@ def _add_workspace(self, _action, param): def add_workspace(self, workspace_id): self._workspace_side_bar.add_workspace(workspace_id) - self._chat_list_stack.add_chat_list(workspace_id) - self._workspace_side_bar.activate_workspace(workspace_id) - self._chat_list_stack.show_chat_list(workspace_id) + self._chat_page.add_chat_list(workspace_id) + if self._startup_finished: + self.activate_workspace(workspace_id) self._workspace_side_bar.store_workspace_order() def _edit_workspace(self, _action, _param): workspace_id = self.get_active_workspace() if workspace_id is not None: - self.edit_workspace(workspace_id) - - @staticmethod - def edit_workspace(workspace_id): - open_window('WorkspaceDialog', workspace_id=workspace_id) + open_window('WorkspaceDialog', workspace_id=workspace_id) def _remove_workspace(self, _action, _param): workspace_id = self.get_active_workspace() @@ -346,10 +289,9 @@ def remove_workspace(self, workspace_id): if was_active: new_active_id = self._workspace_side_bar.get_first_workspace() - self._workspace_side_bar.activate_workspace(new_active_id) - self._chat_list_stack.show_chat_list(new_active_id) + self.activate_workspace(new_active_id) - self._chat_list_stack.remove_chat_list(workspace_id) + self._chat_page.remove_chat_list(workspace_id) app.settings.remove_workspace(workspace_id) def _activate_workspace(self, _action, param): @@ -357,138 +299,59 @@ def _activate_workspace(self, _action, param): self.activate_workspace(workspace_id) def activate_workspace(self, workspace_id): - self._ui.main_stack.set_visible_child_name('chats') + self._main_stack.show_chats(workspace_id) self._workspace_side_bar.activate_workspace(workspace_id) - self._chat_list_stack.show_chat_list(workspace_id) def update_workspace(self, workspace_id): - name = app.settings.get_workspace_setting(workspace_id, 'name') - self._ui.workspace_label.set_text(name) - app.interface.avatar_storage.invalidate_cache(workspace_id) + self._chat_page.update_workspace(workspace_id) self._workspace_side_bar.update_avatar(workspace_id) def _add_group_chat(self, _action, param): - account, jid = param.unpack() - self.add_group_chat(account, jid) + self.add_group_chat(**param.unpack()) def add_group_chat(self, account, jid, select=False): - workspace_id = self._workspace_side_bar.get_active_workspace() - self.add_chat_for_workspace(workspace_id, - account, - jid, - 'groupchat', - select=select) + workspace_id = self.get_active_workspace() + self._chat_page.add_chat_for_workspace(workspace_id, + account, + jid, + 'groupchat', + select=select) def _add_chat(self, _action, param): self.add_chat(**param.unpack()) def add_chat(self, account, jid, type_, select=False): - workspace_id = self._workspace_side_bar.get_active_workspace() - self.add_chat_for_workspace(workspace_id, - account, - jid, - type_, - select=select) + workspace_id = self.get_active_workspace() + self._chat_page.add_chat_for_workspace(workspace_id, + account, + jid, + type_, + select=select) def add_private_chat(self, account, jid, select=False): - workspace_id = self._workspace_side_bar.get_active_workspace() - self.add_chat_for_workspace(workspace_id, - account, - jid, - 'pm', - select=select) - - def add_chat_for_workspace(self, - workspace_id, - account, - jid, - type_, - pinned=False, - select=False): - if self.chat_exists(account, jid): - if select: - self._chat_list_stack.select_chat(account, jid) - self.show_chats() - return - - if type_ == 'groupchat': - self._chat_stack.add_group_chat(account, jid) - elif type_ == 'pm': - if not self._startup_finished: - # TODO: Currently we cant load private chats at start - # because the Contacts dont exist yet - return - self._chat_stack.add_private_chat(account, jid) - else: - self._chat_stack.add_chat(account, jid) - self._chat_list_stack.add_chat(workspace_id, account, jid, type_, - pinned) - - if self._startup_finished: - if select: - self._chat_list_stack.select_chat(account, jid) - self.show_chats() - self._chat_list_stack.store_open_chats(workspace_id) - - def _toggle_chat_pinned(self, _action, param): - workspace_id, account, jid = param.unpack() - self.toggle_chat_pinned(workspace_id, account, jid) - - def toggle_chat_pinned(self, workspace_id, account, jid): - self._chat_list_stack.toggle_chat_pinned(workspace_id, account, jid) - - def _move_chat_to_workspace(self, _action, param): - new_workspace_id, account, jid = param.unpack() - self.move_chat_to_workspace(new_workspace_id, account, jid) - - def move_chat_to_workspace(self, new_workspace_id, account, jid): - current_chatlist = self._chat_list_stack.get_visible_child() - type_ = current_chatlist.get_chat_type(account, jid) - current_chatlist.remove_chat(account, jid) - new_chatlist = self._chat_list_stack.get_chatlist(new_workspace_id) - new_chatlist.add_chat(account, jid, type_) - self._chat_list_stack.store_open_chats(current_chatlist.workspace_id) - self._chat_list_stack.store_open_chats(new_workspace_id) - - def _remove_chat(self, _action, param): - account, jid = param.unpack() - self.remove_chat(account, jid) - - def remove_chat(self, account, jid): - for workspace_id in app.settings.get_workspaces(): - if self.chat_exists_for_workspace(workspace_id, account, jid): - self._chat_list_stack.remove_chat(workspace_id, account, jid) - return + workspace_id = self.get_active_workspace() + self._chat_page.add_chat_for_workspace(workspace_id, + account, + jid, + 'pm', + select=select) @staticmethod def _add_to_roster(_action, param): _workspace, account, jid = param.unpack() open_window('AddNewContactWindow', account=account, contact_jid=jid) - def chat_exists(self, account, jid): - return self._chat_list_stack.contains_chat(account, jid) + def get_control(self, *args, **kwargs): + return self._chat_page.get_control(*args, **kwargs) - def chat_exists_for_workspace(self, workspace_id, account, jid): - return self._chat_list_stack.contains_chat( - account, jid, workspace_id=workspace_id) + def get_controls(self, *args, **kwargs): + return self._chat_page.get_controls(*args, **kwargs) - def get_control(self, account, jid): - return self._chat_stack.get_control(account, jid) + def get_active_control(self, *args, **kwargs): + return self._chat_page.get_active_control(*args, **kwargs) - def get_active_control(self): - chat = self._chat_list_stack.get_selected_chat() - if chat is None: - return None - return self.get_control(chat.account, chat.jid) - - def get_controls(self, account=None): - return self._chat_stack.get_controls(account) - - def _add_accounts(self): - for account in list(app.connections.keys()): - account_page = AccountPage(account) - self._account_pages[account] = account_page - self._ui.main_stack.add_named(account_page, account) + def chat_exists(self, *args, **kwargs): + return self._chat_page.chat_exists(*args, **kwargs) @staticmethod def contact_info(account, jid): @@ -512,7 +375,7 @@ def block_contact(self, account, jid): # TODO: Keep "confirm_block" setting? def _block_contact(report=None): client.get_module('Blocking').block([contact.jid], report) - self.remove_chat(account, contact.jid) + self._chat_page.remove_chat(account, contact.jid) ConfirmationDialog( _('Block Contact'), @@ -533,7 +396,7 @@ def remove_contact(self, account, jid): client = app.get_client(account) def _remove_contact(): - self.remove_chat(account, jid) + self._chat_page.remove_chat(account, jid) client.get_module('Roster').delete_item(jid) contact = client.get_module('Contacts').get_contact(jid) @@ -552,45 +415,13 @@ def _remove_contact(): def _load_chats(self): for workspace_id in app.settings.get_workspaces(): - self._workspace_side_bar.add_workspace(workspace_id) - self._chat_list_stack.add_chat_list(workspace_id) - open_chats = app.settings.get_workspace_setting(workspace_id, - 'open_chats') - - active_accounts = app.settings.get_active_accounts() - for account, jid, type_, pinned in open_chats: - if account not in active_accounts: - continue - - self.add_chat_for_workspace(workspace_id, account, jid, type_, - pinned=pinned) - - for workspace_id in app.settings.get_workspaces(): - self._workspace_side_bar.activate_workspace(workspace_id) - self._chat_list_stack.show_chat_list(workspace_id) - break - - self._startup_finished = True - - @staticmethod - def _on_start_chat_clicked(_button): - app.app.activate_action('start-chat', GLib.Variant('s', '')) - - def _on_chat_selected(self, _chat_list_stack, _workspace_id, *args): - control = self.get_active_control() - if control is not None: - self._search_view.set_context(control.account, control.contact.jid) + self.add_workspace(workspace_id) + self._chat_page.load_workspace_chats(workspace_id) - def _on_search_history(self, _action, _param): - control = self.get_active_control() - if control is not None: - self._search_view.set_context(control.account, control.contact.jid) - self._search_view.clear() - self._search_revealer.show() - self._search_view.set_focus() + workspace_id = self._workspace_side_bar.get_first_workspace() + self.activate_workspace(workspace_id) - def _on_search_hide(self, *args): - self._search_revealer.hide() + self._set_startup_finished() def _on_event(self, event): if event.name == 'caps-update': @@ -600,9 +431,6 @@ def _on_event(self, event): if event.name == 'update-roster-avatar': return - for page in self._account_pages.values(): - page.process_event(event) - if not self.chat_exists(event.account, event.jid): if event.name == 'message-received': if event.properties.is_muc_pm: @@ -617,8 +445,7 @@ def _on_event(self, event): # No chat is open, dont handle any gui events return - self._chat_stack.process_event(event) - self._chat_list_stack.process_event(event) + self._main_stack.process_event(event) def quit(self): accounts = list(app.connections.keys()) diff --git a/gajim/gtk/main_stack.py b/gajim/gtk/main_stack.py new file mode 100644 index 0000000000000000000000000000000000000000..cd0bc6265b7eb59a9b57556c2a0f8789ccea05fe --- /dev/null +++ b/gajim/gtk/main_stack.py @@ -0,0 +1,53 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see <http://www.gnu.org/licenses/>. + +from gi.repository import Gtk + +from gajim.common import app + +from .chat_page import ChatPage +from .account_page import AccountPage + + +class MainStack(Gtk.Stack): + def __init__(self): + Gtk.Stack.__init__(self) + + self._chat_page = ChatPage() + self._chat_page.connect('chat-selected', self._on_chat_selected) + self.add_named(self._chat_page, 'chats') + + for account in list(app.connections.keys()): + account_page = AccountPage(account) + self.add_named(account_page, account) + + def show_chats(self, workspace_id): + self._chat_page.show_workspace_chats(workspace_id) + self.set_visible_child_name('chats') + + def show_account(self, account): + self.set_visible_child_name(account) + + def get_account_page(self, account): + return self.get_child_by_name(account) + + def get_chat_page(self): + return self.get_child_by_name('chats') + + def process_event(self, event): + for page in self.get_children(): + page.process_event(event) + + def _on_chat_selected(self, *args): + self.set_visible_child_name('chats') diff --git a/gajim/gtk/workspace_side_bar.py b/gajim/gtk/workspace_side_bar.py index 382609ad49b9be84dd9a9342007b365ed538e5f3..377348a3c6430cb4b340e9e38fda73ffb213e406 100644 --- a/gajim/gtk/workspace_side_bar.py +++ b/gajim/gtk/workspace_side_bar.py @@ -1,3 +1,16 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see <http://www.gnu.org/licenses/>. from gi.repository import Gdk from gi.repository import Gtk @@ -10,7 +23,7 @@ class WorkspaceSideBar(Gtk.ListBox): - def __init__(self, chat_list_stack): + def __init__(self, chat_page): Gtk.ListBox.__init__(self) self.set_vexpand(True) self.set_valign(Gtk.Align.START) @@ -38,6 +51,7 @@ def __init__(self, chat_list_stack): self.add(AddWorkspace('add')) + chat_list_stack = chat_page.get_chat_list_stack() chat_list_stack.connect('unread-count-changed', self._on_unread_count_changed) chat_list_stack.connect('chat-selected', @@ -286,6 +300,7 @@ def __init__(self, workspace_id): self.update() def update(self): + app.interface.avatar_storage.invalidate_cache(self._workspace_id) scale = self.get_scale_factor() surface = app.interface.avatar_storage.get_workspace_surface( self._workspace_id, AvatarSize.WORKSPACE, scale)