Add Cristian Greco as uploader
[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                         parent.set_focus(entry);
108                 });
109                 
110                 //enable or disable shrten url action
111                 show.connect((event) => {
112                         shortAct.set_sensitive(true);
113                 });
114                 hide.connect((event) => {
115                         shortAct.set_sensitive(false);
116                 });
117                 
118                 //gui setup
119                 border_width = 0;
120                 set_homogeneous(false);
121                 set_spacing(2);
122                 
123                 var l_box = new HBox(false, 2);
124                 status_icon = new Image();
125                 user_label = new Label(_("New status:"));
126                 
127                 direct_entry = new DmEntry(api);
128                 
129                 label = new Label("<b>140</b>");
130                 label.set_use_markup(true);
131                 
132                 Image close_img = new Image();
133                 close_img.set_from_stock("gtk-close", IconSize.MENU);
134                 close_img.set_tooltip_text(_("Hide"));
135                 var event_close = new EventBox();
136                 event_close.add(close_img);
137                 event_close.set_events(Gdk.EventMask.BUTTON_PRESS_MASK);
138                 event_close.button_press_event.connect((event) => {
139                         hide();
140                         return false;
141                 });
142                 
143                 l_box.pack_start(status_icon, false, false, 2);
144                 l_box.pack_start(user_label, false, false, 2);
145                 l_box.pack_start(direct_entry, false, false, 2);
146                 l_box.pack_end(event_close, false, false, 2);
147                 l_box.pack_end(label, false, false, 2);
148                 
149                 entry = new TextView();
150                 entry.set_size_request(-1, 60);
151                 entry.set_wrap_mode(Gtk.WrapMode.WORD);
152                 entry.key_press_event.connect(hide_or_send);
153                 entry.buffer.changed.connect(change);
154                 
155                 entry.buffer.create_tag("red_bg", "background", "#ffa4a4");
156                 entry.buffer.create_tag("url", "foreground", gtk_style.sl_color);
157                 entry.buffer.create_tag("nick", "weight", Pango.Weight.BOLD);
158                 entry.buffer.create_tag("tag", "foreground", gtk_style.sl_color,
159                         "weight", Pango.Weight.BOLD);
160                 
161                 prefs.enableSpellChanged.connect(spellEnabling);
162                 spellEnabling();
163                 
164                 
165                 nicks = new Regex("(^|\\s)@([A-Za-z0-9_]+)");
166                 urls = new Regex("((http|https|ftp)://([\\S]+)\\.([\\S]+))");
167                 tags = new Regex("((^|\\s)\\#[A-Za-z0-9_\\p{Latin}\\p{Greek}]+)");
168                 
169                 var scroll = new ScrolledWindow(null, null);
170         scroll.set_policy(PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
171         scroll.add(entry);
172                 
173                 var hbox = new HBox(false, 1);
174                 hbox.pack_start(scroll, true, true, 0);
175                 
176                 var frame = new Frame(null);
177                 frame.set_size_request(-1, 60);
178                 frame.add(hbox);
179                 
180                 var sep = new HSeparator();
181                 
182                 pack_start(sep, false, false, 0);
183                 pack_start(l_box, false, false, 0);
184                 pack_start(frame, false, true, 0);
185         }
186         
187         private void spellEnabling() {
188                 if(prefs.enableSpell) {
189                         if(prefs.enableSpell) {
190                                 try {
191                                         Spell spell = new Spell.attach(entry, null);
192                                 } catch(Error e) {
193                                         try {
194                                                 Spell spell = new Spell.attach(entry, "en_US");
195                                         } catch(Error e) {
196                                                 warning(e.message);
197                                         }
198                                         warning(e.message);
199                                 }
200                         }
201                 } else {
202                         Spell spell = Spell.get_from_text_view(entry);
203                         if(spell != null)
204                                 spell.detach();
205                 }
206         }
207         
208         public void update_auth() {
209                 var acc = accounts.get_current_account();
210                 
211                 api.set_auth(acc);
212         }
213         
214         public void set_screen_name(string user_name) {
215                 user_label.set_text(_("Reply to <b>%s</b>:").printf(user_name));
216                 user_label.set_use_markup(true);
217         }
218         
219         private void clear() {
220                 text = "";
221         }
222         
223         public void insert(string str) {
224                 entry.buffer.insert_at_cursor(str, (int)str.length);
225         }
226         
227         /* when user clicked on a nick */
228         public void set_nickto(string screen_name) {
229                 if(!visible)
230                         set_state_new();
231                         
232                 insert("@%s ".printf(screen_name));
233         }
234         
235         public void set_state_new() {
236                 state = State.NEW;
237                 
238                 direct_entry.hide();
239                 clear();
240                 show();
241                 status_icon.set_from_stock(STOCK_EDIT, IconSize.SMALL_TOOLBAR);
242                 user_label.set_text(_("New status:"));
243                 parent.set_focus(text_entry);
244         }
245         
246         public void set_state_reply(Status status) {
247                 state = State.REPLY;
248                 reply_id = status.id;
249                 
250                 direct_entry.hide();
251                 clear();
252                 show();
253                 
254                 status_icon.set_from_file(Config.REPLY_PATH);
255                 user_label.set_text(_("Reply to") + " <b>%s</b>:".printf(status.user_screen_name));
256                 user_label.set_use_markup(true);
257                 text = "@%s ".printf(status.user_screen_name);
258                 parent.set_focus(text_entry);
259         }
260         
261         public void set_state_directreply(string screen_name) {
262                 state = State.DIRECT_REPLY;
263                 
264                 direct_entry.show();
265                 direct_entry.set_text(screen_name);
266                 
267                 status_icon.set_from_file(Config.DIRECT_REPLY_PATH);
268                 user_label.set_text(_("Direct message to"));
269                 user_label.set_use_markup(true);
270                 
271                 clear();
272                 show();
273                 
274                 if(screen_name != "") { // if new DM, not a "reply"
275                         direct_entry.check();
276                         parent.set_focus(text_entry);
277                 } else parent.set_focus(direct_entry);
278         }
279         
280         public void set_state_retweet(Status status) {
281                 state = State.RETWEET;
282                 
283                 direct_entry.hide();
284                 clear();
285                 show();
286                 
287                 status_icon.set_from_file(Config.RETWEET_PATH);
288                 user_label.set_text(_("Retweet:"));
289                 
290                 switch(prefs.retweetStyle) {
291                         case ReTweet.Style.UNI:
292                                 text = "♺ @%s: %s".printf(status.user_screen_name, status.text);
293                                 break;
294                         
295                         case ReTweet.Style.CLASSIC:
296                                 text = "RT @%s: %s".printf(status.user_screen_name, status.text);
297                                 break;
298                         
299                         case ReTweet.Style.VIA:
300                                 var msg = status.text;
301                                 var via = " (via @%s)".printf(status.user_screen_name);
302                                 
303                                 if(msg.length > (140 - via.length))
304                                         msg = msg.substring(0, 140 - via.length);
305                                 
306                                 text = msg + via;
307                                 break;
308                 }
309                 
310                 parent.set_focus(text_entry);
311         }
312         
313         private bool too_long() {
314                 if(text.length > 140) {
315                         var message_dialog = new MessageDialog(parent,
316                                 Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
317                                 Gtk.MessageType.INFO, Gtk.ButtonsType.OK, (_("Your status is too long")));
318                 
319                         message_dialog.run();
320                         message_dialog.destroy();
321                         
322                         return true;
323                 }
324                 return false;
325                 
326         }
327         
328         private bool hide_or_send(Gdk.EventKey event) {
329                 switch(event.hardware_keycode) {
330                         case 36: //return key
331                                 if(event.state == 1) { //shift + enter
332                                         entry.buffer.insert_at_cursor("\n", (int)"\n".length);
333                                         return true;
334                                 }
335                                 if(text.length > 0) {
336                                         enter_pressed();
337                                 } else { // if nothing to send
338                                         var message_dialog = new MessageDialog(parent,
339                                         Gtk.DialogFlags.DESTROY_WITH_PARENT | Gtk.DialogFlags.MODAL,
340                                         Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
341                                         (_("Type something first")));
342                                         
343                                         message_dialog.run();
344                                         message_dialog.destroy();
345                                 }
346                                 return true;
347                 
348                 case 9: //esc key
349                         clear();
350                         hide();
351                         break;
352                 }
353                 
354                 return false;
355         }
356         
357         private void enter_pressed() {
358                 switch(state) {
359                         case State.NEW:
360                                 if(!too_long())
361                                         send_new();
362                                 break;
363                         
364                         case State.REPLY:
365                                 if(!too_long())
366                                         send_new(reply_id);
367                                 break;
368                         
369                         case State.RETWEET:
370                                 if(!too_long())
371                                         send_new();
372                                 break;
373                         
374                         case State.DIRECT_REPLY:
375                                 send_dm();
376                                 break;
377                 }
378         }
379         
380         private void send_new(string reply_id = "") {
381                 set_sensitive(false);
382                 
383                 sending_data(_("Sending status...")); //signal
384                 
385                 Status status = null;
386                 try {
387                         status = api.update_status(text, reply_id);
388                 } catch(RestError e) {
389                         set_sensitive(true);
390                         
391                         data_error_sent(e.message);
392                         return;
393                 } catch(ParseError e) {
394                         set_sensitive(true);
395                         
396                         data_error_sent(e.message);
397                         return;
398                 }
399                 
400                 status.tmp = true;
401                 status_updated(status); //signal
402                 
403                 hide();
404                 set_sensitive(true);
405                 
406                 data_sent(_("Your status has been sent successfully")); //signal
407         }
408         
409         private void send_dm() {
410                 set_sensitive(false);
411                 
412                 sending_data(_("Sending direct message...")); //signal
413                 
414                 try {
415                         api.send_dm(direct_entry.text, text);
416                 } catch(RestError e) {
417                         set_sensitive(true);
418                         
419                         data_error_sent(e.message);
420                         return;
421                 }
422                 
423                 hide();
424                 set_sensitive(true);
425                 
426                 data_sent(_("Your direct message has been sent successfully")); //signal
427         }
428         
429         /* styling for nicks in the buffer */
430         private void tagging(TextIter start_pos, string text, Regex regex, string tag_name) {
431                 ArrayList<string> lst = new ArrayList<string>();
432                 int pos = 0;
433                 
434                 while(true) {
435                         MatchInfo match_info;
436                         bool bingo = regex.match_all_full(text, -1, pos, GLib.RegexMatchFlags.NEWLINE_ANY, out match_info);
437                         
438                         if(bingo) {
439                                 foreach(string s in match_info.fetch_all()) {
440                                         lst.add(s);
441                                         
442                                         match_info.fetch_pos(0, null, out pos);
443                                         break;
444                                 }
445                         } else break;
446                 }
447                 
448                 foreach(string item in lst) {
449                         TextIter start_pos_regex;
450                         TextIter end_pos_regex;
451                         
452                         start_pos.forward_search(item, TextSearchFlags.TEXT_ONLY,
453                                 out start_pos_regex, out end_pos_regex, null);
454                         
455                         entry.buffer.apply_tag_by_name(tag_name, start_pos_regex, end_pos_regex);
456                 }
457         }
458         
459         private void change() {
460                 int length = (int)text.len();
461                 
462                 TextIter start_pos;
463                 TextIter end_pos;
464                 
465                 entry.buffer.get_bounds(out start_pos, out end_pos);
466                 
467                 //entry.buffer.remove_all_tags(start_pos, end_pos);
468                 entry.buffer.remove_tag_by_name("red_bg", start_pos, end_pos);
469                 entry.buffer.remove_tag_by_name("url", start_pos, end_pos);
470                 entry.buffer.remove_tag_by_name("nick", start_pos, end_pos);
471                 entry.buffer.remove_tag_by_name("tag", start_pos, end_pos);
472                 
473                 tagging(start_pos, text, urls, "url");
474                 tagging(start_pos, text, nicks, "nick");
475                 tagging(start_pos, text, tags, "tag");
476                 
477                 if(length > 140) {
478                         TextIter start_red_pos;
479                         
480                         entry.buffer.get_iter_at_offset(out start_red_pos, 140);
481                         entry.buffer.get_iter_at_offset(out end_pos, length);
482                         
483                         entry.buffer.apply_tag_by_name("red_bg", start_red_pos, end_pos);
484                 }
485                 
486                 label.set_text("<b>%s</b>".printf((140 - text.len()).to_string()));
487                 label.set_use_markup(true);
488         }
489 }