From e23740acb29b87d0dbac51c30497f2d4bf46f7c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Blin?= <sebastien.blin@savoirfairelinux.com> Date: Mon, 29 Apr 2019 10:31:14 -0400 Subject: [PATCH] mainwindow: add some accelerators Add shortcuts to perform a lot of actions without the mouse. Also add a Keyboard shortcuts window. Change-Id: If3fe70a27696b03bf4a067d7bbf004116d4ed97c --- src/conversationsview.cpp | 19 +++ src/conversationsview.h | 1 + src/incomingcallview.cpp | 8 +- src/ring_client.cpp | 147 +++++++++++++++++++++-- src/ringmainwindow.cpp | 247 +++++++++++++++++++++++++++++++++++--- src/ringmainwindow.h | 14 +++ ui/help-overlay.ui | 191 +++++++++++++++++++++++++++++ ui/incomingcallview.ui | 2 - ui/ringgearsmenu.ui | 6 +- ui/ringmainwindow.ui | 5 + ui/ui.gresource.xml | 1 + 11 files changed, 603 insertions(+), 38 deletions(-) create mode 100644 ui/help-overlay.ui diff --git a/src/conversationsview.cpp b/src/conversationsview.cpp index 2f8ef16a..7cffdafa 100644 --- a/src/conversationsview.cpp +++ b/src/conversationsview.cpp @@ -762,3 +762,22 @@ conversations_view_select_conversation(ConversationsView *self, const std::strin idx++; } } + +int +conversations_view_get_current_selected(ConversationsView *self) +{ + + g_return_val_if_fail(IS_CONVERSATIONS_VIEW(self), -1); + + /* we always drag the selected row */ + auto selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(self)); + GtkTreeModel *model = NULL; + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected(selection, &model, &iter)) { + auto path = gtk_tree_model_get_path(model, &iter); + auto idx = gtk_tree_path_get_indices(path); + return idx[0]; + } + return -1; +} diff --git a/src/conversationsview.h b/src/conversationsview.h index 24db86a2..1cecda7d 100644 --- a/src/conversationsview.h +++ b/src/conversationsview.h @@ -38,5 +38,6 @@ typedef struct _ConversationsViewClass ConversationsViewClass; GType conversations_view_get_type (void) G_GNUC_CONST; GtkWidget *conversations_view_new (AccountInfoPointer const & accountInfo); void conversations_view_select_conversation (ConversationsView *self, const std::string& uid); +int conversations_view_get_current_selected(ConversationsView *self); G_END_DECLS diff --git a/src/incomingcallview.cpp b/src/incomingcallview.cpp index 9b7b318b..c60e45b4 100644 --- a/src/incomingcallview.cpp +++ b/src/incomingcallview.cpp @@ -128,7 +128,7 @@ map_boolean_to_orientation(GValue *value, GVariant *variant, G_GNUC_UNUSED gpoin } static void -reject_incoming_call(G_GNUC_UNUSED GtkWidget *widget, IncomingCallView *self) +reject_incoming_call(IncomingCallView *self) { g_return_if_fail(IS_INCOMING_CALL_VIEW(self)); auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(self); @@ -136,7 +136,7 @@ reject_incoming_call(G_GNUC_UNUSED GtkWidget *widget, IncomingCallView *self) } static void -accept_incoming_call(G_GNUC_UNUSED GtkWidget *widget, IncomingCallView *self) +accept_incoming_call(IncomingCallView *self) { g_return_if_fail(IS_INCOMING_CALL_VIEW(self)); auto priv = INCOMING_CALL_VIEW_GET_PRIVATE(self); @@ -191,8 +191,8 @@ incoming_call_view_init(IncomingCallView *view) map_boolean_to_orientation, nullptr, nullptr, nullptr); - g_signal_connect(priv->button_reject_incoming, "clicked", G_CALLBACK(reject_incoming_call), view); - g_signal_connect(priv->button_accept_incoming, "clicked", G_CALLBACK(accept_incoming_call), view); + g_signal_connect_swapped(priv->button_reject_incoming, "clicked", G_CALLBACK(reject_incoming_call), view); + g_signal_connect_swapped(priv->button_accept_incoming, "clicked", G_CALLBACK(accept_incoming_call), view); } static void diff --git a/src/ring_client.cpp b/src/ring_client.cpp index 04089bc6..e8fab5a3 100644 --- a/src/ring_client.cpp +++ b/src/ring_client.cpp @@ -104,6 +104,7 @@ G_DEFINE_TYPE_WITH_PRIVATE(RingClient, ring_client, GTK_TYPE_APPLICATION); #define RING_CLIENT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_CLIENT_TYPE, RingClientPrivate)) + static void exception_dialog(const char* msg) { @@ -123,10 +124,60 @@ static void ring_accelerators(RingClient *client) { #if GTK_CHECK_VERSION(3,12,0) - const gchar *quit_accels[2] = { "<Ctrl>Q", NULL }; + const gchar *quit_accels[2] = {"<Ctrl>Q", NULL}; gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.quit", quit_accels); + + const gchar *accounts_accels[2] = {"<Ctrl>A", NULL}; + gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.display_account_list", accounts_accels); + + const gchar *search_accels[2] = {"<Ctrl>F", NULL}; + gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.search", search_accels); + + const gchar *conversations_list_accels[2] = {"<Ctrl>L", NULL}; + gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.conversations_list", conversations_list_accels); + const gchar *requests_list_accels[2] = {"<Ctrl>R", NULL}; + gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.requests_list", requests_list_accels); + + const gchar *audio_call_accels[2] = {"<Ctrl><Shift>C", NULL}; + gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.audio_call", audio_call_accels); + const gchar *clear_history_accels[2] = {"<Ctrl><Shift>L", NULL}; + gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.clear_history", clear_history_accels); + const gchar *remove_conversation_accels[2] = {"<Ctrl><Shift>Delete", NULL}; + gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.remove_conversation", remove_conversation_accels); + const gchar *block_contact_accels[2] = {"<Ctrl><Shift>B", NULL}; + gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.block_contact", block_contact_accels); + const gchar *unblock_contact_accels[2] = {"<Ctrl><Shift>U", NULL}; + gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.unblock_contact", unblock_contact_accels); + const gchar *copy_contact_accels[2] = {"<Ctrl><Shift>J", NULL}; + gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.copy_contact", copy_contact_accels); + const gchar *add_contact_accels[2] = {"<Ctrl><Shift>A", NULL}; + gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.add_contact", add_contact_accels); + + const gchar *accept_call_accels[2] = {"<Ctrl>Y", NULL}; + gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.accept_call", accept_call_accels); + const gchar *decline_call_accels[2] = {"<Ctrl>D", NULL}; + gtk_application_set_accels_for_action(GTK_APPLICATION(client), "app.decline_call", decline_call_accels); + #else gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>Q", "app.quit", NULL); + + gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>A", "app.display_account_list", NULL); + + gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>F", "app.search", NULL); + + gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>L", "app.conversations_list", NULL); + gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>R", "app.requests_list", NULL); + + gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control><Shift>C", "app.audio_call", NULL); + gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control><Shift>L", "app.clear_history", NULL); + gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control><Shift>Delete", "app.remove_conversation", NULL); + gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control><Shift>B", "app.block_contact", NULL); + gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control><Shift>U", "app.unblock_contact", NULL); + gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control><Shift>J", "app.copy_contact", NULL); + gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control><Shift>A", "app.add_contact", NULL); + + gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>Y", "app.accept_call", NULL); + gtk_application_add_accelerator(GTK_APPLICATION(client), "<Control>D", "app.decline_call", NULL); #endif } @@ -156,6 +207,50 @@ action_about(G_GNUC_UNUSED GSimpleAction *simple, ring_about_dialog(priv->win); } +static void +exec_action(GSimpleAction *simple, + G_GNUC_UNUSED GVariant *parameter, + gpointer user_data) +{ + g_return_if_fail(G_IS_APPLICATION(user_data)); + RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(user_data); + + GValue value = G_VALUE_INIT; + g_value_init(&value, G_TYPE_STRING); + g_object_get_property(G_OBJECT(simple), "name", &value); + if (!g_value_get_string(&value)) return; + std::string name = g_value_get_string(&value); + + if (name == "display_account_list") + ring_main_window_display_account_list(RING_MAIN_WINDOW(priv->win)); + else if (name == "search") + ring_main_window_search(RING_MAIN_WINDOW(priv->win)); + else if (name == "conversations_list") + ring_main_window_conversations_list(RING_MAIN_WINDOW(priv->win)); + else if (name == "requests_list") + ring_main_window_requests_list(RING_MAIN_WINDOW(priv->win)); + else if (name == "audio_call") + ring_main_window_audio_call(RING_MAIN_WINDOW(priv->win)); + else if (name == "clear_history") + ring_main_window_clear_history(RING_MAIN_WINDOW(priv->win)); + else if (name == "remove_conversation") + ring_main_window_remove_conversation(RING_MAIN_WINDOW(priv->win)); + else if (name == "block_contact") + ring_main_window_block_contact(RING_MAIN_WINDOW(priv->win)); + else if (name == "unblock_contact") + ring_main_window_unblock_contact(RING_MAIN_WINDOW(priv->win)); + else if (name == "copy_contact") + ring_main_window_copy_contact(RING_MAIN_WINDOW(priv->win)); + else if (name == "add_contact") + ring_main_window_add_contact(RING_MAIN_WINDOW(priv->win)); + else if (name == "accept_call") + ring_main_window_accept_call(RING_MAIN_WINDOW(priv->win)); + else if (name == "decline_call") + ring_main_window_decline_call(RING_MAIN_WINDOW(priv->win)); + else + g_warning("Missing implementation for this action: %s", name.c_str()); +} + static void toggle_smartinfo(GSimpleAction *action, GVariant *parameter, gpointer) { @@ -167,19 +262,45 @@ toggle_smartinfo(GSimpleAction *action, GVariant *parameter, gpointer) } } -static const GActionEntry ring_actions[] = +static void +action_show_shortcuts(G_GNUC_UNUSED GSimpleAction *action, G_GNUC_UNUSED GVariant *parameter, gpointer user_data) { - { "accept", NULL, NULL, NULL, NULL, {0} }, - { "hangup", NULL, NULL, NULL, NULL, {0} }, - { "hold", NULL, NULL, "false", NULL, {0} }, - { "quit", action_quit, NULL, NULL, NULL, {0} }, - { "about", action_about, NULL, NULL, NULL, {0} }, - { "mute_audio", NULL, NULL, "false", NULL, {0} }, - { "mute_video", NULL, NULL, "false", NULL, {0} }, - { "record", NULL, NULL, "false", NULL, {0} }, - { "display-smartinfo", NULL, NULL, "false", toggle_smartinfo, {0} }, - /* TODO implement the other actions */ - // { "transfer", NULL, NULL, "flase", NULL, {0} }, + g_return_if_fail(G_IS_APPLICATION(user_data)); + RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(user_data); + + GtkBuilder *builder = gtk_builder_new_from_resource("/net/jami/JamiGnome/help-overlay.ui"); + GtkWidget *overlay = GTK_WIDGET(gtk_builder_get_object (builder, "help_overlay")); + + gtk_window_set_transient_for(GTK_WINDOW(overlay), GTK_WINDOW(priv->win)); + gtk_widget_show(overlay); + + g_object_unref(builder); +} + +static const GActionEntry ring_actions[] = { + {"accept", NULL, NULL, NULL, NULL, {0}}, + {"hangup", NULL, NULL, NULL, NULL, {0}}, + {"hold", NULL, NULL, "false", NULL, {0}}, + {"quit", action_quit, NULL, NULL, NULL, {0}}, + {"about", action_about, NULL, NULL, NULL, {0}}, + {"mute_audio", NULL, NULL, "false", NULL, {0}}, + {"mute_video", NULL, NULL, "false", NULL, {0}}, + {"record", NULL, NULL, "false", NULL, {0}}, + {"display-smartinfo", NULL, NULL, "false", toggle_smartinfo, {0}}, + {"display_account_list", exec_action, NULL, NULL, NULL, {0}}, + {"search", exec_action, NULL, NULL, NULL, {0}}, + {"conversations_list", exec_action, NULL, NULL, NULL, {0}}, + {"requests_list", exec_action, NULL, NULL, NULL, {0}}, + {"audio_call", exec_action, NULL, NULL, NULL, {0}}, + {"clear_history", exec_action, NULL, NULL, NULL, {0}}, + {"remove_conversation", exec_action, NULL, NULL, NULL, {0}}, + {"block_contact", exec_action, NULL, NULL, NULL, {0}}, + {"unblock_contact", exec_action, NULL, NULL, NULL, {0}}, + {"copy_contact", exec_action, NULL, NULL, NULL, {0}}, + {"add_contact", exec_action, NULL, NULL, NULL, {0}}, + {"accept_call", exec_action, NULL, NULL, NULL, {0}}, + {"decline_call", exec_action, NULL, NULL, NULL, {0}}, + {"show_shortcuts", action_show_shortcuts, NULL, NULL, NULL, {0}}, }; static void diff --git a/src/ringmainwindow.cpp b/src/ringmainwindow.cpp index ea38fbea..b39530c0 100644 --- a/src/ringmainwindow.cpp +++ b/src/ringmainwindow.cpp @@ -315,6 +315,10 @@ public: void enterSettingsView(); void leaveSettingsView(); + int getCurrentUid(); + void forCurrentConversation(const std::function<void(const lrc::api::conversation::Info&)>& func); + bool showOkCancelDialog(const std::string& title, const std::string& text); + lrc::api::conversation::Info getCurrentConversation(GtkWidget* frame_call); void showAccountSelectorWidget(bool show = true); @@ -1621,6 +1625,48 @@ CppImpl::leaveSettingsView() } } +int +CppImpl::getCurrentUid() +{ + const auto &treeview = gtk_notebook_get_current_page( + GTK_NOTEBOOK(widgets->notebook_contacts)) == contactRequestsPageNum + ? widgets->treeview_contact_requests + : widgets->treeview_conversations; + return conversations_view_get_current_selected(CONVERSATIONS_VIEW(treeview)); +} + +void +CppImpl::forCurrentConversation(const std::function<void(const lrc::api::conversation::Info&)>& func) +{ + const auto current = getCurrentUid(); + if (current == -1) return; + try { + auto conversation = accountInfo_->conversationModel->filteredConversation(current); + if (conversation.participants.empty()) return; + func(conversation); + } catch (...) { + g_warning("Can't retrieve conversation %d", current); + } +} + +bool +CppImpl::showOkCancelDialog(const std::string &title, const std::string &text) +{ + auto *confirm_dialog = gtk_message_dialog_new( + GTK_WINDOW(self), GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL, + "%s", text.c_str()); + gtk_window_set_title(GTK_WINDOW(confirm_dialog), title.c_str()); + gtk_dialog_set_default_response(GTK_DIALOG(confirm_dialog), + GTK_RESPONSE_CANCEL); + gtk_widget_show_all(confirm_dialog); + + auto res = gtk_dialog_run(GTK_DIALOG(confirm_dialog)); + + gtk_widget_destroy(confirm_dialog); + return res == GTK_RESPONSE_OK; +} + void CppImpl::updateLrc(const std::string& id, const std::string& accountIdToFlagFreeable) { @@ -2244,25 +2290,196 @@ ring_main_window_can_close(RingMainWindow* self) { g_return_val_if_fail(IS_RING_MAIN_WINDOW(self), true); auto* priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(self)); - if (priv->cpp && !lrc::api::Lrc::activeCalls().empty()) { - auto* close_dialog = gtk_message_dialog_new(GTK_WINDOW(self), - GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL, + if (!lrc::api::Lrc::activeCalls().empty()) { + auto res = priv->cpp->showOkCancelDialog( + _("Stop current call?"), _("A call is currently ongoing. Do you want to close the window and stop all current calls?")); - gtk_window_set_title(GTK_WINDOW(close_dialog), _("Stop current call?")); - gtk_dialog_set_default_response(GTK_DIALOG(close_dialog), GTK_RESPONSE_CANCEL); - gtk_widget_show_all(close_dialog); + if (res) lrc::api::NewCallModel::hangupCallsAndConferences(); + return res; + } + return true; +} - auto res = gtk_dialog_run(GTK_DIALOG(close_dialog)); +void +ring_main_window_display_account_list(RingMainWindow *win) +{ + g_return_if_fail(IS_RING_MAIN_WINDOW(win)); + auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); + gtk_combo_box_popup(GTK_COMBO_BOX(priv->combobox_account_selector)); +} - gtk_widget_destroy(close_dialog); - if (res == GTK_RESPONSE_OK) { - lrc::api::NewCallModel::hangupCallsAndConferences(); - return true; - } else { - return false; - } +void +ring_main_window_search(RingMainWindow *win) +{ + g_return_if_fail(IS_RING_MAIN_WINDOW(win)); + auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); + gtk_widget_grab_focus(GTK_WIDGET(priv->search_entry)); +} + +void +ring_main_window_conversations_list(RingMainWindow *win) +{ + g_return_if_fail(IS_RING_MAIN_WINDOW(win)); + auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); + auto smartViewPageNum = gtk_notebook_page_num(GTK_NOTEBOOK(priv->notebook_contacts), + priv->scrolled_window_smartview); + gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook_contacts), smartViewPageNum); + gtk_widget_grab_focus(GTK_WIDGET(priv->treeview_conversations)); +} + +void +ring_main_window_requests_list(RingMainWindow *win) +{ + g_return_if_fail(IS_RING_MAIN_WINDOW(win)); + auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); + if (!priv->cpp->accountInfo_->contactModel->hasPendingRequests()) return; + auto contactRequestsPageNum = gtk_notebook_page_num(GTK_NOTEBOOK(priv->notebook_contacts), + priv->scrolled_window_contact_requests); + gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook_contacts), contactRequestsPageNum); + gtk_widget_grab_focus(GTK_WIDGET(priv->treeview_contact_requests)); +} + +void +ring_main_window_audio_call(RingMainWindow *win) +{ + g_return_if_fail(IS_RING_MAIN_WINDOW(win)); + auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); + g_return_if_fail(priv && priv->cpp); + + priv->cpp->forCurrentConversation([&](const auto& conversation){ + priv->cpp->accountInfo_->conversationModel->placeAudioOnlyCall(conversation.uid); + }); +} + +void +ring_main_window_clear_history(RingMainWindow *win) +{ + g_return_if_fail(IS_RING_MAIN_WINDOW(win)); + auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); + g_return_if_fail(priv && priv->cpp && priv->cpp->accountInfo_); + + priv->cpp->forCurrentConversation([&](const auto &conversation) { + auto res = priv->cpp->showOkCancelDialog( + _("Clear history"), + _("Do you really want to clear the history of this conversation?")); + if (!res) return; + priv->cpp->accountInfo_->conversationModel->clearHistory(conversation.uid); + }); +} + +void +ring_main_window_remove_conversation(RingMainWindow *win) +{ + g_return_if_fail(IS_RING_MAIN_WINDOW(win)); + auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); + g_return_if_fail(priv && priv->cpp && priv->cpp->accountInfo_); + + priv->cpp->forCurrentConversation([&](const auto& conversation){ + auto res = priv->cpp->showOkCancelDialog( + _("Remove conversation"), + _("Do you really want to remove this conversation?")); + if (!res) return; + priv->cpp->accountInfo_->conversationModel->removeConversation(conversation.uid); + }); +} + +void +ring_main_window_block_contact(RingMainWindow *win) +{ + g_return_if_fail(IS_RING_MAIN_WINDOW(win)); + auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); + g_return_if_fail(priv && priv->cpp && priv->cpp->accountInfo_); + + priv->cpp->forCurrentConversation([&](const auto& conversation){ + auto res = priv->cpp->showOkCancelDialog( + _("Block contact"), + _("Do you really want to block this contact?")); + if (!res) return; + priv->cpp->accountInfo_->conversationModel->removeConversation(conversation.uid, true); + }); +} + +void +ring_main_window_unblock_contact(RingMainWindow *win) +{ + g_return_if_fail(IS_RING_MAIN_WINDOW(win)); + auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); + g_return_if_fail(priv && priv->cpp && priv->cpp->accountInfo_); + + priv->cpp->forCurrentConversation([&](const auto& conversation){ + auto& uri = conversation.participants[0]; + + auto contactInfo = priv->cpp->accountInfo_->contactModel->getContact(uri); + if (!contactInfo.isBanned) return; + priv->cpp->accountInfo_->contactModel->addContact(contactInfo); + }); +} + +void +ring_main_window_copy_contact(RingMainWindow *win) +{ + g_return_if_fail(IS_RING_MAIN_WINDOW(win)); + auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); + g_return_if_fail(priv && priv->cpp && priv->cpp->accountInfo_); + + priv->cpp->forCurrentConversation([&](const auto& conversation){ + auto& contact = priv->cpp->accountInfo_->contactModel->getContact(conversation.participants.front()); + auto bestName = contact.registeredName.empty() ? contact.profileInfo.uri : contact.registeredName; + auto text = (gchar *)bestName.c_str(); + GtkClipboard* clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text(clip, text, -1); + clip = gtk_clipboard_get(GDK_SELECTION_PRIMARY); + gtk_clipboard_set_text(clip, text, -1); + }); +} + +void +ring_main_window_add_contact(RingMainWindow *win) +{ + g_return_if_fail(IS_RING_MAIN_WINDOW(win)); + auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); + g_return_if_fail(priv && priv->cpp); + + priv->cpp->forCurrentConversation([&](const auto &conversation) { + priv->cpp->accountInfo_->conversationModel->makePermanent(conversation.uid); + }); +} + +void +ring_main_window_accept_call(RingMainWindow *win) +{ + g_return_if_fail(IS_RING_MAIN_WINDOW(win)); + auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); + g_return_if_fail(priv && priv->cpp && priv->cpp->accountInfo_); + + // Select the first conversation of the list + auto current = priv->cpp->getCurrentUid(); + if (current == -1) return; + try { + auto conversation = priv->cpp->accountInfo_->conversationModel->filteredConversation(current); + if (conversation.participants.empty()) return; + auto contactUri = conversation.participants.at(0); + auto contact = priv->cpp->accountInfo_->contactModel->getContact(contactUri); + // If the contact is pending, we should accept its request + if (contact.profileInfo.type == lrc::api::profile::Type::PENDING) + priv->cpp->accountInfo_->conversationModel->makePermanent(conversation.uid); + // Accept call + priv->cpp->accountInfo_->callModel->accept(conversation.callId); + } catch (...) { + g_warning("Can't retrieve conversation %d", current); } - return true; +} + +void +ring_main_window_decline_call(RingMainWindow *win) +{ + g_return_if_fail(IS_RING_MAIN_WINDOW(win)); + auto *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win)); + g_return_if_fail(priv && priv->cpp); + + priv->cpp->forCurrentConversation([&](const auto &conversation) { + priv->cpp->accountInfo_->callModel->hangUp(conversation.callId); + }); } //============================================================================== diff --git a/src/ringmainwindow.h b/src/ringmainwindow.h index 994abd50..ec5fd300 100644 --- a/src/ringmainwindow.h +++ b/src/ringmainwindow.h @@ -39,6 +39,20 @@ GType ring_main_window_get_type (void) G_GNUC_CONST; GtkWidget *ring_main_window_new (GtkApplication *app); void ring_main_window_reset (RingMainWindow *win); bool ring_main_window_can_close(RingMainWindow *win); +void ring_main_window_display_account_list(RingMainWindow *win); +void ring_main_window_search(RingMainWindow *win); + +void ring_main_window_conversations_list(RingMainWindow *win); +void ring_main_window_requests_list(RingMainWindow *win); +void ring_main_window_audio_call(RingMainWindow *win); +void ring_main_window_clear_history(RingMainWindow *win); +void ring_main_window_remove_conversation(RingMainWindow *win); +void ring_main_window_block_contact(RingMainWindow *win); +void ring_main_window_unblock_contact(RingMainWindow *win); +void ring_main_window_copy_contact(RingMainWindow *win); +void ring_main_window_add_contact(RingMainWindow *win); +void ring_main_window_accept_call(RingMainWindow *win); +void ring_main_window_decline_call(RingMainWindow *win); G_END_DECLS diff --git a/ui/help-overlay.ui b/ui/help-overlay.ui new file mode 100644 index 00000000..f4163d08 --- /dev/null +++ b/ui/help-overlay.ui @@ -0,0 +1,191 @@ +<?xml version="1.0" encoding="UTF-8"?> +<interface> + <object class="GtkShortcutsWindow" id="help_overlay"> + <property name="modal">true</property> + <child> + <object class="GtkShortcutsSection"> + <property name="visible">true</property> + <property name="section-name">shortcuts</property> + <property name="max-height">10</property> + <child> + <object class="GtkShortcutsGroup"> + <property name="visible">true</property> + <property name="title" translatable="yes" context="shortcut window">General</property> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl>m</property> + <property name="title" translatable="yes" context="shortcut window">Open application menu</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl>a</property> + <property name="title" translatable="yes" context="shortcut window">Open account list</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl>f</property> + <property name="title" translatable="yes" context="shortcut window">Select search bar</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl>l</property> + <property name="title" translatable="yes" context="shortcut window">Focus the list of conversations</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl>r</property> + <property name="title" translatable="yes" context="shortcut window">Focus the list pending requests</property> + </object> + </child> + </object> + </child> + + + <child> + <object class="GtkShortcutsGroup"> + <property name="visible">true</property> + <property name="title" translatable="yes" context="shortcut window">Conversations</property> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator">Return</property> + <property name="title" translatable="yes" context="shortcut window">Start a video call</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl><shift>c</property> + <property name="title" translatable="yes" context="shortcut window">Start an audio call</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl><shift>l</property> + <property name="title" translatable="yes" context="shortcut window">Clear history</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl><shift>a</property> + <property name="title" translatable="yes" context="shortcut window">Add conversation</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl><shift>Delete</property> + <property name="title" translatable="yes" context="shortcut window">Remove conversation</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl><shift>b</property> + <property name="title" translatable="yes" context="shortcut window">Block contact</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl><shift>u</property> + <property name="title" translatable="yes" context="shortcut window">Unblock contact</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl><shift>j</property> + <property name="title" translatable="yes" context="shortcut window">Copy contact name</property> + </object> + </child> + </object> + </child> + + + <child> + <object class="GtkShortcutsGroup"> + <property name="visible">true</property> + <property name="title" translatable="yes" context="shortcut window">Call</property> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl>y</property> + <property name="title" translatable="yes" context="shortcut window">Accept call</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl>d</property> + <property name="title" translatable="yes" context="shortcut window">Decline call</property> + </object> + </child> + </object> + </child> + + <child> + <object class="GtkShortcutsGroup"> + <property name="visible">true</property> + <property name="title" translatable="yes" context="shortcut window">Settings</property> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl>s</property> + <property name="title" translatable="yes" context="shortcut window">Open/Close settings page</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl>g</property> + <property name="title" translatable="yes" context="shortcut window">Open general settings</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl>m</property> + <property name="title" translatable="yes" context="shortcut window">Open media settings</property> + </object> + </child> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><ctrl>a</property> + <property name="title" translatable="yes" context="shortcut window">Open account settings</property> + </object> + </child> + </object> + </child> + + + <child> + <object class="GtkShortcutsGroup"> + <property name="visible">true</property> + <property name="title" translatable="yes" context="shortcut window">Chat view</property> + <child> + <object class="GtkShortcutsShortcut"> + <property name="visible">true</property> + <property name="accelerator"><shift>Return</property> + <property name="title" translatable="yes" context="shortcut window">Write on a new line</property> + </object> + </child> + </object> + </child> + + </object> + </child> + </object> +</interface> diff --git a/ui/incomingcallview.ui b/ui/incomingcallview.ui index dd7ed18c..4fb1072c 100644 --- a/ui/incomingcallview.ui +++ b/ui/incomingcallview.ui @@ -151,7 +151,6 @@ <property name="has_tooltip">True</property> <property name="tooltip-text" translatable="yes">Accept</property> <property name="image">image_accept</property> - <property name="action-name">app.accept</property> </object> <packing> <property name="expand">True</property> @@ -173,7 +172,6 @@ <property name="tooltip-text" translatable="yes">Reject</property> <property name="can_focus">True</property> <property name="image">image_reject</property> - <property name="action-name">app.hangup</property> </object> <packing> <property name="expand">True</property> diff --git a/ui/ringgearsmenu.ui b/ui/ringgearsmenu.ui index 4b9c4e67..b0b1b485 100644 --- a/ui/ringgearsmenu.ui +++ b/ui/ringgearsmenu.ui @@ -3,12 +3,10 @@ <!-- interface-requires gtk+ 3.0 --> <menu id="menu"> <section> - <!-- TODO: add help <item> - <attribute name="label" translatable="yes">_Help</attribute> - <attribute name="action">app.help</attribute> + <attribute name="label" translatable="yes">Keyboard _Shortcuts</attribute> + <attribute name="action">app.show_shortcuts</attribute> </item> - --> <item> <attribute name="label" translatable="yes">_About</attribute> <attribute name="action">app.about</attribute> diff --git a/ui/ringmainwindow.ui b/ui/ringmainwindow.ui index 436790bf..9d755705 100644 --- a/ui/ringmainwindow.ui +++ b/ui/ringmainwindow.ui @@ -56,6 +56,7 @@ <property name="receives_default">False</property> <property name="tooltip_text" translatable="yes">Menu</property> <property name="direction">none</property> + <accelerator key="m" signal="activate" modifiers="GDK_CONTROL_MASK"/> <child> <object class="GtkImage" id="image_ring"> <property name="visible">True</property> @@ -82,6 +83,7 @@ <property name="receives_default">False</property> <property name="image">image_general_settings</property> <property name="draw_indicator">False</property> + <accelerator key="g" signal="activate" modifiers="GDK_CONTROL_MASK"/> </object> <packing> <property name="expand">False</property> @@ -98,6 +100,7 @@ <property name="image">image_media_settings</property> <property name="draw_indicator">False</property> <property name="group">radiobutton_general_settings</property> + <accelerator key="m" signal="activate" modifiers="GDK_CONTROL_MASK"/> </object> <packing> <property name="expand">False</property> @@ -114,6 +117,7 @@ <property name="image">image_account_settings</property> <property name="draw_indicator">False</property> <property name="group">radiobutton_general_settings</property> + <accelerator key="a" signal="activate" modifiers="GDK_CONTROL_MASK"/> </object> <packing> <property name="expand">False</property> @@ -144,6 +148,7 @@ <property name="can_focus">False</property> <property name="receives_default">False</property> <property name="tooltip_text" translatable="yes">Settings</property> + <accelerator key="s" signal="activate" modifiers="GDK_CONTROL_MASK"/> <child> <object class="GtkImage" id="image_settings"> <property name="visible">True</property> diff --git a/ui/ui.gresource.xml b/ui/ui.gresource.xml index 5a519b84..2b7a446d 100644 --- a/ui/ui.gresource.xml +++ b/ui/ui.gresource.xml @@ -15,5 +15,6 @@ <file preprocess="xml-stripblanks">avatarmanipulation.ui</file> <file preprocess="xml-stripblanks">webkitchatcontainer.ui</file> <file preprocess="xml-stripblanks">usernameregistrationbox.ui</file> + <file preprocess="xml-stripblanks">help-overlay.ui</file> </gresource> </gresources> -- GitLab