blob: e4678bfb6aad8fd587bfab4b86af2e6b51c757e8 [file] [log] [blame]
aviau2da3d9c2016-09-06 11:28:36 -04001/*
2 * Copyright (C) 2016 Savoir-faire Linux Inc.
3 * Author: Alexandre Viau <alexandre.viau@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
20// GTK+ related
21#include <gtk/gtk.h>
22#include <glib/gi18n.h>
23
24// LRC
25#include <account.h>
26
27// Ring Client
28#include "usernameregistrationbox.h"
29#include "utils/models.h"
30
31struct _UsernameRegistrationBox
32{
33 GtkGrid parent;
34};
35
36struct _UsernameRegistrationBoxClass
37{
38 GtkGridClass parent_class;
39};
40
41typedef struct _UsernameRegistrationBoxPrivate UsernameRegistrationBoxPrivate;
42
43struct _UsernameRegistrationBoxPrivate
44{
45 Account *account;
46
47 //Widgets
48 GtkWidget *entry_username;
49 GtkWidget *icon_username_availability;
50 GtkWidget *spinner;
51 GtkWidget *button_register_username;
52 GtkWidget *label_status;
53
54 QMetaObject::Connection name_registration_ended;
55
56 //Lookup variables
57 QMetaObject::Connection registered_name_found;
58 QString* username_waiting_for_lookup_result;
59 gint lookup_timeout;
60
61 gboolean use_blockchain;
62 gboolean show_register_button;
63};
64
65G_DEFINE_TYPE_WITH_PRIVATE(UsernameRegistrationBox, username_registration_box, GTK_TYPE_GRID);
66
67#define USERNAME_REGISTRATION_BOX_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), USERNAME_REGISTRATION_BOX_TYPE, UsernameRegistrationBoxPrivate))
68
69/* signals */
70enum {
71 USERNAME_AVAILABILITY_CHANGED,
72 USERNAME_REGISTRATION_COMPLETED,
73 LAST_SIGNAL
74};
75
76static guint username_registration_box_signals[LAST_SIGNAL] = { 0 };
77
78static void
79username_registration_box_dispose(GObject *object)
80{
81 auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(object);
82
83 QObject::disconnect(priv->registered_name_found);
84 QObject::disconnect(priv->name_registration_ended);
85
86 if (priv->lookup_timeout) {
87 g_source_remove(priv->lookup_timeout);
88 priv->lookup_timeout = 0;
89 }
90
91 G_OBJECT_CLASS(username_registration_box_parent_class)->dispose(object);
92}
93
94static void
95username_registration_box_init(UsernameRegistrationBox *view)
96{
97 gtk_widget_init_template(GTK_WIDGET(view));
98
99 auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
100
101 priv->registered_name_found = QObject::connect(
102 &NameDirectory::instance(),
103 &NameDirectory::registeredNameFound,
104 [=] (const Account*, NameDirectory::LookupStatus status, const QString&, const QString& name) {
105 // g_debug("Name lookup ended");
106
107 if (!priv->use_blockchain)
108 return;
109
110 // compare to the current username entry
111 const auto username_lookup = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));
112 if (name.compare(username_lookup) != 0) {
113 // assume result is for older lookup
114 return;
115 }
116
117 //We may now stop the spinner
118 gtk_spinner_stop(GTK_SPINNER(priv->spinner));
119 gtk_widget_hide(priv->spinner);
120
121 switch(status)
122 {
123 case NameDirectory::LookupStatus::SUCCESS:
124 {
125 gtk_widget_set_sensitive(priv->button_register_username, FALSE);
126 gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "error", GTK_ICON_SIZE_SMALL_TOOLBAR);
127 gtk_widget_set_tooltip_text(priv->icon_username_availability, _("The entered username is not available"));
128 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Username is not available"));
129 gtk_widget_show(priv->icon_username_availability);
130 g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, FALSE);
131 break;
132 }
133 case NameDirectory::LookupStatus::INVALID_NAME:
134 {
135 gtk_widget_set_sensitive(priv->button_register_username, FALSE);
136 gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "error", GTK_ICON_SIZE_SMALL_TOOLBAR);
137 gtk_widget_set_tooltip_text(priv->icon_username_availability, _("The entered username is not valid"));
138 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Username is not valid"));
139 gtk_widget_show(priv->icon_username_availability);
140 g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, FALSE);
141 break;
142 }
143 case NameDirectory::LookupStatus::NOT_FOUND:
144 {
145 gtk_widget_set_sensitive(priv->button_register_username, TRUE);
146 gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "emblem-default", GTK_ICON_SIZE_SMALL_TOOLBAR);
147 gtk_widget_set_tooltip_text(priv->icon_username_availability, _("The entered username is available"));
148 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Username is available"));
149 gtk_widget_show(priv->icon_username_availability);
150 g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, TRUE);
151 break;
152 }
153 case NameDirectory::LookupStatus::ERROR:
154 {
155 gtk_widget_set_sensitive(priv->button_register_username, FALSE);
156 gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "error", GTK_ICON_SIZE_SMALL_TOOLBAR);
157 gtk_widget_set_tooltip_text(priv->icon_username_availability, _("Failed to perform lookup"));
158 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Could not lookup username"));
159 gtk_widget_show(priv->icon_username_availability);
160 g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, FALSE);
161 break;
162 }
163 }
164 }
165 );
166}
167
168static void
169username_registration_box_class_init(UsernameRegistrationBoxClass *klass)
170{
171 G_OBJECT_CLASS(klass)->dispose = username_registration_box_dispose;
172
173 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
174 "/cx/ring/RingGnome/usernameregistrationbox.ui");
175
176 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, entry_username);
177 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, icon_username_availability);
178 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, spinner);
179 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, button_register_username);
180 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, label_status);
181
182 /* add signals */
183 username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED] = g_signal_new("username-availability-changed",
184 G_TYPE_FROM_CLASS(klass),
185 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
186 0,
187 nullptr,
188 nullptr,
189 g_cclosure_marshal_VOID__BOOLEAN,
190 G_TYPE_NONE,
191 1, G_TYPE_BOOLEAN);
192 username_registration_box_signals[USERNAME_REGISTRATION_COMPLETED] = g_signal_new("username-registration-completed",
193 G_TYPE_FROM_CLASS(klass),
194 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
195 0,
196 nullptr,
197 nullptr,
198 g_cclosure_marshal_VOID__VOID,
199 G_TYPE_NONE, 0);
200}
201
202static gboolean
203lookup_username(UsernameRegistrationBox *view)
204{
205 g_return_val_if_fail(IS_USERNAME_REGISTRATION_BOX(view), G_SOURCE_REMOVE);
206
207 auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
208
209 const auto username = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));
210
211 NameDirectory::instance().lookupName(nullptr, QString(), username);
212
213
214 priv->lookup_timeout = 0;
215 return G_SOURCE_REMOVE;
216}
217
218static void
219entry_username_changed(UsernameRegistrationBox *view)
220{
221 g_return_if_fail(IS_USERNAME_REGISTRATION_BOX(view));
222 UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
223
224 auto username = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));
225
226 // cancel any queued lookup
227 if (priv->lookup_timeout) {
228 g_source_remove(priv->lookup_timeout);
229 priv->lookup_timeout = 0;
230 }
231 gtk_widget_set_sensitive(priv->button_register_username, FALSE);
232
233 if (strlen(username) == 0) {
234 // don't lookup empty username
235 gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "error", GTK_ICON_SIZE_SMALL_TOOLBAR);
236 gtk_widget_show(priv->icon_username_availability);
237
238 gtk_widget_hide(priv->spinner);
239 gtk_spinner_stop(GTK_SPINNER(priv->spinner));
240 gtk_label_set_text(GTK_LABEL(priv->label_status), "");
241 } else if (priv->use_blockchain) {
242 gtk_widget_hide(priv->icon_username_availability);
243 gtk_widget_show(priv->spinner);
244 gtk_spinner_start(GTK_SPINNER(priv->spinner));
245 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Looking up username availability..."));
246
247 // queue lookup with a 500ms delay
248 priv->lookup_timeout = g_timeout_add(500, (GSourceFunc)lookup_username, view);
Stepan Salenikovich4fe5a7f2016-11-03 13:05:56 -0400249 } else {
250 // not using blockchain, any name > 0 is OK
251 gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "emblem-default", GTK_ICON_SIZE_SMALL_TOOLBAR);
252 gtk_widget_show(priv->icon_username_availability);
253 gtk_widget_hide(priv->spinner);
254 gtk_spinner_stop(GTK_SPINNER(priv->spinner));
255 gtk_label_set_text(GTK_LABEL(priv->label_status), "");
aviau2da3d9c2016-09-06 11:28:36 -0400256 }
257}
258
259static void
260button_register_username_clicked(G_GNUC_UNUSED GtkButton* button, UsernameRegistrationBox *view)
261{
262 g_return_if_fail(IS_USERNAME_REGISTRATION_BOX(view));
263 UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
264
265 if (!priv->use_blockchain)
266 return;
267
268 const auto username = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));
269 if (strlen(username) == 0)
270 {
271 //Error message should be displayed already.
272 return;
273 }
274
275 GtkWidget* password_dialog = gtk_message_dialog_new(
276 GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view))),
277 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
278 GTK_MESSAGE_QUESTION,
279 GTK_BUTTONS_OK_CANCEL,
280 "Enter the password of your Ring account"
281 );
282
283 GtkWidget* entry_password = gtk_entry_new();
284 gtk_entry_set_visibility(GTK_ENTRY(entry_password), FALSE);
285 gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(password_dialog))), entry_password, FALSE, FALSE, 0);
286 gtk_widget_show(entry_password);
287
288 gint result = gtk_dialog_run(GTK_DIALOG(password_dialog));
289 const QString password = QString(gtk_entry_get_text(GTK_ENTRY(entry_password)));
290 gtk_widget_destroy(password_dialog);
291
292 switch(result)
293 {
294 case GTK_RESPONSE_OK:
295 {
296 // Show the spinner
297 gtk_widget_hide(priv->icon_username_availability);
298 gtk_widget_show(priv->spinner);
299 gtk_spinner_start(GTK_SPINNER(priv->spinner));
300
301 //Disable the entry
302 gtk_widget_set_sensitive(priv->entry_username, FALSE);
303 gtk_widget_set_sensitive(priv->button_register_username, FALSE);
304
305 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Registering username..."));
306
307 priv->name_registration_ended = QObject::connect(
308 priv->account,
309 &Account::nameRegistrationEnded,
310 [=] (NameDirectory::RegisterNameStatus status, G_GNUC_UNUSED const QString& name) {
311 gtk_spinner_stop(GTK_SPINNER(priv->spinner));
312 gtk_widget_hide(priv->spinner);
313 gtk_widget_set_sensitive(priv->entry_username, TRUE);
314
315 switch(status)
316 {
317 case NameDirectory::RegisterNameStatus::SUCCESS:
318 {
319 gtk_label_set_text(GTK_LABEL(priv->label_status), NULL);
320 gtk_widget_set_sensitive(priv->button_register_username, TRUE);
321 g_object_set(G_OBJECT(priv->entry_username), "editable", FALSE, NULL);
322 gtk_widget_hide(priv->button_register_username);
323 g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_REGISTRATION_COMPLETED], 0);
324 break;
325 }
326 case NameDirectory::RegisterNameStatus::INVALID_NAME:
327 {
328 gtk_widget_set_sensitive(priv->button_register_username, TRUE);
329 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Invalid username"));
330 gtk_widget_show(priv->icon_username_availability);
331 break;
332 }
333 case NameDirectory::RegisterNameStatus::WRONG_PASSWORD:
334 {
335 gtk_widget_set_sensitive(priv->button_register_username, TRUE);
336 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Bad password"));
337 gtk_widget_show(priv->icon_username_availability);
338 break;
339 }
340 case NameDirectory::RegisterNameStatus::ALREADY_TAKEN:
341 {
342 gtk_widget_set_sensitive(priv->button_register_username, TRUE);
343 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Username already taken"));
344 gtk_widget_show(priv->icon_username_availability);
345 break;
346 }
347 case NameDirectory::RegisterNameStatus::NETWORK_ERROR:
348 {
349 gtk_widget_set_sensitive(priv->button_register_username, TRUE);
350 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Network error"));
351 gtk_widget_show(priv->icon_username_availability);
352 break;
353 }
354 }
355 }
356 );
357
358 if (!priv->account->registerName(password, username))
359 {
360 gtk_spinner_stop(GTK_SPINNER(priv->spinner));
361 gtk_widget_hide(priv->spinner);
362 gtk_widget_set_sensitive(priv->entry_username, TRUE);
363
364 gtk_widget_set_sensitive(priv->button_register_username, TRUE);
365 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Count not initiate name registration, try again."));
366 gtk_widget_show(priv->icon_username_availability);
367 }
368 break;
369 }
370 case GTK_RESPONSE_CANCEL:
371 {
372 break;
373 }
374 }
375}
376
377static void
378build_view(UsernameRegistrationBox *view, gboolean register_button)
379{
380 UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
381
382 QString registered_name;
383 if(priv->account)
384 {
385 registered_name = priv->account->registeredName();
386 }
387 else
388 {
389 registered_name = QString();
390 }
391 gtk_entry_set_text(GTK_ENTRY(priv->entry_username), registered_name.toLocal8Bit().constData());
392
393 if (registered_name.isEmpty())
394 {
395 //Make the entry editable
396 g_object_set(G_OBJECT(priv->entry_username), "editable", TRUE, NULL);
397 g_signal_connect_swapped(priv->entry_username, "changed", G_CALLBACK(entry_username_changed), view);
398
399 //Show the status icon
400 gtk_widget_show(priv->icon_username_availability);
401
402 //Show the register button
403 if (register_button && priv->account)
404 {
405 gtk_widget_show(priv->button_register_username);
406 gtk_widget_set_sensitive(priv->button_register_username, FALSE);
407 gtk_widget_set_tooltip_text(priv->button_register_username, _("Register this username on the blockchain"));
408 g_signal_connect(priv->button_register_username, "clicked", G_CALLBACK(button_register_username_clicked), view);
409 }
410
411 //Show the status label
412 gtk_widget_show(priv->label_status);
413 }
414
415}
416
417GtkWidget *
418username_registration_box_new(Account* account, gboolean register_button)
419{
420 gpointer view = g_object_new(USERNAME_REGISTRATION_BOX_TYPE, NULL);
421
422 UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
423 priv->account = account;
424 priv->show_register_button = register_button;
425 priv->use_blockchain = TRUE; // default to true
426
427 build_view(USERNAME_REGISTRATION_BOX(view), register_button);
428
429 return (GtkWidget *)view;
430}
431
432GtkEntry*
433username_registration_box_get_entry(UsernameRegistrationBox* view)
434{
435 UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
436 return GTK_ENTRY(priv->entry_username);
437}
438
439
440void
441username_registration_box_set_use_blockchain(UsernameRegistrationBox* view, gboolean use_blockchain)
442{
443 auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
444
445 if (priv->use_blockchain == use_blockchain)
446 return;
447
448 priv->use_blockchain = use_blockchain;
449
Stepan Salenikovich4fe5a7f2016-11-03 13:05:56 -0400450 entry_username_changed(view);
aviau2da3d9c2016-09-06 11:28:36 -0400451
Stepan Salenikovich4fe5a7f2016-11-03 13:05:56 -0400452 if (use_blockchain) {
aviau2da3d9c2016-09-06 11:28:36 -0400453 if (priv->show_register_button)
454 gtk_widget_show(priv->button_register_username);
455
456 } else {
aviau2da3d9c2016-09-06 11:28:36 -0400457 gtk_widget_hide(priv->button_register_username);
aviau2da3d9c2016-09-06 11:28:36 -0400458 }
459}