3 # Copyright (C) 2008 by Daniel Martin Yerga
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 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 General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 # StocksThis: Application to get stocks data from Yahoo Finance.
22 _version = "StockThis 0.4 rev1"
34 from portrait import FremantleRotation
37 osso_c = osso.Context("net.yerga.stockthis", "0.3", False)
39 #detect if is ran locally or not
40 runningpath = sys.path[0]
42 if '/opt/' in runningpath:
43 runninglocally = False
47 HOME = os.path.expanduser("~")
49 settingsdb, imgdir, configdir, logfile = \
50 settings.define_paths(runninglocally, HOME)
52 logger = logging.getLogger('st')
53 logging.basicConfig(filename=logfile,level=logging.ERROR, filemode='w')
58 #set the main logger to DEBUG
59 logger.setLevel(logging.DEBUG)
61 #Create a handler for console debug
62 console = logging.StreamHandler()
63 console.setLevel(logging.DEBUG)
64 # set a format which is simpler for console use
65 formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
66 # tell the handler to use this format
67 console.setFormatter(formatter)
68 logging.getLogger('').addHandler(console)
70 fhsize = gtk.HILDON_SIZE_FINGER_HEIGHT
71 horbtn = hildon.BUTTON_ARRANGEMENT_HORIZONTAL
72 ui_normal = gtk.HILDON_UI_MODE_NORMAL
73 ui_edit = gtk.HILDON_UI_MODE_EDIT
74 winprogind = hildon.hildon_gtk_window_set_progress_indicator
79 gtk.gdk.threads_init()
84 self.program = hildon.Program()
85 self.program.__init__()
86 gtk.set_application_name("StockThis")
87 self.window = hildon.StackableWindow()
88 self.window.set_default_size(800, 480)
89 self.program.add_window(self.window)
90 self.window.connect("destroy", gtk.main_quit)
92 FremantleRotation('StockThis', None, "0.3", 0)
94 self.create_menu(self.window)
97 toolbar = self.main_toolbar(False, False, None, '', '', True)
99 parea = hildon.PannableArea()
100 tv = hildon.GtkTreeView(ui_normal)
101 inmodel = self.__create_model(marketdata.main, marketdata.idmain)
102 tv.connect("row-activated", self.show_instrument_view, inmodel,
103 marketdata.localmarkets, marketdata.localids,
105 tv.set_model(inmodel)
109 vbox.pack_start(parea, True, True, 0)
110 vbox.pack_start(gtk.HSeparator(), False, False, 5)
111 vbox.pack_start(toolbar, False, False, 0)
113 self.window.add(vbox)
114 self.window.show_all()
116 self.show_info_banner(self.window,
117 ("StockThis uses your network connection to get data.\n"
118 "Be aware of the high costs that your network provider may apply."))
120 def create_menu(self, window):
121 menu = hildon.AppMenu()
122 window.set_app_menu(menu)
123 button = gtk.Button("About")
124 button.connect("clicked", About)
126 button = gtk.Button("Log")
127 button.connect("clicked", Log, logfile)
131 def show_instrument_view(self, widget, path, column, inmodel, names,
133 market = inmodel[path][0]
134 names = names[mindex.index(market)]
135 ids = ids[mindex.index(market)]
137 window = hildon.StackableWindow()
138 self.create_menu(window)
139 window.set_title(inmodel[path][1])
142 toolbar = self.main_toolbar(False, False, None, '', '', False)
144 parea = hildon.PannableArea()
145 parea.connect("horizontal-movement", self.horizontal_mov)
146 tv = hildon.GtkTreeView(ui_normal)
147 model = self.__create_model(names, ids)
148 tv.connect("row-activated", self.show_quotes_view, model, False)
153 vbox.pack_start(parea, True, True, 0)
154 vbox.pack_start(gtk.HSeparator(), False, False, 5)
155 vbox.pack_start(toolbar, False, False, 0)
160 def horizontal_mov(self, parea, direction, initial_x, initial_y):
161 #direction = 2 right-to-left
162 #direction = 3 lefto-to-right
164 vadj = parea.get_vadjustment()
165 val = vadj.get_value()
168 if int(val)-2500 < 0:
169 parea.scroll_to(-1, 0)
171 parea.scroll_to(-1, int(val)-2500)
173 parea.scroll_to(-1, int(val)+3500)
177 def show_quotes_view(self, widget, path, column, model, portfolio):
178 quote = model[path][0], model[path][1]
179 #print "quote:", quote[0]
180 #('EURUSD=X', 'EUR/USD')
182 #Currencies and ETFs should show the list now -> view = True
183 #Other items show a new list with options
185 for i in marketdata.localids[(len(marketdata.localids)-2):]:
192 if quote[0] in marketdata.idindexes:
193 self.show_instrument_view(widget, path, column, model,
194 marketdata.wnamesindexes,
195 marketdata.widsindexes,
196 marketdata.idindexes)
198 if quote[0] in marketdata.idotmarkets:
199 self.show_instrument_view(widget, path, column, model,
201 marketdata.omsymbols,
202 marketdata.idotmarkets)
204 if quote[0] in marketdata.ideumarkets:
205 self.show_instrument_view(widget, path, column, model,
207 marketdata.eusymbols,
208 marketdata.ideumarkets)
210 if quote[0] in marketdata.idusmarkets:
211 self.show_instrument_view(widget, path, column, model,
213 marketdata.ussymbols,
214 marketdata.idusmarkets)
218 win = hildon.StackableWindow()
219 self.create_menu(win)
220 win.set_title(quote[1])
224 ltitle = gtk.Label('')
225 ltitle.set_markup('<b><big>' + quote[1].replace('&', '') +
227 color = gtk.gdk.color_parse("#03A5FF")
228 ltitle.modify_fg(gtk.STATE_NORMAL, color)
230 parea = hildon.PannableArea()
231 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
236 label = gtk.Label('')
237 label.set_markup('<b><big>%39s:</big></b>' % '<u>Price</u>')
238 lprice = gtk.Label('')
239 hbox.pack_start(label, False, False, 0)
240 #if self.is_portrait():
241 hbox.pack_start(lprice, False, False, 25)
243 # hbox.pack_start(lprice, False, False, 245)
245 vbox1.pack_start(hbox, True, True, 0)
248 label = gtk.Label('')
249 label.set_markup('<b><big>%35s:</big></b>' % '<u>Change</u>')
250 lchange = gtk.Label('')
251 lpercent = gtk.Label('')
252 hbox.pack_start(label, False, False, 0)
253 #if self.is_portrait():
254 hbox.pack_start(lchange, False, False, 25)
255 hbox.pack_start(lpercent, False, False, 0)
257 # hbox.pack_start(lchange, False, False, 205)
258 # hbox.pack_start(lpercent, False, False, 0)
260 vbox1.pack_start(hbox, True, True, 0)
263 label = gtk.Label('')
264 label.set_markup('<b><big>%35s:</big></b>' % '<u>Volume</u>')
265 lvolume = gtk.Label('')
266 hbox.pack_start(label, False, False, 0)
267 #if self.is_portrait():
268 hbox.pack_start(lvolume, False, False, 25)
270 # hbox.pack_start(lvolume, False, False, 207)
272 vbox1.pack_start(hbox, True, True, 0)
275 label = gtk.Label('')
276 label.set_markup('<b><big>%30s:</big></b>' % '<u>52 week high</u>')
277 l52whigh = gtk.Label('')
278 hbox.pack_start(label, False, False, 0)
279 #if self.is_portrait():
280 hbox.pack_start(l52whigh, False, False, 25)
282 # hbox.pack_start(l52whigh, False, False, 125)
284 vbox1.pack_start(hbox, True, True, 0)
287 label = gtk.Label('')
288 label.set_markup('<b><big>%30s:</big></b>' % '<u>52 week low</u>')
289 l52wlow = gtk.Label('')
290 hbox.pack_start(label, False, False, 0)
291 #if self.is_portrait():
292 hbox.pack_start(l52wlow, False, False, 26)
294 # hbox.pack_start(l52wlow, False, False, 140)
295 vbox1.pack_start(hbox, True, True, 0)
297 #if self.is_portrait():
301 button1 = hildon.PickerButton(fhsize, horbtn)
302 data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
304 selector = self.create_selector(data, True)
305 button1.set_selector(selector)
306 button1.set_title("Your shares")
307 shares = self.get_shares_from_symbol(quote[0])
308 button1.set_value(shares)
309 hbox.pack_start(button1, True, True, 0)
311 button = hildon.Button(fhsize, horbtn)
312 button.set_title("Add to Portfolio")
313 button.connect("clicked", self.add_to_portfolio, button1, quote[0], quote[1])
314 hbox.pack_start(button, True, True, 0)
317 label = gtk.Label('')
318 label.set_markup('<b><big>%37s:</big></b>' % '<u>Shares</u>')
319 lshares = gtk.Label(shares)
320 hbox1.pack_start(label, False, False, 0)
321 #if self.is_portrait():
322 hbox1.pack_start(lshares, False, False, 25)
324 # hbox1.pack_start(lshares, False, False, 220)
327 label = gtk.Label('')
328 label.set_markup('<b><big>%29s:</big></b>' % '<u>Holdings Value</u>')
329 holdingsvalue = gtk.Label("")
330 hbox2.pack_start(label, False, False, 0)
331 #if self.is_portrait():
332 hbox2.pack_start(holdingsvalue, False, False, 25)
334 # hbox2.pack_start(holdingsvalue, False, False, 105)
337 label = gtk.Label('')
338 label.set_markup("<b><big>%25s:</big></b>" % "<u>Day's Value Change</u>")
339 dayvaluechange = gtk.Label("")
340 hbox3.pack_start(label, False, False, 0)
341 #if self.is_portrait():
342 hbox3.pack_start(dayvaluechange, False, False, 25)
344 # hbox3.pack_start(dayvaluechange, False, False, 45)
347 vbox1.pack_start(hbox, False, False, 0)
349 vbox1.pack_start(hbox1, True, True, 0)
350 vbox1.pack_start(hbox2, True, True, 0)
351 vbox1.pack_start(hbox3, True, True, 0)
353 parea.add_with_viewport(vbox1)
355 widgets = [win, ltitle, lprice, lchange, lpercent, lvolume, l52whigh,
356 l52wlow, lshares, holdingsvalue, dayvaluechange]
358 toolbar = self.main_toolbar(True, portfolio, widgets, quote[0], quote[1], False)
360 vbox.pack_start(ltitle, False, False, 0)
361 vbox.pack_start(gtk.HSeparator(), False, False, 0)
362 vbox.pack_start(parea, True, True, 0)
363 vbox.pack_start(gtk.HSeparator(), False, False, 5)
364 vbox.pack_start(toolbar, False, False, 0)
369 self.show_data(quote[0], widgets, shares)
371 def is_portrait(self):
372 width = gtk.gdk.screen_width()
373 height = gtk.gdk.screen_height()
379 def get_shares_from_symbol(self, symbol):
382 portfolio_data = settings.load_portfolio(settingsdb)
383 for item in portfolio_data :
388 logger.exception("Getting shares from symbol: %s" % symbol)
391 def add_to_portfolio(self, widget, button, symbol, name):
392 shares = button.get_value()
395 portfolio = settings.load_portfolio(settingsdb)
397 for item in portfolio:
399 index = portfolio.index(item)
401 item = [symbol, name, shares, '-']
404 settings.insert_new_item_to_portfolio(settingsdb, item)
406 settings.delete_item_from_portfolio(settingsdb, symbol)
407 settings.insert_new_item_to_portfolio(settingsdb, item)
409 self.show_info_banner(widget, "Added to portfolio")
411 logger.exception("Adding to portfolio: %s, %s" % (symbol, name))
412 self.show_info_banner(widget, "Error adding to portfolio")
415 def create_selector(self, data, entry):
417 selector = hildon.TouchSelectorEntry(text=True)
419 selector = hildon.TouchSelector(text=True)
420 for i in range(len(data)):
421 selector.append_text(data[i])
425 def show_data(self, symbol, widgets, shares):
427 winprogind(widgets[0], 1)
428 thread.start_new_thread(self.get_data, (symbol, widgets, shares))
430 def get_data(self, symbol, widgets, shares):
431 from ystockquote import ystockquote as yt
432 win, ltitle, lprice, lchange, lpercent, lvolume, l52whigh, l52wlow, lshares, holdingsvalue, dayvaluechange = widgets
435 data = yt.get_all(symbol)
437 logger.exception("Getting data from Yahoo: %s" % symbol)
438 data = {'price': 'N/A', 'change': 'N/A', 'volume':'N/A',
439 '52_week_high': 'N/A', '52_week_low': 'N/A'}
440 ltitle.set_markup('<b><big>Failed to get data</big></b>')
444 100.0 * float(data['change'])/(float(data['price']) - \
445 float(data['change']))
449 lprice.set_label(data['price'])
450 lchange.set_label(data['change'])
451 lpercent.set_label('%6.2f %%' % ch_percent)
453 if '-' in data['change']:
454 color = gtk.gdk.color_parse("#FF0000")
456 color = gtk.gdk.color_parse("#16EB78")
458 lpercent.modify_fg(gtk.STATE_NORMAL, color)
459 lchange.modify_fg(gtk.STATE_NORMAL, color)
461 lvolume.set_label(data['volume'])
462 l52whigh.set_label(data['52_week_high'])
463 l52wlow.set_label(data['52_week_low'])
466 daychange = float(shares)*float(data['change'])
470 holdvalue = float(shares)*float(data['price'])
474 dayvaluechange.set_label(str(daychange))
475 holdingsvalue.set_label(str(holdvalue))
479 def refresh_stock_data(self, widget, portfolio, widgets, symbol):
481 shares = self.get_shares_from_symbol(symbol)
485 self.show_data(symbol, widgets, shares)
487 def show_graph_view(self, widget, symbol, name):
488 win = hildon.StackableWindow()
489 self.create_menu(win)
493 toolbar = self.main_toolbar(False, True, None, '', '', False)
495 self.graphs_title = gtk.Label(name)
496 color = gtk.gdk.color_parse("#03A5FF")
497 self.graphs_title.modify_fg(gtk.STATE_NORMAL, color)
499 parea = hildon.PannableArea()
500 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
505 hbox.set_homogeneous(True)
507 button = hildon.Button(fhsize, horbtn)
508 button.set_label('1d')
509 button.connect("clicked", self.show_graph, '1d', win, symbol)
510 hbox.pack_start(button)
512 button = hildon.Button(fhsize, horbtn)
513 button.set_label('5d')
514 button.connect("clicked", self.show_graph, '5d', win, symbol)
515 hbox.pack_start(button)
517 button = hildon.Button(fhsize, horbtn)
518 button.set_label('3m')
519 button.connect("clicked", self.show_graph, '3m', win, symbol)
520 hbox.pack_start(button)
522 button = hildon.Button(fhsize, horbtn)
523 button.set_label('6m')
524 button.connect("clicked", self.show_graph, '6m', win, symbol)
525 hbox.pack_start(button)
527 vbox1.pack_start(hbox, False, False, 0)
529 hbox.set_homogeneous(True)
531 button = hildon.Button(fhsize, horbtn)
532 button.set_label('1y')
533 button.connect("clicked", self.show_graph, '1y', win, symbol)
534 hbox.pack_start(button)
536 button = hildon.Button(fhsize, horbtn)
537 button.set_label('2y')
538 button.connect("clicked", self.show_graph, '2y', win, symbol)
539 hbox.pack_start(button)
541 button = hildon.Button(fhsize, horbtn)
542 button.set_label('5y')
543 button.connect("clicked", self.show_graph, '5y', win, symbol)
544 hbox.pack_start(button)
546 button = hildon.Button(fhsize, horbtn)
547 button.set_label('Max')
548 button.connect("clicked", self.show_graph, 'max', win, symbol)
549 hbox.pack_start(button)
551 vbox1.pack_start(hbox, False, False, 0)
553 self.graph = gtk.Image()
554 vbox1.pack_start(self.graph, True, True, 0)
556 parea.add_with_viewport(vbox1)
558 vbox.pack_start(self.graphs_title, False, False, 0)
559 vbox.pack_start(gtk.HSeparator(), False, False, 0)
560 vbox.pack_start(parea, True, True, 0)
561 vbox.pack_start(gtk.HSeparator(), False, False, 5)
562 vbox.pack_start(toolbar, False, False, 0)
567 self.show_graph(None, '1d', win, symbol)
569 def show_graph(self, widget, option, win, symbol):
572 thread.start_new_thread(self.get_graph_data, (option, win, symbol))
574 def get_graph_data(self, option, win, symbol):
576 url = 'http://uk.ichart.yahoo.com/b?s=%s' % symbol
578 url = 'http://uk.ichart.yahoo.com/w?s=%s' % symbol
580 url = 'http://chart.finance.yahoo.com/c/3m/s/%s' % symbol.lower()
582 url = 'http://chart.finance.yahoo.com/c/6m/s/%s' % symbol.lower()
584 url = 'http://chart.finance.yahoo.com/c/1y/s/%s' % symbol.lower()
586 url = 'http://chart.finance.yahoo.com/c/2y/s/%s' % symbol.lower()
588 url = 'http://chart.finance.yahoo.com/c/5y/s/%s' % symbol.lower()
589 elif option == 'max':
590 url = 'http://chart.finance.yahoo.com/c/my/s/%s' % symbol.lower()
593 myimg = urllib2.urlopen(url)
594 imgdata = myimg.read()
596 pbl = gtk.gdk.PixbufLoader()
599 pbuf = pbl.get_pixbuf()
600 pbuf = pbuf.scale_simple(475, 235, gtk.gdk.INTERP_TILES)
602 self.graph.set_from_pixbuf(pbuf)
605 logger.exception("Getting graph data: %s" % url)
607 self.graphs_title.set_label('Failed to get data')
610 def _tv_columns(self, treeview):
611 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
612 column.set_visible(False)
613 treeview.append_column(column)
615 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
616 treeview.append_column(column)
618 def __create_model(self, names, ids):
619 lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
620 for item in range(len(names)):
621 iter = lstore.append()
622 lstore.set(iter, 0, ids[item], 1, names[item])
625 def main_toolbar(self, quotesview, portfolio, widgets, symbol, name, initial):
627 #toolbar.set_homogeneous(True)
629 portfolio_btn = hildon.Button(fhsize, horbtn)
630 portfolio_btn.set_title("Portfolio")
631 portfolio_btn.connect("clicked", self.show_portfolio_view)
633 graph_btn = hildon.Button(fhsize, horbtn)
634 graph_btn.set_title("Graph")
635 graph_btn.connect("clicked", self.show_graph_view, symbol, name)
637 refresh_btn = hildon.Button(fhsize, horbtn)
638 refresh_btn.set_title("Refresh")
639 refresh_btn.connect("clicked", self.refresh_stock_data, portfolio,
643 info_btn = hildon.Button(fhsize, horbtn)
644 img = gtk.image_new_from_icon_name("general_information", gtk.ICON_SIZE_SMALL_TOOLBAR)
645 info_btn.set_image(img)
646 info_btn.connect("clicked", self.show_app_information)
648 search_btn = hildon.Button(fhsize, horbtn)
649 img = gtk.image_new_from_icon_name("general_search", gtk.ICON_SIZE_SMALL_TOOLBAR)
650 search_btn.set_image(img)
651 search_btn.connect("clicked", self.show_search_dialog)
654 toolbar.pack_start(portfolio_btn)
656 toolbar.pack_start(info_btn, False, False, 0)
658 toolbar.pack_start(graph_btn)
659 toolbar.pack_start(refresh_btn)
662 toolbar.pack_start(search_btn, False, False, 0)
668 def show_search_dialog(self, widget):
669 dlg = gtk.Dialog(title='Search company', parent=None, flags=0)
670 dlg.set_has_separator(False)
674 entry = hildon.Entry(fhsize)
675 entry.connect("activate", self.do_search, entry, dlg)
676 hbox.pack_start(entry, True, True, 0)
678 button = hildon.Button(fhsize, horbtn)
679 img = gtk.image_new_from_icon_name("general_search", gtk.ICON_SIZE_SMALL_TOOLBAR)
680 button.set_image(img)
681 button.connect("clicked", self.do_search, entry, dlg)
682 hbox.pack_start(button, False, False, 0)
684 dlg.vbox.pack_start(hbox, False, False, 0)
691 def do_search(self, widget, entry, dlg):
693 text = entry.get_text()
696 winprogind(self.window, 1)
697 thread.start_new_thread(self._really_do_search, (text,))
699 def _really_do_search(self, text):
702 for market in marketdata.eunames:
703 for company in market:
704 allnames.append(company)
706 for market in marketdata.omnames:
707 for company in market:
708 allnames.append(company)
710 for market in marketdata.usnames:
711 for company in market:
712 allnames.append(company)
715 for market in marketdata.eusymbols:
716 for company in market:
717 allsymbols.append(company)
719 for market in marketdata.omsymbols:
720 for company in market:
721 allsymbols.append(company)
723 for market in marketdata.ussymbols:
724 for company in market:
725 allsymbols.append(company)
727 new_model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
728 for i in range(len(allnames)):
729 if text.lower() in allnames[i].lower():
730 niter = new_model.append()
731 #print allsymbols[i], allnames[i]
732 #FIXME: repeated companies in the results list
733 #print if allnames[i]
735 # if j[1] == allnames[i]:
736 new_model.set(niter, 0, allsymbols[i], 1, allnames[i])
738 if len(new_model) == 0:
739 winprogind(self.window, 0)
740 gtk.gdk.threads_enter()
741 self.show_info_banner(self.window, "No items found for this search")
742 gtk.gdk.threads_leave()
745 gtk.gdk.threads_enter()
746 self.show_search_screen(new_model, text)
747 gtk.gdk.threads_leave()
748 winprogind(self.window, 0)
751 def show_search_screen(self, model, text):
752 window = hildon.StackableWindow()
753 self.create_menu(window)
754 window.set_title("Search for " + text)
757 toolbar = self.main_toolbar(False, False, None, '', '', False)
759 parea = hildon.PannableArea()
760 parea.connect("horizontal-movement", self.horizontal_mov)
761 tv = hildon.GtkTreeView(ui_normal)
762 tv.connect("row-activated", self.show_quotes_view, model, False)
767 vbox.pack_start(parea, True, True, 0)
768 vbox.pack_start(gtk.HSeparator(), False, False, 5)
769 vbox.pack_start(toolbar, False, False, 0)
775 def show_app_information(self, widget):
776 self.show_information_note(self.window, (
777 "The data is got from Yahoo! Finance.\n"
778 "It could be delayed or even wrong.\n"
779 "The author doesn't validate in any way this data and therefore he is not responsible for any damage that may occur.\n\n"
780 "You can scroll large list with gestures:\n"
781 "Left-to-right gesture: scroll down.\n"
782 "Right-to-left gesture: scroll up."))
784 def show_portfolio_view(self, widget):
785 data = settings.load_portfolio(settingsdb)
790 win = hildon.StackableWindow()
791 self.create_menu(win)
792 win.set_title("Portfolio")
796 parea = hildon.PannableArea()
797 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
798 tv = hildon.GtkTreeView(ui_normal)
799 tv.set_headers_visible(True)
800 self.portfolio_model = self._create_portfolio_model(data)
801 tv.connect("row-activated", self.show_quotes_view, self.portfolio_model, True)
802 tv.set_model(self.portfolio_model)
803 self._tv_portfolio_columns(tv)
807 button = hildon.Button(fhsize, horbtn)
808 button.set_title("Refresh All")
809 button.connect("clicked", self.refresh_portfolio, tv, win)
810 hbox.pack_start(button, True, True, 0)
812 button = hildon.Button(fhsize, horbtn)
813 button.set_title("Add manually")
814 button.connect("clicked", self.add_item_dlg)
815 hbox.pack_start(button, True, True, 0)
817 button = hildon.Button(fhsize, horbtn)
818 button.set_title("Remove")
819 button.connect("clicked", self.remove_item)
820 hbox.pack_start(button, True, True, 0)
822 vbox.pack_start(parea, True, True, 0)
823 vbox.pack_start(hbox, False, False, 0)
827 def add_item_dlg(self, widget):
828 dlg = gtk.Dialog(title='Add to portfolio', parent=None, flags=0)
829 dlg.set_has_separator(False)
831 button1 = hildon.PickerButton(fhsize, horbtn)
832 data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
834 selector = self.create_selector(data, True)
835 button1.set_selector(selector)
836 button1.set_title("Your shares")
837 button1.set_value("0")
838 dlg.vbox.pack_start(button1, False, False, 0)
840 entry1 = hildon.Entry(fhsize)
841 entry2 = hildon.Entry(fhsize)
843 entry1.set_placeholder("Name")
844 entry1.connect("activate", self.add_item, dlg, button1, entry1, entry2)
845 dlg.vbox.pack_start(entry1, False, False, 0)
848 entry2.connect("activate", self.add_item, dlg, button1, entry1, entry2)
849 entry2.set_placeholder("Yahoo Finance symbol")
850 dlg.vbox.pack_start(entry2, False, False, 0)
852 button = hildon.Button(fhsize, horbtn)
853 button.set_label("Add")
854 button.connect("clicked", self.add_item, dlg, button1, entry1, entry2)
855 dlg.vbox.pack_start(button, False, False, 0)
861 def add_item(self, widget, dlg, button, entry1, entry2):
862 symbol = entry2.get_text()
863 name = entry1.get_text()
864 shares = button.get_value()
866 if name == '' or symbol == '':
867 self.show_info_banner(widget, "Must add the name and symbol")
870 self.add_to_portfolio(widget, button, symbol, name)
873 niter = self.portfolio_model.append()
874 self.portfolio_model.set(niter, 0, symbol, 1, name, 2, shares, 3, "-")
876 def remove_item(self, widget):
877 win = hildon.StackableWindow()
879 toolbar = hildon.EditToolbar("Choose items to delete", "Delete")
880 win.set_edit_toolbar(toolbar)
883 parea = hildon.PannableArea()
884 tv = hildon.GtkTreeView(ui_edit)
885 selection = tv.get_selection()
886 selection.set_mode(gtk.SELECTION_MULTIPLE)
887 tv.set_model(self.portfolio_model)
888 self._tv_remove_portfolio_columns(tv)
891 toolbar.connect("button-clicked", self.delete_from_portfolio, win, tv,
893 toolbar.connect_object("arrow-clicked", gtk.Window.destroy, win)
895 vbox.pack_start(parea, True, True, 0)
899 def delete_from_portfolio(self, widget, win, tv, selection):
900 if not self.is_treeview_selected(tv):
903 conf = self.show_confirmation(win, "Delete items?")
907 selmodel, selected = selection.get_selected_rows()
908 iters = [selmodel.get_iter(path) for path in selected]
910 symbol = selmodel.get_value(i, 0)
911 settings.delete_item_from_portfolio(settingsdb, symbol)
914 logger.exception("Deleting item from portfolio")
915 self.info_banner(widget, "Error deleting item")
917 def _tv_remove_portfolio_columns(self, treeview):
918 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
919 column.set_visible(False)
920 treeview.append_column(column)
922 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
923 column.set_property("expand", True)
924 treeview.append_column(column)
926 column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
927 column.set_visible(False)
928 treeview.append_column(column)
930 column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
931 column.set_visible(False)
932 treeview.append_column(column)
934 def refresh_portfolio(self, widget, tv, win):
935 data = settings.load_portfolio(settingsdb)
941 thread.start_new_thread(self._do_refresh_portfolio, (data, tv, win))
943 def _do_refresh_portfolio(self, data, tv, win):
946 item[3], item[4] = self.get_portfolio_data(item[0])
949 100.0 * float(item[4])/(float(item[3]) - \
954 item[5] = '%6.2f %%' % ch_percent
958 self.portfolio_model = self._create_portfolio_model(data)
959 tv.set_model(self.portfolio_model)
962 def get_portfolio_data(self, symbol):
963 from ystockquote import ystockquote as yt
965 data = yt.get_all(symbol)
966 return data['price'], data['change']
968 logger.exception("Getting price from Yahoo: %s" % symbol)
971 def _create_portfolio_model(self, data):
972 lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
973 gobject.TYPE_STRING, gobject.TYPE_STRING,
974 gobject.TYPE_STRING, gobject.TYPE_STRING,
977 iter = lstore.append()
982 lstore.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3],
983 4, item[4], 5, item[5], 6, color)
986 def _tv_portfolio_columns(self, treeview):
987 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
988 column.set_visible(False)
989 treeview.append_column(column)
991 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
992 column.set_property("expand", True)
993 treeview.append_column(column)
995 column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
996 treeview.append_column(column)
998 column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
999 treeview.append_column(column)
1001 column = gtk.TreeViewColumn('Change', gtk.CellRendererText(), text=4)
1002 treeview.append_column(column)
1005 renderer = gtk.CellRendererText()
1006 renderer.set_property("foreground-set", True)
1007 column = gtk.TreeViewColumn('%', renderer, text=5, foreground=6)
1008 treeview.append_column(column)
1010 def show_confirmation(self, window, msg):
1011 dialog = hildon.hildon_note_new_confirmation(window, msg)
1013 result = dialog.run()
1014 if result == gtk.RESPONSE_OK:
1021 def show_information_note(self, window, msg):
1022 dialog = hildon.hildon_note_new_information(window, msg)
1024 result = dialog.run()
1027 def show_info_banner(self, widget, msg):
1028 hildon.hildon_banner_show_information(widget, 'qgn_note_infoprint', msg)
1030 def is_treeview_selected(self, treeview):
1031 selection = treeview.get_selection()
1032 if selection.count_selected_rows() == 0:
1033 self.show_info_banner(treeview, 'No selected item')
1040 def __init__(self, widget):
1041 self.abdialog = gtk.Dialog()
1042 self.abdialog.set_title("About StockThis")
1044 notebook = gtk.Notebook()
1045 notebook.set_show_tabs(False)
1046 notebook.set_scrollable(False)
1047 notebook.set_show_border(False)
1049 # Description page #
1053 label.set_markup("<b><big>StockThis %s</big></b>" % VERSION)
1054 vbox.pack_start(label, True, True, 0)
1056 label = gtk.Label("Stocks application with big database")
1057 vbox.pack_start(label, True, True, 0)
1059 label = gtk.Label("GNU General Public License")
1060 vbox.pack_start(label, True, True, 0)
1062 url = "http://stockthis.garage.maemo.org"
1063 webbtn = gtk.LinkButton(url, "Web")
1064 vbox.pack_start(webbtn, True, True, 0)
1065 gtk.link_button_set_uri_hook(self.launch_browser)
1067 notebook.append_page(vbox, gtk.Label())
1071 textview = hildon.TextView()
1072 textview.set_cursor_visible(False)
1073 textview.set_wrap_mode(gtk.WRAP_WORD)
1074 text = "Written by Daniel Martin Yerga (dyerga@gmail.com)"
1075 textview.get_buffer().set_text(text)
1077 parea = hildon.PannableArea()
1080 vbox.pack_start(parea, True, True, 0)
1081 notebook.append_page(vbox, gtk.Label())
1087 textview = hildon.TextView()
1088 textview.set_cursor_visible(False)
1089 textview.set_wrap_mode(gtk.WRAP_WORD)
1090 text = """StockThis is a free software application.
1091 Developing good software takes time and hard work.
1092 StockThis's author develops the program in his spare time.
1093 If you like the program and it's helpful, consider donating a small amount of money.
1094 Donations are a great incentive and help the developer feel that the hard work is appreciated.
1096 textview.get_buffer().set_text(text)
1098 parea = hildon.PannableArea()
1101 button = hildon.Button(fhsize, horbtn)
1102 button.set_title("Make donation")
1103 url = "http://stockthis.garage.maemo.org/donate.html"
1104 button.connect("clicked", self.launch_browser, url)
1105 vbox.pack_start(button, False, False, 0)
1106 vbox.pack_start(parea, True, True, 0)
1108 notebook.append_page(vbox, gtk.Label())
1113 textview = hildon.TextView()
1114 textview.set_cursor_visible(False)
1115 textview.set_wrap_mode(gtk.WRAP_WORD)
1116 text = """StockThis is being improved thanks to bug reports that users have submitted. The author appreciates these reports.
1117 If the application is having an error when you're using it, you have two choices to report this error:
1118 1) Send the log from the button above (if there's an error in the log).
1119 2) Press the button and read how to report a bug."""
1120 textview.get_buffer().set_text(text)
1122 parea = hildon.PannableArea()
1126 hbox.set_homogeneous(True)
1128 button = hildon.Button(fhsize, horbtn)
1129 button.set_title("Report error")
1130 url = "http://stockthis.garage.maemo.org/reporting.html"
1131 button.connect("clicked", self.launch_browser, url)
1132 hbox.pack_start(button, True, True, 0)
1134 button = hildon.Button(fhsize, horbtn)
1135 button.set_title("Log")
1136 button.connect("clicked", self.on_show_log)
1137 hbox.pack_start(button, True, True, 0)
1139 vbox.pack_start(hbox, False, False, 0)
1140 vbox.pack_start(parea, True, True, 0)
1142 notebook.append_page(vbox, gtk.Label())
1147 textview = hildon.TextView()
1148 textview.set_cursor_visible(False)
1149 textview.set_wrap_mode(gtk.WRAP_WORD)
1150 text = """The downloads section in maemo.org has a nice system where you can rate applications.
1151 If you consider StockThis a good application (or a bad one too), you could rate it in maemo.org site."""
1152 textview.get_buffer().set_text(text)
1154 button = hildon.Button(fhsize, horbtn)
1155 button.set_title("Rate StockThis")
1156 url = "http://maemo.org/downloads/product/Maemo5/stockthis"
1157 button.connect("clicked", self.launch_browser, url)
1159 image.set_from_file(imgdir + "maemoorg.png")
1160 vbox.pack_start(button, False, False, 0)
1161 vbox.pack_start(image, False, False, 5)
1162 vbox.pack_start(textview, True, True, 0)
1164 notebook.append_page(vbox, gtk.Label())
1167 self.abdialog.vbox.pack_start(notebook, True, True, 0)
1171 descbutton = hildon.GtkRadioButton(fhsize)
1172 descbutton.set_mode(False)
1173 descbutton.set_active(True)
1174 descbutton.set_label('Description')
1175 descbutton.connect("toggled", self.change_tab, notebook, 0)
1176 hbox.pack_start(descbutton, True, True, 0)
1178 button = hildon.GtkRadioButton(fhsize)
1179 button.set_mode(False)
1180 button.set_active(True)
1181 button.set_label('Credits')
1182 button.set_group(descbutton)
1183 button.connect("toggled", self.change_tab, notebook, 1)
1184 hbox.pack_start(button, True, True, 0)
1186 button = hildon.GtkRadioButton(fhsize)
1187 button.set_mode(False)
1188 button.set_label('Donate')
1189 button.set_group(descbutton)
1190 button.connect("clicked", self.change_tab, notebook, 2)
1191 hbox.pack_start(button, True, True, 0)
1193 button = hildon.GtkRadioButton(fhsize)
1194 button.set_mode(False)
1195 button.set_label('Report')
1196 button.set_group(descbutton)
1197 button.connect("clicked", self.change_tab, notebook, 3)
1198 hbox.pack_start(button, True, True, 0)
1200 button = hildon.GtkRadioButton(fhsize)
1201 button.set_mode(False)
1202 button.set_label('Rate')
1203 button.set_group(descbutton)
1204 button.connect("clicked", self.change_tab, notebook, 4)
1205 hbox.pack_start(button, True, True, 0)
1207 self.abdialog.vbox.pack_start(hbox, False, False, 0)
1209 self.abdialog.show_all()
1211 self.abdialog.destroy()
1213 def change_tab(self, widget, notebook, number):
1214 notebook.set_current_page(number)
1216 def launch_browser(self, widget, url):
1218 bus = dbus.SystemBus()
1219 proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
1220 iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
1222 self.abdialog.destroy()
1224 iface.open_new_window(url)
1226 def on_show_log(self, widget):
1227 Log(widget, logfile)
1232 def __init__(self, widget, logfile):
1234 dialog = gtk.Dialog(title='Log', parent=None)
1236 dialog.set_size_request(600, 350)
1238 parea = hildon.PannableArea()
1239 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
1241 textview = hildon.TextView()
1242 textview.set_property("editable", False)
1243 textview.set_property("wrap-mode", gtk.WRAP_WORD)
1245 log = open(logfile, 'r')
1246 logtext = log.read()
1249 textview.get_buffer().set_text(logtext)
1252 dialog.vbox.pack_start(parea, True, True, 0)
1256 save_btn = hildon.Button(fhsize, horbtn)
1257 save_btn.set_title("Save")
1258 save_btn.connect('clicked', self.save, logfile, dialog)
1260 clear_btn = hildon.Button(fhsize, horbtn)
1261 clear_btn.set_title("Clear")
1262 clear_btn.connect('clicked', self.clear, textview, logfile)
1264 send_btn = hildon.Button(fhsize, horbtn)
1265 send_btn.set_title('Send')
1266 send_btn.connect('clicked', self.send, dialog, logfile)
1268 hbox.pack_start(save_btn, True, True, 0)
1269 hbox.pack_start(clear_btn, True, True, 0)
1270 hbox.pack_start(send_btn, True, True, 0)
1272 dialog.vbox.pack_start(hbox, False, False, 0)
1278 def show_filechooser(self, window, title, name, EXT):
1279 action = gtk.FILE_CHOOSER_ACTION_SAVE
1281 m = hildon.FileSystemModel()
1282 file_dialog = hildon.FileChooserDialog(window, action, m)
1283 file_dialog.set_title(title)
1285 file_dialog.set_current_name(name)
1286 HOME = os.path.expanduser("~")
1288 if os.path.exists(HOME + '/MyDocs/.documents'):
1289 file_dialog.set_current_folder(HOME + '/MyDocs/.documents')
1291 file_dialog.set_current_folder(HOME)
1293 file_dialog.set_default_response(gtk.RESPONSE_CANCEL)
1295 result = file_dialog.run()
1296 if result == gtk.RESPONSE_OK:
1297 namefile = file_dialog.get_filename()
1298 namefile, extension = os.path.splitext(namefile)
1299 namefile = namefile + "." + EXT
1302 file_dialog.destroy()
1307 def clear(self, widget, textview, logfile):
1308 textview.get_buffer().set_text('')
1309 f = open(logfile, 'w')
1312 def save(self, widget, logfile, dlg):
1314 filename = self.show_filechooser(dlg, "Save log file",
1315 "stockthis-log", "txt")
1321 shutil.copyfile(logfile, filename)
1322 stockspy.show_info_banner(widget, 'Log file saved')
1324 logger.exception("Saving log file")
1325 stockspy.show_info_banner(widget, 'Error saving the log file')
1327 def send(self, widget, dlg, logfile):
1328 sendtxt = ("You are going to send the log to the developers.\n"
1329 "This helps the developers to track problems with the application.\n"
1330 "It doesn't send any personal information (like passwords or similar).")
1332 dialog = hildon.hildon_note_new_confirmation(dlg, sendtxt)
1333 dialog.set_button_texts("Send", "Cancel")
1335 response = dialog.run()
1336 if response == gtk.RESPONSE_OK:
1337 self.do_pre_send(dlg, logfile)
1341 def do_pre_send(self, dlg, logfile):
1343 hildon.hildon_gtk_window_set_progress_indicator(dlg, 1)
1344 thread.start_new_thread(self._do_send, (dlg, logfile))
1346 def _do_send(self, dlg, logfile):
1347 import pycurl, shutil, random, commands
1350 for i in random.sample('abcdefghijkl123456789', 18):
1353 rnamepath = HOME + "/.stockthis/" + rname
1354 shutil.copyfile(logfile, rnamepath)
1356 gtkversion = "%s.%s.%s" % gtk.ver
1357 if os.path.exists("/etc/maemo_version"):
1358 mfile = open("/etc/maemo_version", 'r')
1359 maemoversion = mfile.read()
1364 opsystem = ' '.join(os.uname())
1365 pyversion = os.sys.version
1367 comm = ("awk '/Private_Dirty/{sum+=$2}END{print sum \"kB\"}'"
1368 " /proc/%s/smaps") % pid
1369 status, dirtymem = commands.getstatusoutput(comm)
1371 lfile = open(rnamepath, 'r')
1376 log = ("%s\nPython version: %s\nGtk version: %s\n"
1377 "Maemo version: %sOperating system: %s\n"
1378 "Dirty Memory: %s\nLog:\n%s") % (_version, pyversion, gtkversion,
1379 maemoversion, opsystem, dirtymem, log)
1381 lfile = open(rnamepath, 'w')
1385 url = "http://yerga.net/logs/uploader.php"
1386 data = [('uploadedfile', (pycurl.FORM_FILE, rnamepath)),]
1387 mycurl = pycurl.Curl()
1388 mycurl.setopt(pycurl.URL, url)
1389 mycurl.setopt(pycurl.HTTPPOST, data)
1393 os.remove(rnamepath)
1395 gtk.gdk.threads_enter()
1396 stockspy.show_info_banner(dlg, 'Log sent')
1397 gtk.gdk.threads_leave()
1398 hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1400 logger.exception("Sending log file")
1401 gtk.gdk.threads_enter()
1402 stockspy.show_info_banner(dlg, 'Error sending the log file')
1403 gtk.gdk.threads_leave()
1404 hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1407 if __name__ == "__main__":
1408 stockspy = StocksPy()
1409 gtk.gdk.threads_enter()
1411 gtk.gdk.threads_leave()