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>
27 public class ReTweet : VBox {
29 public static enum Style { CLASSIC, UNI, VIA }
31 private static enum State { NEW, REPLY, DIRECT_REPLY, RETWEET }
35 private RestAPIRe api;
37 private TextView entry;
38 public TextView text_entry {
39 public get { return entry; }
44 { return entry.buffer.text; }
46 { entry.buffer.set_text(value, (int)value.size()); }
49 private Accounts accounts;
51 private Image status_icon;
53 public Label user_label;
55 public Action shortAct;
57 private DmEntry direct_entry;
58 private ToolButton close_btn;
59 private Window parent;
61 private UrlShort url_short;
62 private string reply_id = "";
68 SystemStyle gtk_style;
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);
76 public ReTweet(Window _parent, Prefs _prefs, Accounts _accounts, Cache cache, SystemStyle _gtk_style) {
80 gtk_style = _gtk_style;
82 var acc = accounts.get_current_account();
83 api = new RestAPIRe(acc);
84 api.request.connect((req) => start_update(req));
86 url_short = new UrlShort(prefs, api);
88 shortAct = new Action("UrlShort", _("Shorten URLs..."), null, null);
89 shortAct.set_sensitive(false);
90 shortAct.activate.connect(() => {
91 shortAct.set_sensitive(false);
93 sending_data(_("Shortening URLs...")); //signal
95 string reply = url_short.shortit(text);
98 data_sent(_("URLs was shortened successfully")); //signal
100 data_sent(_("Nothing to shorten")); //signal
104 shortAct.set_sensitive(true);
107 parent.set_focus(entry);
110 //enable or disable shrten url action
111 show.connect((event) => {
112 shortAct.set_sensitive(true);
114 hide.connect((event) => {
115 shortAct.set_sensitive(false);
120 set_homogeneous(false);
123 var l_box = new HBox(false, 2);
124 status_icon = new Image();
125 user_label = new Label(_("New status:"));
127 direct_entry = new DmEntry(api);
129 label = new Label("<b>140</b>");
130 label.set_use_markup(true);
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) => {
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);
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);
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);
161 prefs.enableSpellChanged.connect(spellEnabling);
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}]+)");
169 var scroll = new ScrolledWindow(null, null);
170 scroll.set_policy(PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
173 var hbox = new HBox(false, 1);
174 hbox.pack_start(scroll, true, true, 0);
176 var frame = new Frame(null);
177 frame.set_size_request(-1, 60);
180 var sep = new HSeparator();
182 pack_start(sep, false, false, 0);
183 pack_start(l_box, false, false, 0);
184 pack_start(frame, false, true, 0);
187 private void spellEnabling() {
188 if(prefs.enableSpell) {
189 if(prefs.enableSpell) {
191 Spell spell = new Spell.attach(entry, null);
194 Spell spell = new Spell.attach(entry, "en_US");
202 Spell spell = Spell.get_from_text_view(entry);
208 public void update_auth() {
209 var acc = accounts.get_current_account();
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);
219 private void clear() {
223 public void insert(string str) {
224 entry.buffer.insert_at_cursor(str, (int)str.length);
227 /* when user clicked on a nick */
228 public void set_nickto(string screen_name) {
232 insert("@%s ".printf(screen_name));
235 public void set_state_new() {
241 status_icon.set_from_stock(STOCK_EDIT, IconSize.SMALL_TOOLBAR);
242 user_label.set_text(_("New status:"));
243 parent.set_focus(text_entry);
246 public void set_state_reply(Status status) {
248 reply_id = status.id;
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);
261 public void set_state_directreply(string screen_name) {
262 state = State.DIRECT_REPLY;
265 direct_entry.set_text(screen_name);
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);
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);
280 public void set_state_retweet(Status status) {
281 state = State.RETWEET;
287 status_icon.set_from_file(Config.RETWEET_PATH);
288 user_label.set_text(_("Retweet:"));
290 switch(prefs.retweetStyle) {
291 case ReTweet.Style.UNI:
292 text = "♺ @%s: %s".printf(status.user_screen_name, status.text);
295 case ReTweet.Style.CLASSIC:
296 text = "RT @%s: %s".printf(status.user_screen_name, status.text);
299 case ReTweet.Style.VIA:
300 var msg = status.text;
301 var via = " (via @%s)".printf(status.user_screen_name);
303 if(msg.length > (140 - via.length))
304 msg = msg.substring(0, 140 - via.length);
310 parent.set_focus(text_entry);
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")));
319 message_dialog.run();
320 message_dialog.destroy();
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);
335 if(text.length > 0) {
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")));
343 message_dialog.run();
344 message_dialog.destroy();
357 private void enter_pressed() {
374 case State.DIRECT_REPLY:
380 private void send_new(string reply_id = "") {
381 set_sensitive(false);
383 sending_data(_("Sending status...")); //signal
385 Status status = null;
387 status = api.update_status(text, reply_id);
388 } catch(RestError e) {
391 data_error_sent(e.message);
393 } catch(ParseError e) {
396 data_error_sent(e.message);
401 status_updated(status); //signal
406 data_sent(_("Your status has been sent successfully")); //signal
409 private void send_dm() {
410 set_sensitive(false);
412 sending_data(_("Sending direct message...")); //signal
415 api.send_dm(direct_entry.text, text);
416 } catch(RestError e) {
419 data_error_sent(e.message);
426 data_sent(_("Your direct message has been sent successfully")); //signal
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>();
435 MatchInfo match_info;
436 bool bingo = regex.match_all_full(text, -1, pos, GLib.RegexMatchFlags.NEWLINE_ANY, out match_info);
439 foreach(string s in match_info.fetch_all()) {
442 match_info.fetch_pos(0, null, out pos);
448 foreach(string item in lst) {
449 TextIter start_pos_regex;
450 TextIter end_pos_regex;
452 start_pos.forward_search(item, TextSearchFlags.TEXT_ONLY,
453 out start_pos_regex, out end_pos_regex, null);
455 entry.buffer.apply_tag_by_name(tag_name, start_pos_regex, end_pos_regex);
459 private void change() {
460 int length = (int)text.len();
465 entry.buffer.get_bounds(out start_pos, out end_pos);
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);
473 tagging(start_pos, text, urls, "url");
474 tagging(start_pos, text, nicks, "nick");
475 tagging(start_pos, text, tags, "tag");
478 TextIter start_red_pos;
480 entry.buffer.get_iter_at_offset(out start_red_pos, 140);
481 entry.buffer.get_iter_at_offset(out end_pos, length);
483 entry.buffer.apply_tag_by_name("red_bg", start_red_pos, end_pos);
486 label.set_text("<b>%s</b>".printf((140 - text.len()).to_string()));
487 label.set_use_markup(true);