Imported Upstream version 0.2.1
[debian/pino.git] / src / re_tweet.vala
1 /* re_tweet.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 Gtk;
23 using Gee;
24 using Auth;
25 using RestAPI;
26
27 public class ReTweet : VBox {
28         
29         public static enum Style { CLASSIC, UNI, VIA }
30         
31         private static enum State { NEW, REPLY, DIRECT_REPLY, RETWEET }
32         
33         private State state;
34         
35         private RestAPIRe api;
36         
37         private TextView entry;
38         public TextView text_entry {
39                 public get { return entry; }
40         }
41         
42         public string text {
43                 public owned get
44                 { return entry.buffer.text; }
45                 set
46                 { entry.buffer.set_text(value, (int)value.size()); }
47         }
48         
49         private Accounts accounts;
50         
51         private Image status_icon;
52         private Label label;
53         public Label user_label;
54         
55         public Action shortAct;
56         
57         private DmEntry direct_entry;
58         private ToolButton close_btn;
59         private Window parent;
60         private Prefs prefs;
61         private UrlShort url_short;
62         private string reply_id = "";
63         
64         Regex nicks;
65         Regex urls;
66         Regex tags;
67         
68         SystemStyle gtk_style;
69         
70         public signal void start_update(string req_url);
71         public signal void sending_data(string message);
72         public signal void data_sent(string message);
73         public signal void data_error_sent(string message);
74         public signal void status_updated(Status status);
75         
76         public ReTweet(Window _parent, Prefs _prefs, Accounts _accounts, Cache cache, SystemStyle _gtk_style) {
77                 parent = _parent;
78                 prefs = _prefs;
79                 accounts = _accounts;
80                 gtk_style = _gtk_style;
81                 
82                 var acc = accounts.get_current_account();
83                 api = new RestAPIRe(acc);
84                 api.request.connect((req) => start_update(req));
85                 
86                 url_short = new UrlShort(prefs, api);
87                 
88                 shortAct = new Action("UrlShort", _("Shorten URLs..."), null, null);
89                 shortAct.set_sensitive(false);
90                 shortAct.activate.connect(() => {
91                         shortAct.set_sensitive(false);
92                         set_sensitive(false);
93                         sending_data(_("Shortening URLs...")); //signal
94                         
95                         string reply = url_short.shortit(text);
96                         
97                         if(reply != text)
98                                 data_sent(_("URLs was shortened successfully")); //signal
99                         else
100                                 data_sent(_("Nothing to shorten")); //signal
101                         
102                         text = reply;
103                         
104                         shortAct.set_sensitive(true);
105                         set_sensitive(true);
106                 });
107                 
108                 //enable or disable shrten url action
109                 show.connect((event) => {
110                         shortAct.set_sensitive(true);
111                 });
112                 hide.connect((event) => {
113                         shortAct.set_sensitive(false);
114                 });
115                 
116                 //gui setup
117                 border_width = 0;
118                 set_homogeneous(false);
119                 set_spacing(2);
120                 
121                 var l_box = new HBox(false, 2);
122                 status_icon = new Image();
123                 user_label = new Label(_("New status:"));
124                 
125                 direct_entry = new DmEntry(api);
126                 
127                 label = new Label("<b>140</b>");
128                 label.set_use_markup(true);
129                 
130                 Image close_img = new Image();
131                 close_img.set_from_stock("gtk-close", IconSize.MENU);
132                 close_img.set_tooltip_text(_("Hide"));
133                 var event_close = new EventBox();
134                 event_close.add(close_img);
135                 event_close.set_events(Gdk.EventMask.BUTTON_PRESS_MASK);
136                 event_close.button_press_event.connect((event) => {
137                         hide();
138                         return false;
139                 });
140                 
141                 l_box.pack_start(status_icon, false, false, 2);
142                 l_box.pack_start(user_label, false, false, 2);
143                 l_box.pack_start(direct_entry, false, false, 2);
144                 l_box.pack_end(event_close, false, false, 2);
145                 l_box.pack_end(label, false, false, 2);
146                 
147                 entry = new TextView();
148                 entry.set_size_request(-1, 60);
149                 entry.set_wrap_mode(Gtk.WrapMode.WORD);
150                 entry.key_press_event.connect(hide_or_send);
151                 entry.buffer.changed.connect(change);
152                 
153                 entry.buffer.create_tag("red_bg", "background", "#ffa4a4");
154                 entry.buffer.create_tag("url", "foreground", gtk_style.sl_color);
155                 entry.buffer.create_tag("nick", "weight", Pango.Weight.BOLD);
156                 entry.buffer.create_tag("tag", "foreground", gtk_style.sl_color,
157                         "weight", Pango.Weight.BOLD);
158                 
159                 nicks = new Regex("(^|\\s)@([A-Za-z0-9_]+)");
160                 urls = new Regex("((http|https|ftp)://([\\S]+)\\.([\\S]+))");
161                 tags = new Regex("((^|\\s)\\#[A-Za-z0-9_]+)");
162                 
163                 var scroll = new ScrolledWindow(null, null);
164         scroll.set_policy(PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
165         scroll.add(entry);
166                 
167                 var hbox = new HBox(false, 1);
168                 hbox.pack_start(scroll, true, true, 0);
169                 
170                 var frame = new Frame(null);
171                 frame.set_size_request(-1, 60);
172                 frame.add(hbox);
173                 
174                 var sep = new HSeparator();
175                 
176                 pack_start(sep, false, false, 0);
177                 pack_start(l_box, false, false, 0);
178                 pack_start(frame, false, true, 0);
179         }
180         
181         public void update_auth() {
182                 var acc = accounts.get_current_account();
183                 
184                 api.set_auth(acc);
185         }
186         
187         public void set_screen_name(string user_name) {
188                 user_label.set_text(_("Reply to <b>%s</b>:").printf(user_name));
189                 user_label.set_use_markup(true);
190         }
191         
192         private void clear() {
193                 text = "";
194         }
195         
196         public void insert(string str) {
197                 entry.buffer.insert_at_cursor(str, (int)str.length);
198         }
199         
200         /* when user clicked on a nick */
201         public void set_nickto(string screen_name) {
202                 if(!visible)
203                         set_state_new();
204                         
205                 insert("@%s ".printf(screen_name));
206         }
207         
208         public void set_state_new() {
209                 state = State.NEW;
210                 
211                 direct_entry.hide();
212                 clear();
213                 show();
214                 status_icon.set_from_stock(STOCK_EDIT, IconSize.SMALL_TOOLBAR);
215                 user_label.set_text(_("New status:"));
216                 parent.set_focus(text_entry);
217         }
218         
219         public void set_state_reply(Status status) {
220                 state = State.REPLY;
221                 reply_id = status.id;
222                 
223                 direct_entry.hide();
224                 clear();
225                 show();
226                 
227                 status_icon.set_from_file(Config.REPLY_PATH);
228                 user_label.set_text(_("Reply to") + " <b>%s</b>:".printf(status.user_screen_name));
229                 user_label.set_use_markup(true);
230                 text = "@%s ".printf(status.user_screen_name);
231                 parent.set_focus(text_entry);
232         }
233         
234         public void set_state_directreply(string screen_name) {
235                 state = State.DIRECT_REPLY;
236                 
237                 direct_entry.show();
238                 direct_entry.set_text(screen_name);
239                 
240                 status_icon.set_from_file(Config.DIRECT_REPLY_PATH);
241                 user_label.set_text(_("Direct message to"));
242                 user_label.set_use_markup(true);
243                 
244                 clear();
245                 show();
246                 
247                 if(screen_name != "") { // if new DM, not a "reply"
248                         direct_entry.check();
249                         parent.set_focus(text_entry);
250                 } else parent.set_focus(direct_entry);
251         }
252         
253         public void set_state_retweet(Status status) {
254                 state = State.RETWEET;
255                 
256                 direct_entry.hide();
257                 clear();
258                 show();
259                 
260                 status_icon.set_from_file(Config.RETWEET_PATH);
261                 user_label.set_text(_("Retweet:"));
262                 
263                 switch(prefs.retweetStyle) {
264                         case ReTweet.Style.UNI:
265                                 text = "♺ @%s: %s".printf(status.user_screen_name, status.text);
266                                 break;
267                         
268                         case ReTweet.Style.CLASSIC:
269                                 text = "RT @%s: %s".printf(status.user_screen_name, status.text);
270                                 break;
271                         
272                         case ReTweet.Style.VIA:
273                                 var msg = status.text;
274                                 var via = " (via @%s)".printf(status.user_screen_name);
275                                 
276                                 if(msg.length > (140 - via.length))
277                                         msg = msg.substring(0, 140 - via.length);
278                                 
279                                 text = msg + via;
280                                 break;
281                 }
282                 
283                 parent.set_focus(text_entry);
284         }
285         
286         private bool too_long() {
287                 if(text.length > 140) {
288                         var message_dialog = new MessageDialog(parent,
289                                 Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
290                                 Gtk.MessageType.INFO, Gtk.ButtonsType.OK, (_("Your status is too long")));
291                 
292                         message_dialog.run();
293                         message_dialog.destroy();
294                         
295                         return true;
296                 }
297                 return false;
298                 
299         }
300         
301         private bool hide_or_send(Gdk.EventKey event) {
302                 switch(event.hardware_keycode) {
303                         case 36: //return key
304                                 if(event.state == 1) { //shift + enter
305                                         entry.buffer.insert_at_cursor("\n", (int)"\n".length);
306                                         return true;
307                                 }
308                                 if(text.length > 0) {
309                                         enter_pressed();
310                                 } else { // if nothing to send
311                                         var message_dialog = new MessageDialog(parent,
312                                         Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
313                                         Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
314                                         (_("Type something first")));
315                                         
316                                         message_dialog.run();
317                                         message_dialog.destroy();
318                                 }
319                                 return true;
320                 
321                 case 9: //esc key
322                         clear();
323                         hide();
324                         break;
325                 }
326                 
327                 return false;
328         }
329         
330         private void enter_pressed() {
331                 switch(state) {
332                         case State.NEW:
333                                 if(!too_long())
334                                         send_new();
335                                 break;
336                         
337                         case State.REPLY:
338                                 if(!too_long())
339                                         send_new(reply_id);
340                                 break;
341                         
342                         case State.RETWEET:
343                                 if(!too_long())
344                                         send_new();
345                                 break;
346                         
347                         case State.DIRECT_REPLY:
348                                 send_dm();
349                                 break;
350                 }
351         }
352         
353         private void send_new(string reply_id = "") {
354                 set_sensitive(false);
355                 
356                 sending_data(_("Sending status...")); //signal
357                 
358                 Status status = null;
359                 try {
360                         status = api.update_status(text, reply_id);
361                 } catch(RestError e) {
362                         set_sensitive(true);
363                         
364                         data_error_sent(e.message);
365                         return;
366                 }
367                 
368                 status_updated(status); //signal
369                 
370                 hide();
371                 set_sensitive(true);
372                 
373                 data_sent(_("Your status has been sent successfully")); //signal
374         }
375         
376         private void send_dm() {
377                 set_sensitive(false);
378                 
379                 sending_data(_("Sending direct message...")); //signal
380                 
381                 try {
382                         api.send_dm(direct_entry.text, text);
383                 } catch(RestError e) {
384                         set_sensitive(true);
385                         
386                         data_error_sent(e.message);
387                         return;
388                 }
389                 
390                 hide();
391                 set_sensitive(true);
392                 
393                 data_sent(_("Your direct message has been sent successfully")); //signal
394         }
395         
396         /* styling for nicks in the buffer */
397         private void tagging(TextIter start_pos, string text, Regex regex, string tag_name) {
398                 ArrayList<string> lst = new ArrayList<string>();
399                 int pos = 0;
400                 
401                 while(true) {
402                         MatchInfo match_info;
403                         bool bingo = regex.match_all_full(text, -1, pos, GLib.RegexMatchFlags.NEWLINE_ANY, out match_info);
404                         
405                         if(bingo) {
406                                 foreach(string s in match_info.fetch_all()) {
407                                         lst.add(s);
408                                         
409                                         match_info.fetch_pos(0, null, out pos);
410                                         break;
411                                 }
412                         } else break;
413                 }
414                 
415                 foreach(string item in lst) {
416                         TextIter start_pos_regex;
417                         TextIter end_pos_regex;
418                         
419                         start_pos.forward_search(item, TextSearchFlags.TEXT_ONLY,
420                                 out start_pos_regex, out end_pos_regex, null);
421                         
422                         entry.buffer.apply_tag_by_name(tag_name, start_pos_regex, end_pos_regex);
423                 }
424         }
425         
426         private void change() {
427                 int length = (int)text.len();
428                 
429                 TextIter start_pos;
430                 TextIter end_pos;
431                 
432                 entry.buffer.get_bounds(out start_pos, out end_pos);
433                 
434                 entry.buffer.remove_all_tags(start_pos, end_pos);
435                 
436                 tagging(start_pos, text, urls, "url");
437                 tagging(start_pos, text, nicks, "nick");
438                 tagging(start_pos, text, tags, "tag");
439                 
440                 if(length > 140) {
441                         TextIter start_red_pos;
442                         
443                         entry.buffer.get_iter_at_offset(out start_red_pos, 140);
444                         entry.buffer.get_iter_at_offset(out end_pos, length);
445                         
446                         entry.buffer.apply_tag_by_name("red_bg", start_red_pos, end_pos);
447                 }
448                 
449                 label.set_text("<b>%s</b>".printf((140 - text.len()).to_string()));
450                 label.set_use_markup(true);
451         }
452 }