Fix previous commit
[debian/pino.git] / src / timeline_list_abstract.vala
1 /* timeline_list_abstract.vala
2  *
3  * Copyright (C) 2009-2010  troorl
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU Lesser General Public License as published by
7  * the Free Software Foundation, either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  *
18  * Author:
19  *      troorl <troorl@gmail.com>
20  */
21
22 using WebKit;
23 using Auth;
24 using RestAPI;
25 using Gee;
26 using Gtk;
27
28 public abstract class TimelineListAbstract : HBox {
29         
30         protected VBox vbox;
31         protected VScrollbar slider;
32         protected bool get_more_allow = true;
33         protected WebView view;
34         protected ScrolledWindow scroll;
35         
36         protected MoreWindow more;
37         
38         protected Template template;
39         protected RestAPIAbstract api;
40         protected ArrayList<RestAPI.Status> lst;
41         
42         protected double current_scroll_pos;
43         
44         /* statuses in list */
45         protected int _items_count;
46         public int items_count {
47                 get { return _items_count; }
48                 set { _items_count = value; }
49         }
50         
51         public Icon icon;
52         public RadioAction act;
53         
54         private Menu _popup;
55         public Menu popup {
56                 set { _popup = value; }
57         }
58         
59         //protected bool focused;
60         protected int last_focused = 0; //time of the last readed status
61         
62         protected Window parent;
63         protected Accounts accounts;
64         
65         protected bool need_more_button = true;
66         
67         public signal void start_update(string req);
68         public signal void finish_update();
69         public signal void updating_error(string msg);
70         
71         public signal void nickto(string screen_name);
72         public signal void user_info(string screen_name);
73         public signal void replyto(Status status);
74         public signal void retweet(Status status);
75         public signal void directreply(string screen_name);
76         public signal void deleted(string message);
77         
78         protected signal void need_more();
79         
80         //focus of the main window
81         public virtual bool parent_focus {
82                 get { return false; }
83                 set {
84                         if(!value)
85                                 more.hide();
86                 }
87         }
88         
89         public TimelineListAbstract(Window _parent, Accounts _accounts, TimelineType timeline_type,
90                 Template _template, int __items_count, Icon? _icon, string fname = "",
91                 string icon_name = "", string icon_desc = "", bool _active = false) {
92                 
93                 accounts = _accounts;
94                 
95                 view = new WebView();
96                 view.navigation_policy_decision_requested.connect(link_clicking);
97                 
98                 scroll = new ScrolledWindow(null, null);
99                 scroll.set_policy(PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
100                 scroll.add(view);
101                 
102                 slider = (VScrollbar)scroll.get_vscrollbar();
103                 
104                 //return scroll position to the current
105                 view.load_finished.connect((f) => {
106                         slider.set_value(current_scroll_pos);
107                 });
108                 
109                 //emit signal, when scrollbar in the lower boundary
110                 //strange bug (segfault) on some actions
111                 /*
112                 slider.value_changed.connect(() => {
113                         if(!get_more_allow)
114                                 return;
115                         
116                         double max = slider.adjustment.upper;
117                         double current = slider.get_value();
118                         double scroll_size = slider.adjustment.page_size;
119                         
120                         if(current + scroll_size == max) {
121                                 debug("need more");
122                                 need_more(); //emit signal
123                         }
124                 });
125                 
126                 this.need_more.connect(() => {
127                         mb.show();
128
129                 });
130                 */
131                 var event_view = new EventBox();
132                 event_view.add(scroll);
133                 
134                 vbox = new VBox(false, 0);
135                 vbox.pack_end(event_view, true, true, 0);
136                 
137                 more = new MoreWindow();
138                 more.moar_event.connect(get_older);
139                 
140                 event_view.set_events(Gdk.EventMask.BUTTON_MOTION_MASK);
141                 event_view.motion_notify_event.connect((event) => {
142                         if(!need_more_button)
143                                 return true;
144                         
145                         int height = allocation.height;
146                         if(height - event.y > 20 && height - event.y < 60 && event.x > 20 && event.x < 60) {
147                                 int ax = (int)(event.x_root - event.x + 20);
148                                 int ay = (int)(event.y_root + height - event.y - 60);
149                                 more.show_at(ax, ay);
150                                 
151                                 //debug("motion: %fx%f", ax, ay);
152                                 //debug("root: %fx%f", event.x_root, event.y_root);
153                         } else {
154                                 if(more.visible)
155                                         more.hide();
156                         }
157                         return true;
158                 });
159                 
160                 this.pack_start(vbox, true, true, 0);
161                 //this.pack_start(fixed, true, true, 0);
162                 
163                 var acc = accounts.get_current_account();
164                 api = new RestAPITimeline(acc, timeline_type);
165                 api.request.connect((req) => start_update(req));
166                 template = _template;
167                 template.emit_for_refresh.connect(() => refresh());
168                 lst = new ArrayList<RestAPI.Status>();
169                 _items_count = __items_count;
170                 parent = _parent;
171                 parent.focus_in_event.connect((w, e) => {
172                         parent_focus = true;
173                         return false;
174                 });
175                 parent.focus_out_event.connect((w, e) => {
176                         parent_focus = false;
177                         return false;
178                 });
179                 
180                 //create new action
181                 icon = _icon;
182                 act = new RadioAction(fname, icon_name, icon_desc, null, 0);
183                 act.set_gicon(icon);
184                 act.set_active(_active);
185                 act.changed.connect((current) => {
186                         if(act == current)
187                                 show_smart();
188                         else
189                                 hide_smart();
190                 });
191                 
192                 //popup menu actions
193                 view.button_press_event.connect(show_popup_menu);
194                 
195                 if(accounts.accounts.size > 0)
196                         start_screen();
197                 else
198                         set_empty();
199         }
200         
201         public virtual void set_empty(bool full = true) {
202                 lst.clear();
203                 last_focused = 0;
204                 update_content(template.generate_message(_("Empty")));
205         }
206         
207         public virtual ArrayList<Status>? update() {
208                 return null;
209         }
210         
211         public void update_auth() {
212                 var acc = accounts.get_current_account();
213                 api.set_auth(acc);
214                 
215                 lst.clear();
216                 last_focused = 0;
217                 
218                 if(acc == null)
219                         set_empty();
220                 else {
221                         start_screen();
222                         update();
223                 }
224         }
225         
226         public virtual void show_smart() {
227                 show();
228                 if(lst.size > 0)
229                         last_focused = (int)lst.get(0).created_at.mktime();
230         }
231         
232         public virtual void hide_smart() {
233                 hide();
234         }
235         
236         protected void start_screen() {
237                 update_content(template.generate_message(_("Connecting...")));
238         }
239         
240         protected virtual void update_content(string content) {
241                 current_scroll_pos = ((VScrollbar)scroll.get_vscrollbar()).get_value();
242                 
243                 view.load_string(content, "text/html", "utf8", "file:///");
244         }
245         
246         protected virtual void destroy_status(string id) {}
247         
248         protected abstract void get_older();
249         
250         public virtual void refresh(bool with_favorites = false) {
251                 if(lst.size == 0)
252                         set_empty();
253                 else
254                         update_content(template.generate_timeline(lst, last_focused, with_favorites));
255         }
256         
257         /* removing extra statuses or messages */
258         protected void delete_extra() {
259                 while(lst.size > _items_count)
260                         lst.remove_at(lst.size - 1);
261         }
262         
263         /* list events */
264         private bool link_clicking(WebFrame p0, NetworkRequest request,
265                 WebNavigationAction action, WebPolicyDecision decision) {
266                 
267                 if(request.uri == "")
268                         return false;
269                 
270                 var p = request.uri.split("://");
271                 var prot = p[0];
272                 var params = p[1];
273                 
274                 if(prot == "http" || prot == "https" || prot == "ftp") {
275                         GLib.Pid pid;
276                         GLib.Process.spawn_async(".", {"/usr/bin/xdg-open", request.uri}, null,
277                                 GLib.SpawnFlags.STDOUT_TO_DEV_NULL, null, out pid);
278                         return true;
279                 }
280                 
281                 switch(prot) {
282                         case "nickto":
283                                 nickto(params);
284                                 return true;
285                         
286                         case "userinfo":
287                                 string screen_name = params.replace("?user=", "");
288                                 user_info(screen_name);
289                                 return true;
290                         
291                         case "directreply":
292                                 string screen_name = params;
293                                 directreply(screen_name);
294                                 return true;
295                         
296                         case "replyto":
297                                 string status_id = params;
298                                 replyto(find_status(status_id));
299                                 return true;
300                         
301                         case "retweet":
302                                 debug("start");
303                                 string status_id = params;
304                                 retweet(find_status(status_id));
305                                 return true;
306                         
307                         case "favorited":
308                                 string status_id = params;
309                                 favorited(status_id);
310                                 return true;
311                         
312                         case "delete":
313                                 var message_dialog = new MessageDialog(parent,
314                                         Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
315                                         Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO,
316                                         (_("Sure you want to delete this status?")));
317                                 
318                                 var response = message_dialog.run();
319                                 if(response == ResponseType.YES) {
320                                         message_dialog.destroy();
321                                         var status_id = params;
322                                         debug(status_id);
323                                         destroy_status(status_id);
324                                 }
325                                 message_dialog.destroy();
326                                 
327                                 return true;
328                         
329                         case "showtree":
330                                 string status_id = params;
331                                 debug(status_id);
332                                 var d = new StatusViewDialog(parent, accounts, template,
333                                         find_status(status_id));
334                                 //dialog.run();
335                                 
336                                 return true;
337                         
338                         case "file":
339                                 return false;
340                         
341                         default:
342                                 return true;
343                 }
344         }
345         
346         /* get status by id */
347         private Status find_status(string id) {
348                 Status st = null;
349                 foreach(Status _status in lst) {
350                         if(_status.id == id) {
351                                 st = _status;
352                                 break;
353                         }
354                 }
355                 return st;
356         }
357         
358         /** add/remove to favorites */
359         protected virtual void favorited(string id) {
360                 debug("start favorite");
361                 view.set_sensitive(false);
362                 Status? status = null;
363                 
364                 foreach(Status s in lst) {
365                         if(s.id == id) {
366                                 status = s;
367                                 break;
368                         }
369                 }
370                 
371                 if(status == null) {
372                         view.set_sensitive(true);
373                         return;
374                 }
375                 
376                 try {
377                         if(!status.is_favorite) //add to favorites
378                                 api.favorite_create(id);
379                         else
380                                 api.favorite_destroy(id);
381                 } catch(RestError e) {
382                         updating_error(e.message);
383                         view.set_sensitive(true);
384                         return;
385                 }
386                 
387                 status.is_favorite = !status.is_favorite;
388                 
389                 string img_path;
390                 if(status.is_favorite) {
391                         img_path = Config.FAVORITE_PATH;
392                         deleted(_("Message was added to favorites")); //signal
393                 }
394                 else {
395                         img_path = Config.FAVORITE_NO_PATH;
396                         deleted(_("Message was removed from favorites")); //signal
397                 }
398                 
399                 string script = """var m = document.getElementById('fav_%s');
400                         m.src = '%s';""".printf(status.id, img_path);
401                 view.execute_script(script);
402                 
403                 view.set_sensitive(true);
404                 debug("ok");
405         }
406         
407         /* show popup menu */
408         private bool show_popup_menu(Gdk.EventButton event) {
409                 if((event.type == Gdk.EventType.BUTTON_PRESS) && (event.button == 3)) {
410                         if(_popup != null)
411                                 _popup.popup(null, null, null, event.button, event.time);
412                         
413                         return true;
414                 }
415                 
416                 return false;
417         }
418 }