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)
783 win = hildon.StackableWindow()
784 self.create_menu(win)
785 win.set_title("Portfolio")
789 parea = hildon.PannableArea()
790 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
791 tv = hildon.GtkTreeView(ui_normal)
792 tv.set_headers_visible(True)
793 self.portfolio_model = self._create_portfolio_model(data)
794 tv.connect("row-activated", self.show_quotes_view, self.portfolio_model, True)
795 tv.set_model(self.portfolio_model)
796 self._tv_portfolio_columns(tv)
800 button = hildon.Button(fhsize, horbtn)
801 button.set_title("Refresh All")
802 button.connect("clicked", self.refresh_portfolio, tv, win)
803 hbox.pack_start(button, True, True, 0)
805 button = hildon.Button(fhsize, horbtn)
806 button.set_title("Add manually")
807 button.connect("clicked", self.add_item_dlg)
808 hbox.pack_start(button, True, True, 0)
810 button = hildon.Button(fhsize, horbtn)
811 button.set_title("Remove")
812 button.connect("clicked", self.remove_item)
813 hbox.pack_start(button, True, True, 0)
815 vbox.pack_start(parea, True, True, 0)
816 vbox.pack_start(hbox, False, False, 0)
820 def add_item_dlg(self, widget):
821 dlg = gtk.Dialog(title='Add to portfolio', parent=None, flags=0)
822 dlg.set_has_separator(False)
824 button1 = hildon.PickerButton(fhsize, horbtn)
825 data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
827 selector = self.create_selector(data, True)
828 button1.set_selector(selector)
829 button1.set_title("Your shares")
830 button1.set_value("0")
831 dlg.vbox.pack_start(button1, False, False, 0)
833 entry1 = hildon.Entry(fhsize)
834 entry1.set_placeholder("Name")
835 dlg.vbox.pack_start(entry1, False, False, 0)
837 entry2 = hildon.Entry(fhsize)
838 entry2.set_placeholder("Yahoo Finance symbol")
839 dlg.vbox.pack_start(entry2, False, False, 0)
841 button = hildon.Button(fhsize, horbtn)
842 button.set_label("Add")
843 button.connect("clicked", self.add_item, dlg, button1, entry1, entry2)
844 dlg.vbox.pack_start(button, False, False, 0)
851 def add_item(self, widget, dlg, button, entry1, entry2):
852 symbol = entry2.get_text()
853 name = entry1.get_text()
854 shares = button.get_value()
856 if name == '' or symbol == '':
857 self.show_info_banner(widget, "Must add the name and symbol")
860 self.add_to_portfolio(widget, button, symbol, name)
863 niter = self.portfolio_model.append()
864 self.portfolio_model.set(niter, 0, symbol, 1, name, 2, shares, 3, "-")
866 def remove_item(self, widget):
867 win = hildon.StackableWindow()
869 toolbar = hildon.EditToolbar("Choose items to delete", "Delete")
870 win.set_edit_toolbar(toolbar)
873 parea = hildon.PannableArea()
874 tv = hildon.GtkTreeView(ui_edit)
875 selection = tv.get_selection()
876 selection.set_mode(gtk.SELECTION_MULTIPLE)
877 tv.set_model(self.portfolio_model)
878 self._tv_remove_portfolio_columns(tv)
881 toolbar.connect("button-clicked", self.delete_from_portfolio, win, tv,
883 toolbar.connect_object("arrow-clicked", gtk.Window.destroy, win)
885 vbox.pack_start(parea, True, True, 0)
889 def delete_from_portfolio(self, widget, win, tv, selection):
890 if not self.is_treeview_selected(tv):
893 conf = self.show_confirmation(win, "Delete items?")
897 selmodel, selected = selection.get_selected_rows()
898 iters = [selmodel.get_iter(path) for path in selected]
900 symbol = selmodel.get_value(i, 0)
901 settings.delete_item_from_portfolio(settingsdb, symbol)
904 logger.exception("Deleting item from portfolio")
905 self.info_banner(widget, "Error deleting item")
907 def _tv_remove_portfolio_columns(self, treeview):
908 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
909 column.set_visible(False)
910 treeview.append_column(column)
912 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
913 column.set_property("expand", True)
914 treeview.append_column(column)
916 column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
917 column.set_visible(False)
918 treeview.append_column(column)
920 column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
921 column.set_visible(False)
922 treeview.append_column(column)
924 def refresh_portfolio(self, widget, tv, win):
925 data = settings.load_portfolio(settingsdb)
928 thread.start_new_thread(self._do_refresh_portfolio, (data, tv, win))
930 def _do_refresh_portfolio(self, data, tv, win):
932 item[3] = self.get_price(item[0])
934 self.portfolio_model = self._create_portfolio_model(data)
935 tv.set_model(self.portfolio_model)
938 def get_price(self, symbol):
939 from ystockquote import ystockquote as yt
941 price = yt.get_price(symbol)
944 logger.exception("Getting price from Yahoo: %s" % symbol)
947 def _create_portfolio_model(self, data):
948 lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
949 gobject.TYPE_STRING, gobject.TYPE_STRING)
951 iter = lstore.append()
952 lstore.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3])
955 def _tv_portfolio_columns(self, treeview):
956 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
957 column.set_visible(False)
958 treeview.append_column(column)
960 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
961 column.set_property("expand", True)
962 treeview.append_column(column)
964 column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
965 treeview.append_column(column)
967 column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
968 treeview.append_column(column)
970 def show_confirmation(self, window, msg):
971 dialog = hildon.hildon_note_new_confirmation(window, msg)
973 result = dialog.run()
974 if result == gtk.RESPONSE_OK:
981 def show_information_note(self, window, msg):
982 dialog = hildon.hildon_note_new_information(window, msg)
984 result = dialog.run()
987 def show_info_banner(self, widget, msg):
988 hildon.hildon_banner_show_information(widget, 'qgn_note_infoprint', msg)
990 def is_treeview_selected(self, treeview):
991 selection = treeview.get_selection()
992 if selection.count_selected_rows() == 0:
993 self.show_info_banner(treeview, 'No selected item')
1000 def __init__(self, widget):
1001 self.abdialog = gtk.Dialog(title='About', parent=None, flags=0)
1002 self.abdialog.set_has_separator(False)
1003 self.abdialog.set_size_request(-1, 400)
1005 self.info_lb = gtk.Label()
1006 self.info_lb.set_line_wrap(True)
1012 button = hildon.Button(fhsize, horbtn)
1013 button.set_title('Description')
1014 button.connect("clicked", self.show_info, 'description')
1015 hbox1.pack_start(button, True, True, 0)
1017 button = hildon.Button(fhsize, horbtn)
1018 button.set_title('Credits')
1019 button.connect("clicked", self.show_info, 'credits')
1020 hbox1.pack_start(button, True, True, 0)
1022 button = hildon.Button(fhsize, horbtn)
1023 button.set_title('License')
1024 button.connect("clicked", self.show_info, 'license')
1025 hbox1.pack_start(button, True, True, 0)
1027 button = hildon.Button(fhsize, horbtn)
1028 button.set_title('Donate')
1029 button.connect("clicked", self.show_info, 'donate')
1030 hbox1.pack_start(button, True, True, 0)
1032 button = hildon.Button(fhsize, horbtn)
1033 button.set_title('Report ')
1034 button.connect("clicked", self.show_info, 'report')
1035 hbox1.pack_start(button, True, True, 0)
1037 button = hildon.Button(fhsize, horbtn)
1038 button.set_title(' Rate ')
1039 button.connect("clicked", self.show_info, 'vote')
1040 hbox1.pack_start(button, True, True, 0)
1042 self.action_btn = hildon.Button(fhsize, horbtn)
1043 self.image = gtk.Image()
1045 self.show_info(None, 'description')
1047 self.abdialog.vbox.pack_start(self.action_btn, False, False, 0)
1048 self.abdialog.vbox.pack_start(self.image, False, False, 5)
1049 self.abdialog.vbox.pack_start(self.info_lb, True, True, 0)
1050 self.abdialog.vbox.pack_start(hbox1, False, False, 0)
1052 self.abdialog.show_all()
1053 self.action_btn.hide()
1056 self.abdialog.destroy()
1058 def do_action(self, widget, action):
1061 self.abdialog.destroy()
1063 bus = dbus.SystemBus()
1064 proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
1065 iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
1067 if action == "donate":
1068 url = "http://stockthis.garage.maemo.org/donate.html"
1069 elif action == "report":
1070 url = "http://stockthis.garage.maemo.org/reporting.html"
1071 elif action == "vote":
1072 url = "http://maemo.org/downloads/product/Maemo5/stockthis"
1074 iface.open_new_window(url)
1076 def show_info(self, widget, kind):
1077 if kind == 'license':
1078 self.action_btn.hide()
1080 info = """<small><b>StockThis</b> is free software. It's using a GPL version 2 license or at your election any later version.
1082 Logo by Daniel Martin Yerga.
1084 elif kind == 'credits':
1085 self.action_btn.hide()
1087 info = """<small><b>Written by</b> Daniel Martin Yerga (dyerga@gmail.com)
1089 <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>"""
1090 elif kind == 'description':
1091 self.action_btn.hide()
1093 info = """<b><big>StockThis 0.3</big></b>
1095 <i>StockThis is a stocks application for Maemo</i>
1098 stockthis.garage.maemo.org"""
1100 elif kind == 'donate':
1101 self.action_btn.show()
1103 self.action_btn.set_title('Make donation')
1105 self.action_btn.disconnect(self.id)
1106 self.id = self.action_btn.connect("clicked", self.do_action, "donate")
1107 info = """<small><b>StockThis</b> is a free (and gratis) software application.
1108 Developing good software takes time and hard work.
1110 <b>StockThis's author</b> develops the program in his spare time.
1111 If you like the program and it's helpful, consider donating a small amount of money.
1112 Donations are a great incentive and help the developer feel that the hard work is appreciated.</small>
1114 elif kind == 'report':
1115 self.action_btn.show()
1117 self.action_btn.set_title('Report bug')
1119 self.action_btn.disconnect(self.id)
1120 self.id = self.action_btn.connect("clicked", self.do_action, "report")
1121 info = """<small>StockThis is being improved thanks to bug reports that users have submitted. The author appreciates these reports.
1122 If the application is raising an error when you're using it, you have two choices to report this error:
1123 1) Send the log from the application menu (if there's an error in the log).
1124 2) Press the button and write a bug report with as much information as possible.</small>"""
1125 elif kind == 'vote':
1126 self.action_btn.show()
1128 self.image.set_from_file(imgdir + "maemoorg.png")
1129 self.action_btn.set_title('Rate StockThis')
1131 self.action_btn.disconnect(self.id)
1132 self.id = self.action_btn.connect("clicked", self.do_action, "vote")
1133 info = """<small>The downloads section in maemo.org has a nice system where you can rate applications.
1134 If you consider StockThis a good application (or a bad one too), you could rate it in maemo.org site.</small>"""
1136 self.info_lb.set_markup(info)
1141 def __init__(self, widget, logfile):
1143 dialog = gtk.Dialog(title='Log', parent=None)
1145 dialog.set_size_request(600, 350)
1147 parea = hildon.PannableArea()
1148 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
1150 textview = hildon.TextView()
1151 textview.set_property("editable", False)
1152 textview.set_property("wrap-mode", gtk.WRAP_WORD)
1154 log = open(logfile, 'r')
1155 logtext = log.read()
1158 textview.get_buffer().set_text(logtext)
1161 dialog.vbox.pack_start(parea, True, True, 0)
1165 save_btn = hildon.Button(fhsize, horbtn)
1166 save_btn.set_title("Save")
1167 save_btn.connect('clicked', self.save, logfile, dialog)
1169 clear_btn = hildon.Button(fhsize, horbtn)
1170 clear_btn.set_title("Clear")
1171 clear_btn.connect('clicked', self.clear, textview, logfile)
1173 send_btn = hildon.Button(fhsize, horbtn)
1174 send_btn.set_title('Send')
1175 send_btn.connect('clicked', self.send, dialog, logfile)
1177 hbox.pack_start(save_btn, True, True, 0)
1178 hbox.pack_start(clear_btn, True, True, 0)
1179 hbox.pack_start(send_btn, True, True, 0)
1181 dialog.vbox.pack_start(hbox, False, False, 0)
1187 def show_filechooser(self, window, title, name, EXT):
1188 action = gtk.FILE_CHOOSER_ACTION_SAVE
1190 m = hildon.FileSystemModel()
1191 file_dialog = hildon.FileChooserDialog(window, action, m)
1192 file_dialog.set_title(title)
1194 file_dialog.set_current_name(name)
1195 HOME = os.path.expanduser("~")
1197 if os.path.exists(HOME + '/MyDocs/.documents'):
1198 file_dialog.set_current_folder(HOME + '/MyDocs/.documents')
1200 file_dialog.set_current_folder(HOME)
1202 file_dialog.set_default_response(gtk.RESPONSE_CANCEL)
1204 result = file_dialog.run()
1205 if result == gtk.RESPONSE_OK:
1206 namefile = file_dialog.get_filename()
1207 namefile, extension = os.path.splitext(namefile)
1208 namefile = namefile + "." + EXT
1211 file_dialog.destroy()
1216 def clear(self, widget, textview, logfile):
1217 textview.get_buffer().set_text('')
1218 f = open(logfile, 'w')
1221 def save(self, widget, logfile, dlg):
1223 filename = self.show_filechooser(dlg, "Save log file",
1224 "stockthis-log", "txt")
1230 shutil.copyfile(logfile, filename)
1231 stockspy.show_info_banner(widget, 'Log file saved')
1233 logger.exception("Saving log file")
1234 stockspy.show_info_banner(widget, 'Error saving the log file')
1236 def send(self, widget, dlg, logfile):
1237 sendtxt = ("You are going to send the log to the developers.\n"
1238 "This helps the developers to track problems with the application.\n"
1239 "It doesn't send any personal information (like passwords or similar).")
1241 dialog = hildon.hildon_note_new_confirmation(dlg, sendtxt)
1242 dialog.set_button_texts("Send", "Cancel")
1244 response = dialog.run()
1245 if response == gtk.RESPONSE_OK:
1246 self.do_pre_send(dlg, logfile)
1250 def do_pre_send(self, dlg, logfile):
1252 hildon.hildon_gtk_window_set_progress_indicator(dlg, 1)
1253 thread.start_new_thread(self._do_send, (dlg, logfile))
1255 def _do_send(self, dlg, logfile):
1256 import pycurl, shutil, random, commands
1259 for i in random.sample('abcdefghijkl123456789', 18):
1262 rnamepath = HOME + "/.stockthis/" + rname
1263 shutil.copyfile(logfile, rnamepath)
1265 gtkversion = "%s.%s.%s" % gtk.ver
1266 if os.path.exists("/etc/maemo_version"):
1267 mfile = open("/etc/maemo_version", 'r')
1268 maemoversion = mfile.read()
1273 opsystem = ' '.join(os.uname())
1274 pyversion = os.sys.version
1276 comm = ("awk '/Private_Dirty/{sum+=$2}END{print sum \"kB\"}'"
1277 " /proc/%s/smaps") % pid
1278 status, dirtymem = commands.getstatusoutput(comm)
1280 lfile = open(rnamepath, 'r')
1285 log = ("%s\nPython version: %s\nGtk version: %s\n"
1286 "Maemo version: %sOperating system: %s\n"
1287 "Dirty Memory: %s\nLog:\n%s") % (_version, pyversion, gtkversion,
1288 maemoversion, opsystem, dirtymem, log)
1290 lfile = open(rnamepath, 'w')
1294 url = "http://yerga.net/logs/uploader.php"
1295 data = [('uploadedfile', (pycurl.FORM_FILE, rnamepath)),]
1296 mycurl = pycurl.Curl()
1297 mycurl.setopt(pycurl.URL, url)
1298 mycurl.setopt(pycurl.HTTPPOST, data)
1302 os.remove(rnamepath)
1304 gtk.gdk.threads_enter()
1305 stockspy.show_info_banner(dlg, 'Log sent')
1306 gtk.gdk.threads_leave()
1307 hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1309 logger.exception("Sending log file")
1310 gtk.gdk.threads_enter()
1311 stockspy.show_info_banner(dlg, 'Error sending the log file')
1312 gtk.gdk.threads_leave()
1313 hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1316 if __name__ == "__main__":
1317 stockspy = StocksPy()
1318 gtk.gdk.threads_enter()
1320 gtk.gdk.threads_leave()