blob: 42609c9439752a622bb0f1ed6b2fe839f258d6c1 [file] [log] [blame]
Stepan Salenikovich434b88f2015-02-19 17:49:08 -05001/*
2 * Copyright (C) 2015 Savoir-Faire Linux Inc.
3 * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Additional permission under GNU GPL version 3 section 7:
20 *
21 * If you modify this program, or any covered work, by linking or
22 * combining it with the OpenSSL project's OpenSSL library (or a
23 * modified version of that library), containing parts covered by the
24 * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc.
25 * grants you additional permission to convey the resulting work.
26 * Corresponding Source for a non-source form of such a combination
27 * shall include the source code for the parts of OpenSSL used as well
28 * as that of the covered work.
29 */
30
31#include "ringmainwindow.h"
32
33#include <gtk/gtk.h>
Stepan Salenikovicha3557452015-02-20 14:14:12 -050034#include "models/gtkqtreemodel.h"
35#include <callmodel.h>
Stepan Salenikovich69771842015-02-24 18:11:45 -050036#include <call.h>
Stepan Salenikovichd2dbcee2015-02-27 16:52:28 -050037#include <QtCore/QItemSelectionModel>
Stepan Salenikovich69771842015-02-24 18:11:45 -050038#include "incomingcallview.h"
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050039#include <string.h>
Stepan Salenikovich69771842015-02-24 18:11:45 -050040
41#define DEFAULT_VIEW_NAME "placeholder"
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050042
43struct _RingMainWindow
44{
45 GtkApplicationWindow parent;
46};
47
48struct _RingMainWindowClass
49{
50 GtkApplicationWindowClass parent_class;
51};
52
53typedef struct _RingMainWindowPrivate RingMainWindowPrivate;
54
55struct _RingMainWindowPrivate
56{
57 GtkWidget *gears;
58 GtkWidget *gears_image;
Stepan Salenikovicha3557452015-02-20 14:14:12 -050059 GtkWidget *treeview_call;
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -050060 GtkWidget *search_entry;
Stepan Salenikovich69771842015-02-24 18:11:45 -050061 GtkWidget *stack_main_view;
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050062 GtkWidget *button_placecall;
Stepan Salenikovich434b88f2015-02-19 17:49:08 -050063};
64
65G_DEFINE_TYPE_WITH_PRIVATE(RingMainWindow, ring_main_window, GTK_TYPE_APPLICATION_WINDOW);
66
67#define RING_MAIN_WINDOW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_MAIN_WINDOW_TYPE, RingMainWindowPrivate))
68
Stepan Salenikovich69771842015-02-24 18:11:45 -050069static QModelIndex
70get_index_from_selection(GtkTreeSelection *selection)
71{
72 GtkTreeIter iter;
73 GtkTreeModel *model = NULL;
74
75 if (gtk_tree_selection_get_selected(selection, &model, &iter)) {
76 return gtk_q_tree_model_get_source_idx(GTK_Q_TREE_MODEL(model), &iter);
77 } else {
78 return QModelIndex();
79 }
80}
81
82static void
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050083update_call_model_selection(GtkTreeSelection *selection, G_GNUC_UNUSED gpointer user_data)
Stepan Salenikovich69771842015-02-24 18:11:45 -050084{
Stepan Salenikovich69771842015-02-24 18:11:45 -050085 QModelIndex current = get_index_from_selection(selection);
86 if (current.isValid())
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050087 CallModel::instance()->selectionModel()->setCurrentIndex(current, QItemSelectionModel::ClearAndSelect);
Stepan Salenikovich69771842015-02-24 18:11:45 -050088 else
Stepan Salenikovich297b5d12015-02-26 17:51:13 -050089 CallModel::instance()->selectionModel()->clearCurrentIndex();
Stepan Salenikovich69771842015-02-24 18:11:45 -050090}
91
92static void
93call_selection_changed(GtkTreeSelection *selection, gpointer win)
94{
95 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
96
97 /* get the current visible stack child */
98 GtkWidget *child_old = gtk_stack_get_visible_child(GTK_STACK(priv->stack_main_view));
99
100 QModelIndex idx = get_index_from_selection(selection);
101 if (idx.isValid()) {
102 QVariant state = idx.model()->data(idx, Call::Role::CallState);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500103 QVariant name = idx.model()->data(idx, Call::Role::Name);
104 QByteArray ba_name = name.toString().toLocal8Bit();
105 GtkWidget *new_call_view = NULL;
Stepan Salenikovich69771842015-02-24 18:11:45 -0500106
107 switch(state.value<Call::State>()) {
108 case Call::State::INCOMING:
109 {
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500110 new_call_view = incoming_call_view_new();
Stepan Salenikovich69771842015-02-24 18:11:45 -0500111 incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_call_view), idx);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500112 }
113 break;
114
115 /* TODO: implement view(s) for other call states */
116 case Call::State::RINGING:
117 case Call::State::CURRENT:
118 case Call::State::DIALING:
119 case Call::State::HOLD:
120 case Call::State::FAILURE:
121 case Call::State::BUSY:
122 case Call::State::TRANSFERRED:
123 case Call::State::TRANSF_HOLD:
124 case Call::State::OVER:
125 case Call::State::ERROR:
126 case Call::State::CONFERENCE:
127 case Call::State::CONFERENCE_HOLD:
128 case Call::State::INITIALIZATION:
129 case Call::State::COUNT__:
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500130 {
131 new_call_view = incoming_call_view_new();
132 incoming_call_view_set_call_info(INCOMING_CALL_VIEW(new_call_view), idx);
133 }
Stepan Salenikovich69771842015-02-24 18:11:45 -0500134 break;
135 }
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500136
137 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), new_call_view, ba_name.constData());
138 gtk_stack_set_visible_child(GTK_STACK(priv->stack_main_view), new_call_view);
139
Stepan Salenikovich69771842015-02-24 18:11:45 -0500140 } else {
141 /* nothing selected in the call model, so show the default screen */
142
143 /* TODO: replace stack paceholder view */
144 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT);
145 gtk_stack_set_visible_child_name(GTK_STACK(priv->stack_main_view), DEFAULT_VIEW_NAME);
146 gtk_stack_set_transition_type(GTK_STACK(priv->stack_main_view), GTK_STACK_TRANSITION_TYPE_SLIDE_RIGHT);
147
148 }
149
150 /* check if we changed the visible child */
151 GtkWidget *child_current = gtk_stack_get_visible_child(GTK_STACK(priv->stack_main_view));
152 if (child_current != child_old && child_old != NULL) {
153 /* if the previous child was an "incoming call", then remove it from
154 * the stack; removing it should destory it since there should not
155 * be any other references to it */
156 if (IS_INCOMING_CALL_VIEW(child_old)) {
157 gtk_container_remove(GTK_CONTAINER(priv->stack_main_view), child_old);
158 }
159 }
160}
161
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500162static void
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500163search_entry_placecall(G_GNUC_UNUSED GtkWidget *entry, gpointer win)
164{
165 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(RING_MAIN_WINDOW(win));
166
167 const gchar *number = gtk_entry_get_text(GTK_ENTRY(priv->search_entry));
168
169 if (number && strlen(number) > 0) {
170 g_debug("dialing to number: %s", number);
171 Call *call = CallModel::instance()->dialingCall();
172 call->setDialNumber(number);
173
174 call->performAction(Call::Action::ACCEPT);
175
176 /* make this the currently selected call */
177 QModelIndex idx = CallModel::instance()->getIndex(call);
178 CallModel::instance()->selectionModel()->setCurrentIndex(idx, QItemSelectionModel::ClearAndSelect);
179 }
180}
181
182static void
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500183ring_main_window_init(RingMainWindow *win)
184{
185 RingMainWindowPrivate *priv = RING_MAIN_WINDOW_GET_PRIVATE(win);
186 gtk_widget_init_template(GTK_WIDGET(win));
187
188 /* set window icon */
189 GError *error = NULL;
190 GdkPixbuf* icon = gdk_pixbuf_new_from_resource("/cx/ring/RingGnome/ring-symbol-blue", &error);
191 if (icon == NULL) {
192 g_debug("Could not load icon: %s", error->message);
193 g_error_free(error);
194 } else
195 gtk_window_set_icon(GTK_WINDOW(win), icon);
196
197 /* set menu icon */
198 GdkPixbuf* ring_gears = gdk_pixbuf_new_from_resource_at_scale("/cx/ring/RingGnome/ring-logo-blue",
199 -1, 22, TRUE, &error);
200 if (ring_gears == NULL) {
201 g_debug("Could not load icon: %s", error->message);
202 g_error_free(error);
203 } else
204 gtk_image_set_from_pixbuf(GTK_IMAGE(priv->gears_image), ring_gears);
205
206 /* gears menu */
207 GtkBuilder *builder = gtk_builder_new_from_resource("/cx/ring/RingGnome/ringgearsmenu.ui");
208 GMenuModel *menu = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
209 gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(priv->gears), menu);
210 g_object_unref(builder);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500211
212 /* call model */
Stepan Salenikovich69771842015-02-24 18:11:45 -0500213 GtkQTreeModel *call_model;
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500214 GtkCellRenderer *renderer;
215 GtkTreeViewColumn *column;
216
Stepan Salenikovich69771842015-02-24 18:11:45 -0500217 call_model = gtk_q_tree_model_new(CallModel::instance(), 4,
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500218 Call::Role::Name, G_TYPE_STRING,
219 Call::Role::Number, G_TYPE_STRING,
220 Call::Role::Length, G_TYPE_STRING,
221 Call::Role::CallState, G_TYPE_STRING);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500222 gtk_tree_view_set_model(GTK_TREE_VIEW(priv->treeview_call), GTK_TREE_MODEL(call_model));
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500223
224 renderer = gtk_cell_renderer_text_new();
225 column = gtk_tree_view_column_new_with_attributes("Name", renderer, "text", 0, NULL);
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500226 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_call), column);
227
228 renderer = gtk_cell_renderer_text_new();
229 column = gtk_tree_view_column_new_with_attributes("Duration", renderer, "text", 2, NULL);
230 gtk_tree_view_append_column(GTK_TREE_VIEW(priv->treeview_call), column);
231
Stepan Salenikovich69771842015-02-24 18:11:45 -0500232 /* connect signals to and from UserActionModel to sync selection betwee
233 * the QModel and the GtkTreeView */
Stepan Salenikovich69771842015-02-24 18:11:45 -0500234 QObject::connect(
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500235 CallModel::instance()->selectionModel(),
Stepan Salenikovich69771842015-02-24 18:11:45 -0500236 &QItemSelectionModel::currentChanged,
237 [=](const QModelIndex & current, const QModelIndex & previous) {
238 GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_call));
239
240 /* first unselect the previous */
241 if (previous.isValid()) {
242 GtkTreeIter old_iter;
243 if (gtk_q_tree_model_source_index_to_iter(call_model, previous, &old_iter)) {
244 gtk_tree_selection_unselect_iter(selection, &old_iter);
245 } else {
246 g_warning("Trying to unselect invalid GtkTreeIter");
247 }
248 }
249
250 /* select the current */
251 if (current.isValid()) {
252 GtkTreeIter new_iter;
253 if (gtk_q_tree_model_source_index_to_iter(call_model, current, &new_iter)) {
254 gtk_tree_selection_select_iter(selection, &new_iter);
255 } else {
256 g_warning("SelectionModel of CallModel changed to invalid QModelIndex?");
257 }
258 }
259 }
260 );
261
262 GtkTreeSelection *call_selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(priv->treeview_call));
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500263 g_signal_connect(call_selection, "changed", G_CALLBACK(update_call_model_selection), NULL);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500264
265 /* TODO: replace stack paceholder view */
266 GtkWidget *placeholder_view = gtk_tree_view_new();
267 gtk_widget_show(placeholder_view);
268 gtk_stack_add_named(GTK_STACK(priv->stack_main_view), placeholder_view, DEFAULT_VIEW_NAME);
269
270 /* connect signals */
271 g_signal_connect(call_selection, "changed", G_CALLBACK(call_selection_changed), win);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500272 g_signal_connect(priv->button_placecall, "clicked", G_CALLBACK(search_entry_placecall), win);
273 g_signal_connect(priv->search_entry, "activate", G_CALLBACK(search_entry_placecall), win);
274
275 /* style of search entry */
276 gtk_widget_override_font(priv->search_entry, pango_font_description_from_string("monospace 15"));
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500277}
278
279static void
280ring_main_window_class_init(RingMainWindowClass *klass)
281{
282 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
283 "/cx/ring/RingGnome/ringmainwindow.ui");
284
Stepan Salenikovicha3557452015-02-20 14:14:12 -0500285 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, treeview_call);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500286 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, gears);
287 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, gears_image);
Stepan Salenikovich45d25aa2015-02-20 17:39:09 -0500288 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, search_entry);
Stepan Salenikovich69771842015-02-24 18:11:45 -0500289 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, stack_main_view);
Stepan Salenikovich297b5d12015-02-26 17:51:13 -0500290 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), RingMainWindow, button_placecall);
Stepan Salenikovich434b88f2015-02-19 17:49:08 -0500291}
292
293GtkWidget *
294ring_main_window_new (GtkApplication *app)
295{
296 return (GtkWidget *)g_object_new(RING_MAIN_WINDOW_TYPE, "application", app, NULL);
297}