search: Allow searching for people in overview mode
This adds contacts search to shell, powered by libfolks. Changes: - Add Folks and Gee to the build system - ShellContactSystem, a backend in C - ContactDisplay, search frontend in JS https://bugzilla.gnome.org/show_bug.cgi?id=643018
This commit is contained in:
parent
81cee34c17
commit
352fb7b833
@ -68,6 +68,7 @@ CLUTTER_MIN_VERSION=1.7.5
|
|||||||
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
|
GOBJECT_INTROSPECTION_MIN_VERSION=0.10.1
|
||||||
GJS_MIN_VERSION=1.29.15
|
GJS_MIN_VERSION=1.29.15
|
||||||
MUTTER_MIN_VERSION=3.0.0
|
MUTTER_MIN_VERSION=3.0.0
|
||||||
|
FOLKS_MIN_VERSION=0.5.2
|
||||||
GTK_MIN_VERSION=3.0.0
|
GTK_MIN_VERSION=3.0.0
|
||||||
GIO_MIN_VERSION=2.29.10
|
GIO_MIN_VERSION=2.29.10
|
||||||
LIBECAL_MIN_VERSION=2.32.0
|
LIBECAL_MIN_VERSION=2.32.0
|
||||||
@ -82,6 +83,7 @@ STARTUP_NOTIFICATION_MIN_VERSION=0.11
|
|||||||
PKG_CHECK_MODULES(GNOME_SHELL, gio-2.0 >= $GIO_MIN_VERSION
|
PKG_CHECK_MODULES(GNOME_SHELL, gio-2.0 >= $GIO_MIN_VERSION
|
||||||
gio-unix-2.0 dbus-glib-1 libxml-2.0
|
gio-unix-2.0 dbus-glib-1 libxml-2.0
|
||||||
gtk+-3.0 >= $GTK_MIN_VERSION
|
gtk+-3.0 >= $GTK_MIN_VERSION
|
||||||
|
folks >= $FOLKS_MIN_VERSION
|
||||||
libmutter >= $MUTTER_MIN_VERSION
|
libmutter >= $MUTTER_MIN_VERSION
|
||||||
gjs-internals-1.0 >= $GJS_MIN_VERSION
|
gjs-internals-1.0 >= $GJS_MIN_VERSION
|
||||||
libgnome-menu-3.0 $recorder_modules gconf-2.0
|
libgnome-menu-3.0 $recorder_modules gconf-2.0
|
||||||
|
@ -673,6 +673,11 @@ StTooltip StLabel {
|
|||||||
-shell-grid-item-size: 118px;
|
-shell-grid-item-size: 118px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact-grid {
|
||||||
|
spacing: 36px;
|
||||||
|
-shell-grid-item-size: 272px; /* 2 * -shell-grid-item-size + spacing */
|
||||||
|
}
|
||||||
|
|
||||||
.icon-grid .overview-icon {
|
.icon-grid .overview-icon {
|
||||||
icon-size: 96px;
|
icon-size: 96px;
|
||||||
}
|
}
|
||||||
@ -739,11 +744,57 @@ StTooltip StLabel {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact {
|
||||||
|
width: 272px; /* Same width as two normal results + spacing */
|
||||||
|
height: 118px; /* Aspect ratio = 1.75. Normal US business card ratio */
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 3px;
|
||||||
|
border: 1px rgba(0,0,0,0);
|
||||||
|
transition-duration: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-content {
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 8px;
|
||||||
|
width: 232px;
|
||||||
|
height: 84px;
|
||||||
|
background-color: white;
|
||||||
|
color: black;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-icon {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-details {
|
||||||
|
padding: 6px 8px 11px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-details-alias {
|
||||||
|
font-size: 16px;
|
||||||
|
padding-bottom: 11px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-details-status {
|
||||||
|
font-size: 11pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-details-status-icon {
|
||||||
|
padding-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact:hover {
|
||||||
|
background-color: rgba(255,255,255,0.1);
|
||||||
|
transition-duration: 100;
|
||||||
|
}
|
||||||
|
|
||||||
.app-well-app.running > .overview-icon {
|
.app-well-app.running > .overview-icon {
|
||||||
text-shadow: black 0px 2px 2px;
|
text-shadow: black 0px 2px 2px;
|
||||||
background-image: url("running-indicator.svg");
|
background-image: url("running-indicator.svg");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact:selected,
|
||||||
.app-well-app:selected > .overview-icon,
|
.app-well-app:selected > .overview-icon,
|
||||||
.search-result-content:selected > .overview-icon {
|
.search-result-content:selected > .overview-icon {
|
||||||
background-color: rgba(255,255,255,0.33);
|
background-color: rgba(255,255,255,0.33);
|
||||||
@ -757,6 +808,7 @@ StTooltip StLabel {
|
|||||||
transition-duration: 100;
|
transition-duration: 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.contact:focus,
|
||||||
.app-well-app:focus > .overview-icon,
|
.app-well-app:focus > .overview-icon,
|
||||||
.search-result-content:focus > .overview-icon {
|
.search-result-content:focus > .overview-icon {
|
||||||
border: 1px solid #cccccc;
|
border: 1px solid #cccccc;
|
||||||
|
@ -22,6 +22,7 @@ nobase_dist_js_DATA = \
|
|||||||
ui/autorunManager.js \
|
ui/autorunManager.js \
|
||||||
ui/boxpointer.js \
|
ui/boxpointer.js \
|
||||||
ui/calendar.js \
|
ui/calendar.js \
|
||||||
|
ui/contactDisplay.js \
|
||||||
ui/ctrlAltTab.js \
|
ui/ctrlAltTab.js \
|
||||||
ui/dash.js \
|
ui/dash.js \
|
||||||
ui/dateMenu.js \
|
ui/dateMenu.js \
|
||||||
|
179
js/ui/contactDisplay.js
Normal file
179
js/ui/contactDisplay.js
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
||||||
|
|
||||||
|
const Folks = imports.gi.Folks
|
||||||
|
const Lang = imports.lang;
|
||||||
|
const Meta = imports.gi.Meta;
|
||||||
|
const Shell = imports.gi.Shell;
|
||||||
|
const St = imports.gi.St;
|
||||||
|
|
||||||
|
const Util = imports.misc.util;
|
||||||
|
const IconGrid = imports.ui.iconGrid;
|
||||||
|
const Search = imports.ui.search;
|
||||||
|
const SearchDisplay = imports.ui.searchDisplay;
|
||||||
|
|
||||||
|
const MAX_SEARCH_RESULTS_ROWS = 1;
|
||||||
|
const ICON_SIZE = 81;
|
||||||
|
|
||||||
|
function launchContact(id) {
|
||||||
|
Util.spawn(['gnome-contacts', '-i', id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* This class represents a shown contact search result in the overview */
|
||||||
|
function Contact(id) {
|
||||||
|
this._init(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Contact.prototype = {
|
||||||
|
_init: function(id) {
|
||||||
|
this.individual = Shell.ContactSystem.get_default().get_individual(id);
|
||||||
|
|
||||||
|
this.actor = new St.Bin({ style_class: 'contact',
|
||||||
|
reactive: true,
|
||||||
|
track_hover: true });
|
||||||
|
|
||||||
|
let content = new St.BoxLayout( { style_class: 'contact-content',
|
||||||
|
vertical: false });
|
||||||
|
this.actor.set_child(content);
|
||||||
|
|
||||||
|
let icon = new St.Icon({ icon_type: St.IconType.FULLCOLOR,
|
||||||
|
icon_size: ICON_SIZE,
|
||||||
|
style_class: 'contact-icon' });
|
||||||
|
if (this.individual.avatar != null)
|
||||||
|
icon.gicon = this.individual.avatar;
|
||||||
|
else
|
||||||
|
icon.icon_name = 'avatar-default';
|
||||||
|
|
||||||
|
content.add(icon, { x_fill: true,
|
||||||
|
y_fill: false,
|
||||||
|
x_align: St.Align.START,
|
||||||
|
y_align: St.Align.MIDDLE });
|
||||||
|
|
||||||
|
let details = new St.BoxLayout({ style_class: 'contact-details',
|
||||||
|
vertical: true });
|
||||||
|
content.add(details, { x_fill: true,
|
||||||
|
y_fill: false,
|
||||||
|
x_align: St.Align.START,
|
||||||
|
y_align: St.Align.MIDDLE });
|
||||||
|
|
||||||
|
let aliasText = this.individual.alias || _("Unknown");
|
||||||
|
let aliasLabel = new St.Label({ text: aliasText,
|
||||||
|
style_class: 'contact-details-alias' });
|
||||||
|
details.add(aliasLabel, { x_fill: true,
|
||||||
|
y_fill: false,
|
||||||
|
x_align: St.Align.START,
|
||||||
|
y_align: St.Align.START });
|
||||||
|
|
||||||
|
let presence = this._createPresence(this.individual.presence_type);
|
||||||
|
details.add(presence, { x_fill: false,
|
||||||
|
y_fill: true,
|
||||||
|
x_align: St.Align.START,
|
||||||
|
y_align: St.Align.END });
|
||||||
|
},
|
||||||
|
|
||||||
|
_createPresence: function(presence) {
|
||||||
|
let text;
|
||||||
|
let iconName;
|
||||||
|
|
||||||
|
switch(presence) {
|
||||||
|
case Folks.PresenceType.AVAILABLE:
|
||||||
|
text = _("Available");
|
||||||
|
iconName = 'user-available';
|
||||||
|
break;
|
||||||
|
case Folks.PresenceType.AWAY:
|
||||||
|
case Folks.PresenceType.EXTENDED_AWAY:
|
||||||
|
text = _("Away");
|
||||||
|
iconName = 'user-away';
|
||||||
|
break;
|
||||||
|
case Folks.PresenceType.BUSY:
|
||||||
|
text = _("Busy");
|
||||||
|
iconName = 'user-busy';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
text = _("Offline");
|
||||||
|
iconName = 'user-offline';
|
||||||
|
}
|
||||||
|
|
||||||
|
let icon = new St.Icon({ icon_name: iconName,
|
||||||
|
icon_type: St.IconType.FULLCOLOR,
|
||||||
|
icon_size: 16,
|
||||||
|
style_class: 'contact-details-status-icon' });
|
||||||
|
let label = new St.Label({ text: text });
|
||||||
|
|
||||||
|
let box = new St.BoxLayout({ vertical: false,
|
||||||
|
style_class: 'contact-details-status' });
|
||||||
|
box.add(icon, { x_fill: true,
|
||||||
|
y_fill: false,
|
||||||
|
x_align: St.Align.START,
|
||||||
|
y_align: St.Align.START });
|
||||||
|
|
||||||
|
box.add(label, { x_fill: true,
|
||||||
|
y_fill: false,
|
||||||
|
x_align: St.Align.END,
|
||||||
|
y_align: St.Align.START });
|
||||||
|
|
||||||
|
return box;
|
||||||
|
},
|
||||||
|
|
||||||
|
createIcon: function(size) {
|
||||||
|
let tc = St.TextureCache.get_default();
|
||||||
|
let icon = this.individual.avatar;
|
||||||
|
|
||||||
|
if (icon != null) {
|
||||||
|
return tc.load_gicon(null, icon, size);
|
||||||
|
} else {
|
||||||
|
return tc.load_icon_name(null, 'avatar-default', St.IconType.FULLCOLOR, size);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/* Searches for and returns contacts */
|
||||||
|
function ContactSearchProvider() {
|
||||||
|
this._init();
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactSearchProvider.prototype = {
|
||||||
|
__proto__: Search.SearchProvider.prototype,
|
||||||
|
|
||||||
|
_init: function() {
|
||||||
|
Search.SearchProvider.prototype._init.call(this, _("CONTACTS"));
|
||||||
|
this._contactSys = Shell.ContactSystem.get_default();
|
||||||
|
},
|
||||||
|
|
||||||
|
getResultMeta: function(id) {
|
||||||
|
let contact = new Contact(id);
|
||||||
|
return { 'id': id,
|
||||||
|
'name': contact.alias,
|
||||||
|
'createIcon': function(size) {
|
||||||
|
return contact.createIcon(size);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialResultSet: function(terms) {
|
||||||
|
return this._contactSys.initial_search(terms);
|
||||||
|
},
|
||||||
|
|
||||||
|
getSubsearchResultSet: function(previousResults, terms) {
|
||||||
|
return this._contactSys.subsearch(previousResults, terms);
|
||||||
|
},
|
||||||
|
|
||||||
|
createResultActor: function(resultMeta, terms) {
|
||||||
|
let contact = new Contact(resultMeta.id);
|
||||||
|
return contact.actor;
|
||||||
|
},
|
||||||
|
|
||||||
|
createResultContainerActor: function() {
|
||||||
|
let grid = new IconGrid.IconGrid({ rowLimit: MAX_SEARCH_RESULTS_ROWS,
|
||||||
|
xAlign: St.Align.START });
|
||||||
|
grid.actor.style_class = 'contact-grid';
|
||||||
|
|
||||||
|
let actor = new SearchDisplay.GridSearchResults(this, grid);
|
||||||
|
return actor;
|
||||||
|
},
|
||||||
|
|
||||||
|
activateResult: function(id, params) {
|
||||||
|
launchContact(id);
|
||||||
|
}
|
||||||
|
};
|
@ -11,6 +11,7 @@ const Shell = imports.gi.Shell;
|
|||||||
const Gdk = imports.gi.Gdk;
|
const Gdk = imports.gi.Gdk;
|
||||||
|
|
||||||
const AppDisplay = imports.ui.appDisplay;
|
const AppDisplay = imports.ui.appDisplay;
|
||||||
|
const ContactDisplay = imports.ui.contactDisplay;
|
||||||
const Dash = imports.ui.dash;
|
const Dash = imports.ui.dash;
|
||||||
const DND = imports.ui.dnd;
|
const DND = imports.ui.dnd;
|
||||||
const DocDisplay = imports.ui.docDisplay;
|
const DocDisplay = imports.ui.docDisplay;
|
||||||
@ -211,6 +212,7 @@ Overview.prototype = {
|
|||||||
this._viewSelector.addSearchProvider(new AppDisplay.SettingsSearchProvider());
|
this._viewSelector.addSearchProvider(new AppDisplay.SettingsSearchProvider());
|
||||||
this._viewSelector.addSearchProvider(new PlaceDisplay.PlaceSearchProvider());
|
this._viewSelector.addSearchProvider(new PlaceDisplay.PlaceSearchProvider());
|
||||||
this._viewSelector.addSearchProvider(new DocDisplay.DocSearchProvider());
|
this._viewSelector.addSearchProvider(new DocDisplay.DocSearchProvider());
|
||||||
|
this._viewSelector.addSearchProvider(new ContactDisplay.ContactSearchProvider());
|
||||||
|
|
||||||
// TODO - recalculate everything when desktop size changes
|
// TODO - recalculate everything when desktop size changes
|
||||||
this._dash = new Dash.Dash();
|
this._dash = new Dash.Dash();
|
||||||
|
@ -102,6 +102,7 @@ shell_public_headers_h = \
|
|||||||
shell-app-system.h \
|
shell-app-system.h \
|
||||||
shell-app-usage.h \
|
shell-app-usage.h \
|
||||||
shell-arrow.h \
|
shell-arrow.h \
|
||||||
|
shell-contact-system.h \
|
||||||
shell-doc-system.h \
|
shell-doc-system.h \
|
||||||
shell-embedded-window.h \
|
shell-embedded-window.h \
|
||||||
shell-generic-container.h \
|
shell-generic-container.h \
|
||||||
@ -137,6 +138,7 @@ libgnome_shell_la_SOURCES = \
|
|||||||
shell-app-system.c \
|
shell-app-system.c \
|
||||||
shell-app-usage.c \
|
shell-app-usage.c \
|
||||||
shell-arrow.c \
|
shell-arrow.c \
|
||||||
|
shell-contact-system.c \
|
||||||
shell-doc-system.c \
|
shell-doc-system.c \
|
||||||
shell-embedded-window.c \
|
shell-embedded-window.c \
|
||||||
shell-generic-container.c \
|
shell-generic-container.c \
|
||||||
@ -269,7 +271,7 @@ libgnome_shell_la_LIBADD = \
|
|||||||
libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags)
|
libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags)
|
||||||
|
|
||||||
Shell-0.1.gir: libgnome-shell.la St-1.0.gir
|
Shell-0.1.gir: libgnome-shell.la St-1.0.gir
|
||||||
Shell_0_1_gir_INCLUDES = Clutter-1.0 ClutterX11-1.0 Meta-3.0 TelepathyGLib-0.12 TelepathyLogger-0.2 Soup-2.4 GMenu-3.0 NetworkManager-1.0 NMClient-1.0
|
Shell_0_1_gir_INCLUDES = Clutter-1.0 ClutterX11-1.0 Meta-3.0 TelepathyGLib-0.12 TelepathyLogger-0.2 Soup-2.4 GMenu-3.0 NetworkManager-1.0 NMClient-1.0 Folks-0.6
|
||||||
Shell_0_1_gir_CFLAGS = $(libgnome_shell_la_CPPFLAGS) -I $(srcdir)
|
Shell_0_1_gir_CFLAGS = $(libgnome_shell_la_CPPFLAGS) -I $(srcdir)
|
||||||
Shell_0_1_gir_LIBS = libgnome-shell.la
|
Shell_0_1_gir_LIBS = libgnome-shell.la
|
||||||
Shell_0_1_gir_FILES = $(libgnome_shell_la_gir_sources)
|
Shell_0_1_gir_FILES = $(libgnome_shell_la_gir_sources)
|
||||||
|
350
src/shell-contact-system.c
Normal file
350
src/shell-contact-system.c
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
/* This implements a complete suite for caching and searching contacts in the
|
||||||
|
* Shell. We retrieve contacts from libfolks asynchronously and we search
|
||||||
|
* these for display to the user. */
|
||||||
|
|
||||||
|
#include "shell-contact-system.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glib/gprintf.h>
|
||||||
|
#include <gee.h>
|
||||||
|
#include <clutter/clutter.h>
|
||||||
|
#include <folks/folks.h>
|
||||||
|
|
||||||
|
#include "shell-global.h"
|
||||||
|
#include "shell-util.h"
|
||||||
|
#include "st.h"
|
||||||
|
|
||||||
|
G_DEFINE_TYPE (ShellContactSystem, shell_contact_system, G_TYPE_OBJECT);
|
||||||
|
|
||||||
|
#define ALIAS_PREFIX_MATCH_WEIGHT 100
|
||||||
|
#define ALIAS_SUBSTRING_MATCH_WEIGHT 90
|
||||||
|
#define IM_PREFIX_MATCH_WEIGHT 10
|
||||||
|
#define IM_SUBSTRING_MATCH_WEIGHT 5
|
||||||
|
|
||||||
|
|
||||||
|
/* Callbacks */
|
||||||
|
|
||||||
|
static void
|
||||||
|
prepare_individual_aggregator_cb (GObject *obj,
|
||||||
|
GAsyncResult *res,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
FolksIndividualAggregator *aggregator = FOLKS_INDIVIDUAL_AGGREGATOR (obj);
|
||||||
|
|
||||||
|
folks_individual_aggregator_prepare_finish (aggregator, res, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Internal stuff */
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
gchar *key;
|
||||||
|
guint weight;
|
||||||
|
} ContactSearchResult;
|
||||||
|
|
||||||
|
struct _ShellContactSystemPrivate {
|
||||||
|
FolksIndividualAggregator *aggregator;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
shell_contact_system_constructed (GObject *obj)
|
||||||
|
{
|
||||||
|
ShellContactSystem *self = SHELL_CONTACT_SYSTEM (obj);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (shell_contact_system_parent_class)->constructed (obj);
|
||||||
|
|
||||||
|
/* We intentionally do not care about the "individuals-changed" signal, as
|
||||||
|
* we don't intend to update searches after they've been performed.
|
||||||
|
* Therefore, we will simply retrieve the "individuals" property which
|
||||||
|
* represents a snapshot of the individuals in the aggregator.
|
||||||
|
*/
|
||||||
|
self->priv->aggregator = folks_individual_aggregator_new ();
|
||||||
|
folks_individual_aggregator_prepare (self->priv->aggregator, prepare_individual_aggregator_cb, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
shell_contact_system_finalize (GObject *obj)
|
||||||
|
{
|
||||||
|
ShellContactSystem *self = SHELL_CONTACT_SYSTEM (obj);
|
||||||
|
|
||||||
|
g_object_unref (self->priv->aggregator);
|
||||||
|
|
||||||
|
G_OBJECT_CLASS (shell_contact_system_parent_class)->finalize (obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
shell_contact_system_init (ShellContactSystem *self)
|
||||||
|
{
|
||||||
|
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, SHELL_TYPE_CONTACT_SYSTEM, ShellContactSystemPrivate);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
shell_contact_system_class_init (ShellContactSystemClass *klass)
|
||||||
|
{
|
||||||
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||||
|
|
||||||
|
object_class->constructed = shell_contact_system_constructed;
|
||||||
|
object_class->finalize = shell_contact_system_finalize;
|
||||||
|
|
||||||
|
g_type_class_add_private (object_class, sizeof (ShellContactSystemPrivate));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* normalize_terms:
|
||||||
|
* @terms: (element-type utf8): Input search terms
|
||||||
|
*
|
||||||
|
* Returns: (element-type utf8) (transfer full): Unicode-normalized and lowercased terms
|
||||||
|
*/
|
||||||
|
static GSList *
|
||||||
|
normalize_terms (GSList *terms)
|
||||||
|
{
|
||||||
|
GSList *normalized_terms = NULL;
|
||||||
|
GSList *iter;
|
||||||
|
for (iter = terms; iter; iter = iter->next)
|
||||||
|
{
|
||||||
|
const char *term = iter->data;
|
||||||
|
normalized_terms = g_slist_prepend (normalized_terms, shell_util_normalize_and_casefold (term));
|
||||||
|
}
|
||||||
|
return normalized_terms;
|
||||||
|
}
|
||||||
|
|
||||||
|
static guint
|
||||||
|
do_match (ShellContactSystem *self,
|
||||||
|
FolksIndividual *individual,
|
||||||
|
GSList *terms)
|
||||||
|
{
|
||||||
|
GSList *term_iter;
|
||||||
|
guint weight = 0;
|
||||||
|
|
||||||
|
char *alias = shell_util_normalize_and_casefold (folks_alias_details_get_alias (FOLKS_ALIAS_DETAILS (individual)));
|
||||||
|
|
||||||
|
GeeMultiMap *im_addr_map = folks_im_details_get_im_addresses (FOLKS_IM_DETAILS (individual));
|
||||||
|
GeeCollection *im_addrs = gee_multi_map_get_values (im_addr_map);
|
||||||
|
GeeIterator *im_addrs_iter;
|
||||||
|
|
||||||
|
gboolean have_alias_prefix = FALSE;
|
||||||
|
gboolean have_alias_substring = FALSE;
|
||||||
|
|
||||||
|
gboolean have_im_prefix = FALSE;
|
||||||
|
gboolean have_im_substring = FALSE;
|
||||||
|
|
||||||
|
for (term_iter = terms; term_iter; term_iter = term_iter->next)
|
||||||
|
{
|
||||||
|
const char *term = term_iter->data;
|
||||||
|
const char *p;
|
||||||
|
|
||||||
|
/* Match on alias */
|
||||||
|
p = strstr (alias, term);
|
||||||
|
if (p == alias)
|
||||||
|
have_alias_prefix = TRUE;
|
||||||
|
else if (p != NULL)
|
||||||
|
have_alias_substring = TRUE;
|
||||||
|
|
||||||
|
/* Match on one or more IM addresses */
|
||||||
|
im_addrs_iter = gee_iterable_iterator (GEE_ITERABLE (im_addrs));
|
||||||
|
|
||||||
|
while (gee_iterator_next (im_addrs_iter))
|
||||||
|
{
|
||||||
|
const gchar *addr = gee_iterator_get (im_addrs_iter);
|
||||||
|
|
||||||
|
p = strstr (addr, term);
|
||||||
|
if (p == addr)
|
||||||
|
have_im_prefix = TRUE;
|
||||||
|
else if (p != NULL)
|
||||||
|
have_im_substring = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_unref (im_addrs_iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (have_alias_prefix)
|
||||||
|
weight += ALIAS_PREFIX_MATCH_WEIGHT;
|
||||||
|
else if (have_alias_substring)
|
||||||
|
weight += ALIAS_SUBSTRING_MATCH_WEIGHT;
|
||||||
|
|
||||||
|
if (have_im_prefix)
|
||||||
|
weight += IM_PREFIX_MATCH_WEIGHT;
|
||||||
|
else if (have_im_substring)
|
||||||
|
weight += IM_SUBSTRING_MATCH_WEIGHT;
|
||||||
|
|
||||||
|
g_free (alias);
|
||||||
|
g_object_unref (im_addrs);
|
||||||
|
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gint
|
||||||
|
compare_results (gconstpointer a,
|
||||||
|
gconstpointer b)
|
||||||
|
{
|
||||||
|
ContactSearchResult *first = (ContactSearchResult *) a;
|
||||||
|
ContactSearchResult *second = (ContactSearchResult *) b;
|
||||||
|
|
||||||
|
if (first->weight > second->weight)
|
||||||
|
return 1;
|
||||||
|
else if (first->weight < second->weight)
|
||||||
|
return -1;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
free_result (gpointer data,
|
||||||
|
gpointer user_data)
|
||||||
|
{
|
||||||
|
g_slice_free (ContactSearchResult, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* modifies and frees @results */
|
||||||
|
static GSList *
|
||||||
|
sort_and_prepare_results (GSList *results)
|
||||||
|
{
|
||||||
|
GSList *iter;
|
||||||
|
GSList *sorted_results = NULL;
|
||||||
|
|
||||||
|
results = g_slist_sort (results, compare_results);
|
||||||
|
|
||||||
|
for (iter = results; iter; iter = iter->next)
|
||||||
|
{
|
||||||
|
ContactSearchResult *result = iter->data;
|
||||||
|
gchar *id = result->key;
|
||||||
|
sorted_results = g_slist_prepend (sorted_results, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_slist_foreach (results, (GFunc) free_result, NULL);
|
||||||
|
|
||||||
|
return sorted_results;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Methods */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* shell_contact_system_get_default:
|
||||||
|
*
|
||||||
|
* Return Value: (transfer none): The global #ShellContactSystem singleton
|
||||||
|
*/
|
||||||
|
ShellContactSystem *
|
||||||
|
shell_contact_system_get_default (void)
|
||||||
|
{
|
||||||
|
static ShellContactSystem *instance = NULL;
|
||||||
|
|
||||||
|
if (instance == NULL)
|
||||||
|
instance = g_object_new (SHELL_TYPE_CONTACT_SYSTEM, NULL);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* shell_contact_system_get_all:
|
||||||
|
* @self: A #ShellContactSystem
|
||||||
|
*
|
||||||
|
* Returns: (transfer none): All individuals
|
||||||
|
*/
|
||||||
|
GeeMap *
|
||||||
|
shell_contact_system_get_all (ShellContactSystem *self)
|
||||||
|
{
|
||||||
|
GeeMap *individuals;
|
||||||
|
|
||||||
|
g_return_val_if_fail (SHELL_IS_CONTACT_SYSTEM (self), NULL);
|
||||||
|
|
||||||
|
individuals = folks_individual_aggregator_get_individuals (self->priv->aggregator);
|
||||||
|
|
||||||
|
return individuals;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* shell_contact_system_get_individual:
|
||||||
|
* @self: A #ShellContactSystem
|
||||||
|
* @id: A #gchar with the ID of the FolksIndividual to be returned.
|
||||||
|
*
|
||||||
|
* Returns: (transfer full): A #FolksIndividual or NULL if @id could not be found.
|
||||||
|
*/
|
||||||
|
FolksIndividual *
|
||||||
|
shell_contact_system_get_individual (ShellContactSystem *self,
|
||||||
|
gchar *id)
|
||||||
|
{
|
||||||
|
GeeMap *individuals;
|
||||||
|
gpointer key, value;
|
||||||
|
|
||||||
|
key = (gpointer) id;
|
||||||
|
|
||||||
|
g_return_val_if_fail (SHELL_IS_CONTACT_SYSTEM (self), NULL);
|
||||||
|
|
||||||
|
individuals = folks_individual_aggregator_get_individuals (self->priv->aggregator);
|
||||||
|
|
||||||
|
value = gee_map_get (individuals, key);
|
||||||
|
|
||||||
|
return FOLKS_INDIVIDUAL (value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* shell_contact_system_initial_search:
|
||||||
|
* @shell: A #ShellContactSystem
|
||||||
|
* @terms: (element-type utf8): List of terms, logical AND
|
||||||
|
*
|
||||||
|
* Search through contacts for the given search terms.
|
||||||
|
*
|
||||||
|
* Returns: (transfer container) (element-type utf8): List of contact
|
||||||
|
* identifiers
|
||||||
|
*/
|
||||||
|
GSList *
|
||||||
|
shell_contact_system_initial_search (ShellContactSystem *self,
|
||||||
|
GSList *terms)
|
||||||
|
{
|
||||||
|
FolksIndividual *individual;
|
||||||
|
GSList *results = NULL;
|
||||||
|
GeeMap *individuals = NULL;
|
||||||
|
ContactSearchResult *result;
|
||||||
|
GeeMapIterator *iter;
|
||||||
|
gpointer key;
|
||||||
|
guint weight;
|
||||||
|
GSList *normalized_terms = normalize_terms (terms);
|
||||||
|
|
||||||
|
g_return_val_if_fail (SHELL_IS_CONTACT_SYSTEM (self), NULL);
|
||||||
|
|
||||||
|
individuals = folks_individual_aggregator_get_individuals (self->priv->aggregator);
|
||||||
|
|
||||||
|
iter = gee_map_map_iterator (individuals);
|
||||||
|
|
||||||
|
while (gee_map_iterator_next (iter))
|
||||||
|
{
|
||||||
|
individual = gee_map_iterator_get_value (iter);
|
||||||
|
weight = do_match (self, individual, normalized_terms);
|
||||||
|
|
||||||
|
if (weight != 0)
|
||||||
|
{
|
||||||
|
key = gee_map_iterator_get_key (iter);
|
||||||
|
|
||||||
|
result = g_slice_new (ContactSearchResult);
|
||||||
|
result->key = (gchar *) key;
|
||||||
|
result->weight = weight;
|
||||||
|
|
||||||
|
results = g_slist_append (results, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_unref (individual);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sort_and_prepare_results (results);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* shell_contact_system_subsearch:
|
||||||
|
* @shell: A #ShellContactSystem
|
||||||
|
* @previous_results: (element-type utf8): List of previous results
|
||||||
|
* @terms: (element-type utf8): List of terms, logical AND
|
||||||
|
*
|
||||||
|
* Search through a previous result set; for more information see
|
||||||
|
* js/ui/search.js.
|
||||||
|
*
|
||||||
|
* Returns: (transfer container) (element-type utf8): List of contact
|
||||||
|
* identifiers
|
||||||
|
*/
|
||||||
|
GSList *
|
||||||
|
shell_contact_system_subsearch (ShellContactSystem *self,
|
||||||
|
GSList *previous_results,
|
||||||
|
GSList *terms)
|
||||||
|
{
|
||||||
|
return shell_contact_system_initial_search (self, terms);
|
||||||
|
}
|
50
src/shell-contact-system.h
Normal file
50
src/shell-contact-system.h
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
|
||||||
|
#ifndef __SHELL_CONTACT_SYSTEM_H__
|
||||||
|
#define __SHELL_CONTACT_SYSTEM_H__
|
||||||
|
|
||||||
|
#include <clutter/clutter.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <folks/folks.h>
|
||||||
|
|
||||||
|
#define SHELL_TYPE_CONTACT_SYSTEM (shell_contact_system_get_type ())
|
||||||
|
#define SHELL_CONTACT_SYSTEM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), SHELL_TYPE_CONTACT_SYSTEM, ShellContactSystem))
|
||||||
|
#define SHELL_CONTACT_SYSTEM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_CONTACT_SYSTEM, ShellContactSystemClass))
|
||||||
|
#define SHELL_IS_CONTACT_SYSTEM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SHELL_TYPE_CONTACT_SYSTEM))
|
||||||
|
#define SHELL_IS_CONTACT_SYSTEM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_CONTACT_SYSTEM))
|
||||||
|
#define SHELL_CONTACT_SYSTEM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_CONTACT_SYSTEM, ShellContactSystemClass))
|
||||||
|
|
||||||
|
typedef struct _ShellContactSystem ShellContactSystem;
|
||||||
|
typedef struct _ShellContactSystemClass ShellContactSystemClass;
|
||||||
|
typedef struct _ShellContactSystemPrivate ShellContactSystemPrivate;
|
||||||
|
|
||||||
|
struct _ShellContactSystem
|
||||||
|
{
|
||||||
|
GObject parent;
|
||||||
|
|
||||||
|
ShellContactSystemPrivate *priv;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _ShellContactSystemClass
|
||||||
|
{
|
||||||
|
GObjectClass parent_class;
|
||||||
|
};
|
||||||
|
|
||||||
|
GType shell_contact_system_get_type (void) G_GNUC_CONST;
|
||||||
|
|
||||||
|
/* Methods */
|
||||||
|
|
||||||
|
ShellContactSystem * shell_contact_system_get_default (void);
|
||||||
|
|
||||||
|
GeeMap *shell_contact_system_get_all (ShellContactSystem *self);
|
||||||
|
|
||||||
|
FolksIndividual *shell_contact_system_get_individual (ShellContactSystem *self,
|
||||||
|
gchar *id);
|
||||||
|
|
||||||
|
GSList * shell_contact_system_initial_search (ShellContactSystem *shell,
|
||||||
|
GSList *terms);
|
||||||
|
|
||||||
|
GSList * shell_contact_system_subsearch (ShellContactSystem *shell,
|
||||||
|
GSList *previous_results,
|
||||||
|
GSList *terms);
|
||||||
|
|
||||||
|
#endif /* __SHELL_CONTACT_SYSTEM_H__ */
|
@ -16,6 +16,38 @@
|
|||||||
<repository type="git" name="colord.gitorious.org"
|
<repository type="git" name="colord.gitorious.org"
|
||||||
href="git://gitorious.org/colord/master.git"/>
|
href="git://gitorious.org/colord/master.git"/>
|
||||||
|
|
||||||
|
<tarball id="libgee" version="0.6.0">
|
||||||
|
<pkg-config/>
|
||||||
|
<source hash="sha256:e586678d0a88637abeaaf850b62231000772e79ea6d9c4b45dc3cea99f778a7a" href="http://download.gnome.org/sources/libgee/0.6/libgee-0.6.0.tar.bz2" md5sum="4eb513b23ab6ea78884989518a4acf6f" size="477609"/>
|
||||||
|
<dependencies>
|
||||||
|
<dep package="glib"/>
|
||||||
|
</dependencies>
|
||||||
|
<suggests>
|
||||||
|
<dep package="gobject-introspection"/>
|
||||||
|
</suggests>
|
||||||
|
</tarball>
|
||||||
|
|
||||||
|
<tarball id="folks" version="0.6.0">
|
||||||
|
<source hash="sha256:50539da0b42564887bbce67d3a4107f3a7dbd8c6ae3b5d3a2d422b4f75a2b519" href="http://ftp.gnome.org/pub/GNOME/sources/folks/0.6/folks-0.6.0.tar.bz2" md5sum="4d5a3619c8b6e0714d02941c92826160" size="1261916"/>
|
||||||
|
<dependencies>
|
||||||
|
<dep package="libgee"/>
|
||||||
|
<dep package="vala"/>
|
||||||
|
<dep package="gobject-introspection"/>
|
||||||
|
<dep package="telepathy-glib"/>
|
||||||
|
<dep package="evolution-data-server"/>
|
||||||
|
</dependencies>
|
||||||
|
</tarball>
|
||||||
|
|
||||||
|
<tarball id="gnome-contacts">
|
||||||
|
<source hash="sha256:db52d4521e5d3335b5bf77ce5d1a7ec2cdea9ebf37ce12ddadda0503a2968cf9" href="http://ftp.gnome.org/pub/GNOME/sources/gnome-contacts/0.1/gnome-contacts-0.1.2.tar.bz2" size="377930"/>
|
||||||
|
<dependencies>
|
||||||
|
<dep package="folks"/>
|
||||||
|
<dep package="glib"/>
|
||||||
|
<dep package="gtk3"/>
|
||||||
|
<dep package="vala"/>
|
||||||
|
</dependencies>
|
||||||
|
</tarball>
|
||||||
|
|
||||||
<autotools id="gobject-introspection">
|
<autotools id="gobject-introspection">
|
||||||
<branch repo="git.gnome.org" module="gobject-introspection"/>
|
<branch repo="git.gnome.org" module="gobject-introspection"/>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
@ -320,8 +352,10 @@
|
|||||||
<dep package="gjs"/>
|
<dep package="gjs"/>
|
||||||
<dep package="caribou"/>
|
<dep package="caribou"/>
|
||||||
<dep package="dconf"/>
|
<dep package="dconf"/>
|
||||||
|
<dep package="folks"/>
|
||||||
<dep package="gconf"/>
|
<dep package="gconf"/>
|
||||||
<dep package="glib"/>
|
<dep package="glib"/>
|
||||||
|
<dep package="gnome-contacts"/>
|
||||||
<dep package="gnome-menus"/>
|
<dep package="gnome-menus"/>
|
||||||
<dep package="gnome-desktop-3"/>
|
<dep package="gnome-desktop-3"/>
|
||||||
<dep package="gsettings-desktop-schemas"/>
|
<dep package="gsettings-desktop-schemas"/>
|
||||||
|
Loading…
Reference in New Issue
Block a user