Merge lp:~sschuhmann/maya/guestview into lp:~elementary-apps/maya/trunk
- guestview
- Merge into trunk
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 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Corentin Noël | Approve | ||
Danielle Foré | Needs Fixing | ||
Review via email:
|
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?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Steffen Schuhmann (sschuhmann) wrote : | # |
Needs fixing.
This may cause bug 1349853, which i thought is also in the upstream version.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
- 614. By Steffen Schuhmann
-
Fixes
- 615. By Steffen Schuhmann
-
fixes
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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 :)
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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?
- 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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Corentin Noël (tintou) wrote : | # |
With your latest changes it's better than ever, ready to merge.
Preview Diff
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 | +} |
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)