1/* This file is part of Cawbird, a Gtk+ linux Twitter client forked from Corebird. 2 * Copyright (C) 2013 Timm Bäder (Corebird) 3 * 4 * Cawbird is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * Cawbird is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with cawbird. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18class DMListEntry : Gtk.ListBoxRow, Cb.TwitterItem { 19 private Gtk.Grid grid; 20 private AvatarWidget avatar_image; 21 private Gtk.CheckButton delete_checkbutton; 22 private Gtk.Label text_label; 23 private Gtk.Label screen_name_label; 24 private TextButton name_button; 25 private Gtk.Label time_delta_label; 26 private MediaButton media_button; 27 private string _text; 28 private Json.Object? _message_data; 29 private Cb.Media? _media; 30 private Cb.TextEntity[] _entities; 31 32 public bool is_checked { 33 get { 34 return delete_checkbutton.active; 35 } 36 set { 37 set_checked(value); 38 } 39 } 40 41 public signal void avatar_clicked(); 42 43 public string text { 44 set { 45 _text = value; 46 if (_message_data == null) { 47 text_label.label = value; 48 text_label.visible = value != null && value != ""; 49 } 50 } 51 } 52 53 public string screen_name { 54 set { 55 screen_name_label.label = "@" + value; 56 screen_name_label.tooltip_text = "@" + value; 57 } 58 } 59 60 public new string name { 61 set { 62 name_button.set_text (value); 63 name_button.tooltip_text = value; 64 } 65 } 66 67 public Cb.Media media { 68 get { return _media; } 69 set { 70 _media = value; 71 if (_media != null) { 72 if (media_button == null) { 73 media_button = new MediaButton(media); 74 grid.attach (media_button, 1, 2, 3, 1); 75 media_button.clicked.connect(media_clicked_cb); 76 media_button.show(); 77 } 78 else { 79 media_button.media = _media; 80 } 81 } 82 } 83 } 84 85 // A property would be nice, but Cb.TextEntity isn't a GLib.Object so we can't 86 public void set_entities(Cb.TextEntity[] value) { 87 _entities = value; 88 set_dm_text(); 89 } 90 91 public Json.Object? message_data { 92 get { return _message_data; } 93 set { 94 _message_data = value; 95 set_dm_text(); 96 } 97 } 98 99 public Cairo.Surface avatar { 100 set { avatar_image.surface = value; } 101 } 102 103 public bool seen { 104 get { return true; } 105 set {} 106 } 107 108 private GLib.TimeSpan last_timediff; 109 public int64 timestamp; 110 public int64 id; 111 public int64 user_id; 112 public unowned MainWindow main_window; 113 114 public DMListEntry () { 115 this.set_activatable (false); 116 this.get_style_context ().add_class ("tweet"); 117 118 grid = new Gtk.Grid (); 119 grid.margin = 6; 120 grid.show (); 121 this.add (grid); 122 123 this.avatar_image = new AvatarWidget (); 124 avatar_image.size = 48; 125 avatar_image.set_valign (Gtk.Align.START); 126 avatar_image.show(); 127 delete_checkbutton = new Gtk.CheckButton(); 128 delete_checkbutton.halign = Gtk.Align.CENTER; 129 delete_checkbutton.valign = Gtk.Align.CENTER; 130 delete_checkbutton.button_release_event.connect(avatar_button_release_cb); 131 delete_checkbutton.enter_notify_event.connect(Utils.set_pointer_on_mouseover); 132 delete_checkbutton.leave_notify_event.connect(Utils.set_pointer_on_mouseover); 133 var avatar_overlay = new Gtk.Overlay(); 134 avatar_overlay.add(avatar_image); 135 avatar_overlay.add_overlay(delete_checkbutton); 136 avatar_overlay.show(); 137 var event_box = new Gtk.EventBox(); 138 event_box.margin = 4; 139 event_box.margin_end = 12; 140 event_box.valign = Gtk.Align.START; 141 event_box.add(avatar_overlay); 142 event_box.button_release_event.connect(avatar_button_release_cb); 143 event_box.enter_notify_event.connect(Utils.set_pointer_on_mouseover); 144 event_box.leave_notify_event.connect(Utils.set_pointer_on_mouseover); 145 event_box.show(); 146 grid.attach (event_box, 0, 0, 1, 3); 147 148 this.name_button = new TextButton (); 149 name_button.set_valign (Gtk.Align.BASELINE); 150 name_button.show (); 151 grid.attach (name_button, 1, 0, 1, 1); 152 153 this.screen_name_label = new Gtk.Label (null); 154 screen_name_label.set_margin_start (6); 155 screen_name_label.set_margin_end (6); 156 screen_name_label.set_valign (Gtk.Align.BASELINE); 157 screen_name_label.get_style_context ().add_class ("dim-label"); 158 screen_name_label.show (); 159 grid.attach (screen_name_label, 2, 0, 1, 1); 160 161 this.time_delta_label = new Gtk.Label (null); 162 time_delta_label.set_halign (Gtk.Align.END); 163 time_delta_label.set_valign (Gtk.Align.BASELINE); 164 time_delta_label.set_hexpand (true); 165 time_delta_label.show (); 166 time_delta_label.get_style_context ().add_class ("dim-label"); 167 grid.attach (time_delta_label, 3, 0, 1, 1); 168 169 this.text_label = new Gtk.Label (null); 170 text_label.set_margin_top (6); 171 text_label.set_margin_end (6); 172 text_label.set_margin_bottom (6); 173 text_label.set_hexpand (true); 174 text_label.set_vexpand (true); 175 text_label.set_xalign (0.0f); 176 text_label.set_line_wrap (true); 177 text_label.set_line_wrap_mode (Pango.WrapMode.WORD_CHAR); 178 text_label.set_use_markup (true); 179 text_label.set_use_markup (true); 180 text_label.set_selectable (true); 181 text_label.show (); 182 text_label.activate_link.connect((uri) => { 183 this.grab_focus (); 184 return TweetUtils.activate_link (uri, main_window); 185 }); 186 grid.attach (text_label, 1, 1, 3, 1); 187 188 name_button.clicked.connect (() => { 189 var bundle = new Cb.Bundle (); 190 bundle.put_int64 (ProfilePage.KEY_USER_ID, user_id); 191 bundle.put_string (ProfilePage.KEY_SCREEN_NAME, screen_name_label.label.substring (1)); 192 main_window.main_widget.switch_page (Page.PROFILE, bundle); 193 }); 194 195 this._entities = new Cb.TextEntity[0]; 196 197 this.key_release_event.connect(key_released_cb); 198 Settings.get ().changed["text-transform-flags"].connect(set_dm_text); 199 Settings.get ().changed["tweet-scale"].connect (set_dm_text_scale); 200 201 set_dm_text_scale(); 202 this.show (); 203 } 204 205 ~DMListEntry() { 206 Settings.get ().changed["text-transform-flags"].disconnect(set_dm_text); 207 } 208 209 [GtkCallback] 210 private bool key_released_cb (Gdk.EventKey evt) { 211#if DEBUG 212 switch(evt.keyval) { 213 case Gdk.Key.k: 214 if (_message_data != null) { 215 var gen = new Json.Generator(); 216 var node = new Json.Node(Json.NodeType.OBJECT); 217 node.set_object(_message_data); 218 gen.set_root(node); 219 gen.set_pretty(true); 220 string json = gen.to_data(null); 221 stderr.printf(json + "\n"); 222 } 223 else { 224 stderr.printf("Old format DM - no JSON\n"); 225 } 226 return Gdk.EVENT_STOP; 227 } 228#endif 229 return Gdk.EVENT_PROPAGATE; 230 } 231 232 private bool avatar_button_release_cb(Gtk.Widget widget, Gdk.EventButton event) { 233 if (event.button == Gdk.BUTTON_PRIMARY) { 234 set_checked(!is_checked); 235 avatar_clicked(); 236 return Gdk.EVENT_STOP; 237 } 238 return Gdk.EVENT_PROPAGATE; 239 240 } 241 242 private void set_dm_text() { 243 if (_message_data != null) { 244 var msg = _message_data.get_string_member ("text"); 245 // Force removing media links, because they don't actually work for DMs 246 var flags = Settings.get_text_transform_flags () | Cb.TransformFlags.REMOVE_MEDIA_LINKS; 247 msg = Cb.TextTransform.text (msg, _entities, flags, 0, 0); 248 text_label.label = msg; 249 text_label.visible = msg != ""; 250 } 251 } 252 253 private void set_dm_text_scale () { 254 var scale = Settings.get_tweet_scale(); 255 var new_attribs = new Pango.AttrList(); 256 var scale_attr = Pango.attr_scale_new(scale); 257 new_attribs.insert((owned)scale_attr); 258 text_label.set_attributes(new_attribs); 259 } 260 261 public void load_avatar (string avatar_url) { 262 string url = avatar_url; 263 if (this.get_scale_factor () >= 2) 264 url = url.replace ("_normal", "_bigger"); 265 266 Twitter.get ().get_avatar.begin (user_id, url, avatar_image, 48 * this.get_scale_factor ()); 267 } 268 269 public int update_time_delta (GLib.DateTime? now = null) { 270 GLib.DateTime cur_time; 271 if (now == null) 272 cur_time = new GLib.DateTime.now_local (); 273 else 274 cur_time = now; 275 276 GLib.DateTime then = new GLib.DateTime.from_unix_local (timestamp); 277 time_delta_label.label = Utils.get_time_delta (then, cur_time); 278 return (int)(cur_time.difference (then) / 1000.0 / 1000.0); 279 } 280 281 public int64 get_sort_factor () { 282 return timestamp; 283 } 284 285 public int64 get_timestamp () { 286 return timestamp; 287 } 288 289 public GLib.TimeSpan get_last_set_timediff () { 290 return this.last_timediff; 291 } 292 293 public void set_last_set_timediff (GLib.TimeSpan span) { 294 this.last_timediff = span; 295 } 296 297 private void media_clicked_cb(MediaButton button, double px, double py) { 298 TweetUtils.handle_media_click ({media}, this.main_window, 0); 299 } 300 301 private void set_checked(bool checked) { 302 if (checked) { 303 avatar_image.opacity = 0.5; 304 delete_checkbutton.active = true; 305 delete_checkbutton.show(); 306 } 307 else { 308 avatar_image.opacity = 1; 309 delete_checkbutton.active = false; 310 delete_checkbutton.hide(); 311 } 312 } 313} 314 315 316