~ubuntu-branches/ubuntu/oneiric/gwibber/oneiric

« back to all changes in this revision

Viewing changes to libgwibber-gtk/stream-view-tile.vala

  • Committer: Bazaar Package Importer
  • Author(s): Ken VanDine
  • Date: 2011-08-25 15:36:16 UTC
  • mfrom: (1.1.59 upstream)
  • Revision ID: james.westby@ubuntu.com-20110825153616-sd8yqbkdytwh0gi5
Tags: 3.1.6-0ubuntu1
* New upstream release
  - async loading of all images
  - display thumbnails for images and videos
  - display facebook comments inline
  - round the corners of avatars and thumbnails
  - significant performance improvements for rendering the stream views
* debian/libgwibber-gtk2.symbols
  - added symbols

Show diffs side-by-side

added added

removed removed

Lines of Context:
21
21
 
22
22
namespace GwibberGtk
23
23
{
 
24
  public class StreamViewAvatar : Gtk.Image
 
25
  {
 
26
    public StreamViewAvatar()
 
27
    {
 
28
      draw.connect (on_draw);
 
29
    }
 
30
 
 
31
    private bool on_draw (Cairo.Context cr)
 
32
    {
 
33
      Gtk.Allocation alloc;
 
34
      get_allocation(out alloc);
 
35
 
 
36
      var radius = 10.0f;
 
37
      cr.move_to(0, radius);
 
38
      cr.curve_to(0, 0, 0, 0, radius, 0);
 
39
      cr.line_to(alloc.width - radius, 0);
 
40
      cr.curve_to(alloc.width, 0, alloc.width, 0, alloc.width, radius);
 
41
      cr.line_to(alloc.width, alloc.height - radius);
 
42
      cr.curve_to(alloc.width, alloc.height, alloc.width, alloc.height, alloc.width - radius, alloc.height);
 
43
      cr.line_to(radius, alloc.height);
 
44
      cr.curve_to(0, alloc.height, 0, alloc.height, 0, alloc.height - radius);
 
45
      cr.close_path();
 
46
 
 
47
      cr.clip();
 
48
 
 
49
      base.draw(cr);
 
50
      return true;
 
51
    }
 
52
  }
 
53
 
24
54
  public class StreamViewTile : Gtk.EventBox
25
55
  {
26
56
    private const int our_padding = 6;
30
60
    private Gtk.Alignment lalignment;
31
61
    private Gtk.Alignment ralignment;
32
62
    private Gtk.EventBox  icon_box;
33
 
    private Gtk.Image  icon;
 
63
    private StreamViewAvatar  icon;
34
64
    private Gtk.Image  private;
35
65
    private Gtk.VBox   vbox;
 
66
    private Gtk.VBox   comments_box;
36
67
    private Gtk.HBox   likes_hbox;
37
68
    private Gtk.Label  likes_count;
38
69
    private new Gtk.Label  name;
39
70
    //private new Gtk.Label  source;
40
71
    private Gtk.Label  time;
41
72
    private Gtk.Label  message;
 
73
    private StreamViewAvatar  thumbnail;
 
74
    private Gtk.EventBox thumb_box;
42
75
    private Gtk.Label  reply_to;
43
76
    private Gtk.Label  retweeted_by;
44
77
    public Gwibber.Utils utils { get; construct set; }
 
78
    public Gwibber.Service service { get; construct set; }
45
79
    public bool show_fullname { get; construct set; }
46
80
    private uint _update_time_area_id = 0;
 
81
    private uint _cache_avatar_id = 0;
 
82
    private List<uint> _to_disconnect;
47
83
 
48
84
    public uint uid = 0;
49
85
 
54
90
 
55
91
    private GwibberGtk.ActionBox action_box;
56
92
 
57
 
    public StreamViewTile (Gwibber.Utils utils, bool show_fullname)
 
93
    private Cancellable cancellable;
 
94
    private string img_uri;
 
95
    private string img_src;
 
96
    private uint img_id;
 
97
 
 
98
    public StreamViewTile (Gwibber.Utils utils, Gwibber.Service service, bool show_fullname)
58
99
    {
59
 
      Object (above_child:false, visible_window:false, utils:utils, show_fullname:show_fullname);
 
100
      Object (above_child:false, visible_window:false, utils:utils, service:service, show_fullname:show_fullname);
60
101
    }
61
102
 
62
103
    construct
63
104
    {
 
105
      _to_disconnect = new GLib.List<uint> ();
64
106
      draw.connect (on_draw);
65
107
 
66
108
      align = new Gtk.Alignment (0.0f, 0.0f, 1.0f, 1.0f);
77
119
 
78
120
      icon_box = new Gtk.EventBox ();
79
121
      icon_box.set_visible_window (false);
80
 
      icon = new Gtk.Image.from_icon_name ("stock_person", Gtk.IconSize.DIALOG);
 
122
      icon = new StreamViewAvatar();
81
123
      icon_box.add(icon);
82
124
      lalignment.add (icon_box);
83
125
 
166
208
        }
167
209
        });
168
210
 
 
211
      thumb_box = new Gtk.EventBox ();
 
212
      thumb_box.set_visible_window (false);
 
213
      thumb_box.set_no_show_all(true);
 
214
      var thumb_hbox = new Gtk.HBox (false, 2);
 
215
      vbox.pack_start(thumb_hbox, false, false, 4);
 
216
      thumb_hbox.pack_start(thumb_box, false, false, 4);
 
217
      thumb_box.button_release_event.connect(() =>
 
218
      {
 
219
        if (img_src != null)
 
220
          Gtk.show_uri(Gdk.Screen.get_default(), img_src, 0);
 
221
        return false;
 
222
      });
 
223
     
 
224
      thumbnail = new StreamViewAvatar();
 
225
      thumbnail.set_no_show_all(true);
 
226
      thumb_box.add(thumbnail);
 
227
      thumb_box.hide ();
 
228
 
 
229
      comments_box = new Gtk.VBox (false, 0);
 
230
      comments_box.set_no_show_all (true);
 
231
      vbox.pack_start (comments_box, false, false, 4);
 
232
 
169
233
      reply_to = new Gtk.Label ("");
170
234
      reply_to.set_no_show_all(true);
171
235
      reply_to.set_markup ("<span font_weight='light' font_size='small'></span>");
216
280
        return false;
217
281
      });
218
282
 
 
283
      cancellable = new Cancellable();
 
284
 
219
285
      Notify.init ("Gwibber");
220
286
    }
221
287
 
280
346
      if (x > 0 && x < a.width && y > 0 && y < a.height)
281
347
      {
282
348
        time.hide ();
 
349
        action_box.set_no_show_all (false);
283
350
        action_box.show_all ();
284
351
      }
285
352
      else
322
389
                             string _img_url,
323
390
                             string _img_src,
324
391
                             string _img_thumb,
325
 
                             string _img_name)
 
392
                             string _img_name,
 
393
                             string _video_picture,
 
394
                             string _video_src,
 
395
                             string _video_url,
 
396
                             string _video_name,
 
397
                             string _comments)
326
398
    {
 
399
      // hide and reset values to prevent reuse as the tiles change
 
400
      icon.hide ();
 
401
      action_box.hide ();
 
402
      action_box.set_no_show_all (true);
 
403
      comments_box.set_no_show_all (true);
 
404
      comments_box.hide ();
 
405
      thumb_box.set_no_show_all (true);
 
406
      thumb_box.hide ();
 
407
      thumbnail.hide();
 
408
      message.set_markup ("");
 
409
      name.set_markup ("");
 
410
      icon.set_from_icon_name ("stock_person", Gtk.IconSize.DIALOG);
 
411
      img_uri = null;
 
412
      img_src = null;
 
413
      // end hide and reset
 
414
 
 
415
      /* Cancel all pending async operations */
 
416
      if (img_id > 0)
 
417
        Source.remove(img_id);
 
418
      img_id = 0;
 
419
      img_uri = _img_src;
 
420
      if (_stream == "images")
 
421
      {
 
422
        if (_img_thumb != null && _img_thumb.length > 0)
 
423
          img_uri = _img_thumb;
 
424
        img_src = _img_url;
 
425
      }
 
426
      else if (_stream == "links")
 
427
      {
 
428
        img_uri = _link_picture;
 
429
        img_src = _link_url;
 
430
      }
 
431
      else if (_stream == "videos")
 
432
      {
 
433
        img_uri = _video_picture;
 
434
        img_src = _video_src;
 
435
      }
327
436
 
328
437
      if (_stream == "private")
329
438
        private.show ();
330
439
      else
331
440
        private.hide ();
332
441
 
333
 
      message.set_markup ("");
 
442
      foreach (uint _to_disconnect_id in _to_disconnect)
 
443
      {
 
444
        GLib.Source.remove (_to_disconnect_id);
 
445
        _to_disconnect.remove (_to_disconnect_id);
 
446
      }
 
447
 
 
448
      foreach (var w in comments_box.get_children ())
 
449
        if (w is Gtk.Widget)
 
450
          w.destroy ();
 
451
 
 
452
      if (_comments != null)
 
453
      {
 
454
        //comments_box.set_no_show_all (false);
 
455
        var parser = new Json.Parser();
 
456
        parser.load_from_data(_comments, -1);
 
457
        unowned Json.Node comments_node = null;
 
458
        comments_node = parser.get_root();
 
459
        if (comments_node != null)
 
460
        {
 
461
          Json.Object comments_obj = comments_node.get_object ();
 
462
          if (comments_obj != null)
 
463
          {
 
464
            if (comments_obj.has_member ("comments"))
 
465
            {
 
466
              Json.Array comments = comments_obj.get_array_member ("comments");
 
467
              for(int i = 0; i < comments.get_length(); i++) {
 
468
                var obj = comments.get_element(i).get_object();
 
469
                if (obj != null)
 
470
                {
 
471
                  var cvbox = new Gtk.VBox (false, 2);
 
472
                  var chbox = new Gtk.HBox (false, 2);
 
473
                  var clalignment = new Gtk.Alignment (0.5f, 0.0f, 0.0f, 0.0f);
 
474
                  chbox.pack_start (clalignment, false, false, 0);
 
475
  
 
476
                  var cicon = new StreamViewAvatar();
 
477
                  clalignment.add (cicon);
 
478
  
 
479
                  comments_box.pack_start (chbox, false, false, 2);
 
480
                  chbox.pack_start (cvbox, false, false, 2);
 
481
                  if (obj.has_member ("text"))
 
482
                  {
 
483
                    var ctext = obj.get_string_member ("text");
 
484
                    var ctext_label = new Gtk.Label ("");
 
485
                    ctext_label.set_selectable (true);
 
486
                    ctext_label.set_single_line_mode (false);
 
487
                    ctext_label.set_line_wrap (true);
 
488
                    ctext_label.set_line_wrap_mode (Pango.WrapMode.WORD_CHAR);
 
489
                    ctext_label.set_alignment (0.0f, 0.0f);
 
490
                    ctext_label.set_markup ("<span font_size='small'>" + ctext + "</span>");
 
491
                    cvbox.pack_end (ctext_label, false, false, 2);
 
492
                  }
 
493
                  if (obj.has_member ("sender"))
 
494
                  {
 
495
                    var _sender_obj = obj.get_object_member ("sender");
 
496
                    if (_sender_obj != null)
 
497
                    {
 
498
                      if (_sender_obj.has_member ("name"))
 
499
                      {
 
500
                        var cname = _sender_obj.get_string_member ("name");
 
501
                        var cname_box = new Gtk.HBox (false, 2);
 
502
                        var cname_label = new Gtk.Label ("");
 
503
                        cname_label.set_markup ("<b><span font_size='small'>" + cname + "</span></b>");
 
504
                        cname_box.pack_start (cname_label, false, false, 2);
 
505
                        cvbox.pack_end (cname_box, false, false, 2);
 
506
                      }  
 
507
                      if (_sender_obj.has_member ("image"))
 
508
                      {
 
509
                        var cimage = _sender_obj.get_string_member ("image");
 
510
                        var cached_cicon = utils.avatar_path(cimage);
 
511
                        if (cached_cicon != null)
 
512
                          cicon.set_from_file(cached_cicon);
 
513
                        else
 
514
                        {
 
515
                          cicon.set_from_icon_name ("stock_person", Gtk.IconSize.DIALOG);
 
516
                          cicon.show ();
 
517
                          uint cid = Idle.add (() => {
 
518
                            load_avatar_async.begin (cimage, cicon);
 
519
                            return false;
 
520
                          });
 
521
                          _to_disconnect.append (cid);
 
522
                        }
 
523
                      }
 
524
                    }
 
525
                  }
 
526
                  comments_box.set_no_show_all (false);
 
527
                  comments_box.show_all ();
 
528
                }
 
529
              }
 
530
            }
 
531
          }
 
532
        }
 
533
      }
 
534
 
334
535
 
335
536
      string display_name = _sender;
336
537
 
365
566
          if (_w is Gtk.Menu?)
366
567
          {
367
568
            Gtk.Menu _y = _w as Gtk.Menu;
368
 
            debug ("_y before has %u children", _y.get_children ().length());
369
569
            foreach (var _x in _y.get_children ())
370
570
            {
371
571
              _y.remove (_x);
372
572
              _x.destroy ();
373
573
              _x = null;
374
574
            }
375
 
            debug ("_y after has %u children", _y.get_children ().length());
376
575
          }
377
576
          _i.remove (_w);
378
577
          _w.destroy ();
384
583
        _i = null;
385
584
      }
386
585
 
387
 
 
388
 
      //action_box = null;
389
 
      //action_box = new GwibberGtk.ActionBox ();
390
 
 
391
586
      foreach (var _a in _accounts)
392
587
      {
393
588
        string _account;
394
589
        string _service;
395
590
        string _mid;
396
591
        (_account, _service, _mid) = _a.split(":");
 
592
 
397
593
        if (_account in icon_displayed)
398
594
          continue;
399
595
        icon_displayed += _account;
410
606
      else
411
607
        icon_box.reparent(lalignment);
412
608
 
 
609
      /* FIXME: We need to make this perform better before enabling it
413
610
      var _avatar_cache_image = utils.avatar_path(_icon_uri);
414
611
      if (_avatar_cache_image != null)
415
612
        icon.set_from_file(_avatar_cache_image);
416
613
      else
417
614
        icon.set_from_icon_name ("stock_person", Gtk.IconSize.DIALOG);
 
615
      */
418
616
 
419
617
 
420
 
      /* We need to make this perform better before enabling it
421
618
      if (_cache_avatar_id > 0)
422
619
        GLib.Source.remove(_cache_avatar_id);
423
620
 
427
624
        icon.set_from_file(_avatar_cache_image);
428
625
      } else
429
626
      {
430
 
        _cache_avatar_id = Timeout.add(100, () => {
431
 
          _avatar_cache_image = utils.avatar_path (_icon_uri);
432
 
          icon.set_from_file(_avatar_cache_image);
 
627
        icon.set_from_icon_name ("stock_person", Gtk.IconSize.DIALOG);
 
628
        _cache_avatar_id = Idle.add(() => {
 
629
          load_avatar_async.begin (_icon_uri, icon);
433
630
          return false;
434
 
          });
 
631
        });
435
632
      }
436
 
      */
437
633
 
438
634
      if (_stream == "user")
439
635
      {
467
663
        likes_hbox.show_all();
468
664
      }
469
665
 
 
666
      message.set_markup (_html);
 
667
 
 
668
      string link_str = null;
470
669
      if (_stream == "links")
471
670
      {
472
 
        string link_str = "<a href='" + _link_url + "'>" + _link_name + "</a>\n";
 
671
        link_str = "<a href='" + _link_url + "'>" + _link_name + "</a>\n";
473
672
        link_str += _link_desc;
474
 
        link_str += "\n";
475
 
        link_str += _link_caption;
476
673
        message.set_markup (link_str);
477
 
      } else {
478
 
        message.set_markup (_html);
479
 
      }
480
 
 
481
 
      if (_img_url.length > 0)
482
 
      {
483
 
        string img_str = _html;
484
 
        img_str += "\n\n";
485
 
        if (_img_name.length > 0)
486
 
          img_str += "<a href='" + _img_url + "'>" + _img_name + "</a>";
487
 
        else
488
 
          img_str += "<a href='" + _img_url + "'>" + _img_url + "</a>";
489
 
        message.set_markup (img_str);
490
 
      }
491
 
       
 
674
      }
 
675
 
 
676
      string video_str = null;
 
677
      if (_stream == "videos")
 
678
      {
 
679
        video_str = "<a href='" + _video_url + "'>" + _video_name + "</a>\n";
 
680
        video_str += _html;
 
681
        message.set_markup (video_str);
 
682
      }
 
683
 
 
684
      if (img_uri.length > 0)
 
685
      {
 
686
        /* Let's wait a while before loading in case there is some funny
 
687
         * business going on or the user is scrolling
 
688
         */
 
689
        string img_str = null;
 
690
        img_id = Timeout.add (200, () =>
 
691
        {
 
692
 
 
693
          if (_stream == "videos" && video_str.length > 0)
 
694
            img_str = video_str;
 
695
          else if (_stream == "links" && link_str.length > 0)
 
696
            img_str = link_str;
 
697
          else
 
698
          {
 
699
            if (_img_name.length > 0)
 
700
              img_str = "<a href='" + _img_url + "'>" + _img_name + "</a>\n";
 
701
            else
 
702
              img_str = "<a href='" + _img_url + "'>" + _img_url + "</a>\n";
 
703
            img_str += _html;
 
704
          }
 
705
 
 
706
          load_thumbnail_async.begin(img_str);
 
707
          return false;
 
708
        });
 
709
      }
492
710
 
493
711
      time_string = utils.generate_time_string ((uint)long.parse(_timestamp));
494
712
 
527
745
 
528
746
      queue_resize ();
529
747
      show ();
530
 
    }                        
531
 
 
 
748
    } 
 
749
 
 
750
    private async void load_avatar_async(string url, Gtk.Image cicon)
 
751
    {
 
752
      string t;
 
753
      var file = File.new_for_uri(url);
 
754
      try {
 
755
        var stream = yield file.read_async(Priority.DEFAULT, cancellable);
 
756
        Gdk.Pixbuf pixbuf = null;
 
757
        pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream,
 
758
                                                      48,
 
759
                                                      48,
 
760
                                                      false,
 
761
                                                      cancellable);
 
762
        if (pixbuf is Gdk.Pixbuf)
 
763
        {
 
764
          cicon.set_from_pixbuf (pixbuf);
 
765
          cicon.show();
 
766
          var cimage = Path.build_path(Path.DIR_SEPARATOR_S, Environment.get_user_cache_dir(), "gwibber/avatars", url.replace("/",""));
 
767
          t = "png";
 
768
          if (cimage.has_suffix ("JPG") || cimage.has_suffix ("jpeg") || cimage.has_suffix ("jpg"))
 
769
            t = "jpeg";
 
770
          pixbuf.save (cimage, t);
 
771
        }
 
772
      } catch (Error e) {
 
773
        debug("Error trying to load %s: %s", url, e.message);
 
774
        cicon.set_from_icon_name ("stock_person", Gtk.IconSize.DIALOG);
 
775
      }
 
776
     }
 
777
 
 
778
    private async void load_thumbnail_async(string img_markup)
 
779
    {
 
780
      Gtk.Allocation alloc;
 
781
      message.get_allocation (out alloc);
 
782
 
 
783
      img_id = 0;
 
784
      var file = File.new_for_uri(fix_image_uri(img_uri));
 
785
      try {
 
786
        var stream = yield file.read_async(Priority.DEFAULT, cancellable);
 
787
        Gdk.Pixbuf pixbuf = null;
 
788
        pixbuf = new Gdk.Pixbuf.from_stream_at_scale (stream,
 
789
                                                        alloc.width - 24,
 
790
                                                        100,
 
791
                                                        true,
 
792
                                                        cancellable);
 
793
        if (pixbuf is Gdk.Pixbuf)
 
794
        {
 
795
          thumbnail.set_from_pixbuf (pixbuf);
 
796
          thumbnail.show ();
 
797
          thumb_box.set_no_show_all (false);
 
798
          thumb_box.show ();
 
799
        }
 
800
        message.set_markup (img_markup);
 
801
      } catch (Error e) {
 
802
        debug("Error trying to load %s: %s", img_uri, e.message);
 
803
        thumbnail.hide ();
 
804
        thumb_box.set_no_show_all (true);
 
805
        thumb_box.hide ();
 
806
      }
 
807
     }
 
808
 
 
809
    /* Do any magic we want to here */
 
810
    private string fix_image_uri(string uri)
 
811
    {
 
812
      var ret = uri;
 
813
 
 
814
      if (uri.contains("imgur.com") && !uri.has_suffix(".png"))
 
815
      {
 
816
        /* let's point to the static content in imgur */
 
817
        var last = uri.rstr("/").substring(1);
 
818
        ret = "http://i.imgur.com/%s.png".printf(last); 
 
819
      }
 
820
      else if (uri.contains("youtube.com"))
 
821
      {
 
822
        string id = uri.rstr("/").substring(1);
 
823
 
 
824
        if (uri.contains("watch?v="))
 
825
        {
 
826
          if (uri.contains("&"))
 
827
            id = uri.split("v=")[1].split("&")[0];
 
828
          else
 
829
            id = uri.split("v=")[1];
 
830
        }
 
831
        ret = "http://img.youtube.com/vi/%s/default.jpg".printf(id);
 
832
      }
 
833
      return ret;
 
834
    }
532
835
 
533
836
    private bool on_avatar_click (Gdk.EventButton button)
534
837
    {
542
845
        return false;
543
846
    }
544
847
  }
545
 
 
546
 
 
547
848
}