blob: 11c79b5fca68e2fd68655328ee491cdb08569806 [file] [log] [blame]
/*
* Copyright (C) 2016-2018 Savoir-faire Linux Inc.
* Author: Alexandre Viau <alexandre.viau@savoirfairelinux.com>
*
* This program 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.
*
* This program 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 this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
// GTK+ related
#include <gtk/gtk.h>
#include <glib/gi18n.h>
// LRC
#include <account.h>
// Ring Client
#include "usernameregistrationbox.h"
#include "utils/models.h"
struct _UsernameRegistrationBox
{
GtkGrid parent;
};
struct _UsernameRegistrationBoxClass
{
GtkGridClass parent_class;
};
typedef struct _UsernameRegistrationBoxPrivate UsernameRegistrationBoxPrivate;
struct _UsernameRegistrationBoxPrivate
{
Account *account;
//Widgets
GtkWidget *entry_username;
GtkWidget *icon_username_availability;
GtkWidget *spinner;
GtkWidget *button_register_username;
GtkWidget *label_status;
QMetaObject::Connection name_registration_ended;
//Lookup variables
QMetaObject::Connection registered_name_found;
QString* username_waiting_for_lookup_result;
gint lookup_timeout;
gulong entry_changed;
gboolean use_blockchain;
gboolean show_register_button;
};
G_DEFINE_TYPE_WITH_PRIVATE(UsernameRegistrationBox, username_registration_box, GTK_TYPE_GRID);
#define USERNAME_REGISTRATION_BOX_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), USERNAME_REGISTRATION_BOX_TYPE, UsernameRegistrationBoxPrivate))
/* signals */
enum {
USERNAME_AVAILABILITY_CHANGED,
USERNAME_REGISTRATION_COMPLETED,
LAST_SIGNAL
};
static guint username_registration_box_signals[LAST_SIGNAL] = { 0 };
static void
username_registration_box_dispose(GObject *object)
{
auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(object);
QObject::disconnect(priv->registered_name_found);
QObject::disconnect(priv->name_registration_ended);
if (priv->lookup_timeout) {
g_source_remove(priv->lookup_timeout);
priv->lookup_timeout = 0;
}
G_OBJECT_CLASS(username_registration_box_parent_class)->dispose(object);
}
static void
username_registration_box_init(UsernameRegistrationBox *view)
{
gtk_widget_init_template(GTK_WIDGET(view));
auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
priv->registered_name_found = QObject::connect(
&NameDirectory::instance(),
&NameDirectory::registeredNameFound,
[=] (const Account*, NameDirectory::LookupStatus status, const QString&, const QString& name) {
// g_debug("Name lookup ended");
if (!priv->use_blockchain)
return;
// compare to the current username entry
const auto username_lookup = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));
if (name.compare(username_lookup) != 0) {
// assume result is for older lookup
return;
}
//We may now stop the spinner
gtk_spinner_stop(GTK_SPINNER(priv->spinner));
gtk_widget_hide(priv->spinner);
switch(status)
{
case NameDirectory::LookupStatus::SUCCESS:
{
gtk_widget_set_sensitive(priv->button_register_username, FALSE);
gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "error", GTK_ICON_SIZE_SMALL_TOOLBAR);
gtk_widget_set_tooltip_text(priv->icon_username_availability, _("The entered username is not available"));
gtk_label_set_text(GTK_LABEL(priv->label_status), _("Username is not available"));
gtk_widget_show(priv->icon_username_availability);
g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, FALSE);
break;
}
case NameDirectory::LookupStatus::INVALID_NAME:
{
gtk_widget_set_sensitive(priv->button_register_username, FALSE);
gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "error", GTK_ICON_SIZE_SMALL_TOOLBAR);
gtk_widget_set_tooltip_text(priv->icon_username_availability, _("The entered username is not valid"));
gtk_label_set_text(GTK_LABEL(priv->label_status), _("Username is not valid"));
gtk_widget_show(priv->icon_username_availability);
g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, FALSE);
break;
}
case NameDirectory::LookupStatus::NOT_FOUND:
{
gtk_widget_set_sensitive(priv->button_register_username, TRUE);
gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "emblem-default", GTK_ICON_SIZE_SMALL_TOOLBAR);
gtk_widget_set_tooltip_text(priv->icon_username_availability, _("The entered username is available"));
gtk_label_set_text(GTK_LABEL(priv->label_status), _("Username is available"));
gtk_widget_show(priv->icon_username_availability);
g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, TRUE);
break;
}
case NameDirectory::LookupStatus::ERROR:
{
gtk_widget_set_sensitive(priv->button_register_username, FALSE);
gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "error", GTK_ICON_SIZE_SMALL_TOOLBAR);
gtk_widget_set_tooltip_text(priv->icon_username_availability, _("Failed to perform lookup"));
gtk_label_set_text(GTK_LABEL(priv->label_status), _("Could not lookup username"));
gtk_widget_show(priv->icon_username_availability);
g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED], 0, FALSE);
break;
}
}
}
);
}
static void
username_registration_box_class_init(UsernameRegistrationBoxClass *klass)
{
G_OBJECT_CLASS(klass)->dispose = username_registration_box_dispose;
gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS (klass),
"/cx/ring/RingGnome/usernameregistrationbox.ui");
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, entry_username);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, icon_username_availability);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, spinner);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, button_register_username);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), UsernameRegistrationBox, label_status);
/* add signals */
username_registration_box_signals[USERNAME_AVAILABILITY_CHANGED] = g_signal_new("username-availability-changed",
G_TYPE_FROM_CLASS(klass),
(GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
0,
nullptr,
nullptr,
g_cclosure_marshal_VOID__BOOLEAN,
G_TYPE_NONE,
1, G_TYPE_BOOLEAN);
username_registration_box_signals[USERNAME_REGISTRATION_COMPLETED] = g_signal_new("username-registration-completed",
G_TYPE_FROM_CLASS(klass),
(GSignalFlags) (G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION),
0,
nullptr,
nullptr,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
static gboolean
lookup_username(UsernameRegistrationBox *view)
{
g_return_val_if_fail(IS_USERNAME_REGISTRATION_BOX(view), G_SOURCE_REMOVE);
auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
const auto username = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));
if (priv->account) {
NameDirectory::instance().lookupName(priv->account, priv->account->nameServiceURL(), username);
} else {
NameDirectory::instance().lookupName(nullptr, QString(), username);
}
priv->lookup_timeout = 0;
return G_SOURCE_REMOVE;
}
static void
entry_username_changed(UsernameRegistrationBox *view)
{
g_return_if_fail(IS_USERNAME_REGISTRATION_BOX(view));
UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
auto username = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));
// cancel any queued lookup
if (priv->lookup_timeout) {
g_source_remove(priv->lookup_timeout);
priv->lookup_timeout = 0;
}
gtk_widget_set_sensitive(priv->button_register_username, FALSE);
if (priv->use_blockchain) {
if (strlen(username) == 0) {
// don't lookup empty username
gtk_image_set_from_icon_name(GTK_IMAGE(priv->icon_username_availability), "error", GTK_ICON_SIZE_SMALL_TOOLBAR);
gtk_widget_show(priv->icon_username_availability);
gtk_widget_hide(priv->spinner);
gtk_spinner_stop(GTK_SPINNER(priv->spinner));
gtk_label_set_text(GTK_LABEL(priv->label_status), "");
} else {
gtk_widget_hide(priv->icon_username_availability);
gtk_widget_show(priv->spinner);
gtk_spinner_start(GTK_SPINNER(priv->spinner));
gtk_label_set_text(GTK_LABEL(priv->label_status), _("Looking up username availability..."));
// queue lookup with a 500ms delay
priv->lookup_timeout = g_timeout_add(500, (GSourceFunc)lookup_username, view);
}
} else {
// not using blockchain, so don't care about username validity
gtk_image_clear(GTK_IMAGE(priv->icon_username_availability));
gtk_widget_show(priv->icon_username_availability);
gtk_widget_set_size_request(priv->icon_username_availability, 16, 16); // ensure min size of empty icon
gtk_widget_hide(priv->spinner);
gtk_spinner_stop(GTK_SPINNER(priv->spinner));
gtk_label_set_text(GTK_LABEL(priv->label_status), "");
}
}
static void
button_register_username_clicked(G_GNUC_UNUSED GtkButton* button, UsernameRegistrationBox *view)
{
g_return_if_fail(IS_USERNAME_REGISTRATION_BOX(view));
UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
if (!priv->use_blockchain)
return;
const auto username = gtk_entry_get_text(GTK_ENTRY(priv->entry_username));
if (strlen(username) == 0)
{
//Error message should be displayed already.
return;
}
GtkWidget* password_dialog = gtk_message_dialog_new(
GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(view))),
(GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
GTK_MESSAGE_QUESTION,
GTK_BUTTONS_OK_CANCEL,
"Enter the password of your Ring account"
);
GtkWidget* entry_password = gtk_entry_new();
gtk_entry_set_visibility(GTK_ENTRY(entry_password), FALSE);
gtk_box_pack_end(GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(password_dialog))), entry_password, FALSE, FALSE, 0);
gtk_widget_show(entry_password);
gint result = gtk_dialog_run(GTK_DIALOG(password_dialog));
const QString password = QString(gtk_entry_get_text(GTK_ENTRY(entry_password)));
gtk_widget_destroy(password_dialog);
switch(result)
{
case GTK_RESPONSE_OK:
{
// Show the spinner
gtk_widget_hide(priv->icon_username_availability);
gtk_widget_show(priv->spinner);
gtk_spinner_start(GTK_SPINNER(priv->spinner));
//Disable the entry
gtk_widget_set_sensitive(priv->entry_username, FALSE);
gtk_widget_set_sensitive(priv->button_register_username, FALSE);
gtk_label_set_text(GTK_LABEL(priv->label_status), _("Registering username..."));
if (!priv->account->registerName(password, username))
{
gtk_spinner_stop(GTK_SPINNER(priv->spinner));
gtk_widget_hide(priv->spinner);
gtk_widget_set_sensitive(priv->entry_username, TRUE);
gtk_widget_set_sensitive(priv->button_register_username, TRUE);
gtk_label_set_text(GTK_LABEL(priv->label_status), _("Could not initiate name registration, try again."));
gtk_widget_show(priv->icon_username_availability);
}
break;
}
case GTK_RESPONSE_CANCEL:
{
break;
}
}
}
static void
build_view(UsernameRegistrationBox *view, gboolean register_button)
{
UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
QString registered_name;
if(priv->account)
{
registered_name = priv->account->registeredName();
}
else
{
registered_name = QString();
}
gtk_entry_set_text(GTK_ENTRY(priv->entry_username), registered_name.toLocal8Bit().constData());
if (registered_name.isEmpty())
{
//Make the entry editable
g_object_set(G_OBJECT(priv->entry_username), "editable", TRUE, NULL);
priv->entry_changed = g_signal_connect_swapped(priv->entry_username, "changed", G_CALLBACK(entry_username_changed), view);
//Show the status icon
gtk_widget_show(priv->icon_username_availability);
//Show the register button
if (register_button && priv->account)
{
gtk_widget_show(priv->button_register_username);
gtk_widget_set_sensitive(priv->button_register_username, FALSE);
gtk_widget_set_tooltip_text(priv->button_register_username, _("Register this username on the blockchain"));
g_signal_connect(priv->button_register_username, "clicked", G_CALLBACK(button_register_username_clicked), view);
}
//Show the status label
gtk_widget_show(priv->label_status);
if (priv->account) {
priv->name_registration_ended = QObject::connect(
priv->account,
&Account::nameRegistrationEnded,
[=] (NameDirectory::RegisterNameStatus status, G_GNUC_UNUSED const QString& name) {
gtk_spinner_stop(GTK_SPINNER(priv->spinner));
gtk_widget_hide(priv->spinner);
gtk_widget_set_sensitive(priv->entry_username, TRUE);
switch(status)
{
case NameDirectory::RegisterNameStatus::SUCCESS:
{
// update the entry to what was registered, just to be sure
// don't do more lookups
if (priv->entry_changed != 0) {
g_signal_handler_disconnect(priv->entry_username, priv->entry_changed);
priv->entry_changed = 0;
}
gtk_entry_set_text(GTK_ENTRY(priv->entry_username), name.toUtf8().constData());
gtk_label_set_text(GTK_LABEL(priv->label_status), NULL);
g_object_set(G_OBJECT(priv->entry_username), "editable", FALSE, NULL);
gtk_widget_hide(priv->button_register_username);
g_signal_emit(G_OBJECT(view), username_registration_box_signals[USERNAME_REGISTRATION_COMPLETED], 0);
break;
}
case NameDirectory::RegisterNameStatus::INVALID_NAME:
{
gtk_widget_set_sensitive(priv->button_register_username, TRUE);
gtk_label_set_text(GTK_LABEL(priv->label_status), _("Invalid username"));
gtk_widget_show(priv->icon_username_availability);
break;
}
case NameDirectory::RegisterNameStatus::WRONG_PASSWORD:
{
gtk_widget_set_sensitive(priv->button_register_username, TRUE);
gtk_label_set_text(GTK_LABEL(priv->label_status), _("Bad password"));
gtk_widget_show(priv->icon_username_availability);
break;
}
case NameDirectory::RegisterNameStatus::ALREADY_TAKEN:
{
gtk_widget_set_sensitive(priv->button_register_username, TRUE);
gtk_label_set_text(GTK_LABEL(priv->label_status), _("Username already taken"));
gtk_widget_show(priv->icon_username_availability);
break;
}
case NameDirectory::RegisterNameStatus::NETWORK_ERROR:
{
gtk_widget_set_sensitive(priv->button_register_username, TRUE);
gtk_label_set_text(GTK_LABEL(priv->label_status), _("Network error"));
gtk_widget_show(priv->icon_username_availability);
break;
}
}
}
);
}
}
}
GtkWidget *
username_registration_box_new(Account* account, gboolean register_button)
{
gpointer view = g_object_new(USERNAME_REGISTRATION_BOX_TYPE, NULL);
UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
priv->account = account;
priv->show_register_button = register_button;
priv->use_blockchain = TRUE; // default to true
build_view(USERNAME_REGISTRATION_BOX(view), register_button);
return (GtkWidget *)view;
}
GtkEntry*
username_registration_box_get_entry(UsernameRegistrationBox* view)
{
UsernameRegistrationBoxPrivate *priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
return GTK_ENTRY(priv->entry_username);
}
void
username_registration_box_set_use_blockchain(UsernameRegistrationBox* view, gboolean use_blockchain)
{
auto priv = USERNAME_REGISTRATION_BOX_GET_PRIVATE(view);
if (priv->use_blockchain == use_blockchain)
return;
priv->use_blockchain = use_blockchain;
entry_username_changed(view);
if (use_blockchain) {
if (priv->show_register_button)
gtk_widget_show(priv->button_register_username);
} else {
gtk_widget_hide(priv->button_register_username);
}
}