Merge lp:~x1101/heybuddy/reply-all into lp:heybuddy

Proposed by x1101 on 2012-07-16
Status: Merged
Merged at revision: 327
Proposed branch: lp:~x1101/heybuddy/reply-all
Merge into: lp:heybuddy
Diff against target: 1687 lines (+1503/-1) (has conflicts)
7 files modified
Communicator.py (+1/-1)
Dent.py (+17/-0)
MainWindow.py (+1/-0)
SettingsPage.py (+24/-0)
_dev/heybuddy.pot (+48/-0)
heybuddy-debug.py (+1390/-0)
heybuddy.py (+22/-0)
Text conflict in SettingsPage.py
Text conflict in _dev/heybuddy.pot
Text conflict in heybuddy.py
To merge this branch: bzr merge lp:~x1101/heybuddy/reply-all
Reviewer Review Type Date Requested Status
jezra 2012-07-16 Pending
Review via email: mp+115225@code.launchpad.net

Commit message

Adds awesome reply-all feature by @x1101. Thanks @nybill and @jk for testing!

Description of the change

Added retweet fix by @sazius, added reply-all NFUE

To post a comment you must log in.
lp:~x1101/heybuddy/reply-all updated on 2012-07-16
315. By x1101 <email address hidden> on 2012-07-16

Fixed settings page after reply-all removal

316. By x1101 <email address hidden> on 2012-07-16

Fixed 2 spaces after name in reply-all NFUE code

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Communicator.py'
2--- Communicator.py 2012-05-16 04:59:11 +0000
3+++ Communicator.py 2012-07-16 21:04:20 +0000
4@@ -336,7 +336,7 @@
5 self.queue.append( (request,'new-statusXML',None) )
6
7 def send_redent(self,status_id):
8- url="%s/statuses/retweet.xml" % (self.sapiroot)
9+ url="%s/statuses/retweet/%s.xml" % (self.sapiroot,status_id)
10 data={"id":status_id}
11 encoded_data =urlencode(data)
12 print url
13
14=== modified file 'Dent.py'
15--- Dent.py 2012-05-27 16:17:48 +0000
16+++ Dent.py 2012-07-16 21:04:20 +0000
17@@ -13,6 +13,10 @@
18 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
19 (gobject.TYPE_STRING,)
20 ),
21+ 'reply-all-clicked': (
22+ gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
23+ (gobject.TYPE_STRING,gobject.TYPE_STRING)
24+ ),
25 'direct-clicked': (
26 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
27 (gobject.TYPE_STRING,)
28@@ -166,6 +170,15 @@
29 reply_button = DentButton(_("reply"))
30 reply_button.connect("clicked",self.reply_clicked)
31 hbox1.pack_start(reply_button,False,False,0)
32+
33+ #lets see if we should add the reply all button
34+ ''' Trying to only do this when we have the option selected, not working yet
35+ if self.options['reply_all'] and
36+ '''
37+ if data['text'].count("@") > 0 or self.in_reply_to_screen_name:
38+ reply_all_button = DentButton('reply all')
39+ reply_all_button.connect('clicked',self.reply_all_clicked)
40+ hbox1.pack_start(reply_all_button,False,False,0)
41
42 #if there is a in reply to conversation happening
43 if self.in_reply_to_id!=None:
44@@ -212,6 +225,10 @@
45 def reply_clicked(self,button):
46 self.emit('reply-clicked',self.screen_name)
47
48+ def reply_all_clicked(self,button):
49+ text = self.text_label.get_text()
50+ self.emit('reply-all-clicked',self.screen_name, text)
51+
52 def direct_clicked(self,button):
53 self.emit('direct-clicked',self.screen_name)
54
55
56=== modified file 'MainWindow.py'
57--- MainWindow.py 2012-07-03 03:55:02 +0000
58+++ MainWindow.py 2012-07-16 21:04:20 +0000
59@@ -220,6 +220,7 @@
60 def add_dent(self,data,target_index,is_conv=False,conv_backwards=False):
61 d = Dent(data)
62 d.connect('reply-clicked', self.reply_clicked)
63+ d.connect('reply-all-clicked',self.reply_all_clicked)
64 d.connect('view-conversation-clicked', self.view_conversation)
65 d.connect('user-clicked', self.view_user)
66 d.connect('text-label-clicked',self.dent_text_clicked)
67
68=== modified file 'SettingsPage.py'
69--- SettingsPage.py 2012-07-03 03:55:02 +0000
70+++ SettingsPage.py 2012-07-16 21:04:20 +0000
71@@ -49,10 +49,17 @@
72 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
73 (gobject.TYPE_BOOLEAN,)
74 ),
75+<<<<<<< TREE
76 'option-ignore-activity':(
77 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
78 (gobject.TYPE_BOOLEAN,)
79 ),
80+=======
81+ 'option-ignore-activity':(
82+ gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
83+ (gobject.TYPE_BOOLEAN,)
84+ ),
85+>>>>>>> MERGE-SOURCE
86 'initial-dents': (
87 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
88 (gobject.TYPE_INT,)
89@@ -153,6 +160,15 @@
90 self.enable_notifications.connect("toggled", self.option_toggled,'option-notifications')
91 self.enable_notify_replies = gtk.CheckButton(_("Enable @replies notifications"))
92 self.enable_notify_replies.connect('toggled', self.option_toggled,'option-notify-replies')
93+
94+ self.ignore_activity = gtk.CheckButton(_("Ignore *activity* posts"))
95+ self.ignore_activity.connect('toggled', self.option_toggled,'option-ignore-activity')
96+
97+ self.disable_https = gtk.CheckButton(_("Disable HTTPS"))
98+ self.disable_https.connect('toggled', self.option_toggled,'option-disable-https')
99+
100+ self.open_fullscreen = gtk.CheckButton(_("Open in Fullscreen"))
101+ self.open_fullscreen.connect('toggled', self.option_toggled,'option-open-fullscreen')
102
103 self.ignore_activity = gtk.CheckButton(_("Ignore *activity* posts"))
104 self.ignore_activity.connect('toggled', self.option_toggled,'option-ignore-activity')
105@@ -169,8 +185,12 @@
106 options_box.pack_start(self.disable_https,False,False,0)
107 options_box.pack_start(self.ignore_activity,False,False,0)
108 options_box.pack_start(self.api_retweet,False,False,0)
109+<<<<<<< TREE
110 options_box.pack_start(self.open_fullscreen,False,False,0)
111
112+=======
113+ options_box.pack_start(self.open_fullscreen,False,False,0)
114+>>>>>>> MERGE-SOURCE
115 #does the app have notifications?
116 if has_pynotify:
117 options_box.pack_start(self.enable_notifications,False,False,0)
118@@ -286,7 +306,11 @@
119
120 def set_api_retweet(self,boolean):
121 self.api_retweet.set_active(boolean)
122+<<<<<<< TREE
123
124+=======
125+
126+>>>>>>> MERGE-SOURCE
127 #More Code for notifications
128 def set_notifications(self,boolean):
129 self.enable_notifications.set_active(boolean)
130
131=== modified file '_dev/heybuddy.pot'
132--- _dev/heybuddy.pot 2012-07-03 03:55:02 +0000
133+++ _dev/heybuddy.pot 2012-07-16 21:04:20 +0000
134@@ -8,7 +8,11 @@
135 msgstr ""
136 "Project-Id-Version: PACKAGE VERSION\n"
137 "Report-Msgid-Bugs-To: \n"
138+<<<<<<< TREE
139 "POT-Creation-Date: 2012-07-02 20:54-0700\n"
140+=======
141+"POT-Creation-Date: 2012-05-26 21:07-0700\n"
142+>>>>>>> MERGE-SOURCE
143 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
144 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
145 "Language-Team: LANGUAGE <LL@li.org>\n"
146@@ -84,7 +88,11 @@
147 msgid "Context"
148 msgstr ""
149
150+<<<<<<< TREE
151 #: ../heybuddy.py:300 ../SettingsPage.py:239
152+=======
153+#: ../heybuddy.py:300 ../SettingsPage.py:228
154+>>>>>>> MERGE-SOURCE
155 msgid "User"
156 msgstr ""
157
158@@ -211,6 +219,7 @@
159 msgid "Enable @replies notifications"
160 msgstr ""
161
162+<<<<<<< TREE
163 #: ../SettingsPage.py:157
164 msgid "Ignore *activity* posts"
165 msgstr ""
166@@ -228,31 +237,70 @@
167 msgstr ""
168
169 #: ../SettingsPage.py:189
170+=======
171+#: ../SettingsPage.py:157
172+msgid "Ignore *activity* posts"
173+msgstr ""
174+
175+#: ../SettingsPage.py:160
176+msgid "Disable HTTPS"
177+msgstr ""
178+
179+#: ../SettingsPage.py:163
180+msgid "Open in Fullscreen"
181+msgstr ""
182+
183+#: ../SettingsPage.py:178
184+>>>>>>> MERGE-SOURCE
185 msgid "Options"
186 msgstr ""
187
188+<<<<<<< TREE
189 #: ../SettingsPage.py:194
190+=======
191+#: ../SettingsPage.py:183
192+>>>>>>> MERGE-SOURCE
193 msgid "Number of dents at start-up: "
194 msgstr ""
195
196+<<<<<<< TREE
197 #: ../SettingsPage.py:207
198+=======
199+#: ../SettingsPage.py:196
200+>>>>>>> MERGE-SOURCE
201 msgid "Update Interval: "
202 msgstr ""
203
204+<<<<<<< TREE
205 #: ../SettingsPage.py:222
206+=======
207+#: ../SettingsPage.py:211
208+>>>>>>> MERGE-SOURCE
209 msgid "Link Color: "
210 msgstr ""
211
212+<<<<<<< TREE
213 #: ../SettingsPage.py:228
214+=======
215+#: ../SettingsPage.py:217
216+>>>>>>> MERGE-SOURCE
217 msgid ""
218 "enter a hexadecimal color such as '#3333ff' or a color name such as 'yellow'"
219 msgstr ""
220
221+<<<<<<< TREE
222 #: ../SettingsPage.py:235
223+=======
224+#: ../SettingsPage.py:224
225+>>>>>>> MERGE-SOURCE
226 msgid "Filters"
227 msgstr ""
228
229+<<<<<<< TREE
230 #: ../SettingsPage.py:245
231+=======
232+#: ../SettingsPage.py:234
233+>>>>>>> MERGE-SOURCE
234 msgid "String"
235 msgstr ""
236
237
238=== added file 'heybuddy-debug.py'
239--- heybuddy-debug.py 1970-01-01 00:00:00 +0000
240+++ heybuddy-debug.py 2012-07-16 21:04:20 +0000
241@@ -0,0 +1,1390 @@
242+#!/usr/bin/env python
243+'''
244+copyright 2010 jezra lickter http://www.jezra.net
245+
246+heybuddy is free software: you can redistribute it and/or modify
247+it under the terms of the GNU General Public License as published by
248+the Free Software Foundation, either version 3 of the License, or
249+(at your option) any later version.
250+
251+heybuddy is distributed in the hope that it will be useful,
252+but WITHOUT ANY WARRANTY; without even the implied warranty of
253+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
254+GNU General Public License for more details.
255+
256+You should have received a copy of the GNU General Public License
257+along with heybuddy. If not, see <http://www.gnu.org/licenses/>.
258+'''
259+import gc
260+gc.enable()
261+import gtk,gobject
262+import sys, os
263+import gettext
264+import webbrowser
265+locale_dir = os.path.join(
266+ os.path.dirname( os.path.realpath(__file__) ),
267+ 'locales')
268+
269+gettext.bindtextdomain('heybuddy', locale_dir)
270+gettext.textdomain('heybuddy')
271+gettext.install('heybuddy', locale_dir)
272+_ = gettext.gettext
273+from xml.dom.minidom import parse, parseString
274+import re
275+import time
276+import subprocess
277+#this might be on Maemo
278+from PlatformSpecific import has_hildon,links_unavailable
279+#what are the custom classes that I need?
280+from Communicator import Communicator
281+from Configuration import Configuration
282+from XMLProcessor import XMLProcessor
283+from ImageCache import ImageCache
284+from StatusIcon import StatusIcon
285+from GroupPage import GroupPage
286+from About import About
287+from SettingsPage import SettingsPage
288+import MainWindow
289+#from FilterPage import *
290+from DentScroller import DentScroller
291+from UserPage import UserPage
292+from TagPage import TagPage
293+from ScrollPage import ScrollPage
294+from Dent import Dent
295+from ContextPage import ContextPage
296+#for notifications
297+from Notify import Notify, has_pynotify, notifier_reads_markup
298+#this might be on Maemo
299+try:
300+ import hildon
301+ has_hildon = True
302+except:
303+ has_hildon = False
304+
305+global app_name
306+global version
307+global branch
308+app_name = 'heybuddy'
309+version = '0.2.4'
310+branch="testing: 325"
311+
312+#make a fake enumeration
313+DENT,MENTION,DIRECT,CONTEXT,USER,GROUP,TAG,ACCOUNT,ABOUT = range(9)
314+#keep track of high ids
315+highest_id={ "dent":0,
316+ "dent_previous":0,
317+ "mention":0,
318+ "mention_previous":0,
319+ "direct":0,
320+ "direct_previous":0,
321+ "group":0,
322+ "group_previous":0,
323+ "user":0,
324+ "user_previous":0,
325+ "tag":0,
326+ "tag_previous":0,
327+}
328+
329+class application :
330+ def __init__(self):
331+ #init threading
332+ gobject.threads_init()
333+ #keep track of the window size
334+ self.window_height=0
335+ self.window_width=0
336+ self.window_is_hidden = False
337+ self.has_status_icon = False
338+ #keep track of the initial conversation id so that conversations don't colide
339+ self.conversation_id="0"
340+ #keep track of the filters
341+ self.filters ={'users':[],'strings':[]}
342+ self.options={'run_as_tray_app':False,
343+ 'context_backwards':False,
344+ 'no_avatars':False,
345+ 'notifications':True,
346+ 'notify_replies':False,
347+ 'remember_credentials':False}
348+ #keep track of the direct_message requests
349+ self.last_direct_message_time=0
350+ #something something direct message
351+ self.is_direct_message_mode=False
352+ self.direct_message_to=None
353+ self.waiting_requests=0
354+ self.is_first_statuses = True
355+ self.is_first_mentions = True
356+ #keep track of the respond to id
357+ self.respond_to_id=0
358+ self.pre_group_page=None
359+ self.pre_user_page=None
360+ self.pre_tag_page=None
361+ #keep track of the last time statuses where pulled
362+ self.last_get_statuses = 0
363+ #what are the assets?
364+ asset_dir = 'assets'
365+ heybuddy_dir = os.path.dirname( os.path.realpath( __file__ ) )
366+ self.readme_file = os.path.join(heybuddy_dir,'README.txt')
367+ self.standard_icon_path = os.path.join(heybuddy_dir,asset_dir,'icon.png')
368+ self.direct_icon_path = os.path.join(heybuddy_dir,asset_dir,'direct_icon.png')
369+ self.networking_icon_path = os.path.join(heybuddy_dir,asset_dir,'icon1.png')
370+ self.throbber_icon_path = os.path.join(heybuddy_dir,asset_dir,'throbber.gif')
371+ self.default_icon_path = self.standard_icon_path
372+ self.conf = Configuration(app_name)
373+ #has the account info been authenticated?
374+ self.credentials_verified = self.conf.get_bool('access', 'credentials_verified')
375+ self.xmlprocessor = XMLProcessor()
376+ #create a regex to replace parts of a dent
377+ self.regex_tag = re.compile("(^| )#([\w-]+)", re.UNICODE)
378+ self.regex_group = re.compile("(^| )!([\w-]+)")
379+ self.regex_user=re.compile("(^|[^A-z0-9])@(\w+)")
380+ self.regex_just_user=re.compile("@(\w+)")
381+ self.regex_url=re.compile("(http[s]?://.*?)(\.$|\. |\s|$)",re.IGNORECASE)
382+ #get the initial dents
383+ self.initial_dents = self.conf.get('settings','initial_dents',default='20' )
384+ self.dent_limit = int(self.initial_dents)*2
385+ #get the pull time
386+ self.pull_time = self.conf.get('settings','pull_time',default='60')
387+ self.pull_time_changed_bool = False
388+ self.pull_time_mentions = False
389+ #build the gui
390+ self.build_gui()
391+
392+ #where is the certs file?
393+ ca_certs_files = [
394+ "/etc/ssl/certs/ca-certificates.crt",
395+ "/etc/ssl/certs/ca-bundle.crt",
396+ ]
397+ #what if there isn't a cert_file
398+ cert_file=None
399+ #determine the certs_file
400+ for f in ca_certs_files:
401+ if os.path.exists(f):
402+ cert_file=f
403+ break
404+ if branch.startswith("testing"):
405+ hbver = branch
406+ else:
407+ hbver = version
408+ #create the communicator
409+ self.comm = Communicator(app_name, cert_file, version=hbver)
410+ self.comm.connect('statusesXML',self.process_statusesXML,self.dentspage)
411+ self.comm.connect('mentionsXML',self.process_statusesXML,self.mentionspage)
412+ self.comm.connect('group-statusesXML',self.process_statusesXML,self.grouppage)
413+ self.comm.connect('tag-statusesXML',self.process_statusesXML,self.tagpage)
414+ self.comm.connect('user-statusesXML',self.process_statusesXML,self.userpage)
415+ self.comm.connect('direct_messagesXML',self.process_statusesXML,self.directspage,True)
416+ self.comm.connect('conversationXML',self.process_conversationXML)
417+ self.comm.connect('userXML',self.process_userXML)
418+ self.comm.connect('groupXML',self.process_groupXML)
419+ #self.comm.connect('tagXML',self.process_tagXML)
420+ self.comm.connect('new-statusXML',self.process_new_statusXML)
421+ self.comm.connect('redent-statusXML',self.process_new_statusXML)
422+ self.comm.connect('user_is_friendXML',self.process_user_is_friendXML)
423+ self.comm.connect('user_is_memberXML',self.process_user_is_memberXML)
424+ self.comm.connect('friendshipXML',self.process_friendshipXML)
425+ self.comm.connect('exception-caught',self.process_communication_exception)
426+ self.comm.connect('widget-image',self.process_widget_image)
427+ self.comm.connect('direct-messageXML',self.process_new_directXML)
428+ self.comm.connect('verify_credentialsXML',self.process_verifycredentialsXML)
429+ self.comm.connect('configXML',self.process_configXML)
430+ self.comm.connect('shortened_url',self.shortened_url)
431+ #are we disabling https?
432+ self.comm.set_disable_https( self.conf.get_bool_option('disable_https') )
433+ #create an image cacher thingy
434+ self.imagecache = ImageCache()
435+ self.imagecache.set_disabled( self.conf.get_bool_option('no_avatars') )
436+ self.imagecache.connect('get-widget-image', self.get_widget_image)
437+
438+ #Create notify-er if has pynotify
439+ if has_pynotify:
440+ self.notify = Notify()
441+
442+ #align the window
443+ self.align_window()
444+ # do we need to create the status icon
445+ if self.conf.get_bool_option('run_as_tray_app'):
446+ self.create_status_icon()
447+
448+ def build_gui(self):
449+ #build the GUI
450+ self.mainwindow=MainWindow.MainWindow(app_name,version,branch)
451+ try:
452+ self.mainwindow.set_icon_from_file(self.default_icon_path)
453+ except:
454+ pass
455+ self.mainwindow.set_textlimit( self.conf.textlimit() )
456+ self.mainwindow.set_ctrl_enter( self.conf.get_bool_option('ctrl_enter') )
457+ self.mainwindow.connect('quit', self.quit_triggered )
458+ self.mainwindow.connect('window-to-tray', self.window_to_tray )
459+ self.mainwindow.connect('update-status',self.update_status)
460+ self.mainwindow.connect('get-conversation',self.get_conversation)
461+ #self.mainwindow.connect('show-user',self.get_user_info)
462+ #self.mainwindow.connect('show-group',self.get_group_info)
463+ self.mainwindow.connect('notebook-page-change',self.page_change)
464+ self.mainwindow.connect('follow-user',self.follow_user)
465+ self.mainwindow.connect('join-group',self.join_group)
466+ self.mainwindow.connect('clear-respond-to-id',self.clear_respond_to_id)
467+ #keep track of the window size
468+ self.mainwindow.connect('size-allocate', self.window_size_allocate)
469+ #self.mainwindow.connect('window-state-event', self.mainwindow_state_event)
470+ self.mainwindow.connect('delete-event', self.mainwindow_delete_event)
471+ #what about the url shortener?
472+ self.mainwindow.connect('shorten', self.shorten)
473+
474+
475+ # local function to create a closeable tab label for closeable tabs
476+ def closeable_tab_label(caption, tab):
477+ tablabel = gtk.HBox(False,2)
478+ tablabel.pack_start(gtk.Label(caption),False,False,0)
479+ closebutton = gtk.Button()
480+ closeicon = gtk.Image()
481+ closeicon.set_from_stock(gtk.STOCK_CLOSE,gtk.ICON_SIZE_MENU)
482+ closebutton.set_image(closeicon)
483+ closebutton.set_relief(gtk.RELIEF_NONE)
484+ tinybutton_style = gtk.RcStyle()
485+ tinybutton_style.xthickness = 0
486+ tinybutton_style.ythickness = 0
487+ closebutton.modify_style(tinybutton_style)
488+ if tab != None: # if this isn't a mock-add
489+ closebutton.connect('clicked',tab.close)
490+ #don't show a tooltip on maemo
491+ if not has_hildon:
492+ closebutton.set_tooltip_text(_("Close"))
493+ tablabel.pack_start(closebutton,False,False,0)
494+ tablabel.show_all()
495+ return tablabel
496+
497+ # do a mock add of dentspage, with a close label, so as to determine height needed for uncloseable tabs' labels, then break it all down again
498+ # this is ridiculously hacky, but it works, and doesn't leave anything behind
499+ self.dentspage = ScrollPage()
500+ self.mock_label = closeable_tab_label(_("Dents"),None)
501+ self.mainwindow.notebook.append_page(self.dentspage,self.mock_label)
502+ self.mainwindow.show_all() # we have to do this so the tab gets actualised, and thus gets a height
503+ min_tab_height = self.mock_label.allocation.height
504+ self.mainwindow.hide_all()
505+ self.mainwindow.notebook.remove_page(-1)
506+ del self.mock_label
507+
508+ # local function to create a label the same height as the closeable tabs' labels
509+ def uncloseable_tab_label(caption):
510+ tablabel = gtk.Label(caption)
511+ tablabel.set_size_request(-1,min_tab_height)
512+ tablabel.show()
513+ return tablabel
514+
515+ # create and add all of the pages
516+ self.dentspage = ScrollPage()
517+ self.mainwindow.notebook.append_page(self.dentspage,uncloseable_tab_label(_("Dents") ) )
518+
519+ self.mentionspage = ScrollPage()
520+ self.mainwindow.notebook.append_page(self.mentionspage,uncloseable_tab_label(_("Mentions")) )
521+
522+ self.directspage = ScrollPage()
523+ self.mainwindow.notebook.append_page(self.directspage,uncloseable_tab_label(_("Directs")) )
524+
525+ #make the conversation page
526+ self.contextpage=ContextPage()
527+ self.contextpage.connect('context-page-hide',self.hide_contextpage)
528+ #add the contextpage
529+ context_label = closeable_tab_label(_("Context"), self.contextpage)
530+ self.mainwindow.notebook.append_page(self.contextpage, context_label)
531+
532+ #create a user page
533+ self.userpage=UserPage()
534+ self.userpage.connect('user-page-hide',self.hide_userpage)
535+ self.userpage.connect('follow-user',self.follow_user)
536+ self.userpage.connect('direct-clicked',self.direct_clicked)
537+ self.userpage.connect('open-link',self.open_link)
538+ self.userpage.connect('block-create',self.block_create)
539+ self.userpage.connect('block-destroy',self.block_destroy)
540+
541+ #add the userpage
542+ user_label = closeable_tab_label(_("User"),self.userpage )
543+ self.mainwindow.notebook.append_page(self.userpage, user_label)
544+
545+ #create a Group page
546+ self.grouppage=GroupPage()
547+ self.grouppage.connect('group-page-hide',self.hide_grouppage)
548+ self.grouppage.connect('join-group',self.join_group)
549+ self.grouppage.connect('open-link',self.open_link)
550+
551+ #add the Grouppage
552+ group_label = closeable_tab_label(_("Group"),self.grouppage )
553+ self.mainwindow.notebook.append_page(self.grouppage, group_label)
554+
555+ #create and add the tag page
556+ self.tagpage = TagPage()
557+ self.tagpage.connect('tag-page-hide',self.hide_tagpage)
558+ tag_label = closeable_tab_label(_("tag"),self.tagpage )
559+ self.mainwindow.notebook.append_page(self.tagpage, tag_label)
560+
561+ #create and add the account page
562+ self.settingspage = SettingsPage(has_hildon,has_pynotify)
563+ self.settingspage.set_initial_dents( self.initial_dents )
564+ self.settingspage.set_pull_time(self.pull_time)
565+ #update the configuration of the settings
566+ self.settingspage.connect('update-account',self.update_account)
567+ self.options['disable_https'] = self.conf.get_bool_option('disable_https')
568+ self.settingspage.set_disable_https( self.options['disable_https'] )
569+
570+ self.options['open_fullscreen'] = self.conf.get_bool_option('open_fullscreen')
571+ self.settingspage.set_open_fullscreen( self.options['open_fullscreen'] )
572+
573+ self.options['ctrl_enter'] = self.conf.get_bool_option('ctrl_enter')
574+ self.settingspage.set_ctrl_enter( self.options['ctrl_enter'] )
575+ self.options['run_as_tray_app'] = self.conf.get_bool_option('run_as_tray_app')
576+ self.settingspage.set_run_as_tray_app( self.options['run_as_tray_app'] )
577+ self.options['context_backwards'] = self.conf.get_bool_option('context_backwards')
578+ self.settingspage.set_context_backwards( self.options['context_backwards'] )
579+ self.options['no_avatar']= self.conf.get_bool_option('no_avatars')
580+ self.settingspage.set_no_avatars( self.options['no_avatar'] )
581+ self.options['reply_all']= self.conf.get_bool_option('reply_all')
582+ self.settingspage.set_reply_all(self.options['reply_all'])
583+ self.options['api_retweet'] = self.conf.get_bool_option('api_retweet')
584+ self.settingspage.set_api_retweet( self.options['api_retweet'] )
585+ self.options['notifications']= self.conf.get_bool_option('notifications')
586+ self.settingspage.set_notifications( self.options['notifications'] )
587+ self.options['notify_replies']= self.conf.get_bool_option('notify_replies')
588+ self.settingspage.set_notify_replies( self.options['notify_replies'] )
589+ self.options['ignore_activity']= self.conf.get_bool_option('ignore_activity')
590+ self.settingspage.set_ignore_activity( self.options['ignore_activity'] )
591+ link_color = self.conf.get('settings','link_color',default='blue')
592+ self.settingspage.set_link_color( link_color )
593+ self.theme_link_color(link_color)
594+ #connect the setting signals
595+ self.settingspage.connect('option-run-as-tray-app', self.option_changed, 'run_as_tray_app')
596+ self.settingspage.connect('option-open-fullscreen', self.option_changed, 'open_fullscreen')
597+ self.settingspage.connect('option-ctrl-enter', self.option_changed, 'ctrl_enter')
598+ self.settingspage.connect('option-context-backwards',self.option_changed, 'context_backwards')
599+ self.settingspage.connect('option-no-avatars',self.option_changed, 'no_avatars')
600+ self.settingspage.connect('option-api-retweet', self.option_changed, 'api_retweet')
601+ self.settingspage.connect('option-notifications',self.option_changed, 'notifications')
602+ self.settingspage.connect('option-notify-replies',self.option_changed, 'notify_replies')
603+ self.settingspage.connect('option-reply-all',self.option_changed, 'reply_all')
604+ self.settingspage.connect('option-ignore-activity',self.option_changed, 'ignore_activity')
605+ self.settingspage.connect('option-disable-https',self.option_changed, 'disable_https')
606+ self.settingspage.connect('initial-dents',self.set_initial_dents)
607+ self.settingspage.connect('pull-time',self.set_pull_time)
608+ self.settingspage.connect('link-color',self.set_link_color)
609+ self.settingspage.connect('add-string-filter', self.add_string_filter )
610+ self.settingspage.connect('remove-string-filter', self.remove_string_filter )
611+ self.settingspage.connect('add-user-filter', self.add_user_filter )
612+ self.settingspage.connect('remove-user-filter', self.remove_user_filter )
613+ self.settingspage.connect('remember-me', self.remember_credentials )
614+ #add the settings to the mainwindow
615+ self.mainwindow.add_notebook_page(self.settingspage,uncloseable_tab_label(_("Settings") ) )
616+ #create and add the about page
617+ about = About(version,branch,self.standard_icon_path,self.readme_file)
618+ about.connect('open-link',self.open_link)
619+ about.connect('group-clicked',self.view_group)
620+ self.mainwindow.add_notebook_page( about, uncloseable_tab_label(_("About") ) )
621+ self.mainwindow.show_some()
622+ #hide some stuff
623+ self.grouppage.hide_all()
624+ self.userpage.hide_all()
625+ self.tagpage.hide_all()
626+ self.contextpage.hide_all()
627+ #are we supposed to be fullscreen?
628+ if self.options['open_fullscreen']:
629+ self.mainwindow.go_fullscreen()
630+
631+ def align_window(self):
632+ #try to set the mainwindows size based on conf info
633+ try:
634+ w = int( self.conf.get('window_info','width') )
635+ h = int( self.conf.get('window_info','height') )
636+ self.mainwindow.resize(w,h)
637+ except:
638+ pass
639+ try:
640+ x = int( self.conf.get('window_info','x') )
641+ y = int( self.conf.get('window_info','y') )
642+ self.mainwindow.move(x,y)
643+ except:
644+ pass
645+
646+ #get the filters
647+ try:
648+ #read the filters
649+ filters = self.conf.get('settings','filters',True)
650+ #the filters should be an array
651+ if len(filters):
652+ #self.filters = filters
653+ self.filters['users'] = filters['users']
654+ self.filters['strings'] = filters['strings']
655+ #set the filters
656+ self.settingspage.set_string_filters( self.filters['strings'] )
657+ self.settingspage.set_user_filters( self.filters['users'] )
658+ except:
659+ pass
660+
661+ def create_status_icon(self):
662+ if not self.has_status_icon:
663+ #create the statusicon
664+ self.statusicon = StatusIcon()
665+ self.statusicon.connect('activate', self.status_clicked )
666+ self.statusicon.connect('quit-selected',self.quit_app)
667+ self.statusicon.set_icon( self.standard_icon_path )
668+ self.statusicon.set_property("visible",True)
669+ self.has_status_icon=True
670+
671+ def destroy_status_icon(self):
672+ self.statusicon.set_property("visible",False)
673+ self.has_status_icon=False
674+
675+ def update_account(self, widget, n, p, s):
676+ #check if these are valid
677+ self.mainwindow.set_message(_("Authenticating account..."))
678+ self.increment_requests()
679+ self.comm.verify_credentials(n, p, s)
680+
681+ def increment_requests(self):
682+ self.waiting_requests+=1
683+ if self.waiting_requests==1:
684+ try:
685+ self.mainwindow.set_icon_from_file(self.networking_icon_path)
686+ except:
687+ pass
688+ try:
689+ self.mainwindow.set_throbber(self.throbber_icon_path)
690+ except:
691+ pass
692+ if self.has_status_icon:
693+ self.statusicon.set_icon( self.networking_icon_path)
694+
695+ def decrement_requests(self):
696+ self.waiting_requests-=1
697+ if self.waiting_requests==0:
698+ try:
699+ self.mainwindow.set_icon_from_file(self.default_icon_path)
700+ except:
701+ pass
702+ try:
703+ self.mainwindow.set_throbber(self.default_icon_path)
704+ except:
705+ pass
706+ if self.has_status_icon:
707+ self.statusicon.set_icon( self.default_icon_path )
708+ if self.waiting_requests<0:
709+ self.waiting_requests=0
710+
711+ def valid_account_info(self, n, p, s):
712+ self.name = n
713+ self.p = p
714+ self.conf.service( s )
715+ if self.options['remember_credentials']:
716+ self.conf.name( n )
717+ self.conf.password( p )
718+ self.conf.save()
719+ #update the comm
720+ self.comm.set_name_and_password( n,p )
721+ self.comm.set_service( s )
722+
723+ def get_statuses(self,count="20"):
724+ if self.credentials_verified:
725+ #do some time handling for the good doctor
726+ now = time.time()
727+ if now - self.last_get_statuses > 2*self.pull_time:
728+ count = self.initial_dents
729+ self.last_get_statuses = now
730+ self.increment_requests()
731+ self.comm.get_statuses(count=count, since=highest_id['dent_previous'])
732+ #clean up the garbage
733+ gc.collect()
734+
735+ def get_mentions(self,count="20"):
736+ if self.credentials_verified:
737+ self.increment_requests()
738+ self.comm.get_mentions(count=count,since=highest_id['mention_previous'])
739+
740+ def get_direct_messages(self):
741+ if self.credentials_verified:
742+ now = time.time()
743+ #NOTE this could be changed to 3 minutes just setting it
744+ #this way for now
745+ #has it been 3 times the pull time?
746+ if now - (3 * int(self.pull_time) ) > self.last_direct_message_time:
747+ self.increment_requests()
748+ self.comm.get_direct_messages(highest_id['direct_previous'])
749+ self.last_direct_message_time=now
750+
751+ def run(self, enable_logging=False, start_minimized=False):
752+ if (start_minimized and not has_hildon):
753+ #set config to run as tray app
754+ self.option_changed(None, True, 'run_as_tray_app')
755+ #minimize to tray
756+ self.mainwindow.hide_on_delete()
757+ self.window_is_hidden=True
758+ self.logging = enable_logging
759+ #start the communicator
760+ self.comm.start()
761+ self.main_loop = gobject.MainLoop()
762+
763+ #we have to temporarily show the various pages or nothing works
764+ ''' why the fuck do I need to do this? '''
765+ self.mainwindow.set_notebook_page(DIRECT)
766+ self.mainwindow.set_notebook_page(MENTION)
767+ self.mainwindow.set_notebook_page(DENT)
768+ #send the service to the settings page
769+ self.settingspage.set_service(self.conf.service() )
770+
771+ #did the conf read a name and password?
772+ if( self.conf.name() !="" and self.conf.password()!=""):
773+ if self.credentials_verified:
774+ self.comm.set_name_and_password( self.conf.name(),self.conf.password() )
775+ self.comm.set_service( self.conf.service() )
776+ #fill the Account tab name/pw fields to show the user that they're logged in
777+ self.settingspage.set_name(self.conf.name())
778+ self.settingspage.set_password(self.conf.password())
779+ #add a function to the main loop
780+ gobject.timeout_add(int(self.pull_time)*1000, self.update_statuses )
781+ gobject.timeout_add(int(self.pull_time)*1500, self.update_mentions )
782+ #add a timout to get dents
783+ gobject.timeout_add(500,self.get_statuses,self.initial_dents )
784+ gobject.timeout_add(600,self.get_mentions,self.initial_dents )
785+ #are we supposed to be minimized?
786+
787+ else:
788+ #set the focus on the account page; the last page
789+ self.mainwindow.set_notebook_page(ACCOUNT)
790+
791+ self.mainwindow.run()
792+ self.main_loop.run()
793+ #if we ever get here, we should quit
794+
795+ def update_statuses(self):
796+ self.get_statuses()
797+ if(self.pull_time_changed_bool):
798+ self.pull_time_changed_bool= False
799+ gobject.timeout_add(int(self.pull_time)*1100, self.update_statuses)
800+ return False
801+ return True
802+
803+ def update_mentions(self):
804+ self.get_mentions()
805+ if(self.pull_time_mentions):
806+ self.pull_time_mentions=False
807+ gobject.timeout_add(int(self.pull_time)*1200, self.update_mentions)
808+ return False
809+ return True
810+
811+ def update_group_statuses(self):
812+ if self.watching_group:
813+ self.increment_requests()
814+ self.comm.get_group_statuses(self.watching_group,highest_id['group_previous'])
815+ return True
816+ else:
817+ return False
818+
819+ def update_tag_statuses(self, set_timeout=False):
820+ if self.watching_tag:
821+ #do we need to set a timeout?
822+ if set_timeout:
823+ gobject.timeout_add(int(self.pull_time)*1500, self.update_tag_statuses)
824+ self.increment_requests()
825+ self.comm.get_tag_statuses(self.watching_tag,highest_id['tag_previous'])
826+ return True
827+ else:
828+ return False
829+
830+ def update_user_statuses(self, set_timeout=False):
831+ if self.watching_user:
832+ #do we need to set a timeout?
833+ if set_timeout:
834+ gobject.timeout_add(int(self.pull_time)*1500, self.update_user_statuses)
835+ self.increment_requests()
836+ self.comm.get_user_statuses(self.watching_user,highest_id['user_previous'])
837+ return True
838+ else:
839+ return False
840+
841+ def update_direct_messages(self):
842+ self.get_direct_messages()
843+ return True
844+
845+ def update_status(self,widget,text):
846+ self.increment_requests()
847+ #remove newlines from text
848+ text=text.replace("\n","")
849+ #is this direct message mode?
850+ if self.is_direct_message_mode:
851+ self.comm.send_direct_message(text,self.direct_message_to)
852+ self.set_is_direct_message_mode(False)
853+ else:
854+ self.comm.send_update_status(text,self.respond_to_id)
855+ self.clear_respond_to_id()
856+
857+ def quit_app(self,widget=None):
858+ #quit the communicator loop
859+ self.comm.quit()
860+ #quit the main loop
861+ self.main_loop.quit()
862+
863+ def get_widget_image(self,imagecache,image):
864+ self.comm.get_widget_image(image)
865+
866+ def get_conversation(self,id,conversation_id):
867+ if self.credentials_verified:
868+ self.increment_requests()
869+ self.comm.get_conversation(id,conversation_id)
870+
871+ def parse_profile(self, profile=None, name=None ):
872+ #is this a remote user?
873+ remote_user = True
874+ service = ''
875+ #did we get a name?
876+ if profile!=None:
877+ bits = profile.rsplit("/")
878+ service = bits[2]
879+ if name==None:
880+ name = bits[3]
881+ #if there is no name, use the first subdomain as a name
882+ if name==None:
883+ dot_split = service.split(".")
884+ name = dot_split[0]
885+ if service==self.conf.service():
886+ remote_user = False
887+ else:
888+ remote_user = False
889+ return {'service':service, 'remote':remote_user,'name':name}
890+
891+ def get_user_info(self, profile=None, name=None):
892+ #parse the info
893+ result = self.parse_profile( profile, name )
894+ #is this a remote user?
895+ remote_user = result['remote']
896+ name = result['name']
897+ service = result['service']
898+
899+ #is this not a remote user?
900+ if not remote_user:
901+ if self.credentials_verified:
902+ self.increment_requests()
903+ self.comm.get_user_info(name)
904+ #follow this user
905+ self.watching_user=name
906+ #reset the highest_id
907+ highest_id['user_previous'] = 0
908+ highest_id['user'] = 0
909+ self.update_user_statuses(True)
910+ return
911+
912+ self.increment_requests()
913+ self.comm.get_remote_user_info(service,name)
914+ self.increment_requests()
915+ self.comm.get_remote_user_statuses(service,name)
916+
917+
918+ def get_group_info(self,name):
919+ if self.credentials_verified:
920+ self.increment_requests()
921+ self.comm.get_group_info(name)
922+
923+ def follow_user(self,widget,name, service,bool):
924+ if self.credentials_verified:
925+ if bool:
926+ self.comm.friendship_create(name,service)
927+ else:
928+ self.comm.friendship_destroy(name,service)
929+
930+ def join_group(self,widget,name,bool):
931+ if self.credentials_verified:
932+ if bool:
933+ self.comm.group_join(name)
934+ else:
935+ self.comm.group_leave(name)
936+
937+ def process_friendshipXML(self,widget,text):
938+ pass
939+
940+ def page_change(self,widget,old_id,new_id):
941+ if new_id==MainWindow.DIRECT:
942+ self.get_direct_messages()
943+ else:
944+ if self.is_direct_message_mode:
945+ self.set_is_direct_message_mode(False)
946+ #should we clear any warning message?
947+ self.mainwindow.clear_message()
948+
949+ def process_userXML(self,comm,text):
950+ self.decrement_requests()
951+ data = self.xmlprocessor.get_user_info(text)
952+ #we need to parse the profile for possible external users
953+ data['profile'] = self.parse_profile(data['profile_url'], data['screen_name'])
954+ if data['profile_image_url']!=None:
955+ self.imagecache.add_image_to_widget(data['profile_image_url'],self.userpage)
956+ self.userpage.set_user_data(data)
957+
958+ def process_groupXML(self,comm,text):
959+ self.decrement_requests()
960+ data = self.xmlprocessor.get_group_info(text)
961+ if data['stream_logo']!=None:
962+ self.imagecache.add_image_to_widget(data['stream_logo'],self.grouppage)
963+ #reset the highest id
964+ highest_id['group_previous']=0
965+ highest_id['group']=0
966+ #get the group statuses
967+ self.update_group_statuses()
968+ #start following the group
969+ gobject.timeout_add(int(self.pull_time)*1000, self.update_group_statuses)
970+ self.grouppage.set_group_data(data)
971+
972+ def process_user_is_friendXML(self,comm,text):
973+ self.decrement_requests()
974+ is_friend = self.xmlprocessor.get_user_is_friend(text)
975+ self.mainwindow.display_user_is_friend( is_friend )
976+
977+ def process_user_is_memberXML(self,comm,text):
978+ self.decrement_requests()
979+ is_member = self.xmlprocessor.get_user_is_member(text)
980+ self.mainwindow.display_user_is_member( is_member )
981+
982+ def process_new_directXML(self,comm,text):
983+ #a update status was sent
984+ self.decrement_requests()
985+ #clear the textview thing
986+ self.mainwindow.emit_update_textview_responsed()
987+
988+ def process_new_statusXML(self,comm,text):
989+ #a update status was sent
990+ self.decrement_requests()
991+ #clear the textview thing
992+ self.mainwindow.emit_update_textview_responsed()
993+ #there is one status and it is the DOM
994+ status = self.xmlprocessor.get_dom(text)
995+ data = self.get_dent_data(status)
996+ data['markup'] = self.markup_dent_text(data['text'])
997+ #add the dent
998+ dent = self.connect_dent(data,self.dentspage)
999+
1000+ def process_verifycredentialsXML(self, object, text, data):
1001+ print "verified"
1002+ #actually, if we are here, the authorization worked!
1003+ self.conf.set('access', 'credentials_verified', True)
1004+ self.credentials_verified = True
1005+ self.mainwindow.message_label.hide()
1006+ (n, p, s) = data
1007+ self.valid_account_info(n, p, s)
1008+ #get the config
1009+ self.comm.get_config()
1010+ #switch to the dents page
1011+ self.mainwindow.set_notebook_page(DENT)
1012+ self.get_statuses()
1013+ self.get_mentions()
1014+
1015+ def process_conversationXML(self,object,text,conversation_id):
1016+ self.decrement_requests()
1017+ #is this the current conversation Id? if not, then do nothing
1018+ #convert to int because there is a problem with string comparison
1019+ '''is one of the strings unicode?'''
1020+ if int(self.conversation_id)==int(conversation_id):
1021+ #get the status
1022+ status = self.xmlprocessor.get_dom(text)
1023+ data = self.get_dent_data(status)
1024+ data['markup']=self.markup_dent_text(data['text'])
1025+ #since this is a conversation, we can get rid of the in_reply_to_screen_name
1026+ data['in_reply_to_screen_name']=None
1027+ #tell the mainWindow to add the dent
1028+ dent = self.connect_dent(data,self.contextpage,True, self.options['context_backwards'] )
1029+ if data['in_reply_to_id']!=None:
1030+ #recursively get in_reply_to_ids
1031+ self.get_conversation(id=data['in_reply_to_id'],conversation_id=conversation_id)
1032+
1033+ def process_configXML(self, object, text):
1034+ self.decrement_requests()
1035+ server = self.xmlprocessor.get_server_config(text)
1036+ self.conf.textlimit( server['textlimit'] )
1037+ self.conf.save()
1038+ self.mainwindow.set_textlimit( server['textlimit'] )
1039+
1040+ def connect_dent(self,data,target_page,is_conv=False,conv_backwards=False,is_direct_dent=False):
1041+ #make the dent
1042+ dent = Dent(data,is_direct=is_direct_dent)
1043+ if target_page.dentScroller.add_dent( dent, is_conv, conv_backwards ):
1044+ dent.connect('group-clicked', self.view_group)
1045+ dent.connect('tag-clicked', self.view_tag)
1046+ dent.connect('reply-clicked', self.reply_clicked)
1047+ dent.connect('reply-all-clicked', self.reply_all_clicked)
1048+ dent.connect('direct-clicked', self.direct_clicked)
1049+ dent.connect('view-conversation-clicked', self.view_conversation)
1050+ dent.connect('user-clicked', self.view_user_name)
1051+ dent.connect('profile-clicked', self.view_user_profile)
1052+ dent.connect('id-clicked', self.view_id)
1053+ dent.connect('text-label-clicked',self.dent_text_clicked)
1054+ dent.connect('redent-clicked',self.redent_clicked)
1055+ dent.connect('favorite-clicked',self.favorite_clicked)
1056+ dent.connect('unfavorite-clicked',self.unfavorite_clicked)
1057+ dent.connect('open-link',self.open_link)
1058+ if target_page!=self.userpage:
1059+ #get the image for this dent
1060+ self.imagecache.add_image_to_widget(data['profile_image_url'],dent)
1061+ return True
1062+ else:
1063+ dent.destroy()
1064+ del dent
1065+ return False
1066+
1067+ def reply_clicked(self,dent,name):
1068+ #set the text buffer
1069+ self.mainwindow.update_textbuffer.set_text("@%s " % (name) )
1070+ self.mainwindow.update_textview.grab_focus()
1071+ #set the respond_to_id
1072+ self.respond_to_id=dent.id
1073+
1074+ def reply_all_clicked(self,dent,name,text):
1075+ if dent.in_reply_to_screen_name:
1076+ text += "@%s" % dent.in_reply_to_screen_name
1077+ text += "@%s" % name
1078+ users = self.regex_just_user.findall(text)
1079+ start_reply = ""
1080+ for user in users:
1081+ if user in start_reply:
1082+ print "%s already in reply, not adding" % user
1083+ elif user == self.conf.name():
1084+ print "%s is self, not adding" % user
1085+ else:
1086+ start_reply += "@%s " % user
1087+ print "Adding %s to reply" % user
1088+ #set the text buffer
1089+ self.mainwindow.update_textbuffer.set_text("%s " % (start_reply) )
1090+ self.mainwindow.update_textview.grab_focus()
1091+ #set the respond_to_id
1092+ self.respond_to_id=dent.id
1093+
1094+
1095+ def favorite_clicked(self,dent):
1096+ id=dent.id
1097+ self.comm.favorite(id)
1098+
1099+ def unfavorite_clicked(self,dent):
1100+ id=dent.id
1101+ self.comm.unfavorite(id)
1102+
1103+ def direct_clicked(self,widget,name):
1104+ self.direct_message_to = name
1105+ if type(widget).__name__=="Dent":
1106+ self.respond_to_id=widget.id
1107+ else:
1108+ self.respond_to_id=None
1109+ self.set_is_direct_message_mode(True,name)
1110+ #we should be on the directs page
1111+ self.mainwindow.set_notebook_page(DIRECT)
1112+ #set the text buffer
1113+ self.mainwindow.update_textbuffer.set_text("")
1114+ self.mainwindow.update_textview.grab_focus()
1115+
1116+ def view_conversation(self,dent,id):
1117+ self.conversation_id=id
1118+ self.pre_context_page=self.mainwindow.notebook_current_page
1119+ self.contextpage.dentScroller.clear()
1120+ self.contextpage.show_all()
1121+ #get conversation starting with id
1122+ self.get_conversation(id,id)
1123+ self.mainwindow.notebook.set_current_page(CONTEXT)
1124+
1125+ def view_user_profile(self, widget, profile_url, screen_name):
1126+ self.get_user_info(profile=profile_url, name=screen_name)
1127+ #self.get_user_info(screen_name)
1128+ self.pre_user_page=self.mainwindow.notebook_current_page
1129+ #make the user page checkbox insensitive
1130+ self.userpage.disable()
1131+ #show the page
1132+ self.userpage.show_all()
1133+ #rehide its collapsed widgetbox, that doesn't need to be visible yet
1134+ self.userpage.widgetbox_collapsed.hide()
1135+ #clear the userpage stuff
1136+ self.userpage.clear()
1137+ #change to the user page
1138+ self.mainwindow.set_notebook_page(USER)
1139+
1140+ def view_id(self, widget, id):
1141+ url = 'http://%s/notice/%s' % (self.conf.service(), id)
1142+ self.open_link(None, url)
1143+
1144+ def view_user_name(self, widget, screen_name):
1145+ #TODO: merge this function with view_user_profile
1146+ self.get_user_info(name=screen_name)
1147+ #self.get_user_info(screen_name)
1148+ self.pre_user_page=self.mainwindow.notebook_current_page
1149+ #make the user page checkbox insensitive
1150+ self.userpage.disable()
1151+ #show the page
1152+ self.userpage.show_all()
1153+ #rehide its collapsed widgetbox, that doesn't need to be visible yet
1154+ self.userpage.widgetbox_collapsed.hide()
1155+ #clear the userpage stuff
1156+ self.userpage.clear()
1157+ #change to the user page
1158+ self.mainwindow.set_notebook_page(USER)
1159+
1160+ #does this function even get called? get rid of it
1161+ def get_dent_time(self,text):
1162+ #print text
1163+ pass
1164+
1165+ def markup_dent_text(self,text,is_notification=False):
1166+ #process the text to markup
1167+ #xmlencode the string
1168+ text = self.xmlprocessor.xmlentities(text)
1169+ #regex find users
1170+ if has_hildon or links_unavailable or is_notification:
1171+ markup = self.regex_group.sub(r'\1!<b>\2</b>',text)
1172+ markup = self.regex_user.sub(r'\1@<b>\2</b>',markup)
1173+ markup = self.regex_tag.sub(r'\1#<b>\2</b>',markup)
1174+ else:
1175+ #find urls
1176+ markup = self.regex_tag.sub(r'\1#<a href="#\2">\2</a>',text)
1177+ markup = self.regex_url.sub(r'<a href="\1">\1</a>\2',markup)
1178+ markup = self.regex_user.sub(r'\1@<a href="@\2">\2</a>',markup)
1179+ markup = self.regex_group.sub(r'\1!<a href="!\2">\2</a>',markup)
1180+ return markup
1181+
1182+ def process_statusesXML(self,object,text,target_page,is_direct=False):
1183+ #based on the target, what is the counting thingy?
1184+ count_ref = None
1185+ do_notify = False
1186+ if target_page==self.dentspage:
1187+ count_ref = "dent"
1188+ elif target_page==self.mentionspage:
1189+ count_ref = "mention"
1190+ elif target_page==self.directspage:
1191+ count_ref = "direct"
1192+ elif target_page==self.userpage:
1193+ count_ref = "user"
1194+ elif target_page==self.grouppage:
1195+ count_ref = "group"
1196+ elif target_page==self.tagpage:
1197+ count_ref = "tag"
1198+ self.decrement_requests()
1199+ #set the "previous" count ref, do this now and get data twice
1200+ if count_ref!=None:
1201+ highest_id[count_ref+"_previous"] = highest_id[count_ref]
1202+ #get a list of the statuses
1203+ statuses = self.xmlprocessor.get_statuses(text,is_direct)
1204+ #if there aren't any statuses, return
1205+ if len(statuses)==0:
1206+ return
1207+ #reverse the statuses list
1208+ statuses.reverse()
1209+ dent_count=0
1210+ reply_count=0
1211+ #what is the highest id of the target index?
1212+ for status in statuses:
1213+ filtered_status = False
1214+ data = self.get_dent_data(status)
1215+
1216+ if target_page==self.dentspage:
1217+ #we should check for filtration
1218+ filtered = self.check_filtered( data )
1219+ filtered_status=filtered[0]
1220+ #are we ignoring activity?
1221+ if self.options['ignore_activity']:
1222+ #what is the 'source' of the dent?
1223+ if data['source'] == "activity":
1224+ self.log("ignore activity: #%s" %(data['status_id']) )
1225+ continue
1226+
1227+ if not filtered_status:
1228+ #get the markup
1229+ data['markup'] = self.markup_dent_text(data['text'])
1230+ #did this dent connect
1231+ if not self.connect_dent(data,target_page,is_direct_dent=is_direct):
1232+ continue
1233+
1234+ #if the target_page = 0 and not first_dents and not is_conf
1235+ if (target_page==self.dentspage and not self.is_first_statuses) or ( target_page==self.mentionspage and not self.is_first_mentions):
1236+ if "@"+self.conf.name() in data['markup']:
1237+ if target_page==self.dentspage:
1238+ dent=self.connect_dent(data, self.mentionspage )
1239+ if target_page==self.mentionspage:
1240+ dent=self.connect_dent(data, self.dentspage )
1241+
1242+ reply_count+=1
1243+ if self.options['notify_replies'] and has_pynotify:
1244+ do_notify = True
1245+ else:
1246+ dent_count+=1
1247+ if target_page==self.mentionspage and self.options['notify_replies'] and has_pynotify:
1248+ do_notify = True
1249+
1250+ else:
1251+ self.log("filter #%s %s" %(data['status_id'], filtered[1] ) )
1252+
1253+ if do_notify and self.conf.name() != data['name']:
1254+ user = data['screen_name']
1255+ #the message shouldn't be marked up if the server doesn't understand markup
1256+ if notifier_reads_markup:
1257+ message = self.markup_dent_text(data['text'],True)
1258+ else:
1259+ message = data['text']
1260+ avatar = self.imagecache.get_image_path(data['profile_image_url'])
1261+ self.notify.notify_reply(user,message,avatar)
1262+
1263+ #keep track of the last status_id
1264+ if count_ref!=None and data!=None:
1265+ highest_id[count_ref]=data['status_id']
1266+
1267+ if target_page==self.dentspage and self.is_first_statuses:
1268+ self.is_first_statuses = False
1269+ if target_page==self.mentionspage and self.is_first_mentions:
1270+ self.is_first_mentions = False
1271+
1272+ #get the related images
1273+ self.imagecache.get_images()
1274+ #do we notify?
1275+ if (dent_count!=0 or reply_count!=0) and has_pynotify and target_page==self.dentspage:
1276+ if self.options['notifications']:
1277+ if not self.options['notify_replies']:
1278+ self.notify.notify_updates_replies(dent_count,reply_count,self.standard_icon_path)
1279+ else:
1280+ if dent_count!=0:
1281+ self.notify.notify_updates(dent_count,self.standard_icon_path)
1282+ #prune off the extra dents
1283+ target_page.dentScroller.prune(self.dent_limit)
1284+
1285+ def get_dent_data(self, status):
1286+ data = self.xmlprocessor.get_dent_data(status)
1287+ #cool your jets bub! what if the source is "ostatus"?
1288+ if data['source'] == 'ostatus':
1289+ #parse the data
1290+ info = self.parse_profile(data['profile_url'], data['name'])
1291+ #reset the source
1292+ data['source'] = info['service']
1293+ return data
1294+
1295+ def process_widget_image(self,comm,data,name):
1296+ self.imagecache.widget_image_add(data,name)
1297+
1298+ def process_communication_exception(self,communicator,code,data,signal=None):
1299+ if self.logging:
1300+ self.log("code:%s, signal:%s" % (code,signal) )
1301+ self.decrement_requests()
1302+ #is there an error message?
1303+ try:
1304+ error_message = self.xmlprocessor.get_error_message(data)
1305+ except:
1306+ error_message = False
1307+ if error_message and not code:
1308+ self.mainwindow.set_message( error_message, MainWindow.ERROR_MESSAGE)
1309+ elif code=='401':
1310+ #bad authorization
1311+ self.mainwindow.set_notebook_page(MainWindow.ACCOUNT)
1312+ error_message = _("Invalid Authorization: Your name or password is incorrect")
1313+ elif code == '403':
1314+ error_message = _("Forbidden")
1315+ elif code == '404':
1316+ error_message = _("Service Not Found")
1317+ elif code == '503':
1318+ error_message = _("Service Unavailable")
1319+ elif code == 'unknown error':
1320+ error_message = _("Unknown Error")
1321+ else:
1322+ error_message = code
1323+ #send the error message
1324+ self.mainwindow.set_message(error_message, MainWindow.ERROR_MESSAGE)
1325+
1326+ #a signal may have been passed from the comm
1327+ if signal == "new-statusXML":
1328+ #a dent was sent but it failed
1329+ self.mainwindow.emit_update_textview_responsed(error=True)
1330+
1331+ #functions to handle the filtration system
1332+ def add_string_filter(self,winder,string):
1333+ if not string in self.filters['strings']:
1334+ self.filters['strings'].append(string)
1335+ self.filters['strings'].sort()
1336+ self.settingspage.set_string_filters( self.filters['strings'] )
1337+
1338+ def remove_string_filter(self,winder,string):
1339+ if string in self.filters['strings']:
1340+ self.filters['strings'].remove(string)
1341+ self.settingspage.set_string_filters( self.filters['strings'] )
1342+
1343+ def add_user_filter(self,winder,user):
1344+ if not user in self.filters['users']:
1345+ self.filters['users'].append(user)
1346+ self.filters['users'].sort()
1347+ self.settingspage.set_user_filters( self.filters['users'] )
1348+
1349+ def remove_user_filter(self,winder,user):
1350+ if user in self.filters['users']:
1351+ self.filters['users'].remove(user)
1352+ self.settingspage.set_user_filters( self.filters['users'] )
1353+
1354+ def check_filtered(self,data):
1355+ if len(self.filters['users'])!=0 :
1356+ #filter against the user
1357+ for user in self.filters['users']:
1358+ if data['screen_name'].lower()==user.lower():
1359+ return (True, "user: %s" % data['screen_name'] )
1360+
1361+ if len(self.filters['strings'])!=0:
1362+ #filter against the strings
1363+ #get the dent text
1364+ text = data['text']
1365+ #loop through the filter strings
1366+ for string in self.filters['strings']:
1367+ if re.search(string, text, flags=re.IGNORECASE):
1368+ try:
1369+ return (True, "string: %s" % string )
1370+ except:
1371+ return (True, "unknown")
1372+
1373+ #if we get this far, just return
1374+ return (False, None)
1375+
1376+ def status_clicked(self,widget):
1377+ if self.window_is_hidden==True:
1378+ self.window_is_hidden=False
1379+ self.mainwindow.show()
1380+ self.align_window()
1381+ self.mainwindow.present()
1382+ else:
1383+ self.window_to_tray(widget)
1384+
1385+ def window_size_allocate(self,widget,size):
1386+ self.window_width = size[2]
1387+ self.window_height = size[3]
1388+
1389+ def save_app_info(self):
1390+ #save the filters
1391+ self.conf.set('settings','filters', self.filters ,True)
1392+ #save the window info
1393+ self.conf.set('window_info','width', self.window_width )
1394+ self.conf.set('window_info','height', self.window_height )
1395+ try:
1396+ #some themes don't pass root origin?
1397+ origin = self.mainwindow.window.get_root_origin()
1398+ self.window_x = origin[0]
1399+ self.window_y = origin[1]
1400+ self.conf.set('window_info','x', self.window_x )
1401+ self.conf.set('window_info','y', self.window_y )
1402+ except:
1403+ pass
1404+ self.conf.save()
1405+
1406+ def mainwindow_delete_event(self,window,event=None):
1407+ self.save_app_info()
1408+ #are we in tray mode?
1409+ if self.conf.get_bool_option('run_as_tray_app') or self.has_status_icon:
1410+ #is the status icon embedded?
1411+ if self.statusicon.is_embedded():
1412+ self.window_is_hidden=True
1413+ self.mainwindow.hide_on_delete()
1414+ #do not propogate the signal
1415+ return True
1416+ else:
1417+ #we need to quit
1418+ self.quit_app()
1419+
1420+ def quit_triggered(self,widget):
1421+ self.save_app_info()
1422+ self.quit_app()
1423+
1424+ def option_changed(self,widget,value,option):
1425+ #save the option change in the configuration
1426+ self.conf.set('options',option,value)
1427+ self.options[option]=value
1428+ self.conf.save()
1429+ #is this the run_tray_app:
1430+ if option=='run_as_tray_app':
1431+ if value:
1432+ self.create_status_icon()
1433+ else:
1434+ self.destroy_status_icon()
1435+ elif option=='no_avatars':
1436+ self.imagecache.set_disabled(value)
1437+ elif option=='ctrl_enter':
1438+ self.mainwindow.set_ctrl_enter(value)
1439+
1440+ def redent_clicked(self,dent,name,text):
1441+ if self.options['api_retweet']:
1442+ self.comm.send_redent(dent.id)
1443+ else:
1444+ self.respond_to_id=dent.id
1445+ #formatted_text= u'\u267A @%s: %s' % (name,text)
1446+ formatted_text= u'\u267A @%s: %s' % (name,text)
1447+ self.mainwindow.update_textbuffer.set_text(formatted_text )
1448+ self.mainwindow.update_textview.grab_focus()
1449+
1450+ def view_group(self,widget,group_name):
1451+ #where were we?
1452+ self.pre_group_page=self.mainwindow.notebook_current_page
1453+ #make some flags
1454+ self.grouppage.joined_checkbutton.set_sensitive(False)
1455+ #make the grouppage visible
1456+ self.grouppage.show_all()
1457+ #rehide its collapsed widgetbox, that doesn't need to be visible yet
1458+ self.grouppage.widgetbox_collapsed.hide()
1459+ #clear the grouppage
1460+ self.grouppage.clear()
1461+ #switch to the group page
1462+ self.mainwindow.set_notebook_page(GROUP)
1463+ #follow this group with regular updates
1464+ self.watching_group = group_name
1465+ self.get_group_info(group_name)
1466+
1467+ def view_tag(self,widget,tag_name):
1468+ #clear the tagpage
1469+ self.tagpage.clear()
1470+ #where were we?
1471+ self.pre_tag_page=self.mainwindow.notebook_current_page
1472+ #make the tagpage visible
1473+ self.tagpage.show_all()
1474+ #set the tag text
1475+ self.tagpage.set_tag(tag_name)
1476+ #switch to the tag page
1477+ self.mainwindow.set_notebook_page(TAG)
1478+ #follow this tag with regular updates
1479+ self.watching_tag = self.format_tag(tag_name)
1480+ #reset the tag highest id
1481+ highest_id['tag_previous']=0
1482+ highest_id['tag']=0
1483+ self.update_tag_statuses(True)
1484+
1485+ def hide_contextpage(self,widget):
1486+ #end any conversation in progress
1487+ self.conversation_id="0"
1488+ self.mainwindow.set_notebook_page( self.pre_context_page )
1489+ self.contextpage.hide()
1490+
1491+ def hide_grouppage(self,widget):
1492+ self.mainwindow.set_notebook_page( self.pre_group_page )
1493+ self.grouppage.hide()
1494+ #TODO: stop keeping track of this group
1495+ self.watching_group = False
1496+
1497+ def hide_tagpage(self, widget):
1498+ self.mainwindow.set_notebook_page( self.pre_tag_page )
1499+ self.tagpage.hide()
1500+ #stop keeping track of this tag
1501+ self.watching_tag = False
1502+
1503+ def hide_userpage(self,widget):
1504+ self.mainwindow.set_notebook_page( self.pre_user_page )
1505+ self.userpage.hide()
1506+ self.watching_user = False
1507+
1508+ def dent_text_clicked(self,dent,text_label):
1509+ self.mainwindow.last_clicked_label = text_label
1510+
1511+ def clear_respond_to_id(self,widget=None):
1512+ self.respond_to_id = 0
1513+
1514+ def set_initial_dents(self,widget,value):
1515+ self.initial_dents = value
1516+ self.dent_limit = int(self.initial_dents)*2
1517+ self.conf.set('settings', 'initial_dents', value)
1518+
1519+
1520+ def set_pull_time(self, widget, value):
1521+ """A method for setting the time between pulls. obvious comment
1522+ is obvious. Very possible everything will die now that I've added
1523+ this.
1524+ --Muel Kiel (aka samsnotunix)"""
1525+ self.pull_time=value
1526+ self.conf.set('settings', 'pull_time', value)
1527+ self.pull_time_changed_bool = True
1528+ self.pull_time_mentions = True
1529+
1530+ def set_link_color(self,widget,string):
1531+ self.conf.set('settings','link_color',string)
1532+ self.theme_link_color(string)
1533+
1534+ def theme_link_color(self,color):
1535+ style='''
1536+ style "label" {
1537+ GtkLabel::link-color="%s"
1538+ }
1539+ widget_class "*GtkLabel" style "label"
1540+ ''' % (color)
1541+ gtk.rc_parse_string(style)
1542+ #fuck you GTK for not make this work, more than once!
1543+
1544+ #functions for blocking/unblocking
1545+ def block_create(self,widget,user_id):
1546+ self.comm.block_create(user_id)
1547+
1548+ def block_destroy(self,widget,user_id):
1549+ self.comm.block_destroy(user_id)
1550+
1551+
1552+ def set_is_direct_message_mode(self,mode,user=None):
1553+ self.is_direct_message_mode=mode
1554+ self.mainwindow.set_is_direct_message_mode(mode,user)
1555+ if mode:
1556+ self.default_icon_path=self.direct_icon_path
1557+ else:
1558+ self.default_icon_path=self.standard_icon_path
1559+ #if we aren't waiting on any requests, swap the graphic
1560+ if self.waiting_requests==0:
1561+ try:
1562+ self.mainwindow.set_icon_from_file(self.default_icon_path)
1563+ except:
1564+ pass
1565+ try:
1566+ self.mainwindow.set_throbber(self.default_icon_path)
1567+ except:
1568+ pass
1569+
1570+ def simple_decrement(self,text=None,data=None):
1571+ self.decrement_requests()
1572+
1573+ '''
1574+ function window_to_tray
1575+ sets the run_as_tray_app to true and minimizes the app
1576+ '''
1577+ def window_to_tray(self,window):
1578+ if self.options['run_as_tray_app']:
1579+ self.mainwindow_delete_event(window)
1580+
1581+
1582+ def open_link(self, widget, uri):
1583+ webbrowser.open(uri)
1584+ return True
1585+ '''
1586+ command = "xdg-open '%s'" % ( uri )
1587+ #print command
1588+ subprocess.Popen(command,shell=True)
1589+ return True
1590+ '''
1591+
1592+ def log(self,string):
1593+ if self.logging:
1594+ now = time.strftime("%H:%M:%S")
1595+ file = os.path.join( os.path.expanduser("~"),"heybuddy.log")
1596+ f = open(file,'a')
1597+ f.write("%s %s\n" % (now,string) )
1598+ f.close()
1599+
1600+ def shorten(self, widget, url):
1601+ self.comm.shorten( url )
1602+
1603+ def shortened_url(self, widget, url, old_url):
1604+ self.mainwindow.shortened_url( url, old_url)
1605+
1606+ def remember_credentials(self, widget, remember):
1607+ self.options['remember_credentials'] = remember
1608+ if not remember:
1609+ self.conf.name('')
1610+ self.conf.password('')
1611+ else:
1612+ try:
1613+ self.conf.name( self.name )
1614+ self.conf.password( self.password )
1615+ except:
1616+ pass #shit might not be set yet
1617+ self.conf.save()
1618+
1619+ def format_tag(self, tag):
1620+ tag = tag.replace('_','')
1621+ tag = tag.lower()
1622+ return tag
1623+
1624+if(__name__=="__main__"):
1625+ try:
1626+ enable_logging = '--enable-logging' in sys.argv
1627+ start_minimized = '-m' in sys.argv
1628+ except:
1629+ enable_logging = False
1630+ a = application()
1631+ a.run(enable_logging, start_minimized)
1632
1633=== modified file 'heybuddy.py'
1634--- heybuddy.py 2012-07-03 03:55:02 +0000
1635+++ heybuddy.py 2012-07-16 21:04:20 +0000
1636@@ -65,8 +65,13 @@
1637 global version
1638 global branch
1639 app_name = 'heybuddy'
1640+<<<<<<< TREE
1641 version = '0.2.4'
1642 branch="testing: 326"
1643+=======
1644+version = '0.2.4'
1645+branch="testing: 325"
1646+>>>>>>> MERGE-SOURCE
1647
1648 #make a fake enumeration
1649 DENT,MENTION,DIRECT,CONTEXT,USER,GROUP,TAG,ACCOUNT,ABOUT = range(9)
1650@@ -136,6 +141,7 @@
1651 self.regex_tag = re.compile("(^| )#([\w-]+)", re.UNICODE)
1652 self.regex_group = re.compile("(^| )!([\w-]+)")
1653 self.regex_user=re.compile("(^|[^A-z0-9])@(\w+)")
1654+ self.regex_just_user=re.compile("@(\w+)")
1655 self.regex_url=re.compile("(http[s]?://.*?)(\.$|\. |\s|$)",re.IGNORECASE)
1656 #get the initial dents
1657 self.initial_dents = self.conf.get('settings','initial_dents',default='20' )
1658@@ -799,6 +805,7 @@
1659 dent.connect('group-clicked', self.view_group)
1660 dent.connect('tag-clicked', self.view_tag)
1661 dent.connect('reply-clicked', self.reply_clicked)
1662+ dent.connect('reply-all-clicked', self.reply_all_clicked)
1663 dent.connect('direct-clicked', self.direct_clicked)
1664 dent.connect('view-conversation-clicked', self.view_conversation)
1665 dent.connect('user-clicked', self.view_user_name)
1666@@ -825,6 +832,21 @@
1667 #set the respond_to_id
1668 self.respond_to_id=dent.id
1669
1670+ def reply_all_clicked(self,dent,name,text):
1671+ if dent.in_reply_to_screen_name:
1672+ text += "@%s" % dent.in_reply_to_screen_name
1673+ text += "@%s" % name
1674+ users = self.regex_just_user.findall(text)
1675+ start_reply = ""
1676+ for user in users:
1677+ if not user in start_reply and not user == self.conf.name():
1678+ start_reply += "@%s " % user
1679+ #set the text buffer
1680+ self.mainwindow.update_textbuffer.set_text("%s" % (start_reply) )
1681+ self.mainwindow.update_textview.grab_focus()
1682+ #set the respond_to_id
1683+ self.respond_to_id=dent.id
1684+
1685
1686 def favorite_clicked(self,dent):
1687 id=dent.id

Subscribers

People subscribed via source and target branches

to all changes:
to status/vote changes: