OSDN Git Service

2410a622bf400f1c852bdcc7ddfc28b2eaa93d2e
[gwit/gwit.git] / gwitlib / main.py
1 #-*- coding: utf-8 -*-
2
3 '''gwit Main class
4 '''
5
6 ################################################################################
7 #
8 # Copyright (c) 2010 University of Tsukuba Linux User Group
9 #
10 # This file is part of "gwit".
11 #
12 # "gwit" is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
16 #
17 # "gwit" is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with "gwit".  If not, see <http://www.gnu.org/licenses/>.
24 #
25 ################################################################################
26
27 import pygtk
28 pygtk.require('2.0')
29 import gtk
30 import gobject
31 import pango
32
33 import sys
34 import os.path
35 import threading
36 import random
37 import time
38 import uuid
39 import webbrowser
40
41 try:
42     import pynotify
43 except ImportError:
44     USE_NOTIFY = False
45 else:
46     USE_NOTIFY = True
47
48 from timeline import Timeline
49 from statusview import StatusView
50 from timelinethread import BaseThread
51 from twitterapi import TwitterAPI
52 from iconstore import IconStore, IconThread
53 from saveconfig import Config
54 from userselection import UserSelection
55 from listsselection import ListsSelection, ListsView
56 from statusdetail import StatusDetail
57 from twittertools import TwitterTools
58 from getfriendswizard import GetFriendsWizard
59
60 # Main Class
61 class Main(object):
62     # Default settings
63     interval = (300, 300, -1)
64     msgfooter = u""
65     alloc = gtk.gdk.Rectangle(0, 0, 240, 320)
66     scounts = (20, 200)
67     iconmode = True
68     userstream = True
69     # My status, Mentions to me, Reply to, Reply to user, Selected user
70     status_color = ("#CCCCFF", "#FFCCCC", "#FFCC99", "#FFFFCC", "#CCFFCC")
71     
72     # Tweet parameters
73     twparams = dict()
74     
75     # Streaming API filter
76     _filter_tab = None
77     
78     _toggle_change_flag = False
79     
80     # Constractor
81     def __init__(self, screen_name, keys):
82         # Gtk Multithread Setup
83         if sys.platform == "win32":
84             gobject.threads_init()
85         else:
86             gtk.gdk.threads_init()
87         
88         if USE_NOTIFY:
89             pynotify.init("gwit")
90         
91         # force change gtk icon settings
92         settings = gtk.settings_get_default()
93         if not getattr(settings.props, 'gtk_button_images', True):
94             settings.props.gtk_button_images = True
95         
96         # init status timelines
97         self.timelines = list()
98         self.tlhash = dict()
99         self.timeline_mention = None
100         
101         # Twitter class instance
102         self.twitter = TwitterAPI(screen_name, *keys)
103         self.twitter.on_tweet_event = self.refresh_tweet
104         self.twitter.on_notify_event = self.notify
105         
106         self.read_settings()
107         
108         # set event (show remaining api count)
109         self.twitter.on_twitterapi_requested = self.on_timeline_refresh
110         self.twitter.new_timeline = self.new_timeline
111
112         # Get configuration
113         self.twitter.update_configuration_bg()
114
115         # Get users
116         self.twitter.get_followers_bg()
117         if not self.userstream: self.twitter.get_following_bg()
118         
119         # init icon store
120         IconStore.iconmode = self.iconmode
121         self.iconstore = IconStore()
122         
123         # GtkBuilder instance
124         self.builder = gtk.Builder()
125         # Glade file input
126         gladefile = os.path.join(os.path.dirname(__file__), "ui/gwit.ui")
127         self.builder.add_from_file(gladefile)
128         # Connect signals
129         self.builder.connect_signals(self)
130         
131         self.notebook = self.builder.get_object("notebook1")
132         self.textview = self.builder.get_object("textview1")
133         self.btnupdate = self.builder.get_object("button1")
134         self.charcount = self.builder.get_object("label1")
135         self.dsettings = self.builder.get_object("dialog_settings")
136         self.imgtable = self.builder.get_object("table_images")
137         
138         self.menu_tweet = self.builder.get_object("menu_tweet")
139         self.builder.get_object("menuitem_tweet").set_submenu(self.menu_tweet)
140         self.menu_timeline = self.builder.get_object("menu_timeline")
141         self.builder.get_object("menuitem_timeline").set_submenu(
142             self.menu_timeline)
143         
144         # set class variables
145         Timeline.twitter = self.twitter
146         StatusView.twitter = self.twitter
147         StatusView.iconstore = self.iconstore
148         StatusView.iconmode = self.iconmode
149         StatusView.pmenu = self.menu_tweet
150         BaseThread.twitter = self.twitter
151         StatusDetail.twitter = self.twitter
152         StatusDetail.iconstore = self.iconstore
153         ListsView.twitter = self.twitter
154         ListsView.iconstore = self.iconstore
155         UserSelection.twitter = self.twitter
156         UserSelection.iconstore = self.iconstore
157         GetFriendsWizard.twitter = self.twitter
158         IconThread.twitter = self.twitter
159         
160         imgpath = os.path.join(os.path.dirname(__file__), "images/")
161         StatusView.favico_off = gtk.gdk.pixbuf_new_from_file(
162             imgpath + "favorite.png")
163         StatusView.favico_hover = gtk.gdk.pixbuf_new_from_file(
164             imgpath + "favorite_hover.png")
165         StatusView.favico_on = gtk.gdk.pixbuf_new_from_file(
166             imgpath + "favorite_on.png")
167
168         StatusView.rtico_off = gtk.gdk.pixbuf_new_from_file(
169             imgpath + "retweet.png")
170         StatusView.rtico_hover = gtk.gdk.pixbuf_new_from_file(
171             imgpath + "retweet_hover.png")
172         StatusView.rtico_on = gtk.gdk.pixbuf_new_from_file(
173             imgpath + "retweet_on.png")
174         
175         self.initialize()
176     
177     def main(self):
178         window = self.builder.get_object("window1")
179         
180         # settings allocation
181         window.resize(self.alloc.width, self.alloc.height)        
182         window.show_all()
183         
184         # Start gtk main loop
185         gtk.main()
186     
187     def read_settings(self):
188         try:
189             # Read settings
190             d = Config.get_section("DEFAULT")
191             self.interval = eval(d["interval"])
192             self.alloc = eval(d["allocation"])
193             self.scounts = eval(d["counts"])
194             self.iconmode = eval(d["iconmode"])
195             self.userstream = eval(d["userstream"])
196             self.status_color = eval(d["color"])
197             u = Config.get_section(self.twitter.my_name)
198             self.msgfooter = u["footer"]
199         except Exception, e:
200             print "[Error] Read settings: %s" % e
201     
202     # Initialize Tabs (in another thread)
203     def initialize(self):
204         # Set Status Views
205         for i in (("Home", "home_timeline", self.userstream),
206                   ("@Mentions", "mentions")):
207             # create new timeline and tab view
208             deny_close = {"deny_close" : True}
209             self.new_timeline(*i, **deny_close)
210         
211         # Set statusbar (Show API Remaining)
212         self.label_apilimit = gtk.Label()
213         self.statusbar = self.builder.get_object("statusbar1")
214         self.statusbar.pack_start(self.label_apilimit,
215                                   expand = False, padding = 10)
216         self.statusbar.show_all()
217         
218         # Users tab append
219         users = UserSelection()
220         self.new_tab(users, "Users", deny_close = True)
221         
222         # Lists tab append
223         lists = ListsSelection()
224         self.new_tab(lists, "Lists", deny_close = True)
225         
226         self.notebook.set_current_page(0)
227     
228     # Window close event
229     def close(self, widget):
230         # Save Allocation (window position, size)
231         window = self.builder.get_object("window1")
232         alloc = repr(window.allocation)
233         Config.save("DEFAULT", "allocation", alloc)
234         
235         # Stop Icon Refresh
236         self.iconstore.stop()
237     
238     def exit(self, widget):
239         # hide window quickly
240         while gtk.events_pending():
241             gtk.main_iteration()
242         
243         # Stop Timeline
244         for i in self.timelines:
245             if i != None:
246                 i.destroy()
247                 if i.timeline != None: i.timeline.join(1)
248                 if i.stream != None: i.stream.join(1)
249         
250         gtk.main_quit()
251         self.save_settings()
252     
253     # Create new Timeline and append to notebook
254     def new_timeline(self, label, method, userstream = False, *args, **kwargs):
255         # Create Timeline Object
256         tl = Timeline()
257         
258         if method == "filter":
259             if self.get_filter_tab():
260                 # filter method only one connection
261                 self.message_dialog(
262                     "May create only one standing connection to the Streaming API.\n"
263                     "Please close existing Streaming API tab if you want.")
264                 tl.destroy()
265                 return
266             
267             # set Streaming API stream
268             tl.set_stream("filter", kwargs)
269         else:
270             interval = self.get_default_interval(method)        
271             tl.set_timeline(method, interval, self.scounts, args, kwargs)
272             # Put error to statubar
273             tl.timeline.on_twitterapi_error = self.on_twitterapi_error
274         
275         # for Event
276         tl.view.new_timeline = self.new_timeline
277         
278         # Add Notebook (Tab view)
279         uid = self.new_tab(tl, label, tl, kwargs.get("deny_close", False))
280         if method == "filter": self.set_filter_tab(uid)
281         
282         # Set color
283         tl.view.set_color(self.status_color)
284         
285         if method == "mentions":
286             # memory mentions tab_id
287             self.timeline_mention = uid
288             tl.on_status_added = self.on_mentions_added
289         else:
290             tl.on_status_added = self.on_status_added
291         
292         # Put tweet information to statusbar
293         tl.view.on_status_selection_changed = self.on_status_selection_changed
294         # Reply on double click
295         tl.view.on_status_activated = self.on_status_activated
296        
297         # Set UserStream parameter
298         if userstream:
299             tl.set_stream("user")
300         
301         tl.start_stream()
302         tl.start_timeline()
303     
304     # Append Tab to Notebook
305     def new_tab(self, widget, label, timeline = None, deny_close = False):
306         # close button
307         button = gtk.Button()
308         button.set_relief(gtk.RELIEF_NONE)
309         icon = gtk.image_new_from_stock("gtk-close", gtk.ICON_SIZE_MENU)
310         button.set_image(icon)
311         
312         uid = uuid.uuid4().int
313         button.connect("clicked", self.on_tabclose_clicked, uid)
314         n = self.notebook.get_n_pages()
315         self.tlhash[uid] = n
316         self.timelines.append(timeline)
317         
318         # Label
319         lbl = gtk.Label(label)
320         
321         box = gtk.HBox()
322         box.pack_start(lbl, True, True)
323         if not deny_close:
324             box.pack_start(button, False, False)
325         box.show_all()
326         
327         if timeline != None:
328             button.connect("button-press-event", 
329                            self.on_notebook_tabbar_button_press)
330         
331         # append
332         self.notebook.append_page(widget, box)
333         self.notebook.show_all()
334         self.notebook.set_current_page(n)
335         
336         return uid
337     
338     def get_selected_status(self):
339         tab = self.get_current_tab()
340         if tab != None:
341             return tab.view.get_selected_status()
342     
343     def get_current_tab_n(self):
344         return self.notebook.get_current_page()
345     
346     def get_current_tab(self):
347         return self.timelines[self.notebook.get_current_page()]
348     
349     # Get text
350     def get_textview(self):
351         buf = self.textview.get_buffer()
352         start, end = buf.get_start_iter(), buf.get_end_iter()
353         return buf.get_text(start, end)
354     
355     # Set text
356     def set_textview(self, txt, focus = False):
357         buf = self.textview.get_buffer()
358         buf.set_text(txt)
359         if focus: self.textview.grab_focus()
360     
361     # Add text at cursor
362     def add_textview(self, txt, focus = False):
363         buf = self.textview.get_buffer()
364         buf.insert_at_cursor(txt)    
365         if focus: self.textview.grab_focus()
366     
367     # Clear text
368     def clear_textview(self, focus = False):
369         self.set_textview("", focus)
370     
371     # Reply to selected status
372     def reply_to_selected_status(self):
373         status = self.get_selected_status()
374         self.reply_to_status(status)
375     
376     def reply_to_status(self, status):
377         self.twparams["reply_to"] = status.id
378         name = status.user.screen_name
379         
380         buf = self.textview.get_buffer()
381         buf.set_text("@%s " % (name))
382         self.textview.grab_focus()
383     
384     # Color selection dialog run for settings
385     def color_dialog_run(self, title, color, entry):
386         # Sample treeview setup
387         store = gtk.ListStore(gtk.gdk.Pixbuf, str, str)
388         treeview = gtk.TreeView(store)
389         cellp = gtk.CellRendererPixbuf()
390         colp = gtk.TreeViewColumn("icon", cellp, pixbuf = 0)
391         cellt = gtk.CellRendererText()
392         cellt.set_property("wrap-mode", pango.WRAP_CHAR)
393         colt = gtk.TreeViewColumn("status", cellt, markup = 1)
394         colp.add_attribute(cellp, "cell-background", 2)
395         colt.add_attribute(cellt, "cell-background", 2)
396         treeview.append_column(colp)
397         treeview.append_column(colt)
398         
399         status = self.twitter.statuses.values()[0]
400         store.append((self.iconstore.get(status.user),
401                       "<b>%s</b>\n%s" % (status.user.screen_name, status.text),
402                       color))
403         
404         def on_changed_cursor(view):
405             view.get_selection().unselect_all()
406         
407         treeview.set_property("can-focus", False)
408         treeview.set_headers_visible(False)
409         treeview.connect("cursor-changed", on_changed_cursor)
410         treeview.show()
411         
412         swin = gtk.ScrolledWindow()
413         swin.add(treeview)
414         swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_NEVER)
415         swin.show()
416         
417         label = gtk.Label("Sample")
418         label.set_justify(gtk.JUSTIFY_LEFT)
419         label.set_padding(0, 10)
420         label.set_alignment(0, 0.5)
421         label.show()
422         
423         def on_colorselection_color_changed(colorselection, liststore):
424             strcolor = colorselection.get_current_color().to_string()
425             liststore.set_value(liststore.get_iter_first(), 2, strcolor)
426         
427         # Dialog setup
428         dialog = gtk.ColorSelectionDialog(title)
429         selection = dialog.get_color_selection()
430         selection.set_current_color(gtk.gdk.color_parse(color))
431         selection.connect("color-changed", 
432                           on_colorselection_color_changed, store)
433         
434         selection.pack_start(label)
435         selection.pack_start(swin)
436         
437         dialog.show_all()
438         
439         if dialog.run() == -5:
440             color = selection.get_current_color().to_string()
441             entry.set_text(color)
442         else:
443             color = None
444         
445         dialog.destroy()
446         
447         return color
448     
449     def status_update_thread(self, status):
450         t = threading.Thread(target = self._status_update, args = (status,))
451         t.start()
452     
453     def _status_update(self, status):
454         args = dict()
455
456         gtk.gdk.threads_enter()
457         self.textview.set_sensitive(False)
458         self.btnupdate.set_sensitive(False)
459         gtk.gdk.threads_leave()
460         
461         if self.twparams.get("reply_to", None):
462             args["in_reply_to_status_id"] = self.twparams.get("reply_to", None)
463         elif self.msgfooter != "":
464             status = u"%s %s" % (status, self.msgfooter)
465         
466         if self.twparams.get("media", None):            
467             resp = self.twitter.api_wrapper(
468                 self.twitter.api.status_update_with_media,
469                 status, self.twparams["media"], **args)
470         else:
471             resp = self.twitter.api_wrapper(
472                 self.twitter.api.status_update, status, **args)
473         
474         if resp:
475             gtk.gdk.threads_enter()
476             self.clear_textview()
477             gtk.gdk.threads_leave()
478             self.twparams.pop("reply_to", None)
479             self.twparams.pop("media", None)
480             self.imgtable.forall(self.imgtable.remove)
481             self.imgtable.set_visible(False)
482         
483         gtk.gdk.threads_enter()
484         self.textview.set_sensitive(True)
485         self.btnupdate.set_sensitive(True)
486         self.textview.grab_focus()
487         gtk.gdk.threads_leave()
488     
489     def get_default_interval(self, method):
490         if method == "home_timeline":
491             interval = self.interval[0]
492         elif method == "mentions":
493             interval = self.interval[1]
494         else:
495             interval = self.interval[2]
496
497         return interval
498     
499     def get_filter_tab(self):
500         if not self._filter_tab:
501             return None
502         elif self.tlhash.get(self._filter_tab, -1) < 0:
503             # filter tab already closed
504             self.set_filter_tab(None)
505             return None
506         else:
507             return self._filter_tab
508     
509     @classmethod
510     def set_filter_tab(cls, tab_id):
511         cls._filter_tab = tab_id
512     
513     def save_settings(self):
514         conf = (("DEFAULT", "interval", self.interval),
515                 ("DEFAULT", "counts", self.scounts),
516                 ("DEFAULT", "iconmode", self.iconmode),
517                 ("DEFAULT", "userstream", self.userstream),
518                 ("DEFAULT", "color", self.status_color),
519                 (self.twitter.my_name, "footer", self.msgfooter))
520         Config.save_section(conf)
521
522     # desktop notify
523     def notify(self, title, text, icon_user = None):
524         if USE_NOTIFY:
525             notify = pynotify.Notification(title, text)
526             if icon_user:
527                 icon = self.iconstore.get(icon_user)
528                 notify.set_icon_from_pixbuf(icon)
529             notify.show()
530     
531     def refresh_tweet(self, i):
532         for tl in self.timelines:
533             if tl: tl.view.reset_status_text()
534     
535     def message_dialog(self, message, 
536                        type = gtk.MESSAGE_ERROR,
537                        buttons = gtk.BUTTONS_OK):
538         md = gtk.MessageDialog(type = type, buttons = buttons)
539         md.set_markup(message)
540         r = md.run()
541         md.destroy()
542         return r
543     
544     
545     ########################################
546     # Original Events
547     
548     # status added event
549     def on_status_added(self, i):
550         status = self.twitter.statuses[i]
551         myid = self.twitter.my_id
552         myname = self.twitter.my_name
553         
554         if status.in_reply_to_user_id == myid or status.text.find("@%s" % myname) >= 0:
555             # add mentions tab
556             mentiontab = self.timelines[self.tlhash[self.timeline_mention]]
557             if status.id not in mentiontab.get_timeline_ids():
558                 mentiontab.timeline.add_statuses(((status,)))
559     
560     def on_mentions_added(self, i):
561         status = self.twitter.statuses[i]
562         self.notify("@%s mentioned you." % status.user.screen_name,
563                     status.text, status.user)
564     
565     # timeline refreshed event
566     def on_timeline_refresh(self):
567         if self.twitter.api.ratelimit_iplimit != -1:
568             msg = "%d/%d %d/%d" % (
569                 self.twitter.api.ratelimit_remaining,
570                 self.twitter.api.ratelimit_limit,
571                 self.twitter.api.ratelimit_ipremaining,
572                 self.twitter.api.ratelimit_iplimit)
573         else:
574             msg = "%d/%d" % (
575                 self.twitter.api.ratelimit_remaining,
576                 self.twitter.api.ratelimit_limit)
577         
578         try:
579             self.label_apilimit.set_text("API: %s" % msg)
580         except:
581             pass
582     
583     # status selection changed event
584     def on_status_selection_changed(self, status):
585         self.builder.get_object("menuitem_tweet").set_sensitive(True)
586         self.statusbar.pop(0)
587         self.statusbar.push(0, TwitterTools.get_footer(status))
588     
589     # status activated event (to Reply
590     def on_status_activated(self, status):
591         self.reply_to_status(status)
592     
593     # show error on statusbar
594     def on_twitterapi_error(self, timeline, e):
595         if e.code == 400:
596             message = "API rate limiting. Reset: %s" % (
597                 self.twitter.api.ratelimit_reset.strftime("%H:%M:%S"))
598         elif e.code == 500 or e.code == 502:
599             message = "Twitter something is broken. Try again later."
600         elif e.code == 503:
601             message = "Twitter is over capacity. Try again later."
602         else:
603             message = "Oops! Couldn't reload timeline."
604         
605         self.statusbar.pop(0)        
606         self.statusbar.push(0, "[Error] %s %s (%s)" % (
607                 timeline.getName(), message, e.code))
608     
609     ########################################
610     # Gtk Signal Events
611     
612     # Status Update
613     def on_button1_clicked(self, widget):
614         txt = self.get_textview()
615         
616         if txt != "":
617             # Status Update
618             self.status_update_thread(txt)
619         else:
620             # Reload timeline if nothing in textview
621             n = self.get_current_tab_n()
622             self.twparams.pop("reply_to", None)
623             if self.timelines[n] != None:
624                 self.timelines[n].reload()
625     
626     # key_press textview (for update status when press Ctrl + Enter)
627     def on_textview1_key_press_event(self, textview, event):
628         # Enter == 65293
629         if event.keyval == 65293 and event.state & gtk.gdk.CONTROL_MASK:
630             txt = self.get_textview()
631             
632             # if update button enabled (== len(text) <= 140
633             if self.btnupdate.get_sensitive() and txt != "":
634                 self.status_update_thread(txt)
635             
636             return True
637     
638     # Update menu popup
639     def on_button2_button_release_event(self, widget, event):
640         menu = self.builder.get_object("menu_update")
641         menu.popup(None, None, None, event.button, event.time)
642     
643     # Add an image to tweet
644     def on_menuitem_add_image_activate(self, widget):
645         dialog = gtk.FileChooserDialog("Add an image...")
646         dialog.add_button(gtk.STOCK_OPEN, gtk.RESPONSE_OK)
647         dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
648         ret = dialog.run()
649         filename = dialog.get_filename()
650         dialog.destroy()
651         
652         if ret == gtk.RESPONSE_OK:
653             media = self.twparams.setdefault("media", list())
654             
655             # duplicate image?
656             if filename in media:
657                 self.message_dialog("Duplicate image.")
658                 return
659             
660             # max_media_per_upload check
661             max_media = self.twitter.configuration.get("max_media_per_upload",
662                                                        1)
663             if len(media) >= max_media:
664                 self.message_dialog(
665                     "You can upload max %d images per upload." % max_media)
666                 return
667             
668             # ext check
669             ext = os.path.splitext(filename)[1].upper()
670             if ext not in [".JPG", ".JPEG", ".PNG", ".GIF"]:
671                 self.message_dialog(
672                     "File is not Image. Only JPG, PNG and GIF.")
673                 return
674             
675             # filesize check
676             fsize = os.stat(filename).st_size
677             limit = self.twitter.configuration.get("photo_size_limit", 3145728)
678             if fsize > limit:
679                 self.message_dialog(
680                     "Image file size must be less than %d KB." % (
681                         limit / 1024))
682                 return
683             
684             pix = gtk.gdk.pixbuf_new_from_file(filename)
685             ratio = float(pix.get_height()) / float(pix.get_width())
686             pix = pix.scale_simple(120, int(ratio * 120), 
687                                    gtk.gdk.INTERP_BILINEAR)
688             
689             img = gtk.Image()
690             img.set_from_pixbuf(pix)
691             box = gtk.EventBox()
692             box.add(img)         
693             box.connect("button-release-event",
694                         self.on_image_button_release, len(media))
695             
696             # add new row
697             if len(media) % 4 == 0:
698                 self.imgtable.resize(len(media) / 4 + 1, 4)
699             
700             # attach image
701             self.imgtable.attach(
702                 box, len(media) % 4, len(media) % 4 + 1,
703                 len(media) / 4, len(media) / 4 + 1,
704                 xoptions = gtk.SHRINK, yoptions = gtk.SHRINK,
705                 xpadding = 0, ypadding = 10)
706             
707             cursor = gtk.gdk.Cursor(gtk.gdk.HAND1)
708             box.window.set_cursor(cursor)
709             
710             self.imgtable.set_visible(True)
711             self.imgtable.show_all()
712             
713             # add params
714             media.append(filename)
715     
716     def on_image_button_release(self, widget, event, media_num):
717         if event.button == 1:
718             # image clicked
719             if sys.platform == "win32":
720                 os.startfile(self.twparams["media"][media_num])
721             else:
722                 os.system("xdg-open %s" % self.twparams["media"][media_num])
723         elif event.button == 3:
724             # image delete
725             r = self.message_dialog(
726                 "Are you sure you want to delete this image?",
727                 gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO)
728             if r == gtk.RESPONSE_YES:
729                 self.imgtable.remove(widget)
730                 del self.twparams["media"][media_num]
731                 if self.twparams["media"]:
732                     self.imgtable.set_visible(False)
733     
734     # Timeline Tab Close
735     def on_tabclose_clicked(self, widget, uid):
736         n = self.tlhash[uid]
737         del self.tlhash[uid]
738
739         if self.timeline_mention == uid:
740             self.timeline_mention = None
741         
742         self.notebook.remove_page(n)
743         
744         if self.timelines[n] != None:
745             self.timelines[n].destroy()
746         
747         del self.timelines[n]
748         
749         for i, m in self.tlhash.iteritems():
750             if m > n: self.tlhash[i] -= 1
751         
752         p = self.notebook.get_current_page()
753         self.on_notebook1_switch_page(
754             self.notebook, self.notebook.get_nth_page(p), p)
755     
756     # Tab right clicked
757     def on_notebook_tabbar_button_press(self, widget, event):
758         if event.button == 3:
759             self.menu_timeline.popup(
760                 None, None, None, event.button, event.time)
761     
762     # Character count
763     def on_textbuffer1_changed(self, textbuffer):
764         text = self.get_textview().decode("utf-8")
765         
766         if self.msgfooter != "" and self.twparams.get("reply_to", None):
767             text = u"%s %s" % (text, self.msgfooter)
768         
769         n = TwitterTools.get_tweet_length(
770             text, len(self.twparams.get("media", [])),
771             self.twitter.configuration.get("short_url_length", 20),
772             self.twitter.configuration.get("short_url_length_https", 20),
773             self.twitter.configuration.get("characters_reserved_per_media", 20)
774             )
775         
776         if n <= 140:
777             self.charcount.set_text(str(n))
778             self.btnupdate.set_sensitive(True)
779         else:
780             self.charcount.set_markup(
781                 "<b><span foreground='#FF0000'>%s</span></b>" % n)
782             self.btnupdate.set_sensitive(False)
783     
784     # Help - About menu
785     def on_menuitem_about_activate(self, menuitem):
786         self.builder.get_object("dialog_about").show_all()
787         return True
788
789     # About dialog closed
790     def on_dialog_about_response(self, dialog, response_id):
791         dialog.hide_all()
792     
793     # disable menu when switched tab
794     def on_notebook1_switch_page(self, notebook, page, page_num):
795         self.builder.get_object("menuitem_tweet").set_sensitive(False)
796         menuitem_timeline = self.builder.get_object("menuitem_timeline")
797         menuitem_timeline.set_sensitive(False)
798         if page_num < 0: return False
799         
800         tab = self.timelines[page_num]
801         if tab != None and tab.timeline != None:
802             self._toggle_change_flg = True
803             tl = tab.timeline
804             method = tl.method
805             default = self.get_default_interval(method)
806             
807             if default == -1: default = None
808             
809             menu_default = self.builder.get_object("menuitem_time_default")
810             menu_default.get_child().set_text("Default (%s)" % default)
811             
812             interval = tl.interval
813             
814             if interval == default:
815                 menu_default.set_active(True)
816             elif interval == -1:
817                 self.builder.get_object("menuitem_time_none").set_active(True)
818             elif interval == 600:
819                 self.builder.get_object("menuitem_time_600").set_active(True)
820             elif interval == 300:
821                 self.builder.get_object("menuitem_time_300").set_active(True)
822             elif interval == 120:
823                 self.builder.get_object("menuitem_time_120").set_active(True)
824             elif interval == 60:
825                 self.builder.get_object("menuitem_time_60").set_active(True)
826             elif interval == 30:
827                 self.builder.get_object("menuitem_time_30").set_active(True)
828             
829             self._toggle_change_flg = False
830             menuitem_timeline.set_sensitive(True)
831     
832     # Streaming API tab
833     def on_menuitem_streaming_activate(self, menuitem):
834         dialog = gtk.MessageDialog(buttons = gtk.BUTTONS_OK)
835         dialog.set_markup("Please enter track keywords.")
836         dialog.format_secondary_markup(
837             "Examples: <i>hashtag, username, keyword</i>\n(Split comma. Unnecessary #, @)")
838         entry = gtk.Entry()
839         dialog.vbox.pack_start(entry)
840         dialog.show_all()
841         dialog.run()
842         text = entry.get_text()
843         dialog.destroy()
844         
845         params = {"track" : text.split(",")}
846         self.new_timeline("Stream: %s" % text, "filter", **params)
847     
848     def on_destroy(self, widget, *args, **kwargs):
849         widget.destroy()
850     
851     ########################################
852     # Tweet menu event
853     
854     def on_menuitem_reply_activate(self, menuitem):
855         self.reply_to_selected_status()
856     
857     # Retweet menu clicked
858     def on_menuitem_retweet_activate(self, memuitem):
859         self.get_current_tab().view.retweet_selected_status()
860     
861     # Retweet with comment menu clicked
862     def on_menuitem_reteet_with_comment_activate(self, memuitem):
863         status = self.get_selected_status()
864         name = status.user.screen_name
865         text = status.text
866         
867         self.twparams.pop("reply_to", None)
868         self.set_textview("RT @%s: %s" % (name, text), True)
869     
870     # Added user timeline tab
871     def on_menuitem_usertl_activate(self, menuitem):
872         status = self.get_selected_status()
873         self.new_timeline("@%s" % status.user.screen_name,
874                           "user_timeline", user = status.user.id)
875     
876     # view conversation
877     def on_menuitem_detail_activate(self, menuitem):
878         status = self.get_selected_status()
879         detail = StatusDetail(status)
880         self.new_tab(detail, "S: %d" % status.id)
881     
882     # favorite
883     def on_menuitem_fav_activate(self, menuitem):
884         self.get_current_tab().view.favorite_selected_status()
885     
886     # Destroy status
887     def on_menuitem_destroy_activate(self, menuitem):
888         status = self.get_selected_status()
889         self.twitter.destory_tweet(status)
890     
891     # view on twitter.com
892     def on_menuitem_ontwitter_activate(self, menuitem):
893         status = self.get_selected_status()
894         url = "https://twitter.com/%s/status/%s" % (
895             status.user.screen_name, status.id)
896         webbrowser.open_new_tab(url)
897     
898     ########################################
899     # Timeline menu Event
900     
901     def change_interval(self, interval):
902         if self._toggle_change_flg: return
903         
904         tl = self.get_current_tab().timeline
905         
906         if interval == 0:
907             method = tl.api_method.func_name
908             interval = self.get_default_interval(method)
909         
910         old = tl.interval
911         tl.interval = interval
912         if old == -1: tl.lock.set()
913     
914     def on_menuitem_time_600_toggled(self, menuitem):
915         if menuitem.get_active() == True:
916             self.change_interval(600) 
917     def on_menuitem_time_300_toggled(self, menuitem):
918         if menuitem.get_active() == True:
919             self.change_interval(300)
920     def on_menuitem_time_120_toggled(self, menuitem):
921         if menuitem.get_active() == True:
922             self.change_interval(120)
923     def on_menuitem_time_60_toggled(self, menuitem):
924         if menuitem.get_active() == True:
925             self.change_interval(60)
926     def on_menuitem_time_30_toggled(self, menuitem):
927         if menuitem.get_active() == True:
928             self.change_interval(30)
929     def on_menuitem_time_default_toggled(self, menuitem):
930         if menuitem.get_active() == True:
931             self.change_interval(0)
932     def on_menuitem_time_none_toggled(self, menuitem):
933         if menuitem.get_active() == True:
934             self.change_interval(-1)
935     
936     ########################################
937     # Settings dialog event
938     
939     # Settings
940     def on_imageitem_settings_activate(self, menuitem):
941         home, mentions, other = self.interval
942         
943         # interval
944         if home == -1:
945             self.builder.get_object("checkbutton_home").set_active(False)
946         if mentions == -1:
947             self.builder.get_object("checkbutton_mentions").set_active(False)
948         if other == -1:
949             self.builder.get_object("checkbutton_other").set_active(False)        
950         self.builder.get_object("spinbutton_home").set_value(home)
951         self.builder.get_object("spinbutton_mentions").set_value(mentions)
952         self.builder.get_object("spinbutton_other").set_value(other)
953         
954         # status counts
955         self.builder.get_object("spinbutton_firstn").set_value(self.scounts[0])
956         self.builder.get_object("spinbutton_maxn").set_value(self.scounts[1])
957         # show icons
958         self.builder.get_object("checkbutton_showicon").set_active(self.iconmode)
959         # userstream
960         self.builder.get_object("checkbutton_userstream").set_active(self.userstream)
961         
962         # footer
963         self.builder.get_object("entry_footer").set_text(self.msgfooter)
964
965         # OAuth information
966         self.builder.get_object("entry_myname").set_text(self.twitter.my_name)
967         self.builder.get_object("entry_ckey").set_text(self.twitter.api.oauth.ckey)
968         self.builder.get_object("entry_csecret").set_text(self.twitter.api.oauth.csecret)
969         self.builder.get_object("entry_atoken").set_text(self.twitter.api.oauth.atoken)
970         self.builder.get_object("entry_asecret").set_text(self.twitter.api.oauth.asecret)
971         
972         # Color
973         color_entrys = (self.builder.get_object("entry_color_mytweet"),
974                         self.builder.get_object("entry_color_mentions"),
975                         self.builder.get_object("entry_color_replyto"),
976                         self.builder.get_object("entry_color_replyto_user"),
977                         self.builder.get_object("entry_color_selected_user"))
978         for i, entry in enumerate(color_entrys):
979             entry.set_text(self.status_color[i])
980         
981         self.dsettings.show()
982     
983     # Close or Cancel
984     def on_dialog_settings_close(self, widget):
985         self.dsettings.hide()
986     
987     # OK
988     def on_dialog_settings_ok(self, widget):
989         # interval
990         if self.builder.get_object("checkbutton_home").get_active():
991             home = self.builder.get_object("spinbutton_home").get_value_as_int()
992         else:
993             self.charcount.set_markup("<b><span foreground='#FF0000'>%s</span></b>" % n)
994             self.btnupdate.set_sensitive(False)
995             home = -1
996         if self.builder.get_object("checkbutton_mentions").get_active():
997             mentions = self.builder.get_object("spinbutton_mentions").get_value_as_int()
998         else:
999             mentions = -1
1000         if self.builder.get_object("checkbutton_other").get_active():
1001             other = self.builder.get_object("spinbutton_other").get_value_as_int()
1002         else:
1003             other = -1
1004
1005         self.interval = (home, mentions, other)
1006         
1007         # status counts
1008         self.scounts = (
1009             self.builder.get_object("spinbutton_firstn").get_value_as_int(),
1010             self.builder.get_object("spinbutton_maxn").get_value_as_int())
1011         
1012         # show icons
1013         self.iconmode = self.builder.get_object("checkbutton_showicon").get_active()
1014         
1015         # userstream
1016         self.userstream = self.builder.get_object("checkbutton_userstream").get_active()
1017         
1018         # footer
1019         self.msgfooter = unicode(self.builder.get_object("entry_footer").get_text())
1020         
1021         # Color
1022         self.status_color = (self.builder.get_object("entry_color_mytweet").get_text(),
1023                              self.builder.get_object("entry_color_mentions").get_text(),
1024                              self.builder.get_object("entry_color_replyto").get_text(),
1025                              self.builder.get_object("entry_color_replyto_user").get_text(),
1026                              self.builder.get_object("entry_color_selected_user").get_text())
1027         for t in self.timelines:
1028             if t != None:
1029                 t.view.set_color(self.status_color)
1030         
1031         self.save_settings()
1032         self.dsettings.hide()
1033     
1034     # toggle checkbox
1035     def on_checkbutton_home_toggled(self, checkbutton):
1036         sb = self.builder.get_object("spinbutton_home")
1037         sb.set_sensitive(checkbutton.get_active())
1038     def on_checkbutton_mentions_toggled(self, checkbutton):
1039         sb = self.builder.get_object("spinbutton_mentions")
1040         sb.set_sensitive(checkbutton.get_active())
1041     def on_checkbutton_other_toggled(self, checkbutton):
1042         sb = self.builder.get_object("spinbutton_other")
1043         sb.set_sensitive(checkbutton.get_active())
1044     
1045     def on_entry_color_changed(self, entry):
1046         entry.modify_base(gtk.STATE_NORMAL, gtk.gdk.color_parse(entry.get_text()))
1047     
1048     # Color selection dialog open    
1049     def on_button_color1_clicked(self, widget):
1050         entry = self.builder.get_object("entry_color_mytweet")
1051         self.color_dialog_run("My status", entry.get_text(), entry)
1052     def on_button_color2_clicked(self, widget):
1053         entry = self.builder.get_object("entry_color_mentions")
1054         self.color_dialog_run("Mentions to me", entry.get_text(), entry)
1055     def on_button_color3_clicked(self, widget):
1056         entry = self.builder.get_object("entry_color_replyto")
1057         self.color_dialog_run("Reply to", entry.get_text(), entry)
1058     def on_button_color4_clicked(self, widget):
1059         entry = self.builder.get_object("entry_color_replyto_user")
1060         self.color_dialog_run("Reply to user", entry.get_text(), entry)
1061     def on_button_color5_clicked(self, widget):
1062         entry = self.builder.get_object("entry_color_selected_user")
1063         self.color_dialog_run("Selected user", entry.get_text(), entry)