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.3 rev1"
33 from portrait import FremantleRotation
36 osso_c = osso.Context("net.yerga.stockthis", "0.3", False)
38 #detect if is ran locally or not
39 runningpath = sys.path[0]
41 if '/opt/' in runningpath:
42 runninglocally = False
46 HOME = os.path.expanduser("~")
48 settingsdb, imgdir, configdir, logfile = \
49 settings.define_paths(runninglocally, HOME)
51 logger = logging.getLogger('st')
52 logging.basicConfig(filename=logfile,level=logging.ERROR, filemode='w')
57 #set the main logger to DEBUG
58 logger.setLevel(logging.DEBUG)
60 #Create a handler for console debug
61 console = logging.StreamHandler()
62 console.setLevel(logging.DEBUG)
63 # set a format which is simpler for console use
64 formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
65 # tell the handler to use this format
66 console.setFormatter(formatter)
67 logging.getLogger('').addHandler(console)
69 fhsize = gtk.HILDON_SIZE_FINGER_HEIGHT
70 horbtn = hildon.BUTTON_ARRANGEMENT_HORIZONTAL
71 ui_normal = gtk.HILDON_UI_MODE_NORMAL
72 ui_edit = gtk.HILDON_UI_MODE_EDIT
73 winprogind = hildon.hildon_gtk_window_set_progress_indicator
78 gtk.gdk.threads_init()
83 self.program = hildon.Program()
84 self.program.__init__()
85 gtk.set_application_name("StockThis")
86 self.window = hildon.StackableWindow()
87 self.window.set_default_size(800, 480)
88 self.program.add_window(self.window)
89 self.window.connect("destroy", gtk.main_quit)
91 FremantleRotation('StockThis', None, "0.3", 0)
93 self.create_menu(self.window)
96 toolbar = self.main_toolbar(False, False, None, '', '', True)
98 parea = hildon.PannableArea()
99 tv = hildon.GtkTreeView(ui_normal)
100 inmodel = self.__create_model(marketdata.main, marketdata.idmain)
101 tv.connect("row-activated", self.show_instrument_view, inmodel,
102 marketdata.localmarkets, marketdata.localids,
104 tv.set_model(inmodel)
108 vbox.pack_start(parea, True, True, 0)
109 vbox.pack_start(gtk.HSeparator(), False, False, 5)
110 vbox.pack_start(toolbar, False, False, 0)
112 self.window.add(vbox)
113 self.window.show_all()
115 self.show_info_banner(self.window,
116 ("StockThis uses your network connection to get data.\n"
117 "Be aware of the high costs that your network provider may apply."))
119 def create_menu(self, window):
120 menu = hildon.AppMenu()
121 window.set_app_menu(menu)
122 button = gtk.Button("About")
123 button.connect("clicked", About)
125 button = gtk.Button("Log")
126 button.connect("clicked", Log, logfile)
130 def show_instrument_view(self, widget, path, column, inmodel, names,
132 market = inmodel[path][0]
133 names = names[mindex.index(market)]
134 ids = ids[mindex.index(market)]
136 window = hildon.StackableWindow()
137 self.create_menu(window)
138 window.set_title(inmodel[path][1])
141 toolbar = self.main_toolbar(False, False, None, '', '', False)
143 parea = hildon.PannableArea()
144 parea.connect("horizontal-movement", self.horizontal_mov)
145 tv = hildon.GtkTreeView(ui_normal)
146 model = self.__create_model(names, ids)
147 tv.connect("row-activated", self.show_quotes_view, model, False)
152 vbox.pack_start(parea, True, True, 0)
153 vbox.pack_start(gtk.HSeparator(), False, False, 5)
154 vbox.pack_start(toolbar, False, False, 0)
159 def horizontal_mov(self, parea, direction, initial_x, initial_y):
160 #direction = 2 right-to-left
161 #direction = 3 lefto-to-right
163 vadj = parea.get_vadjustment()
164 val = vadj.get_value()
167 if int(val)-2500 < 0:
168 parea.scroll_to(-1, 0)
170 parea.scroll_to(-1, int(val)-2500)
172 parea.scroll_to(-1, int(val)+3500)
176 def show_quotes_view(self, widget, path, column, model, portfolio):
177 quote = model[path][0], model[path][1]
178 #print "quote:", quote[0]
179 #('EURUSD=X', 'EUR/USD')
181 #Currencies and ETFs should show the list now -> view = True
182 #Other items show a new list with options
184 for i in marketdata.localids[(len(marketdata.localids)-2):]:
191 if quote[0] in marketdata.idindexes:
192 self.show_instrument_view(widget, path, column, model,
193 marketdata.wnamesindexes,
194 marketdata.widsindexes,
195 marketdata.idindexes)
197 if quote[0] in marketdata.idotmarkets:
198 self.show_instrument_view(widget, path, column, model,
200 marketdata.omsymbols,
201 marketdata.idotmarkets)
203 if quote[0] in marketdata.ideumarkets:
204 self.show_instrument_view(widget, path, column, model,
206 marketdata.eusymbols,
207 marketdata.ideumarkets)
209 if quote[0] in marketdata.idusmarkets:
210 self.show_instrument_view(widget, path, column, model,
212 marketdata.ussymbols,
213 marketdata.idusmarkets)
217 win = hildon.StackableWindow()
218 self.create_menu(win)
219 win.set_title(quote[1])
223 ltitle = gtk.Label('')
224 ltitle.set_markup('<b><big>' + quote[1].replace('&', '') +
226 color = gtk.gdk.color_parse("#03A5FF")
227 ltitle.modify_fg(gtk.STATE_NORMAL, color)
229 parea = hildon.PannableArea()
230 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
235 label = gtk.Label('')
236 label.set_markup('<b><big>%39s:</big></b>' % '<u>Price</u>')
237 lprice = gtk.Label('')
238 hbox.pack_start(label, False, False, 0)
239 #if self.is_portrait():
240 hbox.pack_start(lprice, False, False, 25)
242 # hbox.pack_start(lprice, False, False, 245)
244 vbox1.pack_start(hbox, True, True, 0)
247 label = gtk.Label('')
248 label.set_markup('<b><big>%35s:</big></b>' % '<u>Change</u>')
249 lchange = gtk.Label('')
250 lpercent = gtk.Label('')
251 hbox.pack_start(label, False, False, 0)
252 #if self.is_portrait():
253 hbox.pack_start(lchange, False, False, 25)
254 hbox.pack_start(lpercent, False, False, 0)
256 # hbox.pack_start(lchange, False, False, 205)
257 # hbox.pack_start(lpercent, False, False, 0)
259 vbox1.pack_start(hbox, True, True, 0)
262 label = gtk.Label('')
263 label.set_markup('<b><big>%35s:</big></b>' % '<u>Volume</u>')
264 lvolume = gtk.Label('')
265 hbox.pack_start(label, False, False, 0)
266 #if self.is_portrait():
267 hbox.pack_start(lvolume, False, False, 25)
269 # hbox.pack_start(lvolume, False, False, 207)
271 vbox1.pack_start(hbox, True, True, 0)
274 label = gtk.Label('')
275 label.set_markup('<b><big>%30s:</big></b>' % '<u>52 week high</u>')
276 l52whigh = gtk.Label('')
277 hbox.pack_start(label, False, False, 0)
278 #if self.is_portrait():
279 hbox.pack_start(l52whigh, False, False, 25)
281 # hbox.pack_start(l52whigh, False, False, 125)
283 vbox1.pack_start(hbox, True, True, 0)
286 label = gtk.Label('')
287 label.set_markup('<b><big>%30s:</big></b>' % '<u>52 week low</u>')
288 l52wlow = gtk.Label('')
289 hbox.pack_start(label, False, False, 0)
290 #if self.is_portrait():
291 hbox.pack_start(l52wlow, False, False, 26)
293 # hbox.pack_start(l52wlow, False, False, 140)
294 vbox1.pack_start(hbox, True, True, 0)
296 #if self.is_portrait():
300 button1 = hildon.PickerButton(fhsize, horbtn)
301 data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
303 selector = self.create_selector(data, True)
304 button1.set_selector(selector)
305 button1.set_title("Your shares")
306 shares = self.get_shares_from_symbol(quote[0])
307 button1.set_value(shares)
308 hbox.pack_start(button1, True, True, 0)
310 button = hildon.Button(fhsize, horbtn)
311 button.set_title("Add to Portfolio")
312 button.connect("clicked", self.add_to_portfolio, button1, quote[0], quote[1])
313 hbox.pack_start(button, True, True, 0)
316 label = gtk.Label('')
317 label.set_markup('<b><big>%37s:</big></b>' % '<u>Shares</u>')
318 lshares = gtk.Label(shares)
319 hbox1.pack_start(label, False, False, 0)
320 #if self.is_portrait():
321 hbox1.pack_start(lshares, False, False, 25)
323 # hbox1.pack_start(lshares, False, False, 220)
326 label = gtk.Label('')
327 label.set_markup('<b><big>%29s:</big></b>' % '<u>Holdings Value</u>')
328 holdingsvalue = gtk.Label("")
329 hbox2.pack_start(label, False, False, 0)
330 #if self.is_portrait():
331 hbox2.pack_start(holdingsvalue, False, False, 25)
333 # hbox2.pack_start(holdingsvalue, False, False, 105)
336 label = gtk.Label('')
337 label.set_markup("<b><big>%25s:</big></b>" % "<u>Day's Value Change</u>")
338 dayvaluechange = gtk.Label("")
339 hbox3.pack_start(label, False, False, 0)
340 #if self.is_portrait():
341 hbox3.pack_start(dayvaluechange, False, False, 25)
343 # hbox3.pack_start(dayvaluechange, False, False, 45)
346 vbox1.pack_start(hbox, False, False, 0)
348 vbox1.pack_start(hbox1, True, True, 0)
349 vbox1.pack_start(hbox2, True, True, 0)
350 vbox1.pack_start(hbox3, True, True, 0)
352 parea.add_with_viewport(vbox1)
354 widgets = [win, ltitle, lprice, lchange, lpercent, lvolume, l52whigh,
355 l52wlow, lshares, holdingsvalue, dayvaluechange]
357 toolbar = self.main_toolbar(True, portfolio, widgets, quote[0], quote[1], False)
359 vbox.pack_start(ltitle, False, False, 0)
360 vbox.pack_start(gtk.HSeparator(), False, False, 0)
361 vbox.pack_start(parea, True, True, 0)
362 vbox.pack_start(gtk.HSeparator(), False, False, 5)
363 vbox.pack_start(toolbar, False, False, 0)
368 self.show_data(quote[0], widgets, shares)
370 def is_portrait(self):
371 width = gtk.gdk.screen_width()
372 height = gtk.gdk.screen_height()
378 def get_shares_from_symbol(self, symbol):
381 portfolio_data = settings.load_portfolio(settingsdb)
382 for item in portfolio_data :
387 logger.exception("Getting shares from symbol: %s" % symbol)
390 def add_to_portfolio(self, widget, button, symbol, name):
391 shares = button.get_value()
394 portfolio = settings.load_portfolio(settingsdb)
396 for item in portfolio:
398 index = portfolio.index(item)
400 item = [symbol, name, shares, '-']
403 settings.insert_new_item_to_portfolio(settingsdb, item)
405 settings.delete_item_from_portfolio(settingsdb, symbol)
406 settings.insert_new_item_to_portfolio(settingsdb, item)
408 self.show_info_banner(widget, "Added to portfolio")
410 logger.exception("Adding to portfolio: %s, %s" % (symbol, name))
411 self.show_info_banner(widget, "Error adding to portfolio")
414 def create_selector(self, data, entry):
416 selector = hildon.TouchSelectorEntry(text=True)
418 selector = hildon.TouchSelector(text=True)
419 for i in range(len(data)):
420 selector.append_text(data[i])
424 def show_data(self, symbol, widgets, shares):
426 winprogind(widgets[0], 1)
427 thread.start_new_thread(self.get_data, (symbol, widgets, shares))
429 def get_data(self, symbol, widgets, shares):
430 from ystockquote import ystockquote as yt
431 win, ltitle, lprice, lchange, lpercent, lvolume, l52whigh, l52wlow, lshares, holdingsvalue, dayvaluechange = widgets
434 data = yt.get_all(symbol)
436 logger.exception("Getting data from Yahoo: %s" % symbol)
437 data = {'price': 'N/A', 'change': 'N/A', 'volume':'N/A',
438 '52_week_high': 'N/A', '52_week_low': 'N/A'}
439 ltitle.set_markup('<b><big>Failed to get data</big></b>')
443 100.0 * float(data['change'])/(float(data['price']) - \
444 float(data['change']))
448 lprice.set_label(data['price'])
449 lchange.set_label(data['change'])
450 lpercent.set_label('%6.2f %%' % ch_percent)
452 if '-' in data['change']:
453 color = gtk.gdk.color_parse("#FF0000")
455 color = gtk.gdk.color_parse("#16EB78")
457 lpercent.modify_fg(gtk.STATE_NORMAL, color)
458 lchange.modify_fg(gtk.STATE_NORMAL, color)
460 lvolume.set_label(data['volume'])
461 l52whigh.set_label(data['52_week_high'])
462 l52wlow.set_label(data['52_week_low'])
465 daychange = float(shares)*float(data['change'])
469 holdvalue = float(shares)*float(data['price'])
473 dayvaluechange.set_label(str(daychange))
474 holdingsvalue.set_label(str(holdvalue))
478 def refresh_stock_data(self, widget, portfolio, widgets, symbol):
480 shares = self.get_shares_from_symbol(symbol)
484 self.show_data(symbol, widgets, shares)
486 def show_graph_view(self, widget, symbol, name):
487 win = hildon.StackableWindow()
488 self.create_menu(win)
492 toolbar = self.main_toolbar(False, True, None, '', '', False)
494 self.graphs_title = gtk.Label(name)
495 color = gtk.gdk.color_parse("#03A5FF")
496 self.graphs_title.modify_fg(gtk.STATE_NORMAL, color)
498 parea = hildon.PannableArea()
499 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
504 hbox.set_homogeneous(True)
506 button = hildon.Button(fhsize, horbtn)
507 button.set_label('1d')
508 button.connect("clicked", self.show_graph, '1d', win, symbol)
509 hbox.pack_start(button)
511 button = hildon.Button(fhsize, horbtn)
512 button.set_label('5d')
513 button.connect("clicked", self.show_graph, '5d', win, symbol)
514 hbox.pack_start(button)
516 button = hildon.Button(fhsize, horbtn)
517 button.set_label('3m')
518 button.connect("clicked", self.show_graph, '3m', win, symbol)
519 hbox.pack_start(button)
521 button = hildon.Button(fhsize, horbtn)
522 button.set_label('6m')
523 button.connect("clicked", self.show_graph, '6m', win, symbol)
524 hbox.pack_start(button)
526 vbox1.pack_start(hbox, False, False, 0)
528 hbox.set_homogeneous(True)
530 button = hildon.Button(fhsize, horbtn)
531 button.set_label('1y')
532 button.connect("clicked", self.show_graph, '1y', win, symbol)
533 hbox.pack_start(button)
535 button = hildon.Button(fhsize, horbtn)
536 button.set_label('2y')
537 button.connect("clicked", self.show_graph, '2y', win, symbol)
538 hbox.pack_start(button)
540 button = hildon.Button(fhsize, horbtn)
541 button.set_label('5y')
542 button.connect("clicked", self.show_graph, '5y', win, symbol)
543 hbox.pack_start(button)
545 button = hildon.Button(fhsize, horbtn)
546 button.set_label('Max')
547 button.connect("clicked", self.show_graph, 'max', win, symbol)
548 hbox.pack_start(button)
550 vbox1.pack_start(hbox, False, False, 0)
552 self.graph = gtk.Image()
553 vbox1.pack_start(self.graph, True, True, 0)
555 parea.add_with_viewport(vbox1)
557 vbox.pack_start(self.graphs_title, False, False, 0)
558 vbox.pack_start(gtk.HSeparator(), False, False, 0)
559 vbox.pack_start(parea, True, True, 0)
560 vbox.pack_start(gtk.HSeparator(), False, False, 5)
561 vbox.pack_start(toolbar, False, False, 0)
566 self.show_graph(None, '1d', win, symbol)
568 def show_graph(self, widget, option, win, symbol):
571 thread.start_new_thread(self.get_graph_data, (option, win, symbol))
573 def get_graph_data(self, option, win, symbol):
575 url = 'http://uk.ichart.yahoo.com/b?s=%s' % symbol
577 url = 'http://uk.ichart.yahoo.com/w?s=%s' % symbol
579 url = 'http://chart.finance.yahoo.com/c/3m/s/%s' % symbol.lower()
581 url = 'http://chart.finance.yahoo.com/c/6m/s/%s' % symbol.lower()
583 url = 'http://chart.finance.yahoo.com/c/1y/s/%s' % symbol.lower()
585 url = 'http://chart.finance.yahoo.com/c/2y/s/%s' % symbol.lower()
587 url = 'http://chart.finance.yahoo.com/c/5y/s/%s' % symbol.lower()
588 elif option == 'max':
589 url = 'http://chart.finance.yahoo.com/c/my/s/%s' % symbol.lower()
592 myimg = urllib2.urlopen(url)
593 imgdata = myimg.read()
595 pbl = gtk.gdk.PixbufLoader()
598 pbuf = pbl.get_pixbuf()
599 pbuf = pbuf.scale_simple(475, 235, gtk.gdk.INTERP_TILES)
601 self.graph.set_from_pixbuf(pbuf)
604 logger.exception("Getting graph data: %s" % url)
606 self.graphs_title.set_label('Failed to get data')
609 def _tv_columns(self, treeview):
610 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
611 column.set_visible(False)
612 treeview.append_column(column)
614 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
615 treeview.append_column(column)
617 def __create_model(self, names, ids):
618 lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
619 for item in range(len(names)):
620 iter = lstore.append()
621 lstore.set(iter, 0, ids[item], 1, names[item])
624 def main_toolbar(self, quotesview, portfolio, widgets, symbol, name, initial):
626 #toolbar.set_homogeneous(True)
628 portfolio_btn = hildon.Button(fhsize, horbtn)
629 portfolio_btn.set_title("Portfolio")
630 portfolio_btn.connect("clicked", self.show_portfolio_view)
632 graph_btn = hildon.Button(fhsize, horbtn)
633 graph_btn.set_title("Graph")
634 graph_btn.connect("clicked", self.show_graph_view, symbol, name)
636 refresh_btn = hildon.Button(fhsize, horbtn)
637 refresh_btn.set_title("Refresh")
638 refresh_btn.connect("clicked", self.refresh_stock_data, portfolio,
642 stockiconspath = "/usr/share/icons/hicolor/48x48/hildon/"
643 info_btn = hildon.Button(fhsize, horbtn)
645 img.set_from_file(stockiconspath + "general_information.png")
646 info_btn.set_image(img)
647 info_btn.connect("clicked", self.show_app_information)
649 search_btn = hildon.Button(fhsize, horbtn)
651 img.set_from_file(stockiconspath + "general_search.png")
652 search_btn.set_image(img)
653 search_btn.connect("clicked", self.show_search_dialog)
656 toolbar.pack_start(portfolio_btn)
658 toolbar.pack_start(info_btn, False, False, 0)
660 toolbar.pack_start(graph_btn)
661 toolbar.pack_start(refresh_btn)
664 toolbar.pack_start(search_btn, False, False, 0)
670 def show_search_dialog(self, widget):
671 dlg = gtk.Dialog(title='Search company', parent=None, flags=0)
672 dlg.set_has_separator(False)
674 entry = hildon.Entry(fhsize)
675 dlg.vbox.pack_start(entry, False, False, 0)
677 button = hildon.Button(fhsize, horbtn)
678 button.set_label("Search")
679 button.connect("clicked", self.do_search, entry, dlg)
680 dlg.vbox.pack_start(button, False, False, 0)
687 def do_search(self, widget, entry, dlg):
689 text = entry.get_text()
692 winprogind(self.window, 1)
693 thread.start_new_thread(self._really_do_search, (text,))
695 def _really_do_search(self, text):
698 for market in marketdata.eunames:
699 for company in market:
700 allnames.append(company)
702 for market in marketdata.omnames:
703 for company in market:
704 allnames.append(company)
706 for market in marketdata.usnames:
707 for company in market:
708 allnames.append(company)
711 for market in marketdata.eusymbols:
712 for company in market:
713 allsymbols.append(company)
715 for market in marketdata.omsymbols:
716 for company in market:
717 allsymbols.append(company)
719 for market in marketdata.ussymbols:
720 for company in market:
721 allsymbols.append(company)
723 new_model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
724 for i in range(len(allnames)):
725 if text.lower() in allnames[i].lower():
726 niter = new_model.append()
727 #print allsymbols[i], allnames[i]
728 #FIXME: repeated companies in the results list
729 #print if allnames[i]
731 # if j[1] == allnames[i]:
732 new_model.set(niter, 0, allsymbols[i], 1, allnames[i])
734 if len(new_model) == 0:
735 winprogind(self.window, 0)
736 gtk.gdk.threads_enter()
737 self.show_info_banner(self.window, "No items found for this search")
738 gtk.gdk.threads_leave()
741 gtk.gdk.threads_enter()
742 self.show_search_screen(new_model, text)
743 gtk.gdk.threads_leave()
744 winprogind(self.window, 0)
747 def show_search_screen(self, model, text):
748 window = hildon.StackableWindow()
749 self.create_menu(window)
750 window.set_title("Search for " + text)
753 toolbar = self.main_toolbar(False, False, None, '', '', False)
755 parea = hildon.PannableArea()
756 parea.connect("horizontal-movement", self.horizontal_mov)
757 tv = hildon.GtkTreeView(ui_normal)
758 tv.connect("row-activated", self.show_quotes_view, model, False)
763 vbox.pack_start(parea, True, True, 0)
764 vbox.pack_start(gtk.HSeparator(), False, False, 5)
765 vbox.pack_start(toolbar, False, False, 0)
771 def show_app_information(self, widget):
772 self.show_information_note(self.window, (
773 "The data is got from Yahoo! Finance.\n"
774 "It could be delayed or even wrong.\n"
775 "The author doesn't validate in any way this data and therefore he is not responsible for any damage that may occur.\n\n"
776 "You can scroll large list with gestures:\n"
777 "Left-to-right gesture: scroll down.\n"
778 "Right-to-left gesture: scroll up."))
780 def show_portfolio_view(self, widget):
781 data = settings.load_portfolio(settingsdb)
786 win = hildon.StackableWindow()
787 self.create_menu(win)
788 win.set_title("Portfolio")
792 parea = hildon.PannableArea()
793 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
794 tv = hildon.GtkTreeView(ui_normal)
795 tv.set_headers_visible(True)
796 self.portfolio_model = self._create_portfolio_model(data)
797 tv.connect("row-activated", self.show_quotes_view, self.portfolio_model, True)
798 tv.set_model(self.portfolio_model)
799 self._tv_portfolio_columns(tv)
803 button = hildon.Button(fhsize, horbtn)
804 button.set_title("Refresh All")
805 button.connect("clicked", self.refresh_portfolio, tv, win)
806 hbox.pack_start(button, True, True, 0)
808 button = hildon.Button(fhsize, horbtn)
809 button.set_title("Add manually")
810 button.connect("clicked", self.add_item_dlg)
811 hbox.pack_start(button, True, True, 0)
813 button = hildon.Button(fhsize, horbtn)
814 button.set_title("Remove")
815 button.connect("clicked", self.remove_item)
816 hbox.pack_start(button, True, True, 0)
818 vbox.pack_start(parea, True, True, 0)
819 vbox.pack_start(hbox, False, False, 0)
823 def add_item_dlg(self, widget):
824 dlg = gtk.Dialog(title='Add to portfolio', parent=None, flags=0)
825 dlg.set_has_separator(False)
827 button1 = hildon.PickerButton(fhsize, horbtn)
828 data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
830 selector = self.create_selector(data, True)
831 button1.set_selector(selector)
832 button1.set_title("Your shares")
833 button1.set_value("0")
834 dlg.vbox.pack_start(button1, False, False, 0)
836 entry1 = hildon.Entry(fhsize)
837 entry1.set_placeholder("Name")
838 dlg.vbox.pack_start(entry1, False, False, 0)
840 entry2 = hildon.Entry(fhsize)
841 entry2.set_placeholder("Yahoo Finance symbol")
842 dlg.vbox.pack_start(entry2, False, False, 0)
844 button = hildon.Button(fhsize, horbtn)
845 button.set_label("Add")
846 button.connect("clicked", self.add_item, dlg, button1, entry1, entry2)
847 dlg.vbox.pack_start(button, False, False, 0)
853 def add_item(self, widget, dlg, button, entry1, entry2):
854 symbol = entry2.get_text()
855 name = entry1.get_text()
856 shares = button.get_value()
858 if name == '' or symbol == '':
859 self.show_info_banner(widget, "Must add the name and symbol")
862 self.add_to_portfolio(widget, button, symbol, name)
865 niter = self.portfolio_model.append()
866 self.portfolio_model.set(niter, 0, symbol, 1, name, 2, shares, 3, "-")
868 def remove_item(self, widget):
869 win = hildon.StackableWindow()
871 toolbar = hildon.EditToolbar("Choose items to delete", "Delete")
872 win.set_edit_toolbar(toolbar)
875 parea = hildon.PannableArea()
876 tv = hildon.GtkTreeView(ui_edit)
877 selection = tv.get_selection()
878 selection.set_mode(gtk.SELECTION_MULTIPLE)
879 tv.set_model(self.portfolio_model)
880 self._tv_remove_portfolio_columns(tv)
883 toolbar.connect("button-clicked", self.delete_from_portfolio, win, tv,
885 toolbar.connect_object("arrow-clicked", gtk.Window.destroy, win)
887 vbox.pack_start(parea, True, True, 0)
891 def delete_from_portfolio(self, widget, win, tv, selection):
892 if not self.is_treeview_selected(tv):
895 conf = self.show_confirmation(win, "Delete items?")
899 selmodel, selected = selection.get_selected_rows()
900 iters = [selmodel.get_iter(path) for path in selected]
902 symbol = selmodel.get_value(i, 0)
903 settings.delete_item_from_portfolio(settingsdb, symbol)
906 logger.exception("Deleting item from portfolio")
907 self.info_banner(widget, "Error deleting item")
909 def _tv_remove_portfolio_columns(self, treeview):
910 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
911 column.set_visible(False)
912 treeview.append_column(column)
914 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
915 column.set_property("expand", True)
916 treeview.append_column(column)
918 column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
919 column.set_visible(False)
920 treeview.append_column(column)
922 column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
923 column.set_visible(False)
924 treeview.append_column(column)
926 def refresh_portfolio(self, widget, tv, win):
927 data = settings.load_portfolio(settingsdb)
933 thread.start_new_thread(self._do_refresh_portfolio, (data, tv, win))
935 def _do_refresh_portfolio(self, data, tv, win):
938 item[3], item[4] = self.get_portfolio_data(item[0])
941 100.0 * float(item[4])/(float(item[3]) - \
946 item[5] = '%6.2f %%' % ch_percent
950 self.portfolio_model = self._create_portfolio_model(data)
951 tv.set_model(self.portfolio_model)
954 def get_portfolio_data(self, symbol):
955 from ystockquote import ystockquote as yt
957 data = yt.get_all(symbol)
958 return data['price'], data['change']
960 logger.exception("Getting price from Yahoo: %s" % symbol)
963 def _create_portfolio_model(self, data):
964 lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
965 gobject.TYPE_STRING, gobject.TYPE_STRING,
966 gobject.TYPE_STRING, gobject.TYPE_STRING,
969 iter = lstore.append()
974 lstore.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3],
975 4, item[4], 5, item[5], 6, color)
978 def _tv_portfolio_columns(self, treeview):
979 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
980 column.set_visible(False)
981 treeview.append_column(column)
983 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
984 column.set_property("expand", True)
985 treeview.append_column(column)
987 column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
988 treeview.append_column(column)
990 column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
991 treeview.append_column(column)
993 column = gtk.TreeViewColumn('Change', gtk.CellRendererText(), text=4)
994 treeview.append_column(column)
997 renderer = gtk.CellRendererText()
998 renderer.set_property("foreground-set", True)
999 column = gtk.TreeViewColumn('%', renderer, text=5, foreground=6)
1000 treeview.append_column(column)
1002 def show_confirmation(self, window, msg):
1003 dialog = hildon.hildon_note_new_confirmation(window, msg)
1005 result = dialog.run()
1006 if result == gtk.RESPONSE_OK:
1013 def show_information_note(self, window, msg):
1014 dialog = hildon.hildon_note_new_information(window, msg)
1016 result = dialog.run()
1019 def show_info_banner(self, widget, msg):
1020 hildon.hildon_banner_show_information(widget, 'qgn_note_infoprint', msg)
1022 def is_treeview_selected(self, treeview):
1023 selection = treeview.get_selection()
1024 if selection.count_selected_rows() == 0:
1025 self.show_info_banner(treeview, 'No selected item')
1032 def __init__(self, widget):
1033 self.abdialog = gtk.Dialog(title='About', parent=None, flags=0)
1034 self.abdialog.set_has_separator(False)
1035 self.abdialog.set_size_request(-1, 400)
1037 self.info_lb = gtk.Label()
1038 self.info_lb.set_line_wrap(True)
1044 button = hildon.Button(fhsize, horbtn)
1045 button.set_title('Description')
1046 button.connect("clicked", self.show_info, 'description')
1047 hbox1.pack_start(button, True, True, 0)
1049 button = hildon.Button(fhsize, horbtn)
1050 button.set_title('Credits')
1051 button.connect("clicked", self.show_info, 'credits')
1052 hbox1.pack_start(button, True, True, 0)
1054 button = hildon.Button(fhsize, horbtn)
1055 button.set_title('License')
1056 button.connect("clicked", self.show_info, 'license')
1057 hbox1.pack_start(button, True, True, 0)
1059 button = hildon.Button(fhsize, horbtn)
1060 button.set_title('Donate')
1061 button.connect("clicked", self.show_info, 'donate')
1062 hbox1.pack_start(button, True, True, 0)
1064 button = hildon.Button(fhsize, horbtn)
1065 button.set_title('Report ')
1066 button.connect("clicked", self.show_info, 'report')
1067 hbox1.pack_start(button, True, True, 0)
1069 button = hildon.Button(fhsize, horbtn)
1070 button.set_title(' Rate ')
1071 button.connect("clicked", self.show_info, 'vote')
1072 hbox1.pack_start(button, True, True, 0)
1074 self.action_btn = hildon.Button(fhsize, horbtn)
1075 self.image = gtk.Image()
1077 self.show_info(None, 'description')
1079 self.abdialog.vbox.pack_start(self.action_btn, False, False, 0)
1080 self.abdialog.vbox.pack_start(self.image, False, False, 5)
1081 self.abdialog.vbox.pack_start(self.info_lb, True, True, 0)
1082 self.abdialog.vbox.pack_start(hbox1, False, False, 0)
1084 self.abdialog.show_all()
1085 self.action_btn.hide()
1088 self.abdialog.destroy()
1090 def do_action(self, widget, action):
1093 self.abdialog.destroy()
1095 bus = dbus.SystemBus()
1096 proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
1097 iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
1099 if action == "donate":
1100 url = "http://stockthis.garage.maemo.org/donate.html"
1101 elif action == "report":
1102 url = "http://stockthis.garage.maemo.org/reporting.html"
1103 elif action == "vote":
1104 url = "http://maemo.org/downloads/product/Maemo5/stockthis"
1106 iface.open_new_window(url)
1108 def show_info(self, widget, kind):
1109 if kind == 'license':
1110 self.action_btn.hide()
1112 info = """<small><b>StockThis</b> is free software. It's using a GPL version 2 license or at your election any later version.
1114 Logo by Daniel Martin Yerga.
1116 elif kind == 'credits':
1117 self.action_btn.hide()
1119 info = """<small><b>Written by</b> Daniel Martin Yerga (dyerga@gmail.com)
1121 <b>Thanks</b> to everyone who has reported bugs, suggestions, giving spirits, critiques, writing blog articles about StockThis, and so on. Like always the list is extremely big and for not forget anybody, THANKS TO ALL!</small>"""
1122 elif kind == 'description':
1123 self.action_btn.hide()
1125 info = """<b><big>StockThis 0.3</big></b>
1127 <i>StockThis is a stocks application for Maemo</i>
1130 stockthis.garage.maemo.org"""
1132 elif kind == 'donate':
1133 self.action_btn.show()
1135 self.action_btn.set_title('Make donation')
1137 self.action_btn.disconnect(self.id)
1138 self.id = self.action_btn.connect("clicked", self.do_action, "donate")
1139 info = """<small><b>StockThis</b> is a free (and gratis) software application.
1140 Developing good software takes time and hard work.
1142 <b>StockThis's author</b> develops the program in his spare time.
1143 If you like the program and it's helpful, consider donating a small amount of money.
1144 Donations are a great incentive and help the developer feel that the hard work is appreciated.</small>
1146 elif kind == 'report':
1147 self.action_btn.show()
1149 self.action_btn.set_title('Report bug')
1151 self.action_btn.disconnect(self.id)
1152 self.id = self.action_btn.connect("clicked", self.do_action, "report")
1153 info = """<small>StockThis is being improved thanks to bug reports that users have submitted. The author appreciates these reports.
1154 If the application is raising an error when you're using it, you have two choices to report this error:
1155 1) Send the log from the application menu (if there's an error in the log).
1156 2) Press the button and write a bug report with as much information as possible.</small>"""
1157 elif kind == 'vote':
1158 self.action_btn.show()
1160 self.image.set_from_file(imgdir + "maemoorg.png")
1161 self.action_btn.set_title('Rate StockThis')
1163 self.action_btn.disconnect(self.id)
1164 self.id = self.action_btn.connect("clicked", self.do_action, "vote")
1165 info = """<small>The downloads section in maemo.org has a nice system where you can rate applications.
1166 If you consider StockThis a good application (or a bad one too), you could rate it in maemo.org site.</small>"""
1168 self.info_lb.set_markup(info)
1173 def __init__(self, widget, logfile):
1175 dialog = gtk.Dialog(title='Log', parent=None)
1177 dialog.set_size_request(600, 350)
1179 parea = hildon.PannableArea()
1180 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
1182 textview = hildon.TextView()
1183 textview.set_property("editable", False)
1184 textview.set_property("wrap-mode", gtk.WRAP_WORD)
1186 log = open(logfile, 'r')
1187 logtext = log.read()
1190 textview.get_buffer().set_text(logtext)
1193 dialog.vbox.pack_start(parea, True, True, 0)
1197 save_btn = hildon.Button(fhsize, horbtn)
1198 save_btn.set_title("Save")
1199 save_btn.connect('clicked', self.save, logfile, dialog)
1201 clear_btn = hildon.Button(fhsize, horbtn)
1202 clear_btn.set_title("Clear")
1203 clear_btn.connect('clicked', self.clear, textview, logfile)
1205 send_btn = hildon.Button(fhsize, horbtn)
1206 send_btn.set_title('Send')
1207 send_btn.connect('clicked', self.send, dialog, logfile)
1209 hbox.pack_start(save_btn, True, True, 0)
1210 hbox.pack_start(clear_btn, True, True, 0)
1211 hbox.pack_start(send_btn, True, True, 0)
1213 dialog.vbox.pack_start(hbox, False, False, 0)
1219 def show_filechooser(self, window, title, name, EXT):
1220 action = gtk.FILE_CHOOSER_ACTION_SAVE
1222 m = hildon.FileSystemModel()
1223 file_dialog = hildon.FileChooserDialog(window, action, m)
1224 file_dialog.set_title(title)
1226 file_dialog.set_current_name(name)
1227 HOME = os.path.expanduser("~")
1229 if os.path.exists(HOME + '/MyDocs/.documents'):
1230 file_dialog.set_current_folder(HOME + '/MyDocs/.documents')
1232 file_dialog.set_current_folder(HOME)
1234 file_dialog.set_default_response(gtk.RESPONSE_CANCEL)
1236 result = file_dialog.run()
1237 if result == gtk.RESPONSE_OK:
1238 namefile = file_dialog.get_filename()
1239 namefile, extension = os.path.splitext(namefile)
1240 namefile = namefile + "." + EXT
1243 file_dialog.destroy()
1248 def clear(self, widget, textview, logfile):
1249 textview.get_buffer().set_text('')
1250 f = open(logfile, 'w')
1253 def save(self, widget, logfile, dlg):
1255 filename = self.show_filechooser(dlg, "Save log file",
1256 "stockthis-log", "txt")
1262 shutil.copyfile(logfile, filename)
1263 stockspy.show_info_banner(widget, 'Log file saved')
1265 logger.exception("Saving log file")
1266 stockspy.show_info_banner(widget, 'Error saving the log file')
1268 def send(self, widget, dlg, logfile):
1269 sendtxt = ("You are going to send the log to the developers.\n"
1270 "This helps the developers to track problems with the application.\n"
1271 "It doesn't send any personal information (like passwords or similar).")
1273 dialog = hildon.hildon_note_new_confirmation(dlg, sendtxt)
1274 dialog.set_button_texts("Send", "Cancel")
1276 response = dialog.run()
1277 if response == gtk.RESPONSE_OK:
1278 self.do_pre_send(dlg, logfile)
1282 def do_pre_send(self, dlg, logfile):
1284 hildon.hildon_gtk_window_set_progress_indicator(dlg, 1)
1285 thread.start_new_thread(self._do_send, (dlg, logfile))
1287 def _do_send(self, dlg, logfile):
1288 import pycurl, shutil, random, commands
1291 for i in random.sample('abcdefghijkl123456789', 18):
1294 rnamepath = HOME + "/.stockthis/" + rname
1295 shutil.copyfile(logfile, rnamepath)
1297 gtkversion = "%s.%s.%s" % gtk.ver
1298 if os.path.exists("/etc/maemo_version"):
1299 mfile = open("/etc/maemo_version", 'r')
1300 maemoversion = mfile.read()
1305 opsystem = ' '.join(os.uname())
1306 pyversion = os.sys.version
1308 comm = ("awk '/Private_Dirty/{sum+=$2}END{print sum \"kB\"}'"
1309 " /proc/%s/smaps") % pid
1310 status, dirtymem = commands.getstatusoutput(comm)
1312 lfile = open(rnamepath, 'r')
1317 log = ("%s\nPython version: %s\nGtk version: %s\n"
1318 "Maemo version: %sOperating system: %s\n"
1319 "Dirty Memory: %s\nLog:\n%s") % (_version, pyversion, gtkversion,
1320 maemoversion, opsystem, dirtymem, log)
1322 lfile = open(rnamepath, 'w')
1326 url = "http://yerga.net/logs/uploader.php"
1327 data = [('uploadedfile', (pycurl.FORM_FILE, rnamepath)),]
1328 mycurl = pycurl.Curl()
1329 mycurl.setopt(pycurl.URL, url)
1330 mycurl.setopt(pycurl.HTTPPOST, data)
1334 os.remove(rnamepath)
1336 gtk.gdk.threads_enter()
1337 stockspy.show_info_banner(dlg, 'Log sent')
1338 gtk.gdk.threads_leave()
1339 hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1341 logger.exception("Sending log file")
1342 gtk.gdk.threads_enter()
1343 stockspy.show_info_banner(dlg, 'Error sending the log file')
1344 gtk.gdk.threads_leave()
1345 hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1348 if __name__ == "__main__":
1349 stockspy = StocksPy()
1350 gtk.gdk.threads_enter()
1352 gtk.gdk.threads_leave()