blob: 27d569b4e1de88a27b94cc59faf24f2841e0e299 [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;
Stepan Salenikovichf9a551d2016-11-04 18:08:49 -040060 gulong entry_changed;
aviau2da3d9c2016-09-06 11:28:36 -040061
62 gboolean use_blockchain;
63 gboolean show_register_button;
64};
65
66G_DEFINE_TYPE_WITH_PRIVATE(UsernameRegistrationBox, username_registration_box, GTK_TYPE_GRID);
67
68#define USERNAME_REGISTRATION_BOX_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), USERNAME_REGISTRATION_BOX_TYPE, UsernameRegistrationBoxPrivate))
69
70/* signals */
71enum {
72 USERNAME_AVAILABILITY_CHANGED,
73 USERNAME_REGISTRATION_COMPLETED,
74 LAST_SIGNAL
75};
76
77static guint username_registration_box_signals[LAST_SIGNAL] = { 0 };
78
79static void
80username_registration_box_dispose(GObject *object)
81{
82 auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(object);
83
84 QObject::disconnect(priv->registered_name_found);
85 QObject::disconnect(priv->name_registration_ended);
86
87 if (priv->lookup_timeout) {
88 g_source_remove(priv->lookup_timeout);
89 priv->lookup_timeout = 0;
90 }
91
92 G_OBJECT_CLASS(username_registration_box_parent_class)->dispose(object);
93}
94
95static void
96username_registration_box_init(UsernameRegistrationBox *view)
97{
98 gtk_widget_init_template(GTK_WIDGET(view));
99
100 auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
101
102 priv->registered_name_found = QObject::connect(
103 &NameDirectory::instance(),
104 &NameDirectory::registeredNameFound,
105 [=] (const Account*, NameDirectory::LookupStatus status, const QString&, const QString& name) {
106 // g_debug("Name lookup ended");
107
108 if (!priv->use_blockchain)
109 return;
110
111 // compare to the current username entry
112 const auto username_lookup = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));
113 if (name.compare(username_lookup) != 0) {
114 // assume result is for older lookup
115 return;
116 }
117
118 //We may now stop the spinner
119 gtk_spinner_stop(GTK_SPINNER(priv->spinner));
120 gtk_widget_hide(priv->spinner);
121
122 switch(status)
123 {
124 case NameDirectory::LookupStatus::SUCCESS:
125 {
126 gtk_widget_set_sensitive(priv->button_register_username, FALSE);
127 gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "error", GTK_ICON_SIZE_SMALL_TOOLBAR);
128 gtk_widget_set_tooltip_text(priv->icon_username_availability, _("The entered username is not available"));
129 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Username is not available"));
130 gtk_widget_show(priv->icon_username_availability);
131 g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, FALSE);
132 break;
133 }
134 case NameDirectory::LookupStatus::INVALID_NAME:
135 {
136 gtk_widget_set_sensitive(priv->button_register_username, FALSE);
137 gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "error", GTK_ICON_SIZE_SMALL_TOOLBAR);
138 gtk_widget_set_tooltip_text(priv->icon_username_availability, _("The entered username is not valid"));
139 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Username is not valid"));
140 gtk_widget_show(priv->icon_username_availability);
141 g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, FALSE);
142 break;
143 }
144 case NameDirectory::LookupStatus::NOT_FOUND:
145 {
146 gtk_widget_set_sensitive(priv->button_register_username, TRUE);
147 gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "emblem-default", GTK_ICON_SIZE_SMALL_TOOLBAR);
148 gtk_widget_set_tooltip_text(priv->icon_username_availability, _("The entered username is available"));
149 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Username is available"));
150 gtk_widget_show(priv->icon_username_availability);
151 g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, TRUE);
152 break;
153 }
154 case NameDirectory::LookupStatus::ERROR:
155 {
156 gtk_widget_set_sensitive(priv->button_register_username, FALSE);
157 gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "error", GTK_ICON_SIZE_SMALL_TOOLBAR);
158 gtk_widget_set_tooltip_text(priv->icon_username_availability, _("Failed to perform lookup"));
159 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Could not lookup username"));
160 gtk_widget_show(priv->icon_username_availability);
161 g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, FALSE);
162 break;
163 }
164 }
165 }
166 );
167}
168
169static void
170username_registration_box_class_init(UsernameRegistrationBoxClass *klass)
171{
172 G_OBJECT_CLASS(klass)->dispose = username_registration_box_dispose;
173
174 gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
175 "/cx/ring/RingGnome/usernameregistrationbox.ui");
176
177 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, entry_username);
178 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, icon_username_availability);
179 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, spinner);
180 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, button_register_username);
181 gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, label_status);
182
183 /* add signals */
184 username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED] = g_signal_new("username-availability-changed",
185 G_TYPE_FROM_CLASS(klass),
186 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
187 0,
188 nullptr,
189 nullptr,
190 g_cclosure_marshal_VOID__BOOLEAN,
191 G_TYPE_NONE,
192 1, G_TYPE_BOOLEAN);
193 username_registration_box_signals[USERNAME_REGISTRATION_COMPLETED] = g_signal_new("username-registration-completed",
194 G_TYPE_FROM_CLASS(klass),
195 (GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
196 0,
197 nullptr,
198 nullptr,
199 g_cclosure_marshal_VOID__VOID,
200 G_TYPE_NONE, 0);
201}
202
203static gboolean
204lookup_username(UsernameRegistrationBox *view)
205{
206 g_return_val_if_fail(IS_USERNAME_REGISTRATION_BOX(view), G_SOURCE_REMOVE);
207
208 auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
209
210 const auto username = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));
211
212 NameDirectory::instance().lookupName(nullptr, QString(), username);
213
214
215 priv->lookup_timeout = 0;
216 return G_SOURCE_REMOVE;
217}
218
219static void
220entry_username_changed(UsernameRegistrationBox *view)
221{
222 g_return_if_fail(IS_USERNAME_REGISTRATION_BOX(view));
223 UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
224
225 auto username = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));
226
227 // cancel any queued lookup
228 if (priv->lookup_timeout) {
229 g_source_remove(priv->lookup_timeout);
230 priv->lookup_timeout = 0;
231 }
232 gtk_widget_set_sensitive(priv->button_register_username, FALSE);
233
Stepan Salenikovichb6185e92016-11-21 14:21:18 -0500234 if (priv->use_blockchain) {
aviau2da3d9c2016-09-06 11:28:36 -0400235
Stepan Salenikovichb6185e92016-11-21 14:21:18 -0500236 if (strlen(username) == 0) {
237 // don't lookup empty username
238 gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "error", GTK_ICON_SIZE_SMALL_TOOLBAR);
239 gtk_widget_show(priv->icon_username_availability);
aviau2da3d9c2016-09-06 11:28:36 -0400240
Stepan Salenikovichb6185e92016-11-21 14:21:18 -0500241 gtk_widget_hide(priv->spinner);
242 gtk_spinner_stop(GTK_SPINNER(priv->spinner));
243 gtk_label_set_text(GTK_LABEL(priv->label_status), "");
244 } else {
245 gtk_widget_hide(priv->icon_username_availability);
246 gtk_widget_show(priv->spinner);
247 gtk_spinner_start(GTK_SPINNER(priv->spinner));
248 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Looking up username availability..."));
249
250 // queue lookup with a 500ms delay
251 priv->lookup_timeout = g_timeout_add(500, (GSourceFunc)lookup_username, view);
252 }
Stepan Salenikovich4fe5a7f2016-11-03 13:05:56 -0400253 } else {
Stepan Salenikovichb6185e92016-11-21 14:21:18 -0500254 // not using blockchain, so don't care about username validity
255 gtk_image_clear(GTK_IMAGE(priv->icon_username_availability));
Stepan Salenikovich4fe5a7f2016-11-03 13:05:56 -0400256 gtk_widget_show(priv->icon_username_availability);
Stepan Salenikovichb6185e92016-11-21 14:21:18 -0500257 gtk_widget_set_size_request(priv->icon_username_availability, 16, 16); // ensure min size of empty icon
Stepan Salenikovich4fe5a7f2016-11-03 13:05:56 -0400258 gtk_widget_hide(priv->spinner);
259 gtk_spinner_stop(GTK_SPINNER(priv->spinner));
260 gtk_label_set_text(GTK_LABEL(priv->label_status), "");
aviau2da3d9c2016-09-06 11:28:36 -0400261 }
262}
263
264static void
265button_register_username_clicked(G_GNUC_UNUSED GtkButton* button, UsernameRegistrationBox *view)
266{
267 g_return_if_fail(IS_USERNAME_REGISTRATION_BOX(view));
268 UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
269
270 if (!priv->use_blockchain)
271 return;
272
273 const auto username = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));
274 if (strlen(username) == 0)
275 {
276 //Error message should be displayed already.
277 return;
278 }
279
280 GtkWidget* password_dialog = gtk_message_dialog_new(
281 GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view))),
282 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
283 GTK_MESSAGE_QUESTION,
284 GTK_BUTTONS_OK_CANCEL,
285 "Enter the password of your Ring account"
286 );
287
288 GtkWidget* entry_password = gtk_entry_new();
289 gtk_entry_set_visibility(GTK_ENTRY(entry_password), FALSE);
290 gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(password_dialog))), entry_password, FALSE, FALSE, 0);
291 gtk_widget_show(entry_password);
292
293 gint result = gtk_dialog_run(GTK_DIALOG(password_dialog));
294 const QString password = QString(gtk_entry_get_text(GTK_ENTRY(entry_password)));
295 gtk_widget_destroy(password_dialog);
296
297 switch(result)
298 {
299 case GTK_RESPONSE_OK:
300 {
301 // Show the spinner
302 gtk_widget_hide(priv->icon_username_availability);
303 gtk_widget_show(priv->spinner);
304 gtk_spinner_start(GTK_SPINNER(priv->spinner));
305
306 //Disable the entry
307 gtk_widget_set_sensitive(priv->entry_username, FALSE);
308 gtk_widget_set_sensitive(priv->button_register_username, FALSE);
309
310 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Registering username..."));
311
Stepan Salenikovichf9a551d2016-11-04 18:08:49 -0400312 if (!priv->account->registerName(password, username))
313 {
314 gtk_spinner_stop(GTK_SPINNER(priv->spinner));
315 gtk_widget_hide(priv->spinner);
316 gtk_widget_set_sensitive(priv->entry_username, TRUE);
317
318 gtk_widget_set_sensitive(priv->button_register_username, TRUE);
Stepan Salenikovich7d8548c2016-12-19 15:11:33 -0500319 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Could not initiate name registration, try again."));
Stepan Salenikovichf9a551d2016-11-04 18:08:49 -0400320 gtk_widget_show(priv->icon_username_availability);
321 }
322 break;
323 }
324 case GTK_RESPONSE_CANCEL:
325 {
326 break;
327 }
328 }
329}
330
331static void
332build_view(UsernameRegistrationBox *view, gboolean register_button)
333{
334 UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
335
336 QString registered_name;
337 if(priv->account)
338 {
339 registered_name = priv->account->registeredName();
340 }
341 else
342 {
343 registered_name = QString();
344 }
345 gtk_entry_set_text(GTK_ENTRY(priv->entry_username), registered_name.toLocal8Bit().constData());
346
347 if (registered_name.isEmpty())
348 {
349 //Make the entry editable
350 g_object_set(G_OBJECT(priv->entry_username), "editable", TRUE, NULL);
351 priv->entry_changed = g_signal_connect_swapped(priv->entry_username, "changed", G_CALLBACK(entry_username_changed), view);
352
353 //Show the status icon
354 gtk_widget_show(priv->icon_username_availability);
355
356 //Show the register button
357 if (register_button && priv->account)
358 {
359 gtk_widget_show(priv->button_register_username);
360 gtk_widget_set_sensitive(priv->button_register_username, FALSE);
361 gtk_widget_set_tooltip_text(priv->button_register_username, _("Register this username on the blockchain"));
362 g_signal_connect(priv->button_register_username, "clicked", G_CALLBACK(button_register_username_clicked), view);
363 }
364
365 //Show the status label
366 gtk_widget_show(priv->label_status);
367
368 if (priv->account) {
aviau2da3d9c2016-09-06 11:28:36 -0400369 priv->name_registration_ended = QObject::connect(
370 priv->account,
371 &Account::nameRegistrationEnded,
372 [=] (NameDirectory::RegisterNameStatus status, G_GNUC_UNUSED const QString& name) {
373 gtk_spinner_stop(GTK_SPINNER(priv->spinner));
374 gtk_widget_hide(priv->spinner);
375 gtk_widget_set_sensitive(priv->entry_username, TRUE);
376
377 switch(status)
378 {
379 case NameDirectory::RegisterNameStatus::SUCCESS:
380 {
Stepan Salenikovichf9a551d2016-11-04 18:08:49 -0400381 // update the entry to what was registered, just to be sure
382 // don't do more lookups
383 if (priv->entry_changed != 0) {
384 g_signal_handler_disconnect(priv->entry_username, priv->entry_changed);
385 priv->entry_changed = 0;
386 }
387 gtk_entry_set_text(GTK_ENTRY(priv->entry_username), name.toUtf8().constData());
aviau2da3d9c2016-09-06 11:28:36 -0400388 gtk_label_set_text(GTK_LABEL(priv->label_status), NULL);
aviau2da3d9c2016-09-06 11:28:36 -0400389 g_object_set(G_OBJECT(priv->entry_username), "editable", FALSE, NULL);
390 gtk_widget_hide(priv->button_register_username);
391 g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_REGISTRATION_COMPLETED], 0);
392 break;
393 }
394 case NameDirectory::RegisterNameStatus::INVALID_NAME:
395 {
396 gtk_widget_set_sensitive(priv->button_register_username, TRUE);
397 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Invalid username"));
398 gtk_widget_show(priv->icon_username_availability);
399 break;
400 }
401 case NameDirectory::RegisterNameStatus::WRONG_PASSWORD:
402 {
403 gtk_widget_set_sensitive(priv->button_register_username, TRUE);
404 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Bad password"));
405 gtk_widget_show(priv->icon_username_availability);
406 break;
407 }
408 case NameDirectory::RegisterNameStatus::ALREADY_TAKEN:
409 {
410 gtk_widget_set_sensitive(priv->button_register_username, TRUE);
411 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Username already taken"));
412 gtk_widget_show(priv->icon_username_availability);
413 break;
414 }
415 case NameDirectory::RegisterNameStatus::NETWORK_ERROR:
416 {
417 gtk_widget_set_sensitive(priv->button_register_username, TRUE);
418 gtk_label_set_text(GTK_LABEL(priv->label_status), _("Network error"));
419 gtk_widget_show(priv->icon_username_availability);
420 break;
421 }
422 }
423 }
424 );
aviau2da3d9c2016-09-06 11:28:36 -0400425 }
426 }
427}
428
aviau2da3d9c2016-09-06 11:28:36 -0400429GtkWidget *
430username_registration_box_new(Account* account, gboolean register_button)
431{
432 gpointer view = g_object_new(USERNAME_REGISTRATION_BOX_TYPE, NULL);
433
434 UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
435 priv->account = account;
436 priv->show_register_button = register_button;
437 priv->use_blockchain = TRUE; // default to true
438
439 build_view(USERNAME_REGISTRATION_BOX(view), register_button);
440
441 return (GtkWidget *)view;
442}
443
444GtkEntry*
445username_registration_box_get_entry(UsernameRegistrationBox* view)
446{
447 UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
448 return GTK_ENTRY(priv->entry_username);
449}
450
451
452void
453username_registration_box_set_use_blockchain(UsernameRegistrationBox* view, gboolean use_blockchain)
454{
455 auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
456
457 if (priv->use_blockchain == use_blockchain)
458 return;
459
460 priv->use_blockchain = use_blockchain;
461
Stepan Salenikovich4fe5a7f2016-11-03 13:05:56 -0400462 entry_username_changed(view);
aviau2da3d9c2016-09-06 11:28:36 -0400463
Stepan Salenikovich4fe5a7f2016-11-03 13:05:56 -0400464 if (use_blockchain) {
aviau2da3d9c2016-09-06 11:28:36 -0400465 if (priv->show_register_button)
466 gtk_widget_show(priv->button_register_username);
467
468 } else {
aviau2da3d9c2016-09-06 11:28:36 -0400469 gtk_widget_hide(priv->button_register_username);
aviau2da3d9c2016-09-06 11:28:36 -0400470 }
471}