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 '/usr/share' in runningpath:
42 runninglocally = False
46 HOME = os.path.expanduser("~")
48 settingsdb, imgdir, configdir, logfile = \
49 settings.define_paths(runninglocally, HOME)
52 logger = logging.getLogger('st')
53 logging.basicConfig(filename=logfile,level=logging.ERROR, filemode='w')
58 #set the main logger to DEBUG
59 logger.setLevel(logging.DEBUG)
61 #Create a handler for console debug
62 console = logging.StreamHandler()
63 console.setLevel(logging.DEBUG)
64 # set a format which is simpler for console use
65 formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
66 # tell the handler to use this format
67 console.setFormatter(formatter)
68 logging.getLogger('').addHandler(console)
70 fhsize = gtk.HILDON_SIZE_FINGER_HEIGHT
71 horbtn = hildon.BUTTON_ARRANGEMENT_HORIZONTAL
72 ui_normal = gtk.HILDON_UI_MODE_NORMAL
73 ui_edit = gtk.HILDON_UI_MODE_EDIT
74 winprogind = hildon.hildon_gtk_window_set_progress_indicator
79 gtk.gdk.threads_init()
84 self.program = hildon.Program()
85 self.program.__init__()
86 gtk.set_application_name("StockThis")
87 self.window = hildon.StackableWindow()
88 self.window.set_default_size(800, 480)
89 self.program.add_window(self.window)
90 self.window.connect("destroy", gtk.main_quit)
92 FremantleRotation('StockThis', None, "0.3", 0)
94 self.create_menu(self.window)
97 toolbar = self.main_toolbar(False, False, None, '', '', True)
99 parea = hildon.PannableArea()
100 tv = hildon.GtkTreeView(ui_normal)
101 inmodel = self.__create_model(marketdata.main, marketdata.idmain)
102 tv.connect("row-activated", self.show_instrument_view, inmodel,
103 marketdata.localmarkets, marketdata.localids,
105 tv.set_model(inmodel)
109 vbox.pack_start(parea, True, True, 0)
110 vbox.pack_start(gtk.HSeparator(), False, False, 5)
111 vbox.pack_start(toolbar, False, False, 0)
113 self.window.add(vbox)
114 self.window.show_all()
116 def create_menu(self, window):
117 menu = hildon.AppMenu()
118 window.set_app_menu(menu)
119 button = gtk.Button("About")
120 button.connect("clicked", About)
122 button = gtk.Button("Log")
123 button.connect("clicked", Log, logfile)
127 def show_instrument_view(self, widget, path, column, inmodel, names,
129 market = inmodel[path][0]
130 names = names[mindex.index(market)]
131 ids = ids[mindex.index(market)]
133 window = hildon.StackableWindow()
134 self.create_menu(window)
135 window.set_title("StockThis - " + inmodel[path][1])
138 toolbar = self.main_toolbar(False, False, None, '', '', False)
140 parea = hildon.PannableArea()
141 parea.connect("horizontal-movement", self.horizontal_mov)
142 tv = hildon.GtkTreeView(ui_normal)
143 model = self.__create_model(names, ids)
144 tv.connect("row-activated", self.show_quotes_view, model, False)
149 vbox.pack_start(parea, True, True, 0)
150 vbox.pack_start(gtk.HSeparator(), False, False, 5)
151 vbox.pack_start(toolbar, False, False, 0)
156 def horizontal_mov(self, parea, direction, initial_x, initial_y):
157 #direction = 2 right-to-left
158 #direction = 3 lefto-to-right
160 vadj = parea.get_vadjustment()
161 val = vadj.get_value()
164 if int(val)-2500 < 0:
165 parea.scroll_to(-1, 0)
167 parea.scroll_to(-1, int(val)-2500)
169 parea.scroll_to(-1, int(val)+3500)
173 def show_quotes_view(self, widget, path, column, model, portfolio):
174 quote = model[path][0], model[path][1]
175 #print "quote:", quote[0]
176 #('EURUSD=X', 'EUR/USD')
178 #Currencies and ETFs should show the list now -> view = True
179 #Other items show a new list with options
181 for i in marketdata.localids[(len(marketdata.localids)-2):]:
188 if quote[0] in marketdata.idindexes:
189 self.show_instrument_view(widget, path, column, model,
190 marketdata.wnamesindexes,
191 marketdata.widsindexes,
192 marketdata.idindexes)
194 if quote[0] in marketdata.idotmarkets:
195 self.show_instrument_view(widget, path, column, model,
197 marketdata.omsymbols,
198 marketdata.idotmarkets)
200 if quote[0] in marketdata.ideumarkets:
201 self.show_instrument_view(widget, path, column, model,
203 marketdata.eusymbols,
204 marketdata.ideumarkets)
206 if quote[0] in marketdata.idusmarkets:
207 self.show_instrument_view(widget, path, column, model,
209 marketdata.ussymbols,
210 marketdata.idusmarkets)
214 win = hildon.StackableWindow()
215 self.create_menu(win)
216 win.set_title("StockThis - Quotes View - " + quote[1])
220 ltitle = gtk.Label('')
221 ltitle.set_markup('<b><big>' + quote[1].replace('&', '') +
223 color = gtk.gdk.color_parse("#03A5FF")
224 ltitle.modify_fg(gtk.STATE_NORMAL, color)
226 parea = hildon.PannableArea()
227 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
232 label = gtk.Label('')
233 label.set_markup('<b><big>%39s:</big></b>' % '<u>Price</u>')
234 lprice = gtk.Label('')
235 hbox.pack_start(label, False, False, 0)
236 #if self.is_portrait():
237 hbox.pack_start(lprice, False, False, 25)
239 # hbox.pack_start(lprice, False, False, 245)
241 vbox1.pack_start(hbox, True, True, 0)
244 label = gtk.Label('')
245 label.set_markup('<b><big>%35s:</big></b>' % '<u>Change</u>')
246 lchange = gtk.Label('')
247 lpercent = gtk.Label('')
248 hbox.pack_start(label, False, False, 0)
249 #if self.is_portrait():
250 hbox.pack_start(lchange, False, False, 25)
251 hbox.pack_start(lpercent, False, False, 0)
253 # hbox.pack_start(lchange, False, False, 205)
254 # hbox.pack_start(lpercent, False, False, 0)
256 vbox1.pack_start(hbox, True, True, 0)
259 label = gtk.Label('')
260 label.set_markup('<b><big>%35s:</big></b>' % '<u>Volume</u>')
261 lvolume = gtk.Label('')
262 hbox.pack_start(label, False, False, 0)
263 #if self.is_portrait():
264 hbox.pack_start(lvolume, False, False, 25)
266 # hbox.pack_start(lvolume, False, False, 207)
268 vbox1.pack_start(hbox, True, True, 0)
271 label = gtk.Label('')
272 label.set_markup('<b><big>%30s:</big></b>' % '<u>52 week high</u>')
273 l52whigh = gtk.Label('')
274 hbox.pack_start(label, False, False, 0)
275 #if self.is_portrait():
276 hbox.pack_start(l52whigh, False, False, 25)
278 # hbox.pack_start(l52whigh, False, False, 125)
280 vbox1.pack_start(hbox, True, True, 0)
283 label = gtk.Label('')
284 label.set_markup('<b><big>%30s:</big></b>' % '<u>52 week low</u>')
285 l52wlow = gtk.Label('')
286 hbox.pack_start(label, False, False, 0)
287 #if self.is_portrait():
288 hbox.pack_start(l52wlow, False, False, 26)
290 # hbox.pack_start(l52wlow, False, False, 140)
291 vbox1.pack_start(hbox, True, True, 0)
293 #if self.is_portrait():
297 button1 = hildon.PickerButton(fhsize, horbtn)
298 data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
300 selector = self.create_selector(data, True)
301 button1.set_selector(selector)
302 button1.set_title("Your shares")
303 shares = self.get_shares_from_symbol(quote[0])
304 button1.set_value(shares)
305 hbox.pack_start(button1, True, True, 0)
307 button = hildon.Button(fhsize, horbtn)
308 button.set_title("Add to Portfolio")
309 button.connect("clicked", self.add_to_portfolio, button1, quote[0], quote[1])
310 hbox.pack_start(button, True, True, 0)
313 label = gtk.Label('')
314 label.set_markup('<b><big>%37s:</big></b>' % '<u>Shares</u>')
315 lshares = gtk.Label(shares)
316 hbox1.pack_start(label, False, False, 0)
317 #if self.is_portrait():
318 hbox1.pack_start(lshares, False, False, 25)
320 # hbox1.pack_start(lshares, False, False, 220)
323 label = gtk.Label('')
324 label.set_markup('<b><big>%29s:</big></b>' % '<u>Holdings Value</u>')
325 holdingsvalue = gtk.Label("")
326 hbox2.pack_start(label, False, False, 0)
327 #if self.is_portrait():
328 hbox2.pack_start(holdingsvalue, False, False, 25)
330 # hbox2.pack_start(holdingsvalue, False, False, 105)
333 label = gtk.Label('')
334 label.set_markup("<b><big>%25s:</big></b>" % "<u>Day's Value Change</u>")
335 dayvaluechange = gtk.Label("")
336 hbox3.pack_start(label, False, False, 0)
337 #if self.is_portrait():
338 hbox3.pack_start(dayvaluechange, False, False, 25)
340 # hbox3.pack_start(dayvaluechange, False, False, 45)
343 vbox1.pack_start(hbox, False, False, 0)
345 vbox1.pack_start(hbox1, True, True, 0)
346 vbox1.pack_start(hbox2, True, True, 0)
347 vbox1.pack_start(hbox3, True, True, 0)
349 parea.add_with_viewport(vbox1)
351 widgets = [win, ltitle, lprice, lchange, lpercent, lvolume, l52whigh,
352 l52wlow, lshares, holdingsvalue, dayvaluechange]
354 toolbar = self.main_toolbar(True, portfolio, widgets, quote[0], quote[1], False)
356 vbox.pack_start(ltitle, False, False, 0)
357 vbox.pack_start(gtk.HSeparator(), False, False, 0)
358 vbox.pack_start(parea, True, True, 0)
359 vbox.pack_start(gtk.HSeparator(), False, False, 5)
360 vbox.pack_start(toolbar, False, False, 0)
365 self.show_data(quote[0], widgets, shares)
367 def is_portrait(self):
368 width = gtk.gdk.screen_width()
369 height = gtk.gdk.screen_height()
375 def get_shares_from_symbol(self, symbol):
378 portfolio_data = settings.load_portfolio(settingsdb)
379 for item in portfolio_data :
384 logger.exception("Getting shares from symbol")
387 def add_to_portfolio(self, widget, button, symbol, name):
388 shares = button.get_value()
391 portfolio = settings.load_portfolio(settingsdb)
393 for item in portfolio:
395 index = portfolio.index(item)
397 item = [symbol, name, shares, '-']
400 settings.insert_new_item_to_portfolio(settingsdb, item)
402 settings.delete_item_from_portfolio(settingsdb, symbol)
403 settings.insert_new_item_to_portfolio(settingsdb, item)
405 self.show_info_banner(widget, "Added to portfolio")
407 logger.exception("Adding to portfolio")
408 self.show_info_banner(widget, "Error adding to portfolio")
411 def create_selector(self, data, entry):
413 selector = hildon.TouchSelectorEntry(text=True)
415 selector = hildon.TouchSelector(text=True)
416 for i in range(len(data)):
417 selector.append_text(data[i])
421 def show_data(self, symbol, widgets, shares):
423 winprogind(widgets[0], 1)
424 thread.start_new_thread(self.get_data, (symbol, widgets, shares))
426 def get_data(self, symbol, widgets, shares):
427 from ystockquote import ystockquote as yt
428 win, ltitle, lprice, lchange, lpercent, lvolume, l52whigh, l52wlow, lshares, holdingsvalue, dayvaluechange = widgets
431 data = yt.get_all(symbol)
433 logger.exception("Getting data from Yahoo")
434 data = {'price': 'N/A', 'change': 'N/A', 'volume':'N/A',
435 '52_week_high': 'N/A', '52_week_low': 'N/A'}
436 ltitle.set_markup('<b><big>Failed to get data</big></b>')
440 100.0 * float(data['change'])/(float(data['price']) - \
441 float(data['change']))
445 lprice.set_label(data['price'])
446 lchange.set_label(data['change'])
447 lpercent.set_label('%6.2f %%' % ch_percent)
449 if '-' in data['change']:
450 color = gtk.gdk.color_parse("#FF0000")
452 color = gtk.gdk.color_parse("#16EB78")
454 lpercent.modify_fg(gtk.STATE_NORMAL, color)
455 lchange.modify_fg(gtk.STATE_NORMAL, color)
457 lvolume.set_label(data['volume'])
458 l52whigh.set_label(data['52_week_high'])
459 l52wlow.set_label(data['52_week_low'])
462 daychange = float(shares)*float(data['change'])
466 holdvalue = float(shares)*float(data['price'])
470 dayvaluechange.set_label(str(daychange))
471 holdingsvalue.set_label(str(holdvalue))
475 def refresh_stock_data(self, widget, portfolio, widgets, symbol):
477 shares = self.get_shares_from_symbol(symbol)
481 self.show_data(symbol, widgets, shares)
483 def show_graph_view(self, widget, symbol, name):
484 win = hildon.StackableWindow()
485 self.create_menu(win)
486 win.set_title("StockThis - Graph View - " + name)
489 toolbar = self.main_toolbar(False, True, None, '', '', False)
491 self.graphs_title = gtk.Label(name)
492 color = gtk.gdk.color_parse("#03A5FF")
493 self.graphs_title.modify_fg(gtk.STATE_NORMAL, color)
495 parea = hildon.PannableArea()
496 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
501 hbox.set_homogeneous(True)
503 button = hildon.Button(fhsize, horbtn)
504 button.set_label('1d')
505 button.connect("clicked", self.show_graph, '1d', win, symbol)
506 hbox.pack_start(button)
508 button = hildon.Button(fhsize, horbtn)
509 button.set_label('5d')
510 button.connect("clicked", self.show_graph, '5d', win, symbol)
511 hbox.pack_start(button)
513 button = hildon.Button(fhsize, horbtn)
514 button.set_label('3m')
515 button.connect("clicked", self.show_graph, '3m', win, symbol)
516 hbox.pack_start(button)
518 button = hildon.Button(fhsize, horbtn)
519 button.set_label('6m')
520 button.connect("clicked", self.show_graph, '6m', win, symbol)
521 hbox.pack_start(button)
523 vbox1.pack_start(hbox, False, False, 0)
525 hbox.set_homogeneous(True)
527 button = hildon.Button(fhsize, horbtn)
528 button.set_label('1y')
529 button.connect("clicked", self.show_graph, '1y', win, symbol)
530 hbox.pack_start(button)
532 button = hildon.Button(fhsize, horbtn)
533 button.set_label('2y')
534 button.connect("clicked", self.show_graph, '2y', win, symbol)
535 hbox.pack_start(button)
537 button = hildon.Button(fhsize, horbtn)
538 button.set_label('5y')
539 button.connect("clicked", self.show_graph, '5y', win, symbol)
540 hbox.pack_start(button)
542 button = hildon.Button(fhsize, horbtn)
543 button.set_label('Max')
544 button.connect("clicked", self.show_graph, 'max', win, symbol)
545 hbox.pack_start(button)
547 vbox1.pack_start(hbox, False, False, 0)
549 self.graph = gtk.Image()
550 vbox1.pack_start(self.graph, True, True, 0)
552 parea.add_with_viewport(vbox1)
554 vbox.pack_start(self.graphs_title, False, False, 0)
555 vbox.pack_start(gtk.HSeparator(), False, False, 0)
556 vbox.pack_start(parea, True, True, 0)
557 vbox.pack_start(gtk.HSeparator(), False, False, 5)
558 vbox.pack_start(toolbar, False, False, 0)
563 self.show_graph(None, '1d', win, symbol)
565 def show_graph(self, widget, option, win, symbol):
568 thread.start_new_thread(self.get_graph_data, (option, win, symbol))
570 def get_graph_data(self, option, win, symbol):
572 url = 'http://uk.ichart.yahoo.com/b?s=%s' % symbol
574 url = 'http://uk.ichart.yahoo.com/w?s=%s' % symbol
576 url = 'http://chart.finance.yahoo.com/c/3m/s/%s' % symbol.lower()
578 url = 'http://chart.finance.yahoo.com/c/6m/s/%s' % symbol.lower()
580 url = 'http://chart.finance.yahoo.com/c/1y/s/%s' % symbol.lower()
582 url = 'http://chart.finance.yahoo.com/c/2y/s/%s' % symbol.lower()
584 url = 'http://chart.finance.yahoo.com/c/5y/s/%s' % symbol.lower()
585 elif option == 'max':
586 url = 'http://chart.finance.yahoo.com/c/my/s/%s' % symbol.lower()
589 myimg = urllib2.urlopen(url)
590 imgdata = myimg.read()
592 pbl = gtk.gdk.PixbufLoader()
595 pbuf = pbl.get_pixbuf()
596 pbuf = pbuf.scale_simple(475, 235, gtk.gdk.INTERP_TILES)
598 self.graph.set_from_pixbuf(pbuf)
601 logger.exception("Getting graph data")
603 self.graphs_title.set_label('Failed to get data')
606 def _tv_columns(self, treeview):
607 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
608 column.set_visible(False)
609 treeview.append_column(column)
611 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
612 treeview.append_column(column)
614 def __create_model(self, names, ids):
615 lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
616 for item in range(len(names)):
617 iter = lstore.append()
618 lstore.set(iter, 0, ids[item], 1, names[item])
621 def main_toolbar(self, quotesview, portfolio, widgets, symbol, name, initial):
623 #toolbar.set_homogeneous(True)
625 portfolio_btn = hildon.Button(fhsize, horbtn)
626 portfolio_btn.set_title("Portfolio")
627 portfolio_btn.connect("clicked", self.show_portfolio_view)
629 graph_btn = hildon.Button(fhsize, horbtn)
630 graph_btn.set_title("Graph")
631 graph_btn.connect("clicked", self.show_graph_view, symbol, name)
633 refresh_btn = hildon.Button(fhsize, horbtn)
634 refresh_btn.set_title("Refresh")
635 refresh_btn.connect("clicked", self.refresh_stock_data, portfolio,
639 stockiconspath = "/usr/share/icons/hicolor/48x48/hildon/"
640 info_btn = hildon.Button(fhsize, horbtn)
642 img.set_from_file(stockiconspath + "general_information.png")
643 info_btn.set_image(img)
644 info_btn.connect("clicked", self.show_app_information)
646 search_btn = hildon.Button(fhsize, horbtn)
648 img.set_from_file(stockiconspath + "general_search.png")
649 search_btn.set_image(img)
650 search_btn.connect("clicked", self.show_search_dialog)
653 toolbar.pack_start(portfolio_btn)
655 toolbar.pack_start(info_btn, False, False, 0)
657 toolbar.pack_start(graph_btn)
658 toolbar.pack_start(refresh_btn)
661 toolbar.pack_start(search_btn, False, False, 0)
667 def show_search_dialog(self, widget):
668 dlg = gtk.Dialog(title='Search company', parent=None, flags=0)
669 dlg.set_has_separator(False)
671 entry = hildon.Entry(fhsize)
672 dlg.vbox.pack_start(entry, False, False, 0)
674 button = hildon.Button(fhsize, horbtn)
675 button.set_label("Search")
676 button.connect("clicked", self.do_search, entry, dlg)
677 dlg.vbox.pack_start(button, False, False, 0)
684 def do_search(self, widget, entry, dlg):
686 text = entry.get_text()
689 winprogind(self.window, 1)
690 thread.start_new_thread(self._really_do_search, (text,))
692 def _really_do_search(self, text):
695 for market in marketdata.eunames:
696 for company in market:
697 if not company in allnames:
698 allnames.append(company)
700 for market in marketdata.omnames:
701 for company in market:
702 if not company in allnames:
703 allnames.append(company)
705 for market in marketdata.usnames:
706 for company in market:
707 if not company in allnames:
708 allnames.append(company)
711 for market in marketdata.eusymbols:
712 for company in market:
713 if not company in allsymbols:
714 allsymbols.append(company)
716 for market in marketdata.omsymbols:
717 for company in market:
718 if not company in allsymbols:
719 allsymbols.append(company)
721 for market in marketdata.ussymbols:
722 for company in market:
723 if not company in allsymbols:
724 allsymbols.append(company)
726 new_model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
727 for i in range(len(allnames)):
728 if text.lower() in allnames[i].lower():
729 niter = new_model.append()
730 new_model.set(niter, 0, allsymbols[i], 1, allnames[i])
732 if len(new_model) == 0:
733 winprogind(self.window, 0)
734 gtk.gdk.threads_enter()
735 self.show_info_banner(self.window, "No items found for this search")
736 gtk.gdk.threads_leave()
739 gtk.gdk.threads_enter()
740 self.show_search_screen(new_model, text)
741 gtk.gdk.threads_leave()
742 winprogind(self.window, 0)
745 def show_search_screen(self, model, text):
746 window = hildon.StackableWindow()
747 self.create_menu(window)
748 window.set_title("StockThis - Search for " + text)
751 toolbar = self.main_toolbar(False, False, None, '', '', False)
753 parea = hildon.PannableArea()
754 parea.connect("horizontal-movement", self.horizontal_mov)
755 tv = hildon.GtkTreeView(ui_normal)
756 tv.connect("row-activated", self.show_quotes_view, model, False)
761 vbox.pack_start(parea, True, True, 0)
762 vbox.pack_start(gtk.HSeparator(), False, False, 5)
763 vbox.pack_start(toolbar, False, False, 0)
769 def show_app_information(self, widget):
770 self.show_information_note(self.window, (
771 "The data is got from Yahoo! Finance.\n"
772 "It could be delayed or even wrong.\n"
773 "The author doesn't validate in any way this data and therefore he is not responsible for any damage that may occur.\n\n"
774 "You can scroll large list with gestures:\n"
775 "Left-to-right gesture: scroll down.\n"
776 "Right-to-left gesture: scroll up."))
778 def show_portfolio_view(self, widget):
779 data = settings.load_portfolio(settingsdb)
781 win = hildon.StackableWindow()
782 self.create_menu(win)
783 win.set_title("StockThis - Portfolio")
787 parea = hildon.PannableArea()
788 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
789 tv = hildon.GtkTreeView(ui_normal)
790 tv.set_headers_visible(True)
791 self.portfolio_model = self._create_portfolio_model(data)
792 tv.connect("row-activated", self.show_quotes_view, self.portfolio_model, True)
793 tv.set_model(self.portfolio_model)
794 self._tv_portfolio_columns(tv)
798 button = hildon.Button(fhsize, horbtn)
799 button.set_title("Refresh All")
800 button.connect("clicked", self.refresh_portfolio, tv, win)
801 hbox.pack_start(button, True, True, 0)
803 button = hildon.Button(fhsize, horbtn)
804 button.set_title("Add manually")
805 button.connect("clicked", self.add_item_dlg)
806 hbox.pack_start(button, True, True, 0)
808 button = hildon.Button(fhsize, horbtn)
809 button.set_title("Remove")
810 button.connect("clicked", self.remove_item)
811 hbox.pack_start(button, True, True, 0)
813 vbox.pack_start(parea, True, True, 0)
814 vbox.pack_start(hbox, False, False, 0)
818 def add_item_dlg(self, widget):
819 dlg = gtk.Dialog(title='Add to portfolio', parent=None, flags=0)
820 dlg.set_has_separator(False)
822 button1 = hildon.PickerButton(fhsize, horbtn)
823 data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
825 selector = self.create_selector(data, True)
826 button1.set_selector(selector)
827 button1.set_title("Your shares")
828 button1.set_value("0")
829 dlg.vbox.pack_start(button1, False, False, 0)
831 entry1 = hildon.Entry(fhsize)
832 entry1.set_placeholder("Name")
833 dlg.vbox.pack_start(entry1, False, False, 0)
835 entry2 = hildon.Entry(fhsize)
836 entry2.set_placeholder("Yahoo Finance symbol")
837 dlg.vbox.pack_start(entry2, False, False, 0)
839 button = hildon.Button(fhsize, horbtn)
840 button.set_label("Add")
841 button.connect("clicked", self.add_item, dlg, button1, entry1, entry2)
842 dlg.vbox.pack_start(button, False, False, 0)
849 def add_item(self, widget, dlg, button, entry1, entry2):
850 symbol = entry2.get_text()
851 name = entry1.get_text()
852 shares = button.get_value()
854 self.add_to_portfolio(widget, button, symbol, name)
857 niter = self.portfolio_model.append()
858 self.portfolio_model.set(niter, 0, symbol, 1, name, 2, shares, 3, "-")
860 def remove_item(self, widget):
861 win = hildon.StackableWindow()
863 toolbar = hildon.EditToolbar("Choose items to delete", "Delete")
864 win.set_edit_toolbar(toolbar)
867 parea = hildon.PannableArea()
868 tv = hildon.GtkTreeView(ui_edit)
869 selection = tv.get_selection()
870 selection.set_mode(gtk.SELECTION_MULTIPLE)
871 tv.set_model(self.portfolio_model)
872 self._tv_remove_portfolio_columns(tv)
875 toolbar.connect("button-clicked", self.delete_from_portfolio, win, tv,
877 toolbar.connect_object("arrow-clicked", gtk.Window.destroy, win)
879 vbox.pack_start(parea, True, True, 0)
883 def delete_from_portfolio(self, widget, win, tv, selection):
884 if not self.is_treeview_selected(tv):
887 conf = self.show_confirmation(win, "Delete items?")
891 selmodel, selected = selection.get_selected_rows()
892 iters = [selmodel.get_iter(path) for path in selected]
894 symbol = selmodel.get_value(i, 0)
895 settings.delete_item_from_portfolio(settingsdb, symbol)
898 logger.exception("Deleting item from portfolio")
899 self.info_banner(widget, "Error deleting item")
901 def _tv_remove_portfolio_columns(self, treeview):
902 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
903 column.set_visible(False)
904 treeview.append_column(column)
906 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
907 column.set_property("expand", True)
908 treeview.append_column(column)
910 column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
911 column.set_visible(False)
912 treeview.append_column(column)
914 column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
915 column.set_visible(False)
916 treeview.append_column(column)
918 def refresh_portfolio(self, widget, tv, win):
919 data = settings.load_portfolio(settingsdb)
922 thread.start_new_thread(self._do_refresh_portfolio, (data, tv, win))
924 def _do_refresh_portfolio(self, data, tv, win):
926 item[3] = self.get_price(item[0])
928 self.portfolio_model = self._create_portfolio_model(data)
929 tv.set_model(self.portfolio_model)
932 def get_price(self, symbol):
933 from ystockquote import ystockquote as yt
935 price = yt.get_price(symbol)
938 logger.exception("Getting price from Yahoo")
941 def _create_portfolio_model(self, data):
942 lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
943 gobject.TYPE_STRING, gobject.TYPE_STRING)
945 iter = lstore.append()
946 lstore.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3])
949 def _tv_portfolio_columns(self, treeview):
950 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
951 column.set_visible(False)
952 treeview.append_column(column)
954 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
955 column.set_property("expand", True)
956 treeview.append_column(column)
958 column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
959 treeview.append_column(column)
961 column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
962 treeview.append_column(column)
964 def show_confirmation(self, window, msg):
965 dialog = hildon.hildon_note_new_confirmation(window, msg)
967 result = dialog.run()
968 if result == gtk.RESPONSE_OK:
975 def show_information_note(self, window, msg):
976 dialog = hildon.hildon_note_new_information(window, msg)
978 result = dialog.run()
981 def show_info_banner(self, widget, msg):
982 hildon.hildon_banner_show_information(widget, 'qgn_note_infoprint', msg)
984 def is_treeview_selected(self, treeview):
985 selection = treeview.get_selection()
986 if selection.count_selected_rows() == 0:
987 self.show_info_banner(treeview, 'No selected item')
994 def __init__(self, widget):
995 self.abdialog = gtk.Dialog(title='About', parent=None, flags=0)
996 self.abdialog.set_has_separator(False)
997 self.abdialog.set_size_request(-1, 400)
999 self.info_lb = gtk.Label()
1000 self.info_lb.set_line_wrap(True)
1006 button = hildon.Button(fhsize, horbtn)
1007 button.set_title('Description')
1008 button.connect("clicked", self.show_info, 'description')
1009 hbox1.pack_start(button, True, True, 0)
1011 button = hildon.Button(fhsize, horbtn)
1012 button.set_title('Credits')
1013 button.connect("clicked", self.show_info, 'credits')
1014 hbox1.pack_start(button, True, True, 0)
1016 button = hildon.Button(fhsize, horbtn)
1017 button.set_title('License')
1018 button.connect("clicked", self.show_info, 'license')
1019 hbox1.pack_start(button, True, True, 0)
1021 button = hildon.Button(fhsize, horbtn)
1022 button.set_title('Donate')
1023 button.connect("clicked", self.show_info, 'donate')
1024 hbox1.pack_start(button, True, True, 0)
1026 button = hildon.Button(fhsize, horbtn)
1027 button.set_title('Report ')
1028 button.connect("clicked", self.show_info, 'report')
1029 hbox1.pack_start(button, True, True, 0)
1031 button = hildon.Button(fhsize, horbtn)
1032 button.set_title(' Rate ')
1033 button.connect("clicked", self.show_info, 'vote')
1034 hbox1.pack_start(button, True, True, 0)
1036 self.action_btn = hildon.Button(fhsize, horbtn)
1037 self.image = gtk.Image()
1039 self.show_info(None, 'description')
1041 self.abdialog.vbox.pack_start(self.action_btn, False, False, 0)
1042 self.abdialog.vbox.pack_start(self.image, False, False, 5)
1043 self.abdialog.vbox.pack_start(self.info_lb, True, True, 0)
1044 self.abdialog.vbox.pack_start(hbox1, False, False, 0)
1046 self.abdialog.show_all()
1047 self.action_btn.hide()
1050 self.abdialog.destroy()
1052 def do_action(self, widget, action):
1055 self.abdialog.destroy()
1057 bus = dbus.SystemBus()
1058 proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
1059 iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
1061 if action == "donate":
1062 url = "http://stockthis.garage.maemo.org/donate.html"
1063 elif action == "report":
1064 url = "http://stockthis.garage.maemo.org/reporting.html"
1065 elif action == "vote":
1066 url = "http://maemo.org/downloads/product/stockthis"
1070 def show_info(self, widget, kind):
1071 if kind == 'license':
1072 self.action_btn.hide()
1074 info = """<small><b>StockThis</b> is free software. It's using a GPL version 2 license or at your election any later version.
1076 Logo by Daniel Martin Yerga.
1078 elif kind == 'credits':
1079 self.action_btn.hide()
1081 info = """<small><b>Written by</b> Daniel Martin Yerga (dyerga@gmail.com)
1083 <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>"""
1084 elif kind == 'description':
1085 self.action_btn.hide()
1087 info = """<b><big>StockThis 0.3</big></b>
1089 <i>StockThis is a stocks application for Maemo</i>
1092 stockthis.garage.maemo.org"""
1094 elif kind == 'donate':
1095 self.action_btn.show()
1097 self.action_btn.set_title('Make donation')
1099 self.action_btn.disconnect(self.id)
1100 self.id = self.action_btn.connect("clicked", self.do_action, "donate")
1101 info = """<small><b>StockThis</b> is a free (and gratis) software application.
1102 Developing good software takes time and hard work.
1104 <b>StockThis's author</b> develops the program in his spare time.
1105 If you like the program and it's helpful, consider donating a small amount of money.
1106 Donations are a great incentive and help the developer feel that the hard work is appreciated.</small>
1108 elif kind == 'report':
1109 self.action_btn.show()
1111 self.action_btn.set_title('Report bug')
1113 self.action_btn.disconnect(self.id)
1114 self.id = self.action_btn.connect("clicked", self.do_action, "report")
1115 info = """<small>StockThis is being improved thanks to bug reports that users have submitted. The author appreciates these reports.
1116 If the application is raising an error when you're using it, you have two choices to report this error:
1117 1) Send the log from the application menu (if there's an error in the log).
1118 2) Press the button and write a bug report with as much information as possible.</small>"""
1119 elif kind == 'vote':
1120 self.action_btn.show()
1122 self.image.set_from_file(imgdir + "maemoorg.png")
1123 self.action_btn.set_title('Rate StockThis')
1125 self.action_btn.disconnect(self.id)
1126 self.id = self.action_btn.connect("clicked", self.do_action, "vote")
1127 info = """<small>The downloads section in maemo.org has a nice system where you can rate applications.
1128 If you consider StockThis a good application (or a bad one too), you could rate it in maemo.org site.</small>"""
1130 self.info_lb.set_markup(info)
1135 def __init__(self, widget, logfile):
1137 dialog = gtk.Dialog(title='Log', parent=None)
1139 dialog.set_size_request(600, 350)
1141 parea = hildon.PannableArea()
1142 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
1144 textview = hildon.TextView()
1145 textview.set_property("editable", False)
1146 textview.set_property("wrap-mode", gtk.WRAP_WORD)
1148 log = open(logfile, 'r')
1149 logtext = log.read()
1152 textview.get_buffer().set_text(logtext)
1155 dialog.vbox.pack_start(parea, True, True, 0)
1159 save_btn = hildon.Button(fhsize, horbtn)
1160 save_btn.set_title("Save")
1161 save_btn.connect('clicked', self.save, logfile, dialog)
1163 clear_btn = hildon.Button(fhsize, horbtn)
1164 clear_btn.set_title("Clear")
1165 clear_btn.connect('clicked', self.clear, textview, logfile)
1167 send_btn = hildon.Button(fhsize, horbtn)
1168 send_btn.set_title('Send')
1169 send_btn.connect('clicked', self.send, dialog, logfile)
1171 hbox.pack_start(save_btn, True, True, 0)
1172 hbox.pack_start(clear_btn, True, True, 0)
1173 hbox.pack_start(send_btn, True, True, 0)
1175 dialog.vbox.pack_start(hbox, False, False, 0)
1181 def show_filechooser(self, window, title, name, EXT):
1182 action = gtk.FILE_CHOOSER_ACTION_SAVE
1184 m = hildon.FileSystemModel()
1185 file_dialog = hildon.FileChooserDialog(window, action, m)
1186 file_dialog.set_title(title)
1188 file_dialog.set_current_name(name)
1189 HOME = os.path.expanduser("~")
1191 if os.path.exists(HOME + '/MyDocs/.documents'):
1192 file_dialog.set_current_folder(HOME + '/MyDocs/.documents')
1194 file_dialog.set_current_folder(HOME)
1196 file_dialog.set_default_response(gtk.RESPONSE_CANCEL)
1198 result = file_dialog.run()
1199 if result == gtk.RESPONSE_OK:
1200 namefile = file_dialog.get_filename()
1201 namefile, extension = os.path.splitext(namefile)
1202 namefile = namefile + "." + EXT
1205 file_dialog.destroy()
1210 def clear(self, widget, textview, logfile):
1211 textview.get_buffer().set_text('')
1212 f = open(logfile, 'w')
1215 def save(self, widget, logfile, dlg):
1217 filename = self.show_filechooser(dlg, "Save log file",
1218 "stockthis-log", "txt")
1224 shutil.copyfile(logfile, filename)
1225 stockspy.show_info_banner(widget, 'Log file saved')
1227 logger.exception("Saving log file")
1228 stockspy.show_info_banner(widget, 'Error saving the log file')
1230 def send(self, widget, dlg, logfile):
1231 sendtxt = ("You are going to send the log to the developers.\n"
1232 "This helps the developers to track problems with the application.\n"
1233 "It doesn't send any personal information (like passwords or similar).")
1235 dialog = hildon.hildon_note_new_confirmation(dlg, sendtxt)
1236 dialog.set_button_texts("Send", "Cancel")
1238 response = dialog.run()
1239 if response == gtk.RESPONSE_OK:
1240 self.do_pre_send(dlg, logfile)
1244 def do_pre_send(self, dlg, logfile):
1246 hildon.hildon_gtk_window_set_progress_indicator(dlg, 1)
1247 thread.start_new_thread(self._do_send, (dlg, logfile))
1249 def _do_send(self, dlg, logfile):
1250 import pycurl, shutil, random, commands
1253 for i in random.sample('abcdefghijkl123456789', 18):
1256 rnamepath = HOME + "/.stockthis/" + rname
1257 shutil.copyfile(logfile, rnamepath)
1259 gtkversion = "%s.%s.%s" % gtk.ver
1260 if os.path.exists("/etc/maemo_version"):
1261 mfile = open("/etc/maemo_version", 'r')
1262 maemoversion = mfile.read()
1267 opsystem = ' '.join(os.uname())
1268 pyversion = os.sys.version
1270 comm = ("awk '/Private_Dirty/{sum+=$2}END{print sum \"kB\"}'"
1271 " /proc/%s/smaps") % pid
1272 status, dirtymem = commands.getstatusoutput(comm)
1274 lfile = open(rnamepath, 'r')
1279 log = ("%s\nPython version: %s\nGtk version: %s\n"
1280 "Maemo version: %sOperating system: %s\n"
1281 "Dirty Memory: %s\nLog:\n%s") % (_version, pyversion, gtkversion,
1282 maemoversion, opsystem, dirtymem, log)
1284 lfile = open(rnamepath, 'w')
1288 url = "http://yerga.net/logs/uploader.php"
1289 data = [('uploadedfile', (pycurl.FORM_FILE, rnamepath)),]
1290 mycurl = pycurl.Curl()
1291 mycurl.setopt(pycurl.URL, url)
1292 mycurl.setopt(pycurl.HTTPPOST, data)
1296 os.remove(rnamepath)
1298 gtk.gdk.threads_enter()
1299 stockspy.show_info_banner(dlg, 'Log sent')
1300 gtk.gdk.threads_leave()
1301 hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1303 logger.exception("Sending log file")
1304 gtk.gdk.threads_enter()
1305 stockspy.show_info_banner(dlg, 'Error sending the log file')
1306 gtk.gdk.threads_leave()
1307 hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1310 if __name__ == "__main__":
1311 stockspy = StocksPy()
1312 gtk.gdk.threads_enter()
1314 gtk.gdk.threads_leave()