mirror of
https://gitlab.gnome.org/GNOME/calls.git
synced 2025-01-05 19:15:32 +00:00
f4f41d9db5
This gives the window some nice rounded corners, and gives us more freedom in how to implement the UI. This purposefully leaves the indentation broken to ease code review, it will be fixed in the next commit.
533 lines
16 KiB
C
533 lines
16 KiB
C
/*
|
||
* Copyright (C) 2018 Purism SPC
|
||
*
|
||
* This file is part of Calls.
|
||
*
|
||
* Calls 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, either version 3 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* Calls 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 Calls. If not, see <http://www.gnu.org/licenses/>.
|
||
*
|
||
* Author: Bob Ham <bob.ham@puri.sm>
|
||
*
|
||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||
*
|
||
*/
|
||
|
||
#include "calls-main-window.h"
|
||
#include "calls-origin.h"
|
||
#include "calls-ussd.h"
|
||
#include "calls-call-selector-item.h"
|
||
#include "calls-new-call-box.h"
|
||
#include "calls-contacts-box.h"
|
||
#include "calls-history-box.h"
|
||
#include "calls-in-app-notification.h"
|
||
#include "calls-manager.h"
|
||
#include "config.h"
|
||
#include "util.h"
|
||
#include "version.h"
|
||
|
||
#include <glib/gi18n.h>
|
||
#include <glib-object.h>
|
||
#include <handy.h>
|
||
|
||
|
||
struct _CallsMainWindow
|
||
{
|
||
HdyApplicationWindow parent_instance;
|
||
|
||
GListModel *record_store;
|
||
|
||
CallsInAppNotification *in_app_notification;
|
||
|
||
HdyViewSwitcherTitle *title_switcher;
|
||
GtkStack *main_stack;
|
||
|
||
GtkRevealer *permanent_error_revealer;
|
||
GtkLabel *permanent_error_label;
|
||
|
||
CallsNewCallBox *new_call;
|
||
|
||
GtkDialog *ussd_dialog;
|
||
GtkStack *ussd_stack;
|
||
GtkSpinner *ussd_spinner;
|
||
GtkBox *ussd_content;
|
||
GtkLabel *ussd_label;
|
||
GtkEntry *ussd_entry;
|
||
GtkButton *ussd_close_button;
|
||
GtkButton *ussd_cancel_button;
|
||
GtkButton *ussd_reply_button;
|
||
};
|
||
|
||
G_DEFINE_TYPE (CallsMainWindow, calls_main_window, HDY_TYPE_APPLICATION_WINDOW);
|
||
|
||
enum {
|
||
PROP_0,
|
||
PROP_RECORD_STORE,
|
||
PROP_LAST_PROP,
|
||
};
|
||
static GParamSpec *props[PROP_LAST_PROP];
|
||
|
||
|
||
static void
|
||
about_action (GSimpleAction *action,
|
||
GVariant *parameter,
|
||
gpointer user_data)
|
||
{
|
||
CallsMainWindow *self = user_data;
|
||
|
||
const gchar *version = NULL;
|
||
|
||
static const gchar *authors[] = {
|
||
"Adrien Plazas <kekun.plazas@laposte.net>",
|
||
"Bob Ham <rah@settrans.net>",
|
||
"Guido Günther <agx@sigxcpu.org>",
|
||
NULL
|
||
};
|
||
|
||
static const gchar *artists[] = {
|
||
"Tobias Bernard <tbernard@gnome.org>",
|
||
NULL
|
||
};
|
||
|
||
static const gchar *documenters[] = {
|
||
"Heather Ellsworth <heather.ellsworth@puri.sm>",
|
||
NULL
|
||
};
|
||
|
||
version = g_str_equal (VCS_TAG, "") ? PACKAGE_VERSION:
|
||
PACKAGE_VERSION "-" VCS_TAG;
|
||
|
||
/*
|
||
* “program-name” defaults to g_get_application_name().
|
||
* Don’t set it explicitly so that there is one less
|
||
* string to translate.
|
||
*/
|
||
gtk_show_about_dialog (GTK_WINDOW (self),
|
||
"artists", artists,
|
||
"authors", authors,
|
||
"copyright", "Copyright © 2018 Purism",
|
||
"documenters", documenters,
|
||
"license-type", GTK_LICENSE_GPL_3_0,
|
||
"logo-icon-name", APP_ID,
|
||
"translator-credits", _("translator-credits"),
|
||
"version", version,
|
||
"website", PACKAGE_URL,
|
||
NULL);
|
||
}
|
||
|
||
|
||
static const GActionEntry window_entries [] =
|
||
{
|
||
{ "about", about_action },
|
||
};
|
||
|
||
|
||
static void
|
||
window_update_ussd_state (CallsMainWindow *self,
|
||
CallsUssd *ussd)
|
||
{
|
||
CallsUssdState state;
|
||
|
||
g_assert (CALLS_IS_MAIN_WINDOW (self));
|
||
g_assert (CALLS_IS_USSD (ussd));
|
||
|
||
state = calls_ussd_get_state (ussd);
|
||
|
||
/* If state changed in an active USSD session, don't update */
|
||
if (state == CALLS_USSD_STATE_ACTIVE &&
|
||
gtk_widget_get_visible (GTK_WIDGET (self->ussd_reply_button)))
|
||
return;
|
||
|
||
gtk_widget_set_visible (GTK_WIDGET (self->ussd_reply_button),
|
||
state == CALLS_USSD_STATE_USER_RESPONSE);
|
||
gtk_widget_set_visible (GTK_WIDGET (self->ussd_entry),
|
||
state == CALLS_USSD_STATE_USER_RESPONSE);
|
||
|
||
if (state == CALLS_USSD_STATE_USER_RESPONSE ||
|
||
state == CALLS_USSD_STATE_ACTIVE)
|
||
gtk_widget_show (GTK_WIDGET (self->ussd_cancel_button));
|
||
else
|
||
gtk_widget_show (GTK_WIDGET (self->ussd_close_button));
|
||
}
|
||
|
||
static void
|
||
window_ussd_added_cb (CallsMainWindow *self,
|
||
CallsUssd *ussd,
|
||
char *response)
|
||
{
|
||
g_assert (CALLS_IS_MAIN_WINDOW (self));
|
||
g_assert (CALLS_IS_USSD (ussd));
|
||
|
||
if (!response || !*response)
|
||
return;
|
||
|
||
gtk_label_set_label (self->ussd_label, response);
|
||
g_object_set_data_full (G_OBJECT (self->ussd_dialog), "ussd",
|
||
g_object_ref (ussd), g_object_unref);
|
||
window_update_ussd_state (self, ussd);
|
||
gtk_window_present (GTK_WINDOW (self->ussd_dialog));
|
||
}
|
||
|
||
static void
|
||
window_ussd_cancel_clicked_cb (CallsMainWindow *self)
|
||
{
|
||
CallsUssd *ussd;
|
||
|
||
g_assert (CALLS_IS_MAIN_WINDOW (self));
|
||
|
||
ussd = g_object_get_data (G_OBJECT (self->ussd_dialog), "ussd");
|
||
|
||
if (ussd)
|
||
calls_ussd_cancel_async (ussd, NULL, NULL, NULL);
|
||
|
||
gtk_window_close (GTK_WINDOW (self->ussd_dialog));
|
||
}
|
||
|
||
static void
|
||
window_ussd_entry_changed_cb (CallsMainWindow *self,
|
||
GtkEntry *entry)
|
||
{
|
||
const char *text;
|
||
gboolean allow_send;
|
||
|
||
g_assert (CALLS_IS_MAIN_WINDOW (self));
|
||
g_assert (GTK_IS_ENTRY (entry));
|
||
|
||
text = gtk_entry_get_text (entry);
|
||
allow_send = text && *text;
|
||
|
||
gtk_widget_set_sensitive (GTK_WIDGET (self->ussd_reply_button), allow_send);
|
||
}
|
||
|
||
static void
|
||
window_ussd_respond_cb (GObject *object,
|
||
GAsyncResult *result,
|
||
gpointer user_data)
|
||
{
|
||
CallsMainWindow *self = user_data;
|
||
g_autofree char *response = NULL;
|
||
g_autoptr (GError) error = NULL;
|
||
CallsUssd *ussd;
|
||
|
||
ussd = g_object_get_data (G_OBJECT (self->ussd_dialog), "ussd");
|
||
response = calls_ussd_respond_finish (ussd, result, &error);
|
||
|
||
if (error)
|
||
{
|
||
gtk_dialog_response (self->ussd_dialog, GTK_RESPONSE_CLOSE);
|
||
g_warning ("USSD Error: %s", error->message);
|
||
return;
|
||
}
|
||
|
||
if (response && *response)
|
||
{
|
||
window_update_ussd_state (self, ussd);
|
||
gtk_label_set_text (self->ussd_label, response);
|
||
}
|
||
|
||
gtk_spinner_stop (self->ussd_spinner);
|
||
gtk_stack_set_visible_child (self->ussd_stack, GTK_WIDGET (self->ussd_content));
|
||
}
|
||
|
||
static void
|
||
window_ussd_reply_clicked_cb (CallsMainWindow *self)
|
||
{
|
||
CallsUssd *ussd;
|
||
g_autofree char *response = NULL;
|
||
|
||
g_assert (CALLS_IS_MAIN_WINDOW (self));
|
||
|
||
ussd = g_object_get_data (G_OBJECT (self->ussd_dialog), "ussd");
|
||
g_assert (CALLS_IS_USSD (ussd));
|
||
|
||
response = g_strdup (gtk_entry_get_text (self->ussd_entry));
|
||
gtk_entry_set_text (self->ussd_entry, "");
|
||
calls_ussd_respond_async (ussd, response, NULL,
|
||
window_ussd_respond_cb, self);
|
||
}
|
||
|
||
static void
|
||
main_window_ussd_send_cb (GObject *object,
|
||
GAsyncResult *result,
|
||
gpointer user_data)
|
||
{
|
||
CallsMainWindow *self = user_data;
|
||
g_autofree char *response = NULL;
|
||
g_autoptr (GError) error = NULL;
|
||
CallsUssd *ussd;
|
||
|
||
response = calls_new_call_box_send_ussd_finish (self->new_call, result, &error);
|
||
ussd = g_task_get_task_data (G_TASK (result));
|
||
|
||
if (error)
|
||
{
|
||
gtk_dialog_response (self->ussd_dialog, GTK_RESPONSE_CLOSE);
|
||
g_warning ("USSD Error: %s", error->message);
|
||
return;
|
||
}
|
||
|
||
g_object_set_data_full (G_OBJECT (self->ussd_dialog), "ussd",
|
||
g_object_ref (ussd), g_object_unref);
|
||
window_update_ussd_state (self, ussd);
|
||
gtk_label_set_text (self->ussd_label, response);
|
||
gtk_spinner_stop (self->ussd_spinner);
|
||
gtk_stack_set_visible_child (self->ussd_stack, GTK_WIDGET (self->ussd_content));
|
||
}
|
||
|
||
static void
|
||
set_property (GObject *object,
|
||
guint property_id,
|
||
const GValue *value,
|
||
GParamSpec *pspec)
|
||
{
|
||
CallsMainWindow *self = CALLS_MAIN_WINDOW (object);
|
||
|
||
switch (property_id) {
|
||
case PROP_RECORD_STORE:
|
||
g_set_object (&self->record_store,
|
||
G_LIST_MODEL (g_value_get_object (value)));
|
||
break;
|
||
|
||
default:
|
||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||
break;
|
||
}
|
||
}
|
||
|
||
|
||
static void
|
||
state_changed_cb (CallsMainWindow *self,
|
||
GParamSpec *pspec,
|
||
CallsManager *manager)
|
||
{
|
||
const gchar *error = NULL;
|
||
switch (calls_manager_get_state (manager))
|
||
{
|
||
case CALLS_MANAGER_STATE_READY:
|
||
break;
|
||
|
||
case CALLS_MANAGER_STATE_NO_VOICE_MODEM:
|
||
error = _("Can't place calls: No voice-capable modem available");
|
||
break;
|
||
|
||
case CALLS_MANAGER_STATE_NO_ORIGIN:
|
||
error = _("Can't place calls: No modem or VoIP account available");
|
||
break;
|
||
|
||
case CALLS_MANAGER_STATE_UNKNOWN:
|
||
case CALLS_MANAGER_STATE_NO_PROVIDER:
|
||
error = _("Can't place calls: No backend service");
|
||
break;
|
||
|
||
case CALLS_MANAGER_STATE_NO_PLUGIN:
|
||
error = _("Can't place calls: No plugin");
|
||
break;
|
||
|
||
default:
|
||
g_assert_not_reached ();
|
||
}
|
||
|
||
gtk_label_set_text (self->permanent_error_label, error);
|
||
gtk_revealer_set_reveal_child (self->permanent_error_revealer, error != NULL);
|
||
}
|
||
|
||
|
||
static void
|
||
constructed (GObject *object)
|
||
{
|
||
CallsMainWindow *self = CALLS_MAIN_WINDOW (object);
|
||
GSimpleActionGroup *simple_action_group;
|
||
GtkContainer *main_stack = GTK_CONTAINER (self->main_stack);
|
||
GtkWidget *widget;
|
||
CallsHistoryBox *history;
|
||
|
||
// Show errors in in-app-notification
|
||
g_signal_connect_swapped (calls_manager_get_default (),
|
||
"error",
|
||
G_CALLBACK (calls_in_app_notification_show),
|
||
self->in_app_notification);
|
||
|
||
g_signal_connect_swapped (calls_manager_get_default (),
|
||
"ussd-added",
|
||
G_CALLBACK (window_ussd_added_cb),
|
||
self);
|
||
g_signal_connect_swapped (calls_manager_get_default (),
|
||
"ussd-state-changed",
|
||
G_CALLBACK (window_update_ussd_state),
|
||
self);
|
||
gtk_window_set_transient_for (GTK_WINDOW (self->ussd_dialog), GTK_WINDOW (self));
|
||
|
||
// Add contacs box
|
||
widget = calls_contacts_box_new ();
|
||
gtk_stack_add_titled (self->main_stack, widget,
|
||
"contacts", _("Contacts"));
|
||
gtk_container_child_set (main_stack, widget,
|
||
"icon-name", "system-users-symbolic",
|
||
NULL);
|
||
gtk_widget_set_visible (widget, TRUE);
|
||
|
||
// Add new call box
|
||
self->new_call = calls_new_call_box_new ();
|
||
widget = GTK_WIDGET (self->new_call);
|
||
gtk_stack_add_titled (self->main_stack, widget,
|
||
"dial-pad", _("Dial Pad"));
|
||
gtk_container_child_set (main_stack, widget,
|
||
"icon-name", "input-dialpad-symbolic",
|
||
NULL);
|
||
// Add call records
|
||
history = calls_history_box_new (self->record_store);
|
||
widget = GTK_WIDGET (history);
|
||
gtk_stack_add_titled (self->main_stack, widget,
|
||
"recent", _("Recent"));
|
||
gtk_container_child_set
|
||
(main_stack, widget,
|
||
"icon-name", "document-open-recent-symbolic",
|
||
"position", 0,
|
||
NULL);
|
||
gtk_widget_set_visible (widget, TRUE);
|
||
gtk_stack_set_visible_child_name (self->main_stack, "recent");
|
||
|
||
// Add actions
|
||
simple_action_group = g_simple_action_group_new ();
|
||
g_action_map_add_action_entries (G_ACTION_MAP (simple_action_group),
|
||
window_entries,
|
||
G_N_ELEMENTS (window_entries),
|
||
self);
|
||
gtk_widget_insert_action_group (GTK_WIDGET (self),
|
||
"win",
|
||
G_ACTION_GROUP (simple_action_group));
|
||
g_object_unref (simple_action_group);
|
||
|
||
|
||
g_signal_connect_swapped (calls_manager_get_default (),
|
||
"notify::state",
|
||
G_CALLBACK (state_changed_cb),
|
||
self);
|
||
|
||
state_changed_cb (self, NULL, calls_manager_get_default ());
|
||
|
||
G_OBJECT_CLASS (calls_main_window_parent_class)->constructed (object);
|
||
}
|
||
|
||
|
||
|
||
static void
|
||
dispose (GObject *object)
|
||
{
|
||
CallsMainWindow *self = CALLS_MAIN_WINDOW (object);
|
||
|
||
g_clear_object (&self->record_store);
|
||
|
||
G_OBJECT_CLASS (calls_main_window_parent_class)->dispose (object);
|
||
}
|
||
|
||
|
||
static void
|
||
size_allocate (GtkWidget *widget,
|
||
GtkAllocation *allocation)
|
||
{
|
||
CallsMainWindow *self = CALLS_MAIN_WINDOW (widget);
|
||
|
||
hdy_view_switcher_title_set_view_switcher_enabled (self->title_switcher,
|
||
allocation->width > 400);
|
||
|
||
GTK_WIDGET_CLASS (calls_main_window_parent_class)->size_allocate (widget, allocation);
|
||
}
|
||
|
||
|
||
static void
|
||
calls_main_window_class_init (CallsMainWindowClass *klass)
|
||
{
|
||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
|
||
|
||
object_class->set_property = set_property;
|
||
object_class->constructed = constructed;
|
||
object_class->dispose = dispose;
|
||
|
||
props[PROP_RECORD_STORE] =
|
||
g_param_spec_object ("record-store",
|
||
"Record store",
|
||
"The store of call records",
|
||
G_TYPE_LIST_MODEL,
|
||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY);
|
||
|
||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||
|
||
|
||
widget_class->size_allocate = size_allocate;
|
||
|
||
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/Calls/ui/main-window.ui");
|
||
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, in_app_notification);
|
||
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, title_switcher);
|
||
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, main_stack);
|
||
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, permanent_error_revealer);
|
||
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, permanent_error_label);
|
||
|
||
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, ussd_dialog);
|
||
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, ussd_stack);
|
||
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, ussd_spinner);
|
||
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, ussd_content);
|
||
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, ussd_label);
|
||
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, ussd_entry);
|
||
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, ussd_close_button);
|
||
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, ussd_cancel_button);
|
||
gtk_widget_class_bind_template_child (widget_class, CallsMainWindow, ussd_reply_button);
|
||
|
||
gtk_widget_class_bind_template_callback (widget_class, window_ussd_cancel_clicked_cb);
|
||
gtk_widget_class_bind_template_callback (widget_class, window_ussd_entry_changed_cb);
|
||
gtk_widget_class_bind_template_callback (widget_class, window_ussd_reply_clicked_cb);
|
||
}
|
||
|
||
|
||
static void
|
||
calls_main_window_init (CallsMainWindow *self)
|
||
{
|
||
gtk_widget_init_template (GTK_WIDGET (self));
|
||
}
|
||
|
||
|
||
CallsMainWindow *
|
||
calls_main_window_new (GtkApplication *application,
|
||
GListModel *record_store)
|
||
{
|
||
g_return_val_if_fail (GTK_IS_APPLICATION (application), NULL);
|
||
g_return_val_if_fail (G_IS_LIST_MODEL (record_store), NULL);
|
||
|
||
return g_object_new (CALLS_TYPE_MAIN_WINDOW,
|
||
"application", application,
|
||
"record-store", record_store,
|
||
NULL);
|
||
}
|
||
|
||
|
||
void
|
||
calls_main_window_dial (CallsMainWindow *self,
|
||
const gchar *target)
|
||
{
|
||
if (calls_number_is_ussd (target))
|
||
{
|
||
gtk_widget_hide (GTK_WIDGET (self->ussd_cancel_button));
|
||
gtk_widget_hide (GTK_WIDGET (self->ussd_reply_button));
|
||
gtk_stack_set_visible_child (self->ussd_stack, GTK_WIDGET (self->ussd_spinner));
|
||
gtk_spinner_start (self->ussd_spinner);
|
||
|
||
calls_new_call_box_send_ussd_async (self->new_call, target, NULL,
|
||
main_window_ussd_send_cb, self);
|
||
|
||
gtk_window_present (GTK_WINDOW (self->ussd_dialog));
|
||
}
|
||
else
|
||
{
|
||
calls_new_call_box_dial (self->new_call, target);
|
||
}
|
||
}
|