Merge lp:~bigwhale/gwibber/DMs into lp:gwibber

Proposed by David Klasinc
Status: Merged
Merged at revision: 1250
Proposed branch: lp:~bigwhale/gwibber/DMs
Merge into: lp:gwibber
Diff against target: 605 lines (+232/-79)
6 files modified
client/tab-bar-item.vala (+5/-3)
libgwibber-gtk/action-box.vala (+69/-29)
libgwibber-gtk/stream-view-tile.vala (+117/-37)
libgwibber-gtk/stream-view.vala (+12/-6)
libgwibber/stream-model-schema.vala (+4/-1)
libgwibber/streams.vala (+25/-3)
To merge this branch: bzr merge lp:~bigwhale/gwibber/DMs
Reviewer Review Type Date Requested Status
Ken VanDine Approve
Review via email: mp+91151@code.launchpad.net

Description of the change

Added support for replying to private messages on twitter, and overlay avatars on private messages.

To post a comment you must log in.
Revision history for this message
Ken VanDine (ken-vandine) wrote :

Looks great, a couple minor tweaks, in action-box.vala we shouldn't display the reply menu if the from_me is true or if the stream is private (since the private reply menu will cover that):

- if (service != "flicker" && service != "pingfm" && service != "foursquare" && service != "digg")
+ if ((service != "flicker" && service != "pingfm" && service != "foursquare" && service != "digg") && !from_me && stream != "private")

We should display the private reply menu for identica and statusnet too:

- if ((service == "twitter" || service != "identica") && !from_me)
+ if ((service == "twitter" || service == "identica" || service == "statusnet") && !from_me)

Of course once I convert the ActionBox to us the features API all that non-sense will go away, but I think this would be better for now.

It would also be nice to do some clipping on the overlay avatar, so the smaller one has rounder edges just like that bigger one. I won't block the merge for that, just a request for the future :)

lp:~bigwhale/gwibber/DMs updated
1178. By David Klasinc

Changes according to merge review.

Revision history for this message
David Klasinc (bigwhale) wrote :

Added necessary changes.

Revision history for this message
Ken VanDine (ken-vandine) wrote :

Looks great

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'client/tab-bar-item.vala'
2--- client/tab-bar-item.vala 2012-01-18 19:45:36 +0000
3+++ client/tab-bar-item.vala 2012-02-01 20:40:23 +0000
4@@ -38,9 +38,9 @@
5
6 view.model = streams_map[stream];
7 view.stream = stream;
8- view.reply.connect ((mid, account, sender) => {
9+ view.send.connect ((mid, account, sender, action) => {
10 entry.text_view.mid = mid;
11- if (stream == "private")
12+ if (action == "private" || stream == "private")
13 {
14 entry.text_view.action = "private";
15 entry.private.show ();
16@@ -49,9 +49,11 @@
17 entry.text_view.action = "reply";
18 entry.target_bar.selected = account;
19 entry.showing = true;
20- entry.text_view.buffer.text = sender + " ";
21+ if (entry.text_view.action != "private")
22+ entry.text_view.buffer.text = sender + " ";
23 entry.text_view.grab_focus ();
24 });
25+
26 notify["active"].connect(() => {
27 view.showing = active;
28 });
29
30=== modified file 'libgwibber-gtk/action-box.vala'
31--- libgwibber-gtk/action-box.vala 2012-01-30 18:21:42 +0000
32+++ libgwibber-gtk/action-box.vala 2012-02-01 20:40:23 +0000
33@@ -60,6 +60,7 @@
34 public string mid { get; construct set; }
35 public string sender { get; construct set; }
36 public string action { get; construct set; }
37+ public bool from_me { get; construct set; }
38 public string tooltip { get; construct set; }
39
40 private Gtk.Image _image;
41@@ -68,9 +69,9 @@
42 private Gtk.Menu? _menu;
43 private Gtk.MenuItem? _amenu;
44
45- public ActionBoxItem (string service, string stream, string account, string mid, string sender, string tooltip = "")
46+ public ActionBoxItem (string service, string stream, string account, string mid, string sender, bool from_me, string tooltip = "")
47 {
48- Object (service:service, stream:stream, account:account, mid:mid, sender:sender, tooltip:tooltip);
49+ Object (service:service, stream:stream, account:account, mid:mid, sender:sender, from_me:from_me, tooltip:tooltip);
50 }
51
52 ~ActionBoxItem ()
53@@ -113,7 +114,7 @@
54 }
55
56 [Signal (action=true)]
57- public virtual signal void reply (string mid, string account, string sender)
58+ public virtual signal void send (string mid, string account, string sender, string action = "send")
59 {
60 }
61
62@@ -140,7 +141,7 @@
63 {
64 _menu = new Gtk.Menu ();
65
66- if (service != "flicker" && service != "pingfm" && service != "foursquare" && service != "digg")
67+ if ((service != "flicker" && service != "pingfm" && service != "foursquare" && service != "digg") && !from_me && stream != "private")
68 {
69 _amenu = new Gtk.MenuItem.with_mnemonic (_("_Reply"));
70 _amenu.activate.connect(() => {
71@@ -156,30 +157,69 @@
72
73 var parser = new Json.Parser();
74 parser.load_from_data(msg);
75-
76- obj = parser.get_root().get_object();
77- if (obj != null)
78- {
79- if (obj.has_member ("sender"))
80- {
81- sender_obj = obj.get_object_member ("sender");
82- if (sender_obj != null)
83- {
84- if (sender_obj.has_member ("nick"))
85- nick = sender_obj.get_string_member ("nick");
86- }
87- }
88- }
89- } catch (GLib.IOError e) {
90- warning (e.message);
91- }
92- if (nick.length > 0)
93- nick = "@" + nick;
94- reply (mid, account, nick);
95- });
96- _menu.append(_amenu);
97- }
98-
99+
100+ obj = parser.get_root().get_object();
101+ if (obj != null)
102+ {
103+ if (obj.has_member ("sender"))
104+ {
105+ sender_obj = obj.get_object_member ("sender");
106+ if (sender_obj != null)
107+ {
108+ if (sender_obj.has_member ("nick"))
109+ nick = sender_obj.get_string_member ("nick");
110+ }
111+ }
112+ }
113+ } catch (GLib.IOError e) {
114+ warning (e.message);
115+ }
116+ if (nick.length > 0)
117+ nick = "@" + nick;
118+ send (mid, account, nick);
119+ });
120+ _menu.append(_amenu);
121+ }
122+
123+ if ((service == "twitter" || service == "identica" || service == "statusnet") && !from_me)
124+ {
125+ _amenu = new Gtk.MenuItem.with_mnemonic (_("Pri_vate Reply"));
126+ _amenu.activate.connect(() => {
127+ hide();
128+ string nick = "";
129+ Json.Object obj = null;
130+ Json.Object sender_obj = null;
131+
132+ try
133+ {
134+ var messages = new Gwibber.Messages ();
135+ var msg = messages.get_message (mid);
136+
137+ var parser = new Json.Parser();
138+ parser.load_from_data(msg);
139+
140+ obj = parser.get_root().get_object();
141+ if (obj != null)
142+ {
143+ if (obj.has_member ("sender"))
144+ {
145+ sender_obj = obj.get_object_member ("sender");
146+ if (sender_obj != null)
147+ {
148+ if (sender_obj.has_member ("nick"))
149+ nick = sender_obj.get_string_member ("nick");
150+ }
151+ }
152+ }
153+ } catch (GLib.IOError e) {
154+ warning (e.message);
155+ }
156+ if (nick.length > 0)
157+ nick = "@" + nick;
158+ send (mid, account, nick, "private");
159+ });
160+ _menu.append(_amenu);
161+ }
162 if (service != "flicker" && service != "pingfm" && service != "foursquare" && service != "digg" && service != "qaiku" && service != "buzz" && stream != "private")
163 {
164 _amenu = new Gtk.MenuItem.with_mnemonic (_("_Like"));
165@@ -198,7 +238,7 @@
166
167 var parser = new Json.Parser();
168 parser.load_from_data(msg);
169-
170+
171 obj = parser.get_root().get_object();
172 if (obj != null)
173 {
174
175=== modified file 'libgwibber-gtk/stream-view-tile.vala'
176--- libgwibber-gtk/stream-view-tile.vala 2012-01-30 15:05:25 +0000
177+++ libgwibber-gtk/stream-view-tile.vala 2012-02-01 20:40:23 +0000
178@@ -82,6 +82,7 @@
179 public bool show_fullname { get; construct set; }
180 private uint _update_time_area_id = 0;
181 private uint _cache_avatar_id = 0;
182+ private uint _cache_overlay_avatar_id = 0;
183 private ulong _expander_id = 0;
184 private ulong _thumb_expander_id = 0;
185 private List<ulong> _to_disconnect;
186@@ -129,10 +130,10 @@
187
188 vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
189 main_box.pack_start (vbox, true, true, 0);
190-
191+
192 var hbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
193 vbox.pack_start (hbox, false, false, 2);
194-
195+
196 private = new Gtk.Image.from_icon_name ("status_lock", Gtk.IconSize.MENU);
197 private.set_no_show_all (true);
198 hbox.pack_start (private, false, false, 0);
199@@ -327,7 +328,7 @@
200 }
201
202 [Signal (action=true)]
203- public virtual signal void reply (string mid, string account, string sender)
204+ public virtual signal void send (string mid, string account, string sender, string action = "send")
205 {
206 }
207
208@@ -399,7 +400,10 @@
209 string _video_src,
210 string _video_url,
211 string _video_name,
212- string _comments)
213+ string _comments,
214+ string _recipient,
215+ string _recipient_nick,
216+ string _recipient_icon)
217 {
218 // hide and reset values to prevent reuse as the tiles change
219 action_box.hide ();
220@@ -489,7 +493,7 @@
221 chbox.pack_start (clalignment, false, false, 0);
222 var clbox = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0);
223 clalignment.add (clbox);
224-
225+
226 comments_box.pack_start (chbox, false, false, 2);
227 chbox.pack_start (cvbox, false, false, 2);
228 if (obj.has_member ("text"))
229@@ -521,7 +525,7 @@
230 cname_label.set_markup ("<b><span font_size='small'>" + cname + "</span></b>");
231 cname_box.pack_start (cname_label, false, false, 2);
232 cvbox.pack_end (cname_box, false, false, 2);
233- }
234+ }
235 if (_sender_obj.has_member ("image"))
236 {
237 var cimage = _sender_obj.get_string_member ("image");
238@@ -545,7 +549,7 @@
239 }
240 }
241 }
242- }
243+ }
244 comments_box.set_no_show_all (false);
245 comments_box.show_all ();
246 }
247@@ -564,12 +568,19 @@
248 }
249 }
250
251- string display_name = _sender;
252+ string display_name = "";
253
254- if (show_fullname && _sender.length > 0)
255- display_name = _sender;
256- else if (_sender_nick.length > 0)
257- display_name = _sender_nick;
258+ if (_from_me && _stream == "private") {
259+ if (show_fullname && _recipient.length > 0)
260+ display_name = _recipient;
261+ else if (_recipient_nick.length > 0)
262+ display_name = _recipient_nick;
263+ } else {
264+ if (show_fullname && _sender.length > 0)
265+ display_name = _sender;
266+ else if (_sender_nick.length > 0)
267+ display_name = _sender_nick;
268+ }
269
270 name.set_markup ("<b>" + display_name + "</b>");
271
272@@ -579,7 +590,7 @@
273 sender += ":";
274 */
275
276- /* iterate over the accounts, display the service icon and attach a menu
277+ /* iterate over the accounts, display the service icon and attach a menu
278 * for actions.
279 * this is a temporary hack until njpatel finishes the proper actions widget
280 */
281@@ -605,10 +616,10 @@
282 if (_account in icon_displayed)
283 continue;
284 icon_displayed += _account;
285- var action_item = new GwibberGtk.ActionBoxItem (_service, _stream, _account, _mid, sender);
286+ var action_item = new GwibberGtk.ActionBoxItem (_service, _stream, _account, _mid, sender, _from_me);
287 action_box.add(action_item);
288- action_item.reply.connect((mid, account, sender) => {
289- reply (mid, account, sender);
290+ action_item.send.connect((mid, account, sender, action) => {
291+ send (mid, account, sender, action);
292 });
293
294 }
295@@ -618,22 +629,74 @@
296 else
297 _avatar_box.reparent(lalignment);
298
299+ var _avatar = new StreamViewAvatar ();
300+ var _overlay_avatar = new StreamViewAvatar ();
301+
302 var _avatar_cache_image = utils.avatar_path (_icon_uri);
303- if (_avatar_cache_image != null)
304- {
305- var _avatar = new StreamViewAvatar ();
306- _avatar.set_from_file (_avatar_cache_image);
307+ var _overlay_avatar_cache_image = "";
308+
309+ if (_stream != "private")
310+ {
311+ if (_avatar_cache_image != null)
312+ {
313+ _avatar.set_from_file (_avatar_cache_image);
314+ _avatar_hbox.add (_avatar);
315+ _avatar_hbox.show ();
316+ _avatar.show ();
317+ } else
318+ {
319+ ulong _cache_avatar_id = Timeout.add (200, () => {
320+ load_avatar_async.begin (_icon_uri, _avatar_hbox);
321+ return false;
322+ });
323+ _to_disconnect.append (_cache_avatar_id);
324+ }
325+ } else
326+ {
327+ _overlay_avatar_cache_image = utils.avatar_path (_recipient_icon);
328+
329+ // Deal with the private streams and avatars, introduce new memory leaks ...
330+ Gdk.Pixbuf first_buf;
331+ Gdk.Pixbuf second_buf;
332+
333+ _avatar.set_from_icon_name("stock-person", Gtk.IconSize.DIALOG);
334+ _overlay_avatar.set_from_icon_name("stock-person", Gtk.IconSize.DIALOG);
335+
336+ first_buf = _avatar.get_pixbuf();
337+ second_buf = _overlay_avatar.get_pixbuf();
338+
339+ if (_avatar_cache_image != null)
340+ {
341+ first_buf = new Gdk.Pixbuf.from_file(_avatar_cache_image);
342+ } else
343+ {
344+ ulong _cache_avatar_id = Timeout.add (200, () => {
345+ load_avatar_async.begin (_icon_uri, _avatar_hbox);
346+ return false;
347+ });
348+ _to_disconnect.append (_cache_avatar_id);
349+ }
350+
351+ if (_overlay_avatar_cache_image != null)
352+ {
353+ second_buf = new Gdk.Pixbuf.from_file(_overlay_avatar_cache_image);
354+ } else
355+ {
356+ ulong _cache_overlay_avatar_id = Timeout.add (200, () => {
357+ load_avatar_async.begin (_recipient_icon, _avatar_hbox, true);
358+ return false;
359+ });
360+ _to_disconnect.append (_cache_overlay_avatar_id);
361+ }
362+
363+ second_buf.composite(first_buf, 24, 24, 24, 24, 24, 24, 0.5, 0.5, Gdk.InterpType.BILINEAR, 255);
364+ _avatar.set_from_pixbuf(first_buf);
365 _avatar_hbox.add (_avatar);
366 _avatar_hbox.show ();
367 _avatar.show ();
368- } else
369- {
370- ulong _cache_avatar_id = Timeout.add (200, () => {
371- load_avatar_async.begin (_icon_uri, _avatar_hbox);
372- return false;
373- });
374- _to_disconnect.append (_cache_avatar_id);
375- }
376+ }
377+
378+
379
380 if (_stream == "user")
381 {
382@@ -646,7 +709,7 @@
383 }
384
385 url = _url;
386-
387+
388 _avatar_box.button_press_event.disconnect(on_avatar_click);
389 _avatar_box.button_press_event.connect(on_avatar_click);
390
391@@ -658,7 +721,7 @@
392 likes_hbox.set_no_show_all (false);
393 likes_hbox.show_all();
394 } else {
395- likes_count.set_markup("<span font_size='small'>" +
396+ likes_count.set_markup("<span font_size='small'>" +
397 ngettext("%i person liked this", "%i people liked this", (int) _likes).printf((int) _likes) +
398 "</span>");
399 likes_hbox.set_no_show_all (false);
400@@ -695,7 +758,7 @@
401 }
402
403 message.set_markup (msg_str);
404-
405+
406 if (img_uri.length > 0 )
407 {
408 if (_thumb_expander_id != 0)
409@@ -754,20 +817,23 @@
410 retweeted_by.show ();
411 }
412 retweeted_by_string += "</span>";
413-
414+
415 retweeted_by.set_markup (retweeted_by_string);
416
417 queue_resize ();
418 show ();
419- }
420+ }
421
422- private async void load_avatar_async (string url, Gtk.Box b)
423+ private async void load_avatar_async (string url, Gtk.Box b, bool overlay = false)
424 {
425 string t;
426 var file = File.new_for_uri (url);
427 try {
428 var stream = yield file.read_async (Priority.DEFAULT);
429+
430 Gdk.Pixbuf pixbuf = null;
431+ Gdk.Pixbuf first_buf = null;
432+
433 pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream,
434 48,
435 48,
436@@ -776,11 +842,25 @@
437 {
438 var avatar = new StreamViewAvatar ();
439 foreach (var _w in b.get_children ())
440+ {
441+ if (overlay && _w is StreamViewAvatar)
442+ {
443+ Gtk.Image _r = (Gtk.Image) _w;
444+ first_buf = _r.get_pixbuf();
445+ }
446 if (_w is Gtk.Widget)
447 _w.destroy ();
448- avatar.set_from_pixbuf (pixbuf);
449+ }
450+ if (overlay)
451+ {
452+ pixbuf.composite(first_buf, 24, 24, 24, 24, 24, 24, 0.5, 0.5, Gdk.InterpType.BILINEAR, 255);
453+ avatar.set_from_pixbuf (first_buf);
454+ } else
455+ avatar.set_from_pixbuf (pixbuf);
456+
457 b.add (avatar);
458 avatar.show ();
459+
460 var cimage = Path.build_path (Path.DIR_SEPARATOR_S, Environment.get_user_cache_dir(), "gwibber/avatars", url.replace("/",""));
461 t = "png";
462 if (cimage.has_suffix ("JPG") || cimage.has_suffix ("jpeg") || cimage.has_suffix ("jpg"))
463@@ -827,7 +907,7 @@
464 {
465 /* let's point to the static content in imgur */
466 var last = uri.rstr("/").substring(1);
467- ret = "http://i.imgur.com/%s.png".printf(last);
468+ ret = "http://i.imgur.com/%s.png".printf(last);
469 }
470 else if (uri.contains("youtube.com"))
471 {
472@@ -847,7 +927,7 @@
473
474 private bool on_avatar_click (Gdk.EventButton button)
475 {
476- /* FIXME: Display a user view with user info and posts instead of
477+ /* FIXME: Display a user view with user info and posts instead of
478 launching external browser */
479 try {
480 AppInfo.launch_default_for_uri (url, null);
481
482=== modified file 'libgwibber-gtk/stream-view.vala'
483--- libgwibber-gtk/stream-view.vala 2012-01-29 06:06:54 +0000
484+++ libgwibber-gtk/stream-view.vala 2012-02-01 20:40:23 +0000
485@@ -288,8 +288,8 @@
486 tile.show_all ();
487 view_box.add (tile);
488 tiles.append (tile);
489- tile.reply.connect((mid, account, sender) => {
490- reply (mid, account, sender);
491+ tile.send.connect((mid, account, sender, action) => {
492+ send (mid, account, sender, action);
493 });
494 }
495
496@@ -387,7 +387,11 @@
497 stream_filter_model.get_string (iter, StreamModelColumn.VIDEO_SRC),
498 stream_filter_model.get_string (iter, StreamModelColumn.VIDEO_URL),
499 stream_filter_model.get_string (iter, StreamModelColumn.VIDEO_NAME),
500- stream_filter_model.get_string (iter, StreamModelColumn.COMMENTS));
501+ stream_filter_model.get_string (iter, StreamModelColumn.COMMENTS),
502+ stream_filter_model.get_string (iter, StreamModelColumn.RECIPIENT),
503+ stream_filter_model.get_string (iter, StreamModelColumn.RECIPIENT_NICK),
504+ stream_filter_model.get_string (iter, StreamModelColumn.RECIPIENT_ICON));
505+
506 tiles_visible = i + 1;
507 }
508 else
509@@ -444,9 +448,11 @@
510 tile.show_all ();
511 view_box.add (tile);
512 tiles.append (tile);
513- tile.reply.connect((mid, account, sender) => {
514- reply (mid, account, sender);
515+
516+ tile.send.connect((mid, account, sender, action) => {
517+ send (mid, account, sender, action);
518 });
519+
520 Idle.add (()=>{
521 queue_draw ();
522 do_refresh ();
523@@ -479,7 +485,7 @@
524 }
525
526 [Signal (action=true)]
527- public virtual signal void reply (string mid, string account, string sender)
528+ public virtual signal void send (string mid, string account, string recipient, string action = "send")
529 {
530 }
531
532
533=== modified file 'libgwibber/stream-model-schema.vala'
534--- libgwibber/stream-model-schema.vala 2011-08-25 06:53:05 +0000
535+++ libgwibber/stream-model-schema.vala 2012-02-01 20:40:23 +0000
536@@ -55,6 +55,9 @@
537 VIDEO_SRC,
538 VIDEO_URL,
539 VIDEO_NAME,
540- COMMENTS
541+ COMMENTS,
542+ RECIPIENT,
543+ RECIPIENT_NICK,
544+ RECIPIENT_ICON
545 }
546 }
547
548=== modified file 'libgwibber/streams.vala'
549--- libgwibber/streams.vala 2012-01-19 18:14:26 +0000
550+++ libgwibber/streams.vala 2012-02-01 20:40:23 +0000
551@@ -175,14 +175,14 @@
552 private Dee.SequenceModel? create_model()
553 {
554 var m = new Dee.SequenceModel();
555- m.set_schema ("as", "s", "s", "s", "s", "b", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "d", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s");
556+ m.set_schema ("as", "s", "s", "s", "s", "b", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "d", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s", "s");
557 debug ("SCHEMA has %u rows", m.get_n_columns ());
558 return m;
559 }
560
561 public Dee.Model? streams_model(bool transients)
562 {
563- uint schema_length = 35;
564+ uint schema_length = 38;
565
566 resources = Dee.ResourceManager.get_default ();
567
568@@ -399,6 +399,25 @@
569 Json.Object _sender_obj = null;
570 string _sender = "";
571 string _sender_nick = "";
572+ Json.Object _recipient_obj = null;
573+ string _recipient = "";
574+ string _recipient_nick = "";
575+ string _recipient_icon = "";
576+
577+ if (obj.has_member("recipient"))
578+ {
579+ _recipient_obj = obj.get_object_member("recipient");
580+
581+ if (_recipient_obj != null)
582+ {
583+ if (_recipient_obj.has_member("name"))
584+ _recipient = _recipient_obj.get_string_member("name");
585+ if (_recipient_obj.has_member("nick"))
586+ _recipient_nick = _recipient_obj.get_string_member("nick");
587+ if (_recipient_obj.has_member("image"))
588+ _recipient_icon = _recipient_obj.get_string_member("image");
589+ }
590+ }
591
592 if (obj.has_member("sender"))
593 {
594@@ -696,7 +715,10 @@
595 _video_src,
596 _video_url,
597 _video_name,
598- _comments
599+ _comments,
600+ _recipient,
601+ _recipient_nick,
602+ _recipient_icon
603 );
604 } catch {
605 iter = null;