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 beta1 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 self.add_to_portfolio(widget, button, symbol, name)
859 niter = self.portfolio_model.append()
860 self.portfolio_model.set(niter, 0, symbol, 1, name, 2, shares, 3, "-")
862 def remove_item(self, widget):
863 win = hildon.StackableWindow()
865 toolbar = hildon.EditToolbar("Choose items to delete", "Delete")
866 win.set_edit_toolbar(toolbar)
869 parea = hildon.PannableArea()
870 tv = hildon.GtkTreeView(ui_edit)
871 selection = tv.get_selection()
872 selection.set_mode(gtk.SELECTION_MULTIPLE)
873 tv.set_model(self.portfolio_model)
874 self._tv_remove_portfolio_columns(tv)
877 toolbar.connect("button-clicked", self.delete_from_portfolio, win, tv,
879 toolbar.connect_object("arrow-clicked", gtk.Window.destroy, win)
881 vbox.pack_start(parea, True, True, 0)
885 def delete_from_portfolio(self, widget, win, tv, selection):
886 if not self.is_treeview_selected(tv):
889 conf = self.show_confirmation(win, "Delete items?")
893 selmodel, selected = selection.get_selected_rows()
894 iters = [selmodel.get_iter(path) for path in selected]
896 symbol = selmodel.get_value(i, 0)
897 settings.delete_item_from_portfolio(settingsdb, symbol)
900 logger.exception("Deleting item from portfolio")
901 self.info_banner(widget, "Error deleting item")
903 def _tv_remove_portfolio_columns(self, treeview):
904 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
905 column.set_visible(False)
906 treeview.append_column(column)
908 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
909 column.set_property("expand", True)
910 treeview.append_column(column)
912 column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
913 column.set_visible(False)
914 treeview.append_column(column)
916 column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
917 column.set_visible(False)
918 treeview.append_column(column)
920 def refresh_portfolio(self, widget, tv, win):
921 data = settings.load_portfolio(settingsdb)
924 thread.start_new_thread(self._do_refresh_portfolio, (data, tv, win))
926 def _do_refresh_portfolio(self, data, tv, win):
928 item[3] = self.get_price(item[0])
930 self.portfolio_model = self._create_portfolio_model(data)
931 tv.set_model(self.portfolio_model)
934 def get_price(self, symbol):
935 from ystockquote import ystockquote as yt
937 price = yt.get_price(symbol)
940 logger.exception("Getting price from Yahoo: %s" % symbol)
943 def _create_portfolio_model(self, data):
944 lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
945 gobject.TYPE_STRING, gobject.TYPE_STRING)
947 iter = lstore.append()
948 lstore.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3])
951 def _tv_portfolio_columns(self, treeview):
952 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
953 column.set_visible(False)
954 treeview.append_column(column)
956 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
957 column.set_property("expand", True)
958 treeview.append_column(column)
960 column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
961 treeview.append_column(column)
963 column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
964 treeview.append_column(column)
966 def show_confirmation(self, window, msg):
967 dialog = hildon.hildon_note_new_confirmation(window, msg)
969 result = dialog.run()
970 if result == gtk.RESPONSE_OK:
977 def show_information_note(self, window, msg):
978 dialog = hildon.hildon_note_new_information(window, msg)
980 result = dialog.run()
983 def show_info_banner(self, widget, msg):
984 hildon.hildon_banner_show_information(widget, 'qgn_note_infoprint', msg)
986 def is_treeview_selected(self, treeview):
987 selection = treeview.get_selection()
988 if selection.count_selected_rows() == 0:
989 self.show_info_banner(treeview, 'No selected item')
996 def __init__(self, widget):
997 self.abdialog = gtk.Dialog(title='About', parent=None, flags=0)
998 self.abdialog.set_has_separator(False)
999 self.abdialog.set_size_request(-1, 400)
1001 self.info_lb = gtk.Label()
1002 self.info_lb.set_line_wrap(True)
1008 button = hildon.Button(fhsize, horbtn)
1009 button.set_title('Description')
1010 button.connect("clicked", self.show_info, 'description')
1011 hbox1.pack_start(button, True, True, 0)
1013 button = hildon.Button(fhsize, horbtn)
1014 button.set_title('Credits')
1015 button.connect("clicked", self.show_info, 'credits')
1016 hbox1.pack_start(button, True, True, 0)
1018 button = hildon.Button(fhsize, horbtn)
1019 button.set_title('License')
1020 button.connect("clicked", self.show_info, 'license')
1021 hbox1.pack_start(button, True, True, 0)
1023 button = hildon.Button(fhsize, horbtn)
1024 button.set_title('Donate')
1025 button.connect("clicked", self.show_info, 'donate')
1026 hbox1.pack_start(button, True, True, 0)
1028 button = hildon.Button(fhsize, horbtn)
1029 button.set_title('Report ')
1030 button.connect("clicked", self.show_info, 'report')
1031 hbox1.pack_start(button, True, True, 0)
1033 button = hildon.Button(fhsize, horbtn)
1034 button.set_title(' Rate ')
1035 button.connect("clicked", self.show_info, 'vote')
1036 hbox1.pack_start(button, True, True, 0)
1038 self.action_btn = hildon.Button(fhsize, horbtn)
1039 self.image = gtk.Image()
1041 self.show_info(None, 'description')
1043 self.abdialog.vbox.pack_start(self.action_btn, False, False, 0)
1044 self.abdialog.vbox.pack_start(self.image, False, False, 5)
1045 self.abdialog.vbox.pack_start(self.info_lb, True, True, 0)
1046 self.abdialog.vbox.pack_start(hbox1, False, False, 0)
1048 self.abdialog.show_all()
1049 self.action_btn.hide()
1052 self.abdialog.destroy()
1054 def do_action(self, widget, action):
1057 self.abdialog.destroy()
1059 bus = dbus.SystemBus()
1060 proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
1061 iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
1063 if action == "donate":
1064 url = "http://stockthis.garage.maemo.org/donate.html"
1065 elif action == "report":
1066 url = "http://stockthis.garage.maemo.org/reporting.html"
1067 elif action == "vote":
1068 url = "http://maemo.org/downloads/product/stockthis"
1070 iface.open_new_window(url)
1072 def show_info(self, widget, kind):
1073 if kind == 'license':
1074 self.action_btn.hide()
1076 info = """<small><b>StockThis</b> is free software. It's using a GPL version 2 license or at your election any later version.
1078 Logo by Daniel Martin Yerga.
1080 elif kind == 'credits':
1081 self.action_btn.hide()
1083 info = """<small><b>Written by</b> Daniel Martin Yerga (dyerga@gmail.com)
1085 <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>"""
1086 elif kind == 'description':
1087 self.action_btn.hide()
1089 info = """<b><big>StockThis 0.3</big></b>
1091 <i>StockThis is a stocks application for Maemo</i>
1094 stockthis.garage.maemo.org"""
1096 elif kind == 'donate':
1097 self.action_btn.show()
1099 self.action_btn.set_title('Make donation')
1101 self.action_btn.disconnect(self.id)
1102 self.id = self.action_btn.connect("clicked", self.do_action, "donate")
1103 info = """<small><b>StockThis</b> is a free (and gratis) software application.
1104 Developing good software takes time and hard work.
1106 <b>StockThis's author</b> develops the program in his spare time.
1107 If you like the program and it's helpful, consider donating a small amount of money.
1108 Donations are a great incentive and help the developer feel that the hard work is appreciated.</small>
1110 elif kind == 'report':
1111 self.action_btn.show()
1113 self.action_btn.set_title('Report bug')
1115 self.action_btn.disconnect(self.id)
1116 self.id = self.action_btn.connect("clicked", self.do_action, "report")
1117 info = """<small>StockThis is being improved thanks to bug reports that users have submitted. The author appreciates these reports.
1118 If the application is raising an error when you're using it, you have two choices to report this error:
1119 1) Send the log from the application menu (if there's an error in the log).
1120 2) Press the button and write a bug report with as much information as possible.</small>"""
1121 elif kind == 'vote':
1122 self.action_btn.show()
1124 self.image.set_from_file(imgdir + "maemoorg.png")
1125 self.action_btn.set_title('Rate StockThis')
1127 self.action_btn.disconnect(self.id)
1128 self.id = self.action_btn.connect("clicked", self.do_action, "vote")
1129 info = """<small>The downloads section in maemo.org has a nice system where you can rate applications.
1130 If you consider StockThis a good application (or a bad one too), you could rate it in maemo.org site.</small>"""
1132 self.info_lb.set_markup(info)
1137 def __init__(self, widget, logfile):
1139 dialog = gtk.Dialog(title='Log', parent=None)
1141 dialog.set_size_request(600, 350)
1143 parea = hildon.PannableArea()
1144 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
1146 textview = hildon.TextView()
1147 textview.set_property("editable", False)
1148 textview.set_property("wrap-mode", gtk.WRAP_WORD)
1150 log = open(logfile, 'r')
1151 logtext = log.read()
1154 textview.get_buffer().set_text(logtext)
1157 dialog.vbox.pack_start(parea, True, True, 0)
1161 save_btn = hildon.Button(fhsize, horbtn)
1162 save_btn.set_title("Save")
1163 save_btn.connect('clicked', self.save, logfile, dialog)
1165 clear_btn = hildon.Button(fhsize, horbtn)
1166 clear_btn.set_title("Clear")
1167 clear_btn.connect('clicked', self.clear, textview, logfile)
1169 send_btn = hildon.Button(fhsize, horbtn)
1170 send_btn.set_title('Send')
1171 send_btn.connect('clicked', self.send, dialog, logfile)
1173 hbox.pack_start(save_btn, True, True, 0)
1174 hbox.pack_start(clear_btn, True, True, 0)
1175 hbox.pack_start(send_btn, True, True, 0)
1177 dialog.vbox.pack_start(hbox, False, False, 0)
1183 def show_filechooser(self, window, title, name, EXT):
1184 action = gtk.FILE_CHOOSER_ACTION_SAVE
1186 m = hildon.FileSystemModel()
1187 file_dialog = hildon.FileChooserDialog(window, action, m)
1188 file_dialog.set_title(title)
1190 file_dialog.set_current_name(name)
1191 HOME = os.path.expanduser("~")
1193 if os.path.exists(HOME + '/MyDocs/.documents'):
1194 file_dialog.set_current_folder(HOME + '/MyDocs/.documents')
1196 file_dialog.set_current_folder(HOME)
1198 file_dialog.set_default_response(gtk.RESPONSE_CANCEL)
1200 result = file_dialog.run()
1201 if result == gtk.RESPONSE_OK:
1202 namefile = file_dialog.get_filename()
1203 namefile, extension = os.path.splitext(namefile)
1204 namefile = namefile + "." + EXT
1207 file_dialog.destroy()
1212 def clear(self, widget, textview, logfile):
1213 textview.get_buffer().set_text('')
1214 f = open(logfile, 'w')
1217 def save(self, widget, logfile, dlg):
1219 filename = self.show_filechooser(dlg, "Save log file",
1220 "stockthis-log", "txt")
1226 shutil.copyfile(logfile, filename)
1227 stockspy.show_info_banner(widget, 'Log file saved')
1229 logger.exception("Saving log file")
1230 stockspy.show_info_banner(widget, 'Error saving the log file')
1232 def send(self, widget, dlg, logfile):
1233 sendtxt = ("You are going to send the log to the developers.\n"
1234 "This helps the developers to track problems with the application.\n"
1235 "It doesn't send any personal information (like passwords or similar).")
1237 dialog = hildon.hildon_note_new_confirmation(dlg, sendtxt)
1238 dialog.set_button_texts("Send", "Cancel")
1240 response = dialog.run()
1241 if response == gtk.RESPONSE_OK:
1242 self.do_pre_send(dlg, logfile)
1246 def do_pre_send(self, dlg, logfile):
1248 hildon.hildon_gtk_window_set_progress_indicator(dlg, 1)
1249 thread.start_new_thread(self._do_send, (dlg, logfile))
1251 def _do_send(self, dlg, logfile):
1252 import pycurl, shutil, random, commands
1255 for i in random.sample('abcdefghijkl123456789', 18):
1258 rnamepath = HOME + "/.stockthis/" + rname
1259 shutil.copyfile(logfile, rnamepath)
1261 gtkversion = "%s.%s.%s" % gtk.ver
1262 if os.path.exists("/etc/maemo_version"):
1263 mfile = open("/etc/maemo_version", 'r')
1264 maemoversion = mfile.read()
1269 opsystem = ' '.join(os.uname())
1270 pyversion = os.sys.version
1272 comm = ("awk '/Private_Dirty/{sum+=$2}END{print sum \"kB\"}'"
1273 " /proc/%s/smaps") % pid
1274 status, dirtymem = commands.getstatusoutput(comm)
1276 lfile = open(rnamepath, 'r')
1281 log = ("%s\nPython version: %s\nGtk version: %s\n"
1282 "Maemo version: %sOperating system: %s\n"
1283 "Dirty Memory: %s\nLog:\n%s") % (_version, pyversion, gtkversion,
1284 maemoversion, opsystem, dirtymem, log)
1286 lfile = open(rnamepath, 'w')
1290 url = "http://yerga.net/logs/uploader.php"
1291 data = [('uploadedfile', (pycurl.FORM_FILE, rnamepath)),]
1292 mycurl = pycurl.Curl()
1293 mycurl.setopt(pycurl.URL, url)
1294 mycurl.setopt(pycurl.HTTPPOST, data)
1298 os.remove(rnamepath)
1300 gtk.gdk.threads_enter()
1301 stockspy.show_info_banner(dlg, 'Log sent')
1302 gtk.gdk.threads_leave()
1303 hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1305 logger.exception("Sending log file")
1306 gtk.gdk.threads_enter()
1307 stockspy.show_info_banner(dlg, 'Error sending the log file')
1308 gtk.gdk.threads_leave()
1309 hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1312 if __name__ == "__main__":
1313 stockspy = StocksPy()
1314 gtk.gdk.threads_enter()
1316 gtk.gdk.threads_leave()