Merge lp:~dazworrall/gwibber/public into lp:gwibber/1.2

Proposed by Darren Worrall
Status: Merged
Merged at revision: not available
Proposed branch: lp:~dazworrall/gwibber/public
Merge into: lp:gwibber/1.2
Diff against target: None lines
To merge this branch: bzr merge lp:~dazworrall/gwibber/public
Reviewer Review Type Date Requested Status
gwibber-committers Pending
Review via email: mp+7668@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Darren Worrall (dazworrall) wrote :

Adds a public timeline tab which can be toggled, and when hidden, public timeline messages are not refreshed. Public timeline support added to the Twitter, Friendfeed and identi.ca protocols in this release.

Of particular interest for review is the extra code in add_msg_tab, add_scrolled_parent and apply_ui_elements to support disabling notifications for messages on a particular tab (show_notifications), and hiding the correct elements if this tab is for toggling (can_toggle).

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'bin/gwibber'
2--- bin/gwibber 1970-01-01 00:00:00 +0000
3+++ bin/gwibber 2009-06-15 19:40:02 +0000
4@@ -0,0 +1,79 @@
5+#!/usr/bin/env python
6+
7+"""
8+
9+Gwibber Client
10+SegPhault (Ryan Paul) - 05/29/2007
11+
12+"""
13+
14+import sys, logging, gtk, optparse
15+from os.path import join, dirname, exists, realpath, abspath
16+
17+
18+######################################################################
19+# Options and configuration
20+def parse_opts():
21+ usage = "usage: %prog [options] <config-file>"
22+
23+ oparse = optparse.OptionParser(usage=usage)
24+
25+ oparse.add_option("-v", "--verbose", action="store_true",
26+ help="Verbose messages", default=False)
27+ oparse.add_option("-d", "--debug", action="store_true",
28+ help="Debug messages", default=False)
29+
30+ opts, rest = oparse.parse_args()
31+ return opts
32+
33+opts = parse_opts()
34+
35+if opts.debug:
36+ logging.root.setLevel(logging.DEBUG)
37+elif opts.verbose:
38+ logging.root.setLevel(logging.INFO)
39+
40+
41+######################################################################
42+# Setup path
43+
44+LAUNCH_DIR = abspath(sys.path[0])
45+logging.debug("Launched from %s", LAUNCH_DIR)
46+source_tree_gwibber = join(LAUNCH_DIR, "..", "gwibber")
47+
48+# If we were invoked from a Gwibber source directory add that as the
49+# preferred module path ...
50+if exists(join(source_tree_gwibber, "client.py")):
51+ logging.info("Running from source tree; adjusting path")
52+ sys.path.insert(0, realpath(dirname(source_tree_gwibber)))
53+ try:
54+ from gwibber.client import GwibberClient
55+ finally:
56+ del sys.path[0]
57+else:
58+ logging.debug("Assuming path is correct")
59+ from gwibber.client import GwibberClient
60+
61+
62+######################################################################
63+# Go...
64+
65+import gconf, dbus, dbus.exceptions
66+from gwibber.gintegration import DBUS_NAME, DBUS_PATH
67+
68+if gconf.Client().get_bool("/apps/gwibber/preferences/allow_multiple_instances"):
69+ GwibberClient()
70+ gtk.main()
71+else:
72+ try:
73+ bus = dbus.SessionBus()
74+ proxy = bus.get_object(DBUS_NAME, DBUS_PATH)
75+ iface = dbus.Interface(proxy, DBUS_NAME)
76+
77+ logging.info("Found existing Gwibber interface: invoking...")
78+ iface.external_invoke()
79+
80+ except dbus.exceptions.DBusException:
81+ logging.info("No existing Gwibber session: starting...")
82+ GwibberClient()
83+ gtk.main()
84
85=== removed file 'bin/gwibber'
86--- bin/gwibber 2009-03-28 16:23:57 +0000
87+++ bin/gwibber 1970-01-01 00:00:00 +0000
88@@ -1,79 +0,0 @@
89-#!/usr/bin/env python
90-
91-"""
92-
93-Gwibber Client
94-SegPhault (Ryan Paul) - 05/29/2007
95-
96-"""
97-
98-import sys, logging, gtk, optparse
99-from os.path import join, dirname, exists, realpath, abspath
100-
101-
102-######################################################################
103-# Options and configuration
104-def parse_opts():
105- usage = "usage: %prog [options] <config-file>"
106-
107- oparse = optparse.OptionParser(usage=usage)
108-
109- oparse.add_option("-v", "--verbose", action="store_true",
110- help="Verbose messages", default=False)
111- oparse.add_option("-d", "--debug", action="store_true",
112- help="Debug messages", default=False)
113-
114- opts, rest = oparse.parse_args()
115- return opts
116-
117-opts = parse_opts()
118-
119-if opts.debug:
120- logging.root.setLevel(logging.DEBUG)
121-elif opts.verbose:
122- logging.root.setLevel(logging.INFO)
123-
124-
125-######################################################################
126-# Setup path
127-
128-LAUNCH_DIR = abspath(sys.path[0])
129-logging.debug("Launched from %s", LAUNCH_DIR)
130-source_tree_gwibber = join(LAUNCH_DIR, "..", "gwibber")
131-
132-# If we were invoked from a Gwibber source directory add that as the
133-# preferred module path ...
134-if exists(join(source_tree_gwibber, "client.py")):
135- logging.info("Running from source tree; adjusting path")
136- sys.path.insert(0, realpath(dirname(source_tree_gwibber)))
137- try:
138- from gwibber.client import GwibberClient
139- finally:
140- del sys.path[0]
141-else:
142- logging.debug("Assuming path is correct")
143- from gwibber.client import GwibberClient
144-
145-
146-######################################################################
147-# Go...
148-
149-import gconf, dbus, dbus.exceptions
150-from gwibber.gintegration import DBUS_NAME, DBUS_PATH
151-
152-if gconf.Client().get_bool("/apps/gwibber/preferences/allow_multiple_instances"):
153- GwibberClient()
154- gtk.main()
155-else:
156- try:
157- bus = dbus.SessionBus()
158- proxy = bus.get_object(DBUS_NAME, DBUS_PATH)
159- iface = dbus.Interface(proxy, DBUS_NAME)
160-
161- logging.info("Found existing Gwibber interface: invoking...")
162- iface.external_invoke()
163-
164- except dbus.exceptions.DBusException:
165- logging.info("No existing Gwibber session: starting...")
166- GwibberClient()
167- gtk.main()
168
169=== modified file 'gwibber/client.py'
170--- gwibber/client.py 2009-05-31 22:07:24 +0000
171+++ gwibber/client.py 2009-06-18 16:32:49 +0000
172@@ -50,13 +50,15 @@
173 "editor": N_("_Editor"),
174 "statusbar": N_("_Statusbar"),
175 "tray_icon": N_("Tray _Icon"),
176+ "public_view": N_("_Public Timeline"),
177 }
178
179 CONFIGURABLE_ACCOUNT_ACTIONS = {
180 # Translators: these are checkbox
181 "receive": N_("_Receive Messages"),
182 "send": N_("_Send Messages"),
183- "search": N_("Search _Messages")
184+ "search": N_("Search _Messages"),
185+ "public": N_("_Public Timeline")
186 }
187
188 DEFAULT_PREFERENCES = {
189@@ -167,6 +169,7 @@
190 self.tabs.set_scrollable(True)
191 self.messages_view = self.add_msg_tab(self.client.receive, _("Messages"), show_icon = "go-home")
192 self.add_msg_tab(self.client.responses, _("Replies"), show_icon = "mail-reply-all", add_indicator=True)
193+ self.public_view = self.add_msg_tab(self.client.public_timeline, _("Public"), show_icon = "language-selector", add_indicator=True, can_toggle=True, show_notifications=False)
194
195 saved_position = config.GCONF.get_list("%s/%s" % (config.GCONF_PREFERENCES_DIR, "saved_position"), config.gconf.VALUE_INT)
196 if saved_position:
197@@ -224,7 +227,7 @@
198
199 self.statusbar = gtk.Statusbar()
200 self.statusbar.pack_start(self.status_icon, False, False)
201-
202+
203 layout.pack_start(self.setup_menus(), False)
204 layout.pack_start(vb, True, True)
205 layout.pack_start(self.statusbar, False)
206@@ -393,9 +396,12 @@
207 if view:
208 self.update([view.get_parent()])
209
210- def add_scrolled_parent(self, view, text, show_close=False, show_icon=None, make_active=False, save=None):
211+ def add_scrolled_parent(self, view, text, show_close=False, show_icon=None, make_active=False, save=None, can_toggle=False, show_notifications=True):
212 scroll = gtk.ScrolledWindow()
213 scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
214+ scroll.show_notifications = show_notifications
215+ # Tabs that can be toggled (like public timeline) should be excluded from the parent show_all() - they will maintain their own state
216+ scroll.set_no_show_all(can_toggle)
217 scroll.add(view)
218 scroll.saved_query = save
219 view.scroll = scroll
220@@ -422,13 +428,13 @@
221 btn.connect("clicked", self.on_tab_close, scroll)
222 self.on_theme_change()
223
224- def add_msg_tab(self, data_handler, text, show_close=False, show_icon=None, make_active=False, save=None, add_indicator=False):
225+ def add_msg_tab(self, data_handler, text, show_close=False, show_icon=None, make_active=False, save=None, add_indicator=False, can_toggle=False, show_notifications=True):
226 view = gwui.MessageView(self.preferences["theme"], self)
227 view.link_handler = self.on_link_clicked
228 view.data_retrieval_handler = data_handler
229 view.add_indicator = add_indicator
230
231- self.add_scrolled_parent(view, text, show_close, show_icon, make_active, save)
232+ self.add_scrolled_parent(view, text, show_close, show_icon, make_active, save, can_toggle, show_notifications)
233 return view
234
235 def add_user_tab(self, data_handler, text, show_close=False, show_icon=None, make_active=False, save=None):
236@@ -510,7 +516,13 @@
237 if hasattr(self, i):
238 getattr(self, i).set_property(
239 "visible", self.preferences["show_%s" % i])
240-
241+ # If we have a parent, and if that parent has no_show_all=True, we're dealing with a tab which can be toggled, so set the visible property on the parent to hide it properly as required
242+ if hasattr(getattr(self, i), "parent") and getattr(self, i).parent.props.no_show_all:
243+ getattr(self, i).parent.set_property(
244+ "visible", self.preferences["show_%s" % i])
245+ # If the tab isn't visible clear messages, better to see no data than stale data upon reveal
246+ if not getattr(self, i).parent.props.visible : getattr(self, i).execute_script("clearMessages()")
247+
248 self.set_property("skip-taskbar-hint",
249 self.preferences["hide_taskbar_entry"])
250
251@@ -1117,7 +1129,7 @@
252
253 for tab in self.target_tabs:
254 view = tab.get_child()
255- if view:
256+ if view and view.props.visible:
257 view.message_store = [m for m in
258 view.data_retrieval_handler() if not self.last_clear or m.time > self.last_clear
259 and m.time <= mx.DateTime.gmt()]
260@@ -1129,7 +1141,7 @@
261 self.manage_indicator_items(view.message_store, tab_num=self.tabs.page_num(tab))
262
263 gtk.gdk.threads_leave()
264- self.show_notification_bubbles(view.message_store)
265+ if tab.show_notifications: self.show_notification_bubbles(view.message_store)
266
267 self.statusbar.pop(0)
268 self.statusbar.push(0, _("Last update: %s") % time.strftime("%X"))
269
270=== modified file 'gwibber/configui.py'
271--- gwibber/configui.py 2009-06-11 04:15:55 +0000
272+++ gwibber/configui.py 2009-06-16 08:13:24 +0000
273@@ -126,6 +126,7 @@
274 col_receive = gtk.CellRendererToggle()
275 col_send = gtk.CellRendererToggle()
276 col_search = gtk.CellRendererToggle()
277+ col_public = gtk.CellRendererToggle()
278
279 def generate_account_name(acct):
280 if hasattr(acct.get_protocol(), "account_name"):
281@@ -143,12 +144,16 @@
282 ["search", (col_search, {
283 "active": lambda a: a["search_enabled"],
284 "visible": lambda a: a.supports(microblog.can.SEARCH)}), _("Search")],
285+ ["public", (col_public, {
286+ "active": lambda a: a["public_enabled"],
287+ "visible": lambda a: a.supports(microblog.can.PUBLIC)}), _("Public")],
288 ["protocol", lambda a: a.get_protocol().PROTOCOL_INFO["name"], _("Protocol")],
289 ])
290
291 col_receive.connect("toggled", toggle_table_checkbox, "receive_enabled", data)
292 col_send.connect("toggled", toggle_table_checkbox, "send_enabled", data)
293 col_search.connect("toggled", toggle_table_checkbox, "search_enabled", data)
294+ col_public.connect("toggled", toggle_table_checkbox, "public_enabled", data)
295
296 for a in self.accounts: data += a
297
298
299=== modified file 'gwibber/microblog/__init__.py'
300--- gwibber/microblog/__init__.py 2009-05-27 05:41:04 +0000
301+++ gwibber/microblog/__init__.py 2009-06-15 19:35:52 +0000
302@@ -142,3 +142,10 @@
303 # Translators: this message appears in the Errors dialog
304 # Indicates with wich action the error happened
305 lambda c: c.group(query.lower().replace("!", "")), _("perform group query"), filter)
306+
307+ def public_timeline(self, filter=list(PROTOCOLS.keys())):
308+ return self.perform_operation(
309+ lambda a: a["public_enabled"] and supports(a, can.PUBLIC),
310+ # Translators: this message appears in the Errors dialog
311+ # Indicates with wich action the error happened
312+ lambda c: c.public_timeline(), _("retrieve public timeline"), filter)
313
314=== modified file 'gwibber/microblog/can.py'
315--- gwibber/microblog/can.py 2009-05-27 07:39:19 +0000
316+++ gwibber/microblog/can.py 2009-06-15 19:35:52 +0000
317@@ -20,4 +20,5 @@
318 READ = 12
319 RETWEET = 13
320 LIKE = 14
321+PUBLIC = 15
322 GEO_FRIEND_POSITIONS = 50
323
324=== modified file 'gwibber/microblog/friendfeed.py'
325--- gwibber/microblog/friendfeed.py 2009-05-30 01:15:23 +0000
326+++ gwibber/microblog/friendfeed.py 2009-06-17 18:20:50 +0000
327@@ -23,6 +23,7 @@
328 "send_enabled",
329 "search_enabled",
330 "receive_count",
331+ "public_enabled",
332 ],
333
334 "features": [
335@@ -37,6 +38,7 @@
336 can.DELETE,
337 can.SEARCH_URL,
338 can.USER_MESSAGES,
339+ can.PUBLIC,
340 ],
341 }
342
343@@ -135,6 +137,9 @@
344 return self.account["receive_enabled"] and \
345 self.account["username"] != None and \
346 self.account["private:password"] != None
347+
348+ def public_enabled(self):
349+ return self.account["public_enabled"]
350
351 def get_auth(self):
352 return "Basic %s" % base64.encodestring(
353@@ -148,6 +153,15 @@
354 return simplejson.loads(self.connect(
355 "https://friendfeed.com/api/feed/home?" +
356 urllib.urlencode({"num": self.account["receive_count"] or "80"})))["entries"]
357+
358+ def get_public_timeline(self):
359+ return simplejson.loads(self.connect(
360+ "http://friendfeed.com/api/feed/public" +'?'+
361+ urllib.urlencode({"num": self.account["receive_count"] or "20"})))["entries"]
362+
363+ def public_timeline(self):
364+ for data in self.get_public_timeline():
365+ yield Message(self, data)
366
367 def get_user_messages(self, screen_name):
368 try:
369
370=== modified file 'gwibber/microblog/identica.py'
371--- gwibber/microblog/identica.py 2009-05-27 07:39:19 +0000
372+++ gwibber/microblog/identica.py 2009-06-17 18:20:50 +0000
373@@ -21,6 +21,7 @@
374 "send_enabled",
375 "search_enabled",
376 "receive_count",
377+ "public_enabled",
378 ],
379
380 "features": [
381@@ -37,6 +38,7 @@
382 #can.THREAD,
383 can.THREAD_REPLY,
384 can.USER_MESSAGES,
385+ can.PUBLIC,
386 ],
387 }
388
389@@ -122,6 +124,9 @@
390 return "Basic %s" % base64.encodestring(
391 ("%s:%s" % (self.account["username"], self.account["private:password"]))).strip()
392
393+ def public_enabled(self):
394+ return self.account["public_enabled"]
395+
396 def connect(self, url, data = None):
397 return urllib2.urlopen(urllib2.Request(
398 url, data, {"Authorization": self.get_auth()})).read()
399@@ -140,6 +145,15 @@
400 profile = [simplejson.loads(self.connect(
401 "https://identi.ca/api/users/show/"+ screen_name +".json"))]
402 return profile
403+
404+ def get_public_timeline(self):
405+ return simplejson.loads(self.connect(
406+ "https://identi.ca/api/statuses/public_timeline.json",
407+ urllib.urlencode({"num": self.account["receive_count"] or "20"})))
408+
409+ def public_timeline(self):
410+ for data in self.get_public_timeline():
411+ yield Message(self, data)
412
413 def get_responses(self):
414 return simplejson.loads(self.connect(
415
416=== modified file 'gwibber/microblog/twitter.py'
417--- gwibber/microblog/twitter.py 2009-05-27 07:39:19 +0000
418+++ gwibber/microblog/twitter.py 2009-06-15 19:35:52 +0000
419@@ -24,6 +24,7 @@
420 "send_enabled",
421 "search_enabled",
422 "receive_count",
423+ "public_enabled",
424 ],
425
426 "features": [
427@@ -40,6 +41,7 @@
428 can.THREAD_REPLY,
429 can.SEARCH_URL,
430 can.USER_MESSAGES,
431+ can.PUBLIC,
432 ],
433 }
434
435@@ -148,6 +150,11 @@
436 return self.account["receive_enabled"] and \
437 self.account["username"] != None and \
438 self.account["private:password"] != None
439+
440+ def public_enabled(self):
441+ return self.account["public_enabled"] and \
442+ self.account["username"] != None and \
443+ self.account["private:password"] != None
444
445 def get_auth(self):
446 return "Basic %s" % base64.encodestring(
447@@ -162,6 +169,15 @@
448 "https://twitter.com/statuses/friends_timeline.json" +'?'+
449 urllib.urlencode({"count": self.account["receive_count"] or "20"})))
450
451+ def get_public_timeline(self):
452+ return simplejson.loads(self.connect(
453+ "https://twitter.com/statuses/public_timeline.json" +'?'+
454+ urllib.urlencode({"count": self.account["receive_count"] or "20"})))
455+
456+ def public_timeline(self):
457+ for data in self.get_public_timeline():
458+ yield Message(self, data)
459+
460 def get_user_messages(self, screen_name):
461 try:
462 return simplejson.loads(self.connect(
463@@ -237,4 +253,3 @@
464 urllib.urlencode({"status":message,
465 "in_reply_to_status_id":target.id, "source": "gwibbernet"})))
466 return Message(self, data)
467-
468
469=== modified file 'ui/preferences.glade'
470--- ui/preferences.glade 2009-05-30 19:22:22 +0000
471+++ ui/preferences.glade 2009-06-17 18:20:50 +0000
472@@ -771,6 +771,19 @@
473 <property name="position">1</property>
474 </packing>
475 </child>
476+ <child>
477+ <widget class="GtkCheckButton" id="twitter_public_enabled">
478+ <property name="label" translatable="yes" context="yes">_Public Timeline</property>
479+ <property name="visible">True</property>
480+ <property name="can_focus">True</property>
481+ <property name="receives_default">False</property>
482+ <property name="use_underline">True</property>
483+ <property name="draw_indicator">True</property>
484+ </widget>
485+ <packing>
486+ <property name="position">2</property>
487+ </packing>
488+ </child>
489 </widget>
490 </child>
491 </widget>
492@@ -1538,6 +1551,19 @@
493 <property name="position">1</property>
494 </packing>
495 </child>
496+ <child>
497+ <widget class="GtkCheckButton" id="friendfeed_public_enabled">
498+ <property name="label" translatable="yes">_Public Timeline</property>
499+ <property name="visible">True</property>
500+ <property name="can_focus">True</property>
501+ <property name="receives_default">False</property>
502+ <property name="use_underline">True</property>
503+ <property name="draw_indicator">True</property>
504+ </widget>
505+ <packing>
506+ <property name="position">2</property>
507+ </packing>
508+ </child>
509 </widget>
510 </child>
511 </widget>
512@@ -2346,6 +2372,19 @@
513 <property name="position">1</property>
514 </packing>
515 </child>
516+ <child>
517+ <widget class="GtkCheckButton" id="identica_public_enabled">
518+ <property name="label" translatable="yes">_Public Timeline</property>
519+ <property name="visible">True</property>
520+ <property name="can_focus">True</property>
521+ <property name="receives_default">False</property>
522+ <property name="use_underline">True</property>
523+ <property name="draw_indicator">True</property>
524+ </widget>
525+ <packing>
526+ <property name="position">2</property>
527+ </packing>
528+ </child>
529 </widget>
530 </child>
531 </widget>

Subscribers

People subscribed via source and target branches