Merge lp:~sschuhmann/maya/guestview into lp:~elementary-apps/maya/trunk

Proposed by Steffen Schuhmann
Status: Merged
Approved by: Corentin Noël
Approved revision: 617
Merged at revision: 609
Proposed branch: lp:~sschuhmann/maya/guestview
Merge into: lp:~elementary-apps/maya/trunk
Diff against target: 471 lines (+282/-42)
4 files modified
CMakeLists.txt (+3/-1)
src/CMakeLists.txt (+2/-1)
src/EventEdition/ContactImage.vala (+125/-0)
src/EventEdition/GuestsPanel.vala (+152/-40)
To merge this branch: bzr merge lp:~sschuhmann/maya/guestview
Reviewer Review Type Date Requested Status
Corentin Noël Approve
Danielle Foré Needs Fixing
Review via email: mp+228572@code.launchpad.net

Commit message

Guest View:
 * Added libfolks support.
 * Ported to ListBox.
 * Changed entry string.
 * Invitation status now shows the real status.

Description of the change

Contact lookup added.
ListBox added.

ListBox seems not to themed in egtk, should this be done in maya itself?

To post a comment you must log in.
Revision history for this message
Corentin Noël (tintou) wrote :

The code is great, adding the design team to review the problem with the listbox (if it's not themeable, a Frame can be added)

review: Approve
Revision history for this message
Steffen Schuhmann (sschuhmann) wrote :

Needs fixing.

This may cause bug 1349853, which i thought is also in the upstream version.

Revision history for this message
Steffen Schuhmann (sschuhmann) wrote :

> Needs fixing.
>
> This may cause bug 1349853, which i thought is also in the upstream version.

Tested the development branch contains this bug also.

lp:~sschuhmann/maya/guestview updated
614. By Steffen Schuhmann

Fixes

615. By Steffen Schuhmann

fixes

Revision history for this message
Danielle Foré (danrabbit) wrote :

I've added theming for gtk 3.14. I'll have to add it for 3.12 later :p

Can we remove the ellipsis from the placeholder text in the search entry?

Also, adding a contact should probably clear the entry.

How does the "pending" functionality work? If you add someone that isn't tied to a contact (just some random name) we probably shouldn't show the status since they won't be able to confirm.

also, if these are bugs that are present in trunk and don't have anything to do with your branch feel free to ignore them :)

review: Needs Fixing
Revision history for this message
Steffen Schuhmann (sschuhmann) wrote :

> Also, adding a contact should probably clear the entry.

I'll add that

> How does the "pending" functionality work? If you add someone that isn't tied to a contact (just some random name) we probably shouldn't show the status since they won't be able to confirm.

The user should enter an mail address than just a random name, or is that something, that is uncertain?

lp:~sschuhmann/maya/guestview updated
616. By Steffen Schuhmann

Deleting content of Gtk.Entry when choosing a suggestion.
Removed ellipsis in the "Invite" string.

617. By Steffen Schuhmann

Invitation status now only displays approved and declined invitation status. If there isn't a invitation send or it's pendig it doesn't display anything.

Revision history for this message
Corentin Noël (tintou) wrote :

With your latest changes it's better than ever, ready to merge.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'CMakeLists.txt'
2--- CMakeLists.txt 2014-06-03 14:34:39 +0000
3+++ CMakeLists.txt 2014-08-06 07:31:22 +0000
4@@ -102,6 +102,7 @@
5 champlain-0.12
6 clutter-1.0
7 geocode-glib-1.0
8+ folks
9 )
10
11 set (DEPS_PKG
12@@ -109,6 +110,7 @@
13 champlain-0.12
14 clutter-1.0
15 geocode-glib-1.0
16+ folks
17 )
18
19 set (BASIC_VALAC_OPTIONS
20@@ -137,4 +139,4 @@
21 add_subdirectory (src)
22 add_subdirectory (daemon)
23 add_subdirectory (plugins)
24-add_subdirectory (schemas)
25+add_subdirectory (schemas)
26\ No newline at end of file
27
28=== modified file 'src/CMakeLists.txt'
29--- src/CMakeLists.txt 2014-04-15 21:59:26 +0000
30+++ src/CMakeLists.txt 2014-08-06 07:31:22 +0000
31@@ -4,6 +4,7 @@
32 SourceItem.vala
33 MayaToolbar.vala
34 SourceSelector.vala
35+ EventEdition/ContactImage.vala
36 EventEdition/EventDialog.vala
37 EventEdition/GuestsPanel.vala
38 EventEdition/InfoPanel.vala
39@@ -44,4 +45,4 @@
40
41 target_link_libraries(${EXEC_NAME} ${DEPS_LIBRARIES} -lm)
42
43-install(TARGETS ${EXEC_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR})
44+install(TARGETS ${EXEC_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_FULL_BINDIR})
45\ No newline at end of file
46
47=== added file 'src/EventEdition/ContactImage.vala'
48--- src/EventEdition/ContactImage.vala 1970-01-01 00:00:00 +0000
49+++ src/EventEdition/ContactImage.vala 2014-08-06 07:31:22 +0000
50@@ -0,0 +1,125 @@
51+// -*- Mode: vala; indent-tabs-mode: nil; tab-width: 4 -*-
52+/*-
53+ * Copyright (c) 2013-2014 Pantheon Chat Developers (http://launchpad.net/pantheon-chat)
54+ *
55+ * This library is free software; you can redistribute it and/or
56+ * modify it under the terms of the GNU Library General Public
57+ * License as published by the Free Software Foundation; either
58+ * version 2 of the License, or (at your option) any later version.
59+ *
60+ * This library is distributed in the hope that it will be useful,
61+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
62+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
63+ * Library General Public License for more details.
64+ *
65+ * You should have received a copy of the GNU Library General Public
66+ * License along with this library; if not, write to the
67+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
68+ * Boston, MA 02111-1307, USA.
69+ *
70+ * Authored by: Corentin Noël <corentin@elementaryos.org>
71+ */
72+
73+/*
74+ * This widget auto-updates his image when the contact changes his avatar.
75+ * It draws circle icons following the current trend.
76+ */
77+public class Maya.ContactImage : Gtk.Stack {
78+ private Gtk.IconSize icon_size;
79+ private Gtk.Image current_image = null;
80+ private bool default_avatar = true;
81+ public ContactImage (Gtk.IconSize icon_size, Folks.Individual? individual = null) {
82+ this.icon_size = icon_size;
83+ transition_type = Gtk.StackTransitionType.CROSSFADE;
84+
85+ var force_size_image = new Gtk.Image.from_icon_name ("avatar-default", icon_size);
86+ add (force_size_image);
87+ show_default_avatar ();
88+
89+ if (individual != null) {
90+ add_contact (individual);
91+ }
92+
93+ show_all ();
94+ }
95+
96+ public void add_contact (Folks.Individual individual) {
97+ if (individual.avatar != null && default_avatar == true) {
98+ show_avatar_from_loadable_icon (individual.avatar);
99+ }
100+
101+ individual.notify["avatar"].connect (() => {
102+ if (individual.avatar != null && default_avatar == true) {
103+ show_avatar_from_loadable_icon (individual.avatar);
104+ } else {
105+ show_default_avatar ();
106+ }
107+ });
108+ }
109+
110+ private void show_avatar_from_loadable_icon (LoadableIcon icon) {
111+ var image = new Gtk.Image ();
112+ image.draw.connect ((cr) => {
113+ try {
114+ var width = get_allocated_width ();
115+ var height = get_allocated_height ();
116+ int size = (int) double.min (width, height);
117+ var stream = icon.load (size, null);
118+ var img_pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream, size, size, true);
119+ cr.set_operator (Cairo.Operator.OVER);
120+ var x = (width-size)/2;
121+ var y = (height-size)/2;
122+ Granite.Drawing.Utilities.cairo_rounded_rectangle (cr, x, y, size, size, size/2);
123+ Gdk.cairo_set_source_pixbuf (cr, img_pixbuf, x, y);
124+ cr.fill_preserve ();
125+ cr.set_line_width (0);
126+ cr.set_source_rgba (0, 0, 0, 0.3);
127+ cr.stroke ();
128+ } catch (Error e) {
129+ critical (e.message);
130+ return false;
131+ }
132+
133+ return true;
134+ });
135+ show_avatar_image (image);
136+ }
137+
138+ private void show_default_avatar () {
139+ var image = new Gtk.Image ();
140+ image.draw.connect ((cr) => {
141+ try {
142+ var width = get_allocated_width ();
143+ var height = get_allocated_height ();
144+ int size = (int) double.min (width, height);
145+ var img_pixbuf = Gtk.IconTheme.get_default ().load_icon ("avatar-default", size, Gtk.IconLookupFlags.GENERIC_FALLBACK);
146+ cr.set_operator (Cairo.Operator.OVER);
147+ var x = (width-size)/2;
148+ var y = (height-size)/2;
149+ Granite.Drawing.Utilities.cairo_rounded_rectangle (cr, x, y, size, size, size/2);
150+ Gdk.cairo_set_source_pixbuf (cr, img_pixbuf, x, y);
151+ cr.fill_preserve ();
152+ cr.set_line_width (0);
153+ cr.set_source_rgba (0, 0, 0, 0.3);
154+ cr.stroke ();
155+ } catch (Error e) {
156+ critical (e.message);
157+ return false;
158+ }
159+
160+ return true;
161+ });
162+
163+ show_avatar_image (image);
164+ default_avatar = true;
165+ }
166+
167+ private void show_avatar_image (Gtk.Image image) {
168+ add (image);
169+ image.show ();
170+ set_visible_child (image);
171+ if (current_image != null)
172+ current_image.destroy ();
173+ current_image = image;
174+ }
175+}
176\ No newline at end of file
177
178=== modified file 'src/EventEdition/GuestsPanel.vala'
179--- src/EventEdition/GuestsPanel.vala 2014-04-07 15:50:13 +0000
180+++ src/EventEdition/GuestsPanel.vala 2014-08-06 07:31:22 +0000
181@@ -13,16 +13,14 @@
182 //
183 // You should have received a copy of the GNU General Public License
184 // along with this program. If not, see <http://www.gnu.org/licenses/>.
185-//
186-
187-// TODO: Use Folks to get contact informations such as name and picture.
188
189 public class Maya.View.EventEdition.GuestsPanel : Gtk.Grid {
190 private EventDialog parent_dialog;
191 private Gtk.Entry guest_entry;
192- private Gtk.Grid guest_grid;
193- private int guest_grid_id = 0;
194+ private Gtk.EntryCompletion guest_completion;
195+ private Gtk.ListBox guest_list;
196 private Gee.ArrayList<unowned iCal.Property> attendees;
197+ private Gtk.ListStore guest_store;
198
199 private enum COLUMNS {
200 ICON = 0,
201@@ -44,27 +42,49 @@
202 var guest_label = Maya.View.EventDialog.make_label (_("Participants:"));
203
204 guest_entry = new Gtk.SearchEntry ();
205- guest_entry.placeholder_text = _("Invite…");
206+ guest_entry.placeholder_text = _("Invite");
207 guest_entry.hexpand = true;
208+
209 guest_entry.activate.connect (() => {
210 var attendee = new iCal.Property (iCal.PropertyKind.ATTENDEE);
211 attendee.set_attendee (guest_entry.text);
212- add_attendee ((owned)attendee);
213+ attendees.add (attendee);
214+ add_guest ((owned)attendee);
215 });
216
217- guest_grid = new Gtk.Grid ();
218- var guest_scrolledwindow = new Gtk.ScrolledWindow (null, null);
219- guest_scrolledwindow.add_with_viewport (guest_grid);
220+ load_contacts.begin ();
221+
222+ guest_completion = new Gtk.EntryCompletion ();
223+ guest_entry.set_completion (guest_completion);
224+
225+ Gtk.EntryCompletionMatchFunc matcher = (completion, key, iter) => {
226+ Value val1, val2;
227+ Gtk.ListStore model = (Gtk.ListStore)completion.get_model ();
228+
229+ model.get_value (iter, 0, out val1);
230+ model.get_value (iter, 1, out val2);
231+
232+ if (val1.get_string ().casefold (-1).contains (key) || val2.get_string ().casefold (-1).contains (key))
233+ return true;
234+
235+ return false;
236+ };
237+
238+ guest_completion.set_match_func (matcher);
239+ guest_completion.set_minimum_key_length (3);
240+
241+ guest_store = new Gtk.ListStore(2, typeof (string), typeof (string));
242+ guest_completion.set_model (guest_store);
243+ guest_completion.set_text_column (0);
244+ guest_completion.set_text_column (1);
245+ guest_completion.match_selected.connect ((model, iter) => suggestion_selected (model, iter));
246+
247+ guest_list = new Gtk.ListBox ();
248+ guest_list.set_selection_mode (Gtk.SelectionMode.NONE);
249+ var guest_scrolledwindow = new Gtk.ScrolledWindow (null, null);
250+ guest_scrolledwindow.add_with_viewport (guest_list);
251 guest_scrolledwindow.expand = true;
252
253- var fake_grid_l = new Gtk.Grid ();
254- fake_grid_l.hexpand = true;
255- guest_grid.attach (fake_grid_l, 0, 0, 1, 1);
256-
257- var fake_grid_r = new Gtk.Grid ();
258- fake_grid_r.hexpand = true;
259- guest_grid.attach (fake_grid_r, 2, 0, 1, 1);
260-
261 attach (guest_label, 0, 0, 1, 1);
262 attach (guest_entry, 0, 1, 1, 1);
263 attach (guest_scrolledwindow, 0, 2, 1, 1);
264@@ -76,9 +96,8 @@
265
266 unowned iCal.Property property = comp.get_first_property (iCal.PropertyKind.ATTENDEE);
267 for (int i = 0; i < count; i++) {
268-
269 if (property.get_attendee () != null)
270- add_attendee (property);
271+ add_guest (property);
272
273 property = comp.get_next_property (iCal.PropertyKind.ATTENDEE);
274 }
275@@ -88,6 +107,20 @@
276 }
277
278 /**
279+ * Add the contacts to the EntryCompletion model.
280+ */
281+ private void apply_contact_store_model (Gee.Map<string, Folks.Individual> contacts) {
282+ Gtk.TreeIter contact;
283+ var map_iterator = contacts.map_iterator ();
284+ while (map_iterator.next ()) {
285+ foreach (var address in map_iterator.get_value ().email_addresses) {
286+ guest_store.append (out contact);
287+ guest_store.set (contact, 0, map_iterator.get_value ().full_name, 1, address.value);
288+ }
289+ }
290+ }
291+
292+ /**
293 * Save the values in the dialog into the component.
294 */
295 public void save () {
296@@ -95,7 +128,7 @@
297 // Save the guests
298 // First, clear the guests
299 int count = comp.count_properties (iCal.PropertyKind.ATTENDEE);
300-
301+
302 for (int i = 0; i < count; i++) {
303 unowned iCal.Property remove_prop;
304 if (i == 0) {
305@@ -127,28 +160,65 @@
306 }
307 }
308
309- private void add_attendee (iCal.Property attendee) {
310- var guest_element = new GuestGrid (attendee);
311- guest_grid.attach (guest_element, 1, guest_grid_id, 1, 1);
312- guest_grid_id++;
313- attendees.add (guest_element.attendee);
314- guest_element.removed.connect (() => {
315+ private void add_guest (iCal.Property attendee) {
316+ var row = new Gtk.ListBoxRow ();
317+ var guest_element = new GuestGrid (attendee);
318+ row.add (guest_element);
319+ guest_list.add (row);
320+
321+ attendees.add (guest_element.attendee);
322+ guest_element.removed.connect (() => {
323 attendees.remove (guest_element.attendee);
324 });
325- guest_element.show_all ();
326+
327+ row.show_all ();
328+ }
329+
330+ private bool suggestion_selected (Gtk.TreeModel model, Gtk.TreeIter iter) {
331+ var attendee = new iCal.Property (iCal.PropertyKind.ATTENDEE);
332+ Value selected_value;
333+
334+ model.get_value (iter, 1, out selected_value);;
335+ attendee.set_attendee (selected_value.get_string ());
336+ add_guest ((owned)attendee);
337+ guest_entry.delete_text (0, -1);
338+ return true;
339+ }
340+
341+ private async void load_contacts () {
342+ var aggregator = Folks.IndividualAggregator.dup ();
343+
344+ if (aggregator.is_prepared) {
345+ apply_contact_store_model (aggregator.individuals);
346+ } else {
347+ aggregator.notify["is-quiescent"].connect (() => {
348+ load_contacts.begin ();
349+ });
350+
351+ aggregator.prepare.begin ();
352+ }
353 }
354 }
355
356 public class Maya.View.EventEdition.GuestGrid : Gtk.Grid {
357 public signal void removed ();
358 public iCal.Property attendee;
359+ private Folks.Individual individual;
360+ private Gtk.Label name_label;
361+ private Gtk.Label mail_label;
362+ private ContactImage icon_image;
363
364 public GuestGrid (iCal.Property attendee) {
365 this.attendee = new iCal.Property.clone (attendee);
366 row_spacing = 6;
367 column_spacing = 12;
368+ individual = null;
369+
370+ set_margin_bottom (6);
371+ set_margin_right (6);
372+ set_margin_left (6);
373
374- string status = "<b><span color=\'darkgrey\'>%s</span></b>".printf (_("Pending"));
375+ string status = "";
376 unowned iCal.Parameter parameter = attendee.get_first_parameter (iCal.ParameterKind.PARTSTAT);
377 if (parameter != null) {
378 switch (parameter.get_partstat ()) {
379@@ -159,27 +229,26 @@
380 status = "<b><span color=\'red\'>%s</span></b>".printf (_("Declined"));
381 break;
382 default:
383- status = "<b><span color=\'darkgrey\'>%s</span></b>".printf (_("Pending"));
384+ status = "";
385 break;
386 }
387- }
388+ }
389
390 var status_label = new Gtk.Label ("");
391 status_label.set_markup (status);
392 status_label.justify = Gtk.Justification.RIGHT;
393+ icon_image = new ContactImage (Gtk.IconSize.DIALOG);
394
395- var icon_image = new Gtk.Image.from_icon_name ("avatar-default", Gtk.IconSize.DIALOG);
396-
397 var mail = attendee.get_attendee ().replace ("mailto:", "");
398
399- var name_label = new Gtk.Label ("");
400+ name_label = new Gtk.Label ("");
401 name_label.xalign = 0;
402- name_label.set_markup ("<b><big>%s</big></b>".printf (Markup.escape_text (mail.split ("@", 2)[0])));
403+ set_name_label (mail.split ("@", 2)[0]);
404
405- var mail_label = new Gtk.Label ("");
406+ mail_label = new Gtk.Label ("");
407 mail_label.hexpand = true;
408 mail_label.xalign = 0;
409- mail_label.set_markup ("<b><span color=\'darkgrey\'>%s</span></b>".printf (Markup.escape_text (mail)));
410+ set_mail_label (mail);
411
412 var remove_button = new Gtk.Button.from_icon_name ("edit-delete-symbolic", Gtk.IconSize.BUTTON);
413 remove_button.relief = Gtk.ReliefStyle.NONE;
414@@ -188,10 +257,53 @@
415 remove_grid.add (remove_button);
416 remove_grid.valign = Gtk.Align.CENTER;
417
418+ get_contact_by_mail.begin (attendee.get_attendee ().replace ("mailto:", ""));
419+
420 attach (icon_image, 0, 0, 1, 4);
421- attach (name_label, 1, 1, 1, 2/*1*/);
422- //attach (mail_label, 1, 2, 1, 1); Once Folks is enabled, separate email and name !
423+ attach (name_label, 1, 1, 1, 1);
424+ attach (mail_label, 1, 2, 1, 1);
425 attach (status_label, 2, 1, 1, 2);
426 attach (remove_grid, 3, 1, 1, 2);
427 }
428-}
429\ No newline at end of file
430+
431+ private async void get_contact_by_mail (string mail_address) {
432+ Folks.IndividualAggregator aggregator = Folks.IndividualAggregator.dup ();
433+ if (aggregator.is_prepared) {
434+ Gee.MapIterator <string, Folks.Individual> map_iterator;
435+ map_iterator = aggregator.individuals.map_iterator ();
436+
437+ while (map_iterator.next ()) {
438+ foreach (var address in map_iterator.get_value ().email_addresses) {
439+ if(address.value == mail_address) {
440+ individual = map_iterator.get_value ();
441+ if (individual != null) {
442+ icon_image.add_contact (individual);
443+ if (individual.full_name != null && individual.full_name != "") {
444+ set_name_label (individual.full_name);
445+ set_mail_label (attendee.get_attendee ());
446+ }
447+ }
448+ }
449+ }
450+ }
451+ } else {
452+ aggregator.notify["is-quiescent"].connect (() => {
453+ get_contact_by_mail.begin (mail_address);
454+ });
455+
456+ try {
457+ yield aggregator.prepare ();
458+ } catch (Error e) {
459+ critical (e.message);
460+ }
461+ }
462+ }
463+
464+ private void set_name_label (string name) {
465+ name_label.set_markup ("<b><big>%s</big></b>".printf (Markup.escape_text (name)));
466+ }
467+
468+ private void set_mail_label (string mail) {
469+ mail_label.set_markup ("<b><span color=\'darkgrey\'>%s</span></b>".printf (Markup.escape_text (mail)));
470+ }
471+}

Subscribers

People subscribed via source and target branches