1 #!/usr/bin/env python2.5
4 # Copyright (c) 2007-2008 INdT.
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Lesser General Public License for more details.
15 # You should have received a copy of the GNU Lesser General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 # ============================================================================
21 # Author : Yves Marcoz
23 # Description : Simple RSS Reader
24 # ============================================================================
34 from os.path import isfile, isdir
39 from portrait import FremantleRotation
41 from feedingitdbus import ServerObject
45 class AddWidgetWizard(hildon.WizardDialog):
47 def __init__(self, parent, urlIn):
49 self.notebook = gtk.Notebook()
51 self.nameEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
52 self.nameEntry.set_placeholder("Enter Feed Name")
53 vbox = gtk.VBox(False,10)
54 label = gtk.Label("Enter Feed Name:")
55 vbox.pack_start(label)
56 vbox.pack_start(self.nameEntry)
57 self.notebook.append_page(vbox, None)
59 self.urlEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
60 self.urlEntry.set_placeholder("Enter a URL")
61 self.urlEntry.set_text(urlIn)
62 vbox = gtk.VBox(False,10)
63 label = gtk.Label("Enter Feed Name:")
64 vbox.pack_start(label)
65 vbox.pack_start(self.urlEntry)
66 self.notebook.append_page(vbox, None)
68 labelEnd = gtk.Label("Success")
70 self.notebook.append_page(labelEnd, None)
72 hildon.WizardDialog.__init__(self, parent, "Add Feed", self.notebook)
74 # Set a handler for "switch-page" signal
75 #self.notebook.connect("switch_page", self.on_page_switch, self)
77 # Set a function to decide if user can go to next page
78 self.set_forward_page_func(self.some_page_func)
83 return (self.nameEntry.get_text(), self.urlEntry.get_text())
85 def on_page_switch(self, notebook, page, num, dialog):
88 def some_page_func(self, nb, current, userdata):
89 # Validate data for 1st page
91 return len(self.nameEntry.get_text()) != 0
93 # Check the url is not null, and starts with http
94 return ( (len(self.urlEntry.get_text()) != 0) and (self.urlEntry.get_text().lower().startswith("http")) )
101 class Download(threading.Thread):
102 def __init__(self, listing, key):
103 threading.Thread.__init__(self)
104 self.listing = listing
108 self.listing.updateFeed(self.key)
111 class DownloadDialog():
112 def __init__(self, parent, listing, listOfKeys):
113 self.listOfKeys = listOfKeys
114 self.listing = listing
115 self.total = len(self.listOfKeys)
119 self.progress = gtk.ProgressBar()
120 self.waitingWindow = hildon.Note("cancel", parent, "Downloading",
121 progressbar=self.progress)
122 self.progress.set_text("Downloading")
124 self.progress.set_fraction(self.fraction)
126 self.timeout_handler_id = gobject.timeout_add(50, self.update_progress_bar)
127 self.waitingWindow.show_all()
128 response = self.waitingWindow.run()
130 while threading.activeCount() > 1:
131 # Wait for current downloads to finish
133 self.waitingWindow.destroy()
135 def update_progress_bar(self):
136 #self.progress_bar.pulse()
137 if threading.activeCount() < 4:
138 x = threading.activeCount() - 1
139 k = len(self.listOfKeys)
140 fin = self.total - k - x
141 fraction = float(fin)/float(self.total) + float(x)/(self.total*2.)
142 #print x, k, fin, fraction
143 self.progress.set_fraction(fraction)
145 if len(self.listOfKeys)>0:
146 self.current = self.current+1
147 key = self.listOfKeys.pop()
148 download = Download(self.listing, key)
151 elif threading.activeCount() > 1:
154 self.waitingWindow.destroy()
159 class DisplayArticle(hildon.StackableWindow):
160 def __init__(self, title, text, index):
161 hildon.StackableWindow.__init__(self)
164 self.set_title(title)
166 # Init the article display
167 self.view = gtkhtml2.View()
168 self.pannable_article = hildon.PannableArea()
169 self.pannable_article.add(self.view)
170 self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
171 self.document = gtkhtml2.Document()
172 self.view.set_document(self.document)
174 self.document.clear()
175 self.document.open_stream("text/html")
176 self.document.write_stream(self.text)
177 self.document.close_stream()
179 menu = hildon.AppMenu()
180 # Create a button and add it to the menu
181 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
182 button.set_label("Display Images")
183 button.connect("clicked", self.reloadArticle)
185 self.set_app_menu(menu)
188 self.add(self.pannable_article)
192 self.document.connect("link_clicked", self._signal_link_clicked)
193 self.document.connect("request-url", self._signal_request_url)
194 self.connect("destroy", self.destroyWindow)
195 self.timeout_handler_id = gobject.timeout_add(200, self.reloadArticle)
197 def destroyWindow(self, *args):
198 self.emit("article-closed", self.index)
201 def reloadArticle(self, *widget):
202 self.document.open_stream("text/html")
203 self.document.write_stream(self.text)
204 self.document.close_stream()
206 def _signal_link_clicked(self, object, link):
207 bus = dbus.SystemBus()
208 proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
209 iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
210 #webbrowser.open(link)
211 iface.open_new_window(link)
213 def _signal_request_url(self, object, url, stream):
214 f = urllib2.urlopen(url)
215 stream.write(f.read())
219 class DisplayFeed(hildon.StackableWindow):
220 def __init__(self, listing, feed, title, key):
221 hildon.StackableWindow.__init__(self)
222 self.listing = listing
224 self.feedTitle = title
225 self.set_title(title)
228 menu = hildon.AppMenu()
229 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
230 button.set_label("Update Feed")
231 button.connect("clicked", self.button_update_clicked)
233 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
234 button.set_label("Mark All As Read")
235 button.connect("clicked", self.buttonReadAllClicked)
237 self.set_app_menu(menu)
242 self.connect("destroy", self.destroyWindow)
244 def destroyWindow(self, *args):
246 self.emit("feed-closed", self.key)
249 def displayFeed(self):
250 self.vboxFeed = gtk.VBox(False, 10)
251 self.pannableFeed = hildon.PannableArea()
252 self.pannableFeed.add_with_viewport(self.vboxFeed)
253 self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
255 for index in range(self.feed.getNumberOfEntries()):
256 button = gtk.Button(self.feed.getTitle(index))
257 button.set_alignment(0,0)
259 if self.feed.isEntryRead(index):
260 label.modify_font(pango.FontDescription("sans 16"))
262 label.modify_font(pango.FontDescription("sans bold 16"))
263 label.set_line_wrap(True)
265 label.set_size_request(self.get_size()[0]-50, -1)
266 button.connect("clicked", self.button_clicked, index)
267 self.buttons.append(button)
269 self.vboxFeed.pack_start(button, expand=False)
272 self.add(self.pannableFeed)
276 self.remove(self.pannableFeed)
278 def button_clicked(self, button, index):
279 disp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), index)
280 disp.connect("article-closed", self.onArticleClosed)
282 def onArticleClosed(self, object, index):
283 label = self.buttons[index].child
284 label.modify_font(pango.FontDescription("sans 16"))
285 self.buttons[index].show()
287 def button_update_clicked(self, button):
288 disp = DownloadDialog(self, self.listing, [self.key,] )
289 #self.feed.updateFeed()
293 def buttonReadAllClicked(self, button):
294 for index in range(self.feed.getNumberOfEntries()):
295 self.feed.setEntryRead(index)
296 label = self.buttons[index].child
297 label.modify_font(pango.FontDescription("sans 16"))
298 self.buttons[index].show()
303 self.listing = Listing()
306 self.window = hildon.StackableWindow()
307 self.window.set_title("FeedingIt")
308 FremantleRotation("FeedingIt", main_window=self.window)
309 menu = hildon.AppMenu()
310 # Create a button and add it to the menu
311 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
312 button.set_label("Update All Feeds")
313 button.connect("clicked", self.button_update_clicked, "All")
315 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
316 button.set_label("Add Feed")
317 button.connect("clicked", self.button_add_clicked)
320 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
321 button.set_label("Delete Feed")
322 button.connect("clicked", self.button_delete_clicked)
325 self.window.set_app_menu(menu)
328 self.feedWindow = hildon.StackableWindow()
329 self.articleWindow = hildon.StackableWindow()
331 self.displayListing()
333 def button_add_clicked(self, button, urlIn="http://"):
334 wizard = AddWidgetWizard(self.window, urlIn)
337 (title, url) = wizard.getData()
338 if (not title == '') and (not url == ''):
339 self.listing.addFeed(title, url)
341 self.displayListing()
343 def button_update_clicked(self, button, key):
344 disp = DownloadDialog(self.window, self.listing, self.listing.getListOfFeeds() )
345 self.displayListing()
347 def button_delete_clicked(self, button):
348 self.pickerDialog = hildon.PickerDialog(self.window)
350 self.pickerDialog.set_selector(self.create_selector())
351 self.pickerDialog.show_all()
353 def create_selector(self):
354 selector = hildon.TouchSelector(text=True)
356 #selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE)
358 selector.connect("changed", self.selection_changed)
360 for key in self.listing.getListOfFeeds():
361 title=self.listing.getFeedTitle(key)
362 selector.append_text(title)
363 self.mapping[title]=key
367 def selection_changed(self, widget, data):
368 current_selection = widget.get_current_text()
369 #print 'Current selection: %s' % current_selection
370 #print "To Delete: %s" % self.mapping[current_selection]
371 self.pickerDialog.destroy()
372 if self.show_confirmation_note(self.window, current_selection):
373 self.listing.removeFeed(self.mapping[current_selection])
376 self.displayListing()
378 def show_confirmation_note(self, parent, title):
379 note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
381 retcode = gtk.Dialog.run(note)
384 if retcode == gtk.RESPONSE_OK:
389 def displayListing(self):
391 self.window.remove(self.pannableListing)
394 self.vboxListing = gtk.VBox(False,10)
395 self.pannableListing = hildon.PannableArea()
396 self.pannableListing.add_with_viewport(self.vboxListing)
399 for key in self.listing.getListOfFeeds():
400 #button = gtk.Button(item)
401 button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
402 hildon.BUTTON_ARRANGEMENT_VERTICAL)
403 button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / "
404 + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
405 button.set_alignment(0,0,1,1)
406 button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
407 self.vboxListing.pack_start(button, expand=False)
408 self.buttons[key] = button
409 self.window.add(self.pannableListing)
410 self.window.show_all()
412 def buttonFeedClicked(widget, button, self, window, key):
413 disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key)
414 disp.connect("feed-closed", self.onFeedClosed)
416 def onFeedClosed(self, object, key):
417 self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / "
418 + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
419 self.buttons[key].show()
422 self.window.connect("destroy", gtk.main_quit)
426 if __name__ == "__main__":
427 gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
428 gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
429 gobject.threads_init()
430 if not isdir(CONFIGDIR):
434 print "Error: Can't create configuration directory"
437 dbusHandler = ServerObject(app)