diff --git a/toxygen/app.py b/toxygen/app.py index 77864e7..a7fbaff 100644 --- a/toxygen/app.py +++ b/toxygen/app.py @@ -366,10 +366,11 @@ class App: widgets_factory = None widgets_factory_provider = Provider(lambda: widgets_factory) self._groups_service = GroupsService(self._tox, self._contacts_manager, self._contacts_provider, self._ms, - widgets_factory_provider, self._profile) + widgets_factory_provider) widgets_factory = WidgetsFactory(self._settings, self._profile, self._profile_manager, self._contacts_manager, self._file_transfer_handler, self._smiley_loader, self._plugin_loader, - self._toxes, self._version, self._groups_service, history) + self._toxes, self._version, self._groups_service, history, + self._contacts_provider) self._tray = tray.init_tray(self._profile, self._settings, self._ms, self._toxes) self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, self._profile, self._plugin_loader, self._file_transfer_handler, history, self._calls_manager, diff --git a/toxygen/groups/group_invite.py b/toxygen/groups/group_invite.py new file mode 100644 index 0000000..a2eed47 --- /dev/null +++ b/toxygen/groups/group_invite.py @@ -0,0 +1,23 @@ + + +class GroupInvite: + + def __init__(self, friend_public_key, chat_name, invite_data): + self._friend_public_key = friend_public_key + self._chat_name = chat_name + self._invite_data = invite_data[:] + + def get_friend_public_key(self): + return self._friend_public_key + + friend_public_key = property(get_friend_public_key) + + def get_chat_name(self): + return self._chat_name + + chat_name = property(get_chat_name) + + def get_invite_data(self): + return self._invite_data[:] + + invite_data = property(get_invite_data) diff --git a/toxygen/groups/groups_service.py b/toxygen/groups/groups_service.py index f9cdfc9..76ecdaf 100644 --- a/toxygen/groups/groups_service.py +++ b/toxygen/groups/groups_service.py @@ -1,18 +1,19 @@ import common.tox_save as tox_save import utils.ui as util_ui from groups.peers_list import PeersListGenerator +from groups.group_invite import GroupInvite import wrapper.toxcore_enums_and_consts as constants class GroupsService(tox_save.ToxSave): - def __init__(self, tox, contacts_manager, contacts_provider, main_screen, widgets_factory_provider, profile): + def __init__(self, tox, contacts_manager, contacts_provider, main_screen, widgets_factory_provider): super().__init__(tox) self._contacts_manager = contacts_manager self._contacts_provider = contacts_provider self._peers_list_widget = main_screen.peers_list self._widgets_factory_provider = widgets_factory_provider - self._profile = profile + self._group_invites = [] self._peer_screen = None def set_tox(self, tox): @@ -37,10 +38,6 @@ class GroupsService(tox_save.ToxSave): group_number = self._tox.group_join(chat_id, password, nick, status) self._add_new_group_by_number(group_number) - def join_gc_via_invite(self, invite_data, friend_number, nick, status, password): - group_number = self._tox.group_invite_accept(invite_data, friend_number, nick, status, password) - self._add_new_group_by_number(group_number) - # ----------------------------------------------------------------------------------------------------------------- # Groups reconnect and leaving # ----------------------------------------------------------------------------------------------------------------- @@ -71,9 +68,23 @@ class GroupsService(tox_save.ToxSave): def process_group_invite(self, friend_number, group_name, invite_data): friend = self._get_friend_by_number(friend_number) - text = util_ui.tr('Friend {} invites you to group "{}". Accept?') - if util_ui.question(text.format(friend.name, group_name), util_ui.tr('Group invite')): - self.join_gc_via_invite(invite_data, friend_number, self._profile.name, self._profile.status or 0, None) + invite = GroupInvite(friend.tox_id, group_name, invite_data) + self._group_invites.append(invite) + # TODO: notification on main screen + + def accept_group_invite(self, invite, name, status, password): + pk = invite.friend_public_key + friend = self._get_friend_by_public_key(pk) + self._join_gc_via_invite(invite.invite_data, friend.number, name, status, password) + self._delete_group_invite(invite) + + def decline_group_invite(self, invite): + self._delete_group_invite(invite) + + def get_group_invites(self): + return self._group_invites[:] + + group_invites = property(get_group_invites) # ----------------------------------------------------------------------------------------------------------------- # Group info methods @@ -153,6 +164,17 @@ class GroupsService(tox_save.ToxSave): def _get_friend_by_number(self, friend_number): return self._contacts_provider.get_friend_by_number(friend_number) + def _get_friend_by_public_key(self, public_key): + return self._contacts_provider.get_friend_by_public_key(public_key) + def _clear_peers_list(self, group): group.remove_all_peers_except_self() self.generate_peers_list() + + def _delete_group_invite(self, invite): + if invite in self._group_invites: + self._group_invites.remove(invite) + + def _join_gc_via_invite(self, invite_data, friend_number, nick, status, password): + group_number = self._tox.group_invite_accept(invite_data, friend_number, nick, status, password) + self._add_new_group_by_number(group_number) diff --git a/toxygen/middleware/callbacks.py b/toxygen/middleware/callbacks.py index 3d40cc9..3f25e0c 100644 --- a/toxygen/middleware/callbacks.py +++ b/toxygen/middleware/callbacks.py @@ -438,7 +438,7 @@ def group_peer_name(contacts_provider, groups_service): def wrapped(tox, group_number, peer_id, name, length, user_data): group = contacts_provider.get_group_by_number(group_number) peer = group.get_peer_by_id(peer_id) - peer.name = str(name[:length]) + peer.name = str(name[:length], 'utf-8') invoke_in_main_thread(groups_service.generate_peers_list) return wrapped diff --git a/toxygen/ui/group_invites_widgets.py b/toxygen/ui/group_invites_widgets.py new file mode 100644 index 0000000..77403d9 --- /dev/null +++ b/toxygen/ui/group_invites_widgets.py @@ -0,0 +1,120 @@ +from PyQt5 import uic, QtWidgets +import utils.util as util +from ui.widgets import * + + +class GroupInviteItem(QtWidgets.QWidget): + + def __init__(self, parent, chat_name, avatar, friend_name): + super().__init__(parent) + uic.loadUi(util.get_views_path('gc_invite_item'), self) + + self.groupNameLabel.setText(chat_name) + self.friendNameLabel.setText(friend_name) + self.friendAvatarLabel.setPixmap(avatar) + + def is_selected(self): + return self.selectCheckBox.isChecked() + + def subscribe_checked_event(self, callback): + self.selectCheckBox.clicked.connect(callback) + + +class GroupInvitesScreen(CenteredWidget): + + def __init__(self, groups_service, profile, contacts_provider): + super().__init__() + self._groups_service = groups_service + self._profile = profile + self._contacts_provider = contacts_provider + + uic.loadUi(util.get_views_path('group_invites_screen'), self) + + self._update_ui() + + def _update_ui(self): + self._retranslate_ui() + + self._refresh_invites_list() + + self.nickLineEdit.setText(self._profile.name) + self.statusComboBox.setCurrentIndex(self._profile.status or 0) + + self.nickLineEdit.textChanged.connect(self._nick_changed) + self.acceptPushButton.clicked.connect(self._accept_invites) + self.declinePushButton.clicked.connect(self._decline_invites) + + self.invitesListWidget.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + + self._update_buttons_state() + + def _retranslate_ui(self): + self.setWindowTitle(util_ui.tr('Group chat invites')) + self.noInvitesLabel.setText(util_ui.tr('No group invites found')) + self.acceptPushButton.setText(util_ui.tr('Accept')) + self.declinePushButton.setText(util_ui.tr('Decline')) + self.statusComboBox.addItem(util_ui.tr('Online')) + self.statusComboBox.addItem(util_ui.tr('Away')) + self.statusComboBox.addItem(util_ui.tr('Busy')) + self.nickLineEdit.setPlaceholderText(util_ui.tr('Your nick in chat')) + self.passwordLineEdit.setPlaceholderText(util_ui.tr('Optional password')) + + def _get_friend(self, public_key): + return self._contacts_provider.get_friend_by_public_key(public_key) + + def _accept_invites(self): + nick = self.nickLineEdit.text() + password = self.passwordLineEdit.text() + status = self.statusComboBox.currentIndex() + + selected_invites = self._get_selected_invites() + for invite in selected_invites: + self._groups_service.accept_group_invite(invite, nick, status, password) + + self._refresh_invites_list() + + def _decline_invites(self): + selected_invites = self._get_selected_invites() + for invite in selected_invites: + self._groups_service.decline_group_invite(invite) + + self._refresh_invites_list() + + def _get_selected_invites(self): + all_invites = self._groups_service.get_group_invites() + selected = [] + items_count = len(all_invites) + for index in range(items_count): + list_item = self.invitesListWidget.item(index) + item_widget = self.invitesListWidget.itemWidget(list_item) + if item_widget.is_selected(): + selected.append(all_invites[index]) + + return selected + + def _refresh_invites_list(self): + self.invitesListWidget.clear() + invites = self._groups_service.get_group_invites() + for invite in invites: + self._create_invite_item(invite) + + def _create_invite_item(self, invite): + friend = self._get_friend(invite.friend_public_key) + item = GroupInviteItem(self.invitesListWidget, invite.chat_name, friend.get_pixmap(), friend.name) + item.subscribe_checked_event(self._item_selected) + elem = QtWidgets.QListWidgetItem() + elem.setSizeHint(QtCore.QSize(item.width(), item.height())) + self.invitesListWidget.addItem(elem) + self.invitesListWidget.setItemWidget(elem, item) + + def _item_selected(self): + self._update_buttons_state() + + def _nick_changed(self): + self._update_buttons_state() + + def _update_buttons_state(self): + nick = self.nickLineEdit.text() + selected_items = self._get_selected_invites() + self.acceptPushButton.setEnabled(bool(nick) and len(selected_items)) + self.declinePushButton.setEnabled(len(selected_items) > 0) diff --git a/toxygen/ui/main_screen.py b/toxygen/ui/main_screen.py index 1bc3612..d02c689 100644 --- a/toxygen/ui/main_screen.py +++ b/toxygen/ui/main_screen.py @@ -88,12 +88,14 @@ class MainWindow(QtWidgets.QMainWindow): self.lockApp = QtWidgets.QAction(window) self.createGC = QtWidgets.QAction(window) self.joinGC = QtWidgets.QAction(window) + self.gc_invites = QtWidgets.QAction(window) self.menuProfile.addAction(self.actionAdd_friend) self.menuProfile.addAction(self.actionSettings) self.menuProfile.addAction(self.lockApp) self.menuGC.addAction(self.createGC) self.menuGC.addAction(self.joinGC) + self.menuGC.addAction(self.gc_invites) self.menuSettings.addAction(self.actionPrivacy_settings) self.menuSettings.addAction(self.actionInterface_settings) self.menuSettings.addAction(self.actionNotifications) @@ -128,6 +130,7 @@ class MainWindow(QtWidgets.QMainWindow): self.lockApp.triggered.connect(self.lock_app) self.importPlugin.triggered.connect(self.import_plugin) self.reloadPlugins.triggered.connect(self.reload_plugins) + self.gc_invites.triggered.connect(self._open_gc_invites_list) def languageChange(self, *args, **kwargs): self.retranslateUi() @@ -149,6 +152,7 @@ class MainWindow(QtWidgets.QMainWindow): self.actionAdd_friend.setText(util_ui.tr("Add contact")) self.createGC.setText(util_ui.tr("Create group chat")) self.joinGC.setText(util_ui.tr("Join group chat")) + self.gc_invites.setText(util_ui.tr("Group invites")) self.actionprofilesettings.setText(util_ui.tr("Profile")) self.actionPrivacy_settings.setText(util_ui.tr("Privacy")) self.actionInterface_settings.setText(util_ui.tr("Interface")) @@ -733,3 +737,7 @@ class MainWindow(QtWidgets.QMainWindow): if self._should_show_group_peers_list: self._toggle_gc_peers_list() self.resizeEvent() + + def _open_gc_invites_list(self): + self._modal_window = self._widget_factory.create_group_invites_window() + self._modal_window.show() diff --git a/toxygen/ui/views/gc_invite_item.ui b/toxygen/ui/views/gc_invite_item.ui new file mode 100644 index 0000000..1048a55 --- /dev/null +++ b/toxygen/ui/views/gc_invite_item.ui @@ -0,0 +1,71 @@ + + + Form + + + + 0 + 0 + 600 + 150 + + + + Form + + + + + 250 + 30 + 300 + 21 + + + + TextLabel + + + + + + 250 + 70 + 300 + 21 + + + + TextLabel + + + + + + 140 + 30 + 60 + 60 + + + + TextLabel + + + + + + 40 + 50 + 16 + 23 + + + + + + + + + + diff --git a/toxygen/ui/views/group_invites_screen.ui b/toxygen/ui/views/group_invites_screen.ui new file mode 100644 index 0000000..183f801 --- /dev/null +++ b/toxygen/ui/views/group_invites_screen.ui @@ -0,0 +1,113 @@ + + + Form + + + + 0 + 0 + 600 + 500 + + + + + 600 + 500 + + + + + 600 + 500 + + + + Form + + + + + 0 + 150 + 600 + 25 + + + + TextLabel + + + Qt::AlignCenter + + + + + + 0 + 0 + 600 + 341 + + + + + + + 10 + 360 + 350 + 35 + + + + + + + 10 + 410 + 350 + 35 + + + + + + + 390 + 390 + 200 + 35 + + + + + + + 40 + 460 + 201 + 31 + + + + PushButton + + + + + + 360 + 460 + 201 + 31 + + + + PushButton + + + + + + diff --git a/toxygen/ui/views/self_peer_screen.ui b/toxygen/ui/views/self_peer_screen.ui new file mode 100644 index 0000000..38e1f88 --- /dev/null +++ b/toxygen/ui/views/self_peer_screen.ui @@ -0,0 +1,119 @@ + + + Form + + + + 0 + 0 + 600 + 500 + + + + + 600 + 500 + + + + + 600 + 500 + + + + Form + + + + + 50 + 120 + 67 + 20 + + + + TextLabel + + + + + + 50 + 250 + 500 + 50 + + + + PushButton + + + + + + 140 + 110 + 400 + 40 + + + + + + + 50 + 40 + 67 + 20 + + + + TextLabel + + + + + + 50 + 190 + 67 + 20 + + + + TextLabel + + + + + + 140 + 190 + 411 + 20 + + + + TextLabel + + + + + + 50 + 330 + 500 + 50 + + + + PushButton + + + + + + diff --git a/toxygen/ui/widgets_factory.py b/toxygen/ui/widgets_factory.py index 8f341df..75b83b4 100644 --- a/toxygen/ui/widgets_factory.py +++ b/toxygen/ui/widgets_factory.py @@ -3,12 +3,13 @@ from ui.menu import * from ui.groups_widgets import * from ui.peer_screen import * from ui.self_peer_screen import * +from ui.group_invites_widgets import * class WidgetsFactory: def __init__(self, settings, profile, profile_manager, contacts_manager, file_transfer_handler, smiley_loader, - plugin_loader, toxes, version, groups_service, history): + plugin_loader, toxes, version, groups_service, history, contacts_provider): self._settings = settings self._profile = profile self._profile_manager = profile_manager @@ -20,6 +21,7 @@ class WidgetsFactory: self._version = version self._groups_service = groups_service self._history = history + self._contacts_provider = contacts_provider def create_screenshot_window(self, *args): return ScreenShotWindow(self._file_transfer_handler, self._contacts_manager, *args) @@ -77,3 +79,6 @@ class WidgetsFactory: def create_self_peer_screen_window(self, group): return SelfPeerScreen(self._contacts_manager, self._groups_service, group) + + def create_group_invites_window(self): + return GroupInvitesScreen(self._groups_service, self._profile, self._contacts_provider) diff --git a/toxygen/wrapper/tox.py b/toxygen/wrapper/tox.py index cb2f25e..d705293 100644 --- a/toxygen/wrapper/tox.py +++ b/toxygen/wrapper/tox.py @@ -1651,6 +1651,7 @@ class Tox: """ error = c_int() + name = bytes(name, 'utf-8') result = Tox.libtoxcore.tox_group_self_set_name(self._tox_pointer, group_number, name, len(name), byref(error)) return result