Commit 246c6ae1 authored by Sébastien Blin's avatar Sébastien Blin

conversationpopup: replace "Copy name" by a details card

This patch adds a detailled card for each contacts. This card
contains the best name, the avatar, the username, the uri and
the QR Code of the contact.

In the future, custom options by contact can be added in this
profile card.

Change-Id: I0d09840b9c5ee3bb7cadb07e45b5f98d12cd4ea7
Gitlab: #792
parent dbef114d
......@@ -313,6 +313,8 @@ SET( SRC_FILES
src/conversationpopupmenu.h
src/conversationpopupmenu.cpp
src/accountinfopointer.h
src/profileview.h
src/profileview.cpp
)
# compile glib resource files to c code
......
......@@ -28,6 +28,7 @@
#include <api/conversationmodel.h>
#include "accountinfopointer.h"
#include "profileview.h"
// Qt
#include <QItemSelectionModel>
......@@ -57,24 +58,11 @@ G_DEFINE_TYPE_WITH_PRIVATE(ConversationPopupMenu, conversation_popup_menu, GTK_T
#define CONVERSATION_POPUP_MENU_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), CONVERSATION_POPUP_MENU_TYPE, ConversationPopupMenuPrivate))
static void
copy_contact_info(G_GNUC_UNUSED GtkWidget *menu, ConversationPopupMenuPrivate* priv)
show_profile(G_GNUC_UNUSED GtkWidget *menu, ConversationPopupMenuPrivate* priv)
{
try
{
auto conversation = (*priv->accountInfo_)->conversationModel->filteredConversation(priv->row_);
if (conversation.participants.empty()) return;
auto& contact = (*priv->accountInfo_)->contactModel->getContact(conversation.participants.front());
auto bestName = contact.registeredName.empty() ? contact.profileInfo.uri : contact.registeredName;
auto text = (gchar *)bestName.c_str();
GtkClipboard* clip = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD);
gtk_clipboard_set_text(clip, text, -1);
clip = gtk_clipboard_get(GDK_SELECTION_PRIMARY);
gtk_clipboard_set_text(clip, text, -1);
}
catch (...)
{
g_warning("Can't get conversation at row %i", priv->row_);
}
if (!priv) return;
auto *profile = profile_view_new(*priv->accountInfo_, priv->row_);
if (profile) gtk_widget_show_all(GTK_WIDGET(profile));
}
static void
......@@ -249,9 +237,9 @@ update(GtkTreeSelection *selection, ConversationPopupMenu *self)
}
}
auto copy_name = gtk_menu_item_new_with_mnemonic(_("_Copy name"));
gtk_menu_shell_append(GTK_MENU_SHELL(self), copy_name);
g_signal_connect(copy_name, "activate", G_CALLBACK(copy_contact_info), priv);
auto profile = gtk_menu_item_new_with_mnemonic(_("_Profile"));
gtk_menu_shell_append(GTK_MENU_SHELL(self), profile);
g_signal_connect(profile, "activate", G_CALLBACK(show_profile), priv);
/* show all conversations */
gtk_widget_show_all(GTK_WIDGET(self));
......
/*
* Copyright(C) 2019 Savoir-faire Linux Inc.
* Author: Sebastien Blin <sebastien.blin@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.
*/
#include "profileview.h"
#include "native/pixbufmanipulator.h"
#include "utils/drawing.h"
#include <QSize>
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <api/account.h>
#include <api/contact.h>
#include <api/contactmodel.h>
#include <api/conversationmodel.h>
#include <globalinstances.h>
struct _ProfileView
{
GtkDialog parent;
};
typedef struct _ProfileViewPrivate ProfileViewPrivate;
struct _ProfileViewPrivate
{
AccountInfoPointer const *accountInfo_ = nullptr;
int row_;
GtkWidget* avatar;
GtkWidget* grid_infos;
GtkWidget* best_name_label;
GtkWidget* username_label;
GtkWidget* id_label;
GtkWidget* qr_image;
};
G_DEFINE_TYPE_WITH_PRIVATE(ProfileView, profile_view, GTK_TYPE_DIALOG)
#define PROFILE_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), PROFILE_VIEW_TYPE, _ProfileViewPrivate))
static void
profile_view_init(ProfileView *prefs)
{
gtk_widget_init_template(GTK_WIDGET(prefs));
}
static void
profile_view_dispose(GObject *object)
{
G_OBJECT_CLASS(profile_view_parent_class)->dispose(object);
}
static void
profile_view_class_init(ProfileViewClass *klass)
{
G_OBJECT_CLASS(klass)->dispose = profile_view_dispose;
gtk_widget_class_set_template_from_resource(GTK_WIDGET_CLASS(klass), "/net/jami/JamiGnome/profile.ui");
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), ProfileView, avatar);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), ProfileView, grid_infos);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), ProfileView, best_name_label);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), ProfileView, username_label);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), ProfileView, id_label);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS(klass), ProfileView, qr_image);
}
static bool
build_view(ProfileView* view)
{
g_return_val_if_fail(IS_PROFILE_VIEW(view), false);
auto* priv = PROFILE_VIEW_GET_PRIVATE(view);
g_return_val_if_fail(priv || !(*priv->accountInfo_), false);
try
{
const auto& conversation = (*priv->accountInfo_)->conversationModel->filteredConversation(priv->row_);
if (conversation.participants.empty()) return false;
const auto& contact = (*priv->accountInfo_)->contactModel->getContact(conversation.participants.front());
auto alias = contact.profileInfo.alias;
alias.erase(std::remove(alias.begin(), alias.end(), '\r'), alias.end());
alias.erase(std::remove(alias.begin(), alias.end(), '\n'), alias.end());
if (alias.empty()) alias = contact.registeredName;
if (alias.empty()) alias = contact.profileInfo.uri;
gtk_label_set_text(GTK_LABEL(priv->best_name_label), alias.c_str());
GtkStyleContext* context;
context = gtk_widget_get_style_context(GTK_WIDGET(priv->best_name_label));
gtk_style_context_add_class(context, "bestname");
if (contact.registeredName.empty()) {
gtk_label_set_text(GTK_LABEL(priv->username_label), _("(None)"));
context = gtk_widget_get_style_context(GTK_WIDGET(priv->username_label));
gtk_style_context_add_class(context, "empty");
gtk_label_set_selectable(GTK_LABEL(priv->username_label), false);
} else {
gtk_label_set_text(GTK_LABEL(priv->username_label), contact.registeredName.c_str());
}
gtk_label_set_text(GTK_LABEL(priv->id_label), contact.profileInfo.uri.c_str());
uint32_t img_size = 128;
std::shared_ptr<GdkPixbuf> image;
auto var_photo = GlobalInstances::pixmapManipulator().conversationPhoto(
conversation,
**(priv->accountInfo_),
QSize(img_size, img_size),
false
);
image = var_photo.value<std::shared_ptr<GdkPixbuf>>();
gtk_image_set_from_pixbuf(GTK_IMAGE(priv->avatar), image.get());
auto* surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, img_size, img_size);
auto* cr = cairo_create(surface);
if (draw_qrcode(cr, contact.profileInfo.uri, img_size)) {
GdkPixbuf *pixbuf = gdk_pixbuf_get_from_surface(cairo_get_target(cr), 0, 0, img_size, img_size);
gtk_image_set_from_pixbuf(GTK_IMAGE(priv->qr_image), pixbuf);
}
g_free(surface);
g_free(cr);
gtk_window_set_title(GTK_WINDOW(view), std::string("Profile - " + alias).c_str());
gtk_window_set_modal(GTK_WINDOW(view), false);
}
catch (...)
{
g_warning("Can't get conversation at row %i", priv->row_);
return false;
}
return true;
}
GtkWidget*
profile_view_new(AccountInfoPointer const & accountInfo, int row)
{
gpointer view = g_object_new(PROFILE_VIEW_TYPE, NULL);
auto* priv = PROFILE_VIEW_GET_PRIVATE(view);
priv->accountInfo_ = &accountInfo;
priv->row_ = row;
if (!build_view(PROFILE_VIEW(view))) return nullptr;
auto provider = gtk_css_provider_new();
std::string css = ".bestname { font-size: 3em; font-weight: 100; }";
css += ".section_title { font-size: 1.2em; font-weight: bold; }";
css += ".sub_section_title { font-size: 1.2em; opacity: 0.7; }";
css += ".value { font-size: 1.2em; }";
css += ".empty { font-size: 1.2em; font-style: italic; opacity: 0.7; }";
gtk_css_provider_load_from_data(provider, css.c_str(), -1, nullptr);
gtk_style_context_add_provider_for_screen(gdk_display_get_default_screen(gdk_display_get_default()),
GTK_STYLE_PROVIDER(provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
return static_cast<GtkWidget*>(view);
}
\ No newline at end of file
/*
* Copyright (C) 2019 Savoir-faire Linux Inc.
* Author: Sebastien Blin <sebastien.blin@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.
*/
#pragma once
#include <gtk/gtk.h>
#include "accountinfopointer.h"
#define PROFILE_VIEW_TYPE (profile_view_get_type ())
#define IS_PROFILE_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), PROFILE_VIEW_TYPE))
#define IS_PROFILE_VIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), PROFILE_VIEW_TYPE))
G_DECLARE_FINAL_TYPE (ProfileView, profile_view, PROFILE, VIEW, GtkDialog)
GtkWidget* profile_view_new(AccountInfoPointer const & accountInfo, int row_);
\ No newline at end of file
......@@ -20,10 +20,10 @@
*/
#include "ringwelcomeview.h"
#include "utils/drawing.h"
#include <gtk/gtk.h>
#include <glib/gi18n.h>
#include <qrencode.h>
#include <api/newaccountmodel.h>
......@@ -61,7 +61,7 @@ G_DEFINE_TYPE_WITH_PRIVATE(RingWelcomeView, ring_welcome_view, GTK_TYPE_SCROLLED
#define RING_WELCOME_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), RING_WELCOME_VIEW_TYPE, RingWelcomeViewPrivate))
static gboolean draw_qrcode(GtkWidget*,cairo_t*,RingWelcomeView*);
static gboolean draw_qr_event(GtkWidget*,cairo_t*,RingWelcomeView*);
static void switch_qrcode(RingWelcomeView* self);
void
......@@ -214,7 +214,7 @@ ring_welcome_view_init(RingWelcomeView *self)
auto drawingarea_qrcode = gtk_drawing_area_new();
auto qrsize = 200;
gtk_widget_set_size_request(drawingarea_qrcode, qrsize, qrsize);
g_signal_connect(drawingarea_qrcode, "draw", G_CALLBACK(draw_qrcode), self);
g_signal_connect(drawingarea_qrcode, "draw", G_CALLBACK(draw_qr_event), self);
gtk_widget_set_visible(drawingarea_qrcode, TRUE);
/* revealer which will show the qr code */
......@@ -284,51 +284,13 @@ ring_welcome_view_new(AccountInfoPointer const & accountInfo)
static gboolean
draw_qrcode(G_GNUC_UNUSED GtkWidget* diese,
draw_qr_event(G_GNUC_UNUSED GtkWidget* diese,
cairo_t* cr,
RingWelcomeView* self)
{
auto priv = RING_WELCOME_VIEW_GET_PRIVATE(self);
auto rcode = QRcode_encodeString((*priv->accountInfo_)->profileInfo.uri.c_str(),
0, //Let the version be decided by libqrencode
QR_ECLEVEL_L, // Lowest level of error correction
QR_MODE_8, // 8-bit data mode
1);
if (!rcode) { // no rcode, no draw
g_warning("Failed to generate QR code");
return FALSE;
}
auto margin = 5;
auto qrsize = 200;
int qrwidth = rcode->width + margin * 2;
/* scaling */
auto scale = qrsize/qrwidth;
cairo_scale(cr, scale, scale);
/* fill the background in white */
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_rectangle(cr, 0, 0, qrwidth, qrwidth);
cairo_fill (cr);
unsigned char *p;
p = rcode->data;
cairo_set_source_rgb(cr, 0, 0, 0); // back in black
for(int y = 0; y < rcode->width; y++) {
unsigned char* row = (p + (y * rcode->width));
for(int x = 0; x < rcode->width; x++) {
if(*(row + x) & 0x1) {
cairo_rectangle(cr, margin + x, margin + y, 1, 1);
cairo_fill(cr);
}
}
}
QRcode_free(rcode);
return TRUE;
g_return_val_if_fail(priv, false);
return draw_qrcode(cr, (*priv->accountInfo_)->profileInfo.uri.c_str(), 200);
}
static void
......
......@@ -22,6 +22,7 @@
#include <gtk/gtk.h>
#include <math.h>
#include <algorithm>
#include <qrencode.h>
static constexpr const char* MSG_COUNT_FONT = "Sans";
static constexpr int MSG_COUNT_FONT_SIZE = 12;
......@@ -205,6 +206,49 @@ create_rounded_rectangle_path(cairo_t *cr, double corner_radius, double x, doubl
cairo_close_path (cr);
}
gboolean
draw_qrcode(cairo_t* cr, const std::string& to_encode, uint32_t size)
{
auto rcode = QRcode_encodeString(to_encode.c_str(),
0, //Let the version be decided by libqrencode
QR_ECLEVEL_L, // Lowest level of error correction
QR_MODE_8, // 8-bit data mode
1);
if (!rcode) { // no rcode, no draw
g_warning("Failed to generate QR code");
return FALSE;
}
auto margin = 5;
int qrwidth = rcode->width + margin * 2;
/* scaling */
auto scale = size/qrwidth;
cairo_scale(cr, scale, scale);
/* fill the background in white */
cairo_set_source_rgb(cr, 1, 1, 1);
cairo_rectangle(cr, 0, 0, qrwidth, qrwidth);
cairo_fill (cr);
unsigned char *p;
p = rcode->data;
cairo_set_source_rgb(cr, 0, 0, 0); // back in black
for(int y = 0; y < rcode->width; y++) {
unsigned char* row = (p + (y * rcode->width));
for(int x = 0; x < rcode->width; x++) {
if(*(row + x) & 0x1) {
cairo_rectangle(cr, margin + x, margin + y, 1, 1);
cairo_fill(cr);
}
}
}
QRcode_free(rcode);
return TRUE;
}
/**
* Draws the presence icon in the top right corner of the given image.
*/
......
......@@ -31,6 +31,8 @@ GdkPixbuf *ring_frame_avatar(GdkPixbuf *avatar);
GdkPixbuf *ring_draw_unread_messages(const GdkPixbuf *avatar, int unread_count);
gboolean draw_qrcode(cairo_t* cr, const std::string& to_encode, uint32_t size);
enum class IconStatus {
ABSENT,
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.8"/>
<template class="ProfileView" parent="GtkDialog">
<property name="can_focus">False</property>
<property name="title" translatable="yes">Profile</property>
<property name="resizable">False</property>
<property name="modal">True</property>
<property name="type_hint">normal</property>
<child>
<placeholder/>
</child>
<child internal-child="vbox">
<object class="GtkBox" id="vbox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="grid_infos">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="valign">center</property>
<property name="margin_left">64</property>
<property name="margin_right">64</property>
<property name="margin_top">32</property>
<property name="margin_bottom">32</property>
<property name="row_spacing">12</property>
<property name="column_spacing">24</property>
<child>
<object class="GtkImage" id="avatar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_bottom">32</property>
<property name="stock">gtk-missing-image</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_top">12</property>
<property name="margin_bottom">12</property>
<property name="label" translatable="yes">Informations</property>
<style>
<class name="section_title"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Username</property>
<style>
<class name="sub_section_title"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">ID</property>
<style>
<class name="sub_section_title"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="best_name_label">
<property name="name">1</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="margin_bottom">32</property>
<property name="selectable">True</property>
<property name="ellipsize">end</property>
<property name="max_width_chars">24</property>
<style>
<class name="bestname"/>
</style>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="username_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="selectable">True</property>
<style>
<class name="value"/>
</style>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="id_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="selectable">True</property>
<style>
<class name="value"/>
</style>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="valign">start</property>
<property name="label" translatable="yes">QR Code</property>
<style>
<class name="sub_section_title"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkImage" id="qr_image">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
</child>
</template>
</interface>
......@@ -16,5 +16,6 @@
<file preprocess="xml-stripblanks">webkitchatcontainer.ui</file>
<file preprocess="xml-stripblanks">usernameregistrationbox.ui</file>
<file preprocess="xml-stripblanks">help-overlay.ui</file>
<file preprocess="xml-stripblanks">profile.ui</file>
</gresource>
</gresources>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment