1 /* timeline_list_abstract.vala
3 * Copyright (C) 2009-2010 troorl
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.
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.
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/>.
19 * troorl <troorl@gmail.com>
28 public abstract class TimelineListAbstract : HBox {
31 protected VScrollbar slider;
32 protected bool get_more_allow = true;
33 protected WebView view;
34 protected ScrolledWindow scroll;
36 protected MoreWindow more;
38 protected Template template;
39 protected RestAPIAbstract api;
40 protected ArrayList<RestAPI.Status> lst;
42 protected double current_scroll_pos;
44 /* statuses in list */
45 protected int _items_count;
46 public int items_count {
47 get { return _items_count; }
48 set { _items_count = value; }
52 public RadioAction act;
56 set { _popup = value; }
59 //protected bool focused;
60 protected int last_focused = 0; //time of the last readed status
62 protected Window parent;
63 protected Accounts accounts;
65 protected bool need_more_button = true;
67 public signal void start_update(string req);
68 public signal void finish_update();
69 public signal void updating_error(string msg);
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);
78 protected signal void need_more();
80 //focus of the main window
81 public virtual bool parent_focus {
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) {
96 view.navigation_policy_decision_requested.connect(link_clicking);
98 scroll = new ScrolledWindow(null, null);
99 scroll.set_policy(PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
102 slider = (VScrollbar)scroll.get_vscrollbar();
104 //return scroll position to the current
105 view.load_finished.connect((f) => {
106 slider.set_value(current_scroll_pos);
109 //emit signal, when scrollbar in the lower boundary
110 //strange bug (segfault) on some actions
112 slider.value_changed.connect(() => {
116 double max = slider.adjustment.upper;
117 double current = slider.get_value();
118 double scroll_size = slider.adjustment.page_size;
120 if(current + scroll_size == max) {
122 need_more(); //emit signal
126 this.need_more.connect(() => {
131 var event_view = new EventBox();
132 event_view.add(scroll);
134 vbox = new VBox(false, 0);
135 vbox.pack_end(event_view, true, true, 0);
137 more = new MoreWindow();
138 more.moar_event.connect(get_older);
140 event_view.set_events(Gdk.EventMask.BUTTON_MOTION_MASK);
141 event_view.motion_notify_event.connect((event) => {
142 if(!need_more_button)
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);
151 //debug("motion: %fx%f", ax, ay);
152 //debug("root: %fx%f", event.x_root, event.y_root);
160 this.pack_start(vbox, true, true, 0);
161 //this.pack_start(fixed, true, true, 0);
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;
171 parent.focus_in_event.connect((w, e) => {
175 parent.focus_out_event.connect((w, e) => {
176 parent_focus = false;
182 act = new RadioAction(fname, icon_name, icon_desc, null, 0);
184 act.set_active(_active);
185 act.changed.connect((current) => {
193 view.button_press_event.connect(show_popup_menu);
195 if(accounts.accounts.size > 0)
201 public virtual void set_empty(bool full = true) {
204 update_content(template.generate_message(_("Empty")));
207 public virtual ArrayList<Status>? update() {
211 public void update_auth() {
212 var acc = accounts.get_current_account();
226 public virtual void show_smart() {
229 last_focused = (int)lst.get(0).created_at.mktime();
232 public virtual void hide_smart() {
236 protected void start_screen() {
237 update_content(template.generate_message(_("Connecting...")));
240 protected virtual void update_content(string content) {
241 current_scroll_pos = ((VScrollbar)scroll.get_vscrollbar()).get_value();
243 view.load_string(content, "text/html", "utf8", "file:///");
246 protected virtual void destroy_status(string id) {}
248 protected abstract void get_older();
250 public virtual void refresh(bool with_favorites = false) {
254 update_content(template.generate_timeline(lst, last_focused, with_favorites));
257 /* removing extra statuses or messages */
258 protected void delete_extra() {
259 while(lst.size > _items_count)
260 lst.remove_at(lst.size - 1);
264 private bool link_clicking(WebFrame p0, NetworkRequest request,
265 WebNavigationAction action, WebPolicyDecision decision) {
267 if(request.uri == "")
270 var p = request.uri.split("://");
274 if(prot == "http" || prot == "https" || prot == "ftp") {
276 GLib.Process.spawn_async(".", {"/usr/bin/xdg-open", request.uri}, null,
277 GLib.SpawnFlags.STDOUT_TO_DEV_NULL, null, out pid);
287 string screen_name = params.replace("?user=", "");
288 user_info(screen_name);
292 string screen_name = params;
293 directreply(screen_name);
297 string status_id = params;
298 replyto(find_status(status_id));
303 string status_id = params;
304 retweet(find_status(status_id));
308 string status_id = params;
309 favorited(status_id);
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?")));
318 var response = message_dialog.run();
319 if(response == ResponseType.YES) {
320 message_dialog.destroy();
321 var status_id = params;
323 destroy_status(status_id);
325 message_dialog.destroy();
330 string status_id = params;
332 var d = new StatusViewDialog(parent, accounts, template,
333 find_status(status_id));
346 /* get status by id */
347 private Status find_status(string id) {
349 foreach(Status _status in lst) {
350 if(_status.id == id) {
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;
364 foreach(Status s in lst) {
372 view.set_sensitive(true);
377 if(!status.is_favorite) //add to favorites
378 api.favorite_create(id);
380 api.favorite_destroy(id);
381 } catch(RestError e) {
382 updating_error(e.message);
383 view.set_sensitive(true);
387 status.is_favorite = !status.is_favorite;
390 if(status.is_favorite) {
391 img_path = Config.FAVORITE_PATH;
392 deleted(_("Message was added to favorites")); //signal
395 img_path = Config.FAVORITE_NO_PATH;
396 deleted(_("Message was removed from favorites")); //signal
399 string script = """var m = document.getElementById('fav_%s');
400 m.src = '%s';""".printf(status.id, img_path);
401 view.execute_script(script);
403 view.set_sensitive(true);
407 /* show popup menu */
408 private bool show_popup_menu(Gdk.EventButton event) {
409 if((event.type == Gdk.EventType.BUTTON_PRESS) && (event.button == 3)) {
411 _popup.popup(null, null, null, event.button, event.time);