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 # StocksPy: Application to get stocks data from Yahoo Finance.
23 _version = "StockThis 0.3 beta1 rev1"
35 osso_c = osso.Context("net.yerga.stockthis", "0.3", False)
37 #detect if is ran locally or not
38 runningpath = sys.path[0]
40 if '/usr/share' in runningpath:
41 runninglocally = False
45 HOME = os.path.expanduser("~")
47 settingsdb, imgdir, configdir, logfile = \
48 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 self.create_menu(self.window)
94 toolbar = self.main_toolbar(False, False, None, '', '', True)
96 parea = hildon.PannableArea()
97 tv = hildon.GtkTreeView(ui_normal)
98 inmodel = self.__create_model(marketdata.main, marketdata.idmain)
99 tv.connect("row-activated", self.show_instrument_view, inmodel,
100 marketdata.localmarkets, marketdata.localids,
102 tv.set_model(inmodel)
106 vbox.pack_start(parea, True, True, 0)
107 vbox.pack_start(gtk.HSeparator(), False, False, 5)
108 vbox.pack_start(toolbar, False, False, 0)
110 self.window.add(vbox)
111 self.window.show_all()
113 def create_menu(self, window):
114 menu = hildon.AppMenu()
115 window.set_app_menu(menu)
116 button = gtk.Button("About")
117 button.connect("clicked", About)
119 button = gtk.Button("Log")
120 button.connect("clicked", Log, logfile)
124 def show_instrument_view(self, widget, path, column, inmodel, names,
126 market = inmodel[path][0]
127 names = names[mindex.index(market)]
128 ids = ids[mindex.index(market)]
130 window = hildon.StackableWindow()
131 self.create_menu(window)
132 window.set_title("StockThis - " + inmodel[path][1])
135 toolbar = self.main_toolbar(False, False, None, '', '', False)
137 parea = hildon.PannableArea()
138 parea.connect("horizontal-movement", self.horizontal_mov)
139 tv = hildon.GtkTreeView(ui_normal)
140 model = self.__create_model(names, ids)
141 tv.connect("row-activated", self.show_quotes_view, model, False)
146 vbox.pack_start(parea, True, True, 0)
147 vbox.pack_start(gtk.HSeparator(), False, False, 5)
148 vbox.pack_start(toolbar, False, False, 0)
153 def horizontal_mov(self, parea, direction, initial_x, initial_y):
154 #direction = 2 right-to-left
155 #direction = 3 lefto-to-right
157 vadj = parea.get_vadjustment()
158 val = vadj.get_value()
161 if int(val)-2500 < 0:
162 parea.scroll_to(-1, 0)
164 parea.scroll_to(-1, int(val)-2500)
166 parea.scroll_to(-1, int(val)+3500)
170 def show_quotes_view(self, widget, path, column, model, portfolio):
171 quote = model[path][0], model[path][1]
172 #print "quote:", quote[0]
173 #('EURUSD=X', 'EUR/USD')
175 #Currencies and ETFs should show the list now -> view = True
176 #Other items show a new list with options
178 for i in marketdata.localids[(len(marketdata.localids)-2):]:
185 if quote[0] in marketdata.idindexes:
186 self.show_instrument_view(widget, path, column, model,
187 marketdata.wnamesindexes,
188 marketdata.widsindexes,
189 marketdata.idindexes)
191 if quote[0] in marketdata.idotmarkets:
192 self.show_instrument_view(widget, path, column, model,
194 marketdata.omsymbols,
195 marketdata.idotmarkets)
197 if quote[0] in marketdata.ideumarkets:
198 self.show_instrument_view(widget, path, column, model,
200 marketdata.eusymbols,
201 marketdata.ideumarkets)
203 if quote[0] in marketdata.idusmarkets:
204 self.show_instrument_view(widget, path, column, model,
206 marketdata.ussymbols,
207 marketdata.idusmarkets)
211 win = hildon.StackableWindow()
212 self.create_menu(win)
213 win.set_title("StockThis - Quotes View - " + quote[1])
217 ltitle = gtk.Label('')
218 ltitle.set_markup('<b><big>' + quote[1].replace('&', '') +
220 color = gtk.gdk.color_parse("#03A5FF")
221 ltitle.modify_fg(gtk.STATE_NORMAL, color)
223 parea = hildon.PannableArea()
228 label = gtk.Label('')
229 label.set_markup('<b><big>Price:</big></b>')
230 lprice = gtk.Label('')
231 hbox.pack_start(label, False, False, 20)
232 hbox.pack_start(lprice, False, False, 245)
233 vbox1.pack_start(hbox, True, True, 0)
236 label = gtk.Label('')
237 label.set_markup('<b><big>Change:</big></b>')
238 lchange = gtk.Label('')
239 lpercent = gtk.Label('')
240 hbox.pack_start(label, False, False, 20)
241 hbox.pack_start(lchange, False, False, 205)
242 hbox.pack_start(lpercent, False, False, 0)
243 vbox1.pack_start(hbox, True, True, 0)
246 label = gtk.Label('')
247 label.set_markup('<b><big>Volume:</big></b>')
248 lvolume = gtk.Label('')
249 hbox.pack_start(label, False, False, 20)
250 hbox.pack_start(lvolume, False, False, 207)
251 vbox1.pack_start(hbox, True, True, 0)
254 label = gtk.Label('')
255 label.set_markup('<b><big>52 week high:</big></b>')
256 l52whigh = gtk.Label('')
257 hbox.pack_start(label, False, False, 20)
258 hbox.pack_start(l52whigh, False, False, 125)
259 vbox1.pack_start(hbox, True, True, 0)
262 label = gtk.Label('')
263 label.set_markup('<b><big>52 week low:</big></b>')
264 l52wlow = gtk.Label('')
265 hbox.pack_start(label, False, False, 20)
266 hbox.pack_start(l52wlow, False, False, 140)
267 vbox1.pack_start(hbox, True, True, 0)
270 button1 = hildon.PickerButton(fhsize, horbtn)
271 data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
273 selector = self.create_selector(data, True)
274 button1.set_selector(selector)
275 button1.set_title("Your shares")
276 shares = self.get_shares_from_symbol(quote[0])
277 button1.set_value(shares)
278 hbox.pack_start(button1, True, True, 0)
280 button = hildon.Button(fhsize, horbtn)
281 button.set_title("Add to Portfolio")
282 button.connect("clicked", self.add_to_portfolio, button1, quote[0], quote[1])
283 hbox.pack_start(button, True, True, 0)
286 label = gtk.Label('')
287 label.set_markup('<b><big>Shares:</big></b>')
288 lshares = gtk.Label(shares)
289 hbox1.pack_start(label, False, False, 20)
290 hbox1.pack_start(lshares, False, False, 220)
293 label = gtk.Label('')
294 label.set_markup('<b><big>Holdings Value:</big></b>')
295 holdingsvalue = gtk.Label("")
296 hbox2.pack_start(label, False, False, 20)
297 hbox2.pack_start(holdingsvalue, False, False, 105)
300 label = gtk.Label('')
301 label.set_markup("<b><big>Day's Value Change:</big></b>")
302 dayvaluechange = gtk.Label("")
303 hbox3.pack_start(label, False, False, 20)
304 hbox3.pack_start(dayvaluechange, False, False, 45)
307 vbox1.pack_start(hbox, False, False, 0)
309 vbox1.pack_start(hbox1, True, True, 0)
310 vbox1.pack_start(hbox2, True, True, 0)
311 vbox1.pack_start(hbox3, True, True, 0)
313 parea.add_with_viewport(vbox1)
315 widgets = [win, ltitle, lprice, lchange, lpercent, lvolume, l52whigh,
316 l52wlow, lshares, holdingsvalue, dayvaluechange]
318 toolbar = self.main_toolbar(True, portfolio, widgets, quote[0], quote[1], False)
320 vbox.pack_start(ltitle, False, False, 0)
321 vbox.pack_start(gtk.HSeparator(), False, False, 0)
322 vbox.pack_start(parea, True, True, 0)
323 vbox.pack_start(gtk.HSeparator(), False, False, 5)
324 vbox.pack_start(toolbar, False, False, 0)
329 self.show_data(quote[0], widgets, shares)
331 def get_shares_from_symbol(self, symbol):
334 portfolio_data = settings.load_portfolio(settingsdb)
335 for item in portfolio_data :
340 logger.exception("Getting shares from symbol")
343 def add_to_portfolio(self, widget, button, symbol, name):
344 shares = button.get_value()
347 portfolio = settings.load_portfolio(settingsdb)
349 for item in portfolio:
351 index = portfolio.index(item)
353 item = [symbol, name, shares, '-']
356 settings.insert_new_item_to_portfolio(settingsdb, item)
358 settings.delete_item_from_portfolio(settingsdb, symbol)
359 settings.insert_new_item_to_portfolio(settingsdb, item)
361 self.show_info_banner(widget, "Added to portfolio")
363 logger.exception("Adding to portfolio")
364 self.show_info_banner(widget, "Error adding to portfolio")
367 def create_selector(self, data, entry):
369 selector = hildon.TouchSelectorEntry(text=True)
371 selector = hildon.TouchSelector(text=True)
372 for i in range(len(data)):
373 selector.append_text(data[i])
377 def show_data(self, symbol, widgets, shares):
379 winprogind(widgets[0], 1)
380 thread.start_new_thread(self.get_data, (symbol, widgets, shares))
382 def get_data(self, symbol, widgets, shares):
383 from ystockquote import ystockquote as yt
384 win, ltitle, lprice, lchange, lpercent, lvolume, l52whigh, l52wlow, lshares, holdingsvalue, dayvaluechange = widgets
387 data = yt.get_all(symbol)
389 logger.exception("Getting data from Yahoo")
390 data = {'price': 'N/A', 'change': 'N/A', 'volume':'N/A',
391 '52_week_high': 'N/A', '52_week_low': 'N/A'}
392 ltitle.set_markup('<b><big>Failed to get data</big></b>')
396 100.0 * float(data['change'])/(float(data['price']) - \
397 float(data['change']))
401 lprice.set_label(data['price'])
402 lchange.set_label(data['change'])
403 lpercent.set_label('%6.2f %%' % ch_percent)
405 if '-' in data['change']:
406 color = gtk.gdk.color_parse("#FF0000")
408 color = gtk.gdk.color_parse("#16EB78")
410 lpercent.modify_fg(gtk.STATE_NORMAL, color)
411 lchange.modify_fg(gtk.STATE_NORMAL, color)
413 lvolume.set_label(data['volume'])
414 l52whigh.set_label(data['52_week_high'])
415 l52wlow.set_label(data['52_week_low'])
418 daychange = float(shares)*float(data['change'])
422 holdvalue = float(shares)*float(data['price'])
426 dayvaluechange.set_label(str(daychange))
427 holdingsvalue.set_label(str(holdvalue))
431 def refresh_stock_data(self, widget, portfolio, widgets, symbol):
433 shares = self.get_shares_from_symbol(symbol)
437 self.show_data(symbol, widgets, shares)
439 def show_graph_view(self, widget, symbol, name):
440 win = hildon.StackableWindow()
441 self.create_menu(win)
442 win.set_title("StockThis - Graph View - " + name)
445 toolbar = self.main_toolbar(False, True, None, '', '', False)
447 self.graphs_title = gtk.Label(name)
448 color = gtk.gdk.color_parse("#03A5FF")
449 self.graphs_title.modify_fg(gtk.STATE_NORMAL, color)
451 parea = hildon.PannableArea()
454 hbox.set_homogeneous(True)
456 button = hildon.Button(fhsize, horbtn)
457 button.set_label('1d')
458 button.connect("clicked", self.show_graph, '1d', win, symbol)
459 hbox.pack_start(button)
461 button = hildon.Button(fhsize, horbtn)
462 button.set_label('5d')
463 button.connect("clicked", self.show_graph, '5d', win, symbol)
464 hbox.pack_start(button)
466 button = hildon.Button(fhsize, horbtn)
467 button.set_label('3m')
468 button.connect("clicked", self.show_graph, '3m', win, symbol)
469 hbox.pack_start(button)
471 button = hildon.Button(fhsize, horbtn)
472 button.set_label('6m')
473 button.connect("clicked", self.show_graph, '6m', win, symbol)
474 hbox.pack_start(button)
476 button = hildon.Button(fhsize, horbtn)
477 button.set_label('1y')
478 button.connect("clicked", self.show_graph, '1y', win, symbol)
479 hbox.pack_start(button)
481 button = hildon.Button(fhsize, horbtn)
482 button.set_label('2y')
483 button.connect("clicked", self.show_graph, '2y', win, symbol)
484 hbox.pack_start(button)
486 button = hildon.Button(fhsize, horbtn)
487 button.set_label('5y')
488 button.connect("clicked", self.show_graph, '5y', win, symbol)
489 hbox.pack_start(button)
491 button = hildon.Button(fhsize, horbtn)
492 button.set_label('Max')
493 button.connect("clicked", self.show_graph, 'max', win, symbol)
494 hbox.pack_start(button)
497 vbox1.pack_start(hbox, False, False, 0)
499 self.graph = gtk.Image()
500 vbox1.pack_start(self.graph, True, True, 0)
502 parea.add_with_viewport(vbox1)
504 vbox.pack_start(self.graphs_title, False, False, 0)
505 vbox.pack_start(gtk.HSeparator(), False, False, 0)
506 vbox.pack_start(parea, True, True, 0)
507 vbox.pack_start(gtk.HSeparator(), False, False, 5)
508 vbox.pack_start(toolbar, False, False, 0)
513 self.show_graph(None, '1d', win, symbol)
515 def show_graph(self, widget, option, win, symbol):
518 thread.start_new_thread(self.get_graph_data, (option, win, symbol))
520 def get_graph_data(self, option, win, symbol):
522 url = 'http://uk.ichart.yahoo.com/b?s=%s' % symbol
524 url = 'http://uk.ichart.yahoo.com/w?s=%s' % symbol
526 url = 'http://chart.finance.yahoo.com/c/3m/s/%s' % symbol.lower()
528 url = 'http://chart.finance.yahoo.com/c/6m/s/%s' % symbol.lower()
530 url = 'http://chart.finance.yahoo.com/c/1y/s/%s' % symbol.lower()
532 url = 'http://chart.finance.yahoo.com/c/2y/s/%s' % symbol.lower()
534 url = 'http://chart.finance.yahoo.com/c/5y/s/%s' % symbol.lower()
535 elif option == 'max':
536 url = 'http://chart.finance.yahoo.com/c/my/s/%s' % symbol.lower()
539 myimg = urllib2.urlopen(url)
540 imgdata = myimg.read()
542 pbl = gtk.gdk.PixbufLoader()
545 pbuf = pbl.get_pixbuf()
547 self.graph.set_from_pixbuf(pbuf)
550 logger.exception("Getting graph data")
552 self.graphs_title.set_label('Failed to get data')
555 def _tv_columns(self, treeview):
556 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
557 column.set_visible(False)
558 treeview.append_column(column)
560 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
561 treeview.append_column(column)
563 def __create_model(self, names, ids):
564 lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
565 for item in range(len(names)):
566 iter = lstore.append()
567 lstore.set(iter, 0, ids[item], 1, names[item])
570 def main_toolbar(self, quotesview, portfolio, widgets, symbol, name, initial):
572 #toolbar.set_homogeneous(True)
574 portfolio_btn = hildon.Button(fhsize, horbtn)
575 portfolio_btn.set_title("Portfolio")
576 portfolio_btn.connect("clicked", self.show_portfolio_view)
578 graph_btn = hildon.Button(fhsize, horbtn)
579 graph_btn.set_title("Graph")
580 graph_btn.connect("clicked", self.show_graph_view, symbol, name)
582 refresh_btn = hildon.Button(fhsize, horbtn)
583 refresh_btn.set_title("Refresh")
584 refresh_btn.connect("clicked", self.refresh_stock_data, portfolio,
588 stockiconspath = "/usr/share/icons/hicolor/48x48/hildon/"
589 info_btn = hildon.Button(fhsize, horbtn)
591 img.set_from_file(stockiconspath + "general_information.png")
592 info_btn.set_image(img)
593 info_btn.connect("clicked", self.show_app_information)
595 search_btn = hildon.Button(fhsize, horbtn)
597 img.set_from_file(stockiconspath + "general_search.png")
598 search_btn.set_image(img)
599 search_btn.connect("clicked", self.show_search_dialog)
602 toolbar.pack_start(portfolio_btn)
604 toolbar.pack_start(info_btn, False, False, 0)
606 toolbar.pack_start(graph_btn)
607 toolbar.pack_start(refresh_btn)
610 toolbar.pack_start(search_btn, False, False, 0)
616 def show_search_dialog(self, widget):
617 dlg = gtk.Dialog(title='Search company', parent=None, flags=0)
618 dlg.set_has_separator(False)
620 entry = hildon.Entry(fhsize)
621 dlg.vbox.pack_start(entry, False, False, 0)
623 button = hildon.Button(fhsize, horbtn)
624 button.set_label("Search")
625 button.connect("clicked", self.do_search, entry, dlg)
626 dlg.vbox.pack_start(button, False, False, 0)
633 def do_search(self, widget, entry, dlg):
635 text = entry.get_text()
638 winprogind(self.window, 1)
639 thread.start_new_thread(self._really_do_search, (text,))
641 def _really_do_search(self, text):
644 for market in marketdata.eunames:
645 for company in market:
646 if not company in allnames:
647 allnames.append(company)
649 for market in marketdata.omnames:
650 for company in market:
651 if not company in allnames:
652 allnames.append(company)
654 for market in marketdata.usnames:
655 for company in market:
656 if not company in allnames:
657 allnames.append(company)
660 for market in marketdata.eusymbols:
661 for company in market:
662 if not company in allsymbols:
663 allsymbols.append(company)
665 for market in marketdata.omsymbols:
666 for company in market:
667 if not company in allsymbols:
668 allsymbols.append(company)
670 for market in marketdata.ussymbols:
671 for company in market:
672 if not company in allsymbols:
673 allsymbols.append(company)
675 new_model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
676 for i in range(len(allnames)):
677 if text.lower() in allnames[i].lower():
678 niter = new_model.append()
679 new_model.set(niter, 0, allsymbols[i], 1, allnames[i])
681 if len(new_model) == 0:
682 winprogind(self.window, 0)
683 gtk.gdk.threads_enter()
684 self.show_info_banner(self.window, "No items found for this search")
685 gtk.gdk.threads_leave()
688 gtk.gdk.threads_enter()
689 self.show_search_screen(new_model, text)
690 gtk.gdk.threads_leave()
691 winprogind(self.window, 0)
694 def show_search_screen(self, model, text):
695 window = hildon.StackableWindow()
696 self.create_menu(window)
697 window.set_title("StockThis - Search for " + text)
700 toolbar = self.main_toolbar(False, False, None, '', '', False)
702 parea = hildon.PannableArea()
703 parea.connect("horizontal-movement", self.horizontal_mov)
704 tv = hildon.GtkTreeView(ui_normal)
705 tv.connect("row-activated", self.show_quotes_view, model, False)
710 vbox.pack_start(parea, True, True, 0)
711 vbox.pack_start(gtk.HSeparator(), False, False, 5)
712 vbox.pack_start(toolbar, False, False, 0)
718 def show_app_information(self, widget):
719 self.show_information_note(self.window, (
720 "The data is got from Yahoo! Finance.\n"
721 "It could be delayed or even wrong.\n"
722 "The author doesn't validate in any way this data and therefore he is not responsible for any damage that may occur.\n\n"
723 "You can scroll large list with gestures:\n"
724 "Left-to-right gesture: scroll down.\n"
725 "Right-to-left gesture: scroll up."))
727 def show_portfolio_view(self, widget):
728 data = settings.load_portfolio(settingsdb)
730 win = hildon.StackableWindow()
731 self.create_menu(win)
732 win.set_title("StockThis - Portfolio")
736 parea = hildon.PannableArea()
737 tv = hildon.GtkTreeView(ui_normal)
738 tv.set_headers_visible(True)
739 self.portfolio_model = self._create_portfolio_model(data)
740 tv.connect("row-activated", self.show_quotes_view, self.portfolio_model, True)
741 tv.set_model(self.portfolio_model)
742 self._tv_portfolio_columns(tv)
746 button = hildon.Button(fhsize, horbtn)
747 button.set_title("Refresh All")
748 button.connect("clicked", self.refresh_portfolio, tv, win)
749 hbox.pack_start(button, True, True, 0)
751 button = hildon.Button(fhsize, horbtn)
752 button.set_title("Add manually")
753 button.connect("clicked", self.add_item_dlg)
754 hbox.pack_start(button, True, True, 0)
756 button = hildon.Button(fhsize, horbtn)
757 button.set_title("Remove")
758 button.connect("clicked", self.remove_item)
759 hbox.pack_start(button, True, True, 0)
761 vbox.pack_start(parea, True, True, 0)
762 vbox.pack_start(hbox, False, False, 0)
766 def add_item_dlg(self, widget):
767 dlg = gtk.Dialog(title='Add to portfolio', parent=None, flags=0)
768 dlg.set_has_separator(False)
770 button1 = hildon.PickerButton(fhsize, horbtn)
771 data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
773 selector = self.create_selector(data, True)
774 button1.set_selector(selector)
775 button1.set_title("Your shares")
776 button1.set_value("0")
777 dlg.vbox.pack_start(button1, False, False, 0)
779 entry1 = hildon.Entry(fhsize)
780 entry1.set_placeholder("Name")
781 dlg.vbox.pack_start(entry1, False, False, 0)
783 entry2 = hildon.Entry(fhsize)
784 entry2.set_placeholder("Yahoo Finance symbol")
785 dlg.vbox.pack_start(entry2, False, False, 0)
787 button = hildon.Button(fhsize, horbtn)
788 button.set_label("Add")
789 button.connect("clicked", self.add_item, dlg, button1, entry1, entry2)
790 dlg.vbox.pack_start(button, False, False, 0)
797 def add_item(self, widget, dlg, button, entry1, entry2):
798 symbol = entry2.get_text()
799 name = entry1.get_text()
800 shares = button.get_value()
802 self.add_to_portfolio(widget, button, symbol, name)
805 niter = self.portfolio_model.append()
806 self.portfolio_model.set(niter, 0, symbol, 1, name, 2, shares, 3, "-")
808 def remove_item(self, widget):
809 win = hildon.StackableWindow()
811 toolbar = hildon.EditToolbar("Choose items to delete", "Delete")
812 win.set_edit_toolbar(toolbar)
815 parea = hildon.PannableArea()
816 tv = hildon.GtkTreeView(ui_edit)
817 selection = tv.get_selection()
818 selection.set_mode(gtk.SELECTION_MULTIPLE)
819 tv.set_model(self.portfolio_model)
820 self._tv_remove_portfolio_columns(tv)
823 toolbar.connect("button-clicked", self.delete_from_portfolio, win, tv,
825 toolbar.connect_object("arrow-clicked", gtk.Window.destroy, win)
827 vbox.pack_start(parea, True, True, 0)
831 def delete_from_portfolio(self, widget, win, tv, selection):
832 if not self.is_treeview_selected(tv):
835 conf = self.show_confirmation(win, "Delete items?")
839 selmodel, selected = selection.get_selected_rows()
840 iters = [selmodel.get_iter(path) for path in selected]
842 symbol = selmodel.get_value(i, 0)
843 settings.delete_item_from_portfolio(settingsdb, symbol)
846 logger.exception("Deleting item from portfolio")
847 self.info_banner(widget, "Error deleting item")
849 def _tv_remove_portfolio_columns(self, treeview):
850 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
851 column.set_visible(False)
852 treeview.append_column(column)
854 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
855 column.set_property("expand", True)
856 treeview.append_column(column)
858 column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
859 column.set_visible(False)
860 treeview.append_column(column)
862 column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
863 column.set_visible(False)
864 treeview.append_column(column)
866 def refresh_portfolio(self, widget, tv, win):
867 data = settings.load_portfolio(settingsdb)
870 thread.start_new_thread(self._do_refresh_portfolio, (data, tv, win))
872 def _do_refresh_portfolio(self, data, tv, win):
874 item[3] = self.get_price(item[0])
876 self.portfolio_model = self._create_portfolio_model(data)
877 tv.set_model(self.portfolio_model)
880 def get_price(self, symbol):
881 from ystockquote import ystockquote as yt
883 price = yt.get_price(symbol)
886 logger.exception("Getting price from Yahoo")
889 def _create_portfolio_model(self, data):
890 lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
891 gobject.TYPE_STRING, gobject.TYPE_STRING)
893 iter = lstore.append()
894 lstore.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3])
897 def _tv_portfolio_columns(self, treeview):
898 column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
899 column.set_visible(False)
900 treeview.append_column(column)
902 column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
903 column.set_property("expand", True)
904 treeview.append_column(column)
906 column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
907 treeview.append_column(column)
909 column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
910 treeview.append_column(column)
912 def show_confirmation(self, window, msg):
913 dialog = hildon.hildon_note_new_confirmation(window, msg)
915 result = dialog.run()
916 if result == gtk.RESPONSE_OK:
923 def show_information_note(self, window, msg):
924 dialog = hildon.hildon_note_new_information(window, msg)
926 result = dialog.run()
929 def show_info_banner(self, widget, msg):
930 hildon.hildon_banner_show_information(widget, 'qgn_note_infoprint', msg)
932 def is_treeview_selected(self, treeview):
933 selection = treeview.get_selection()
934 if selection.count_selected_rows() == 0:
935 self.show_info_banner(treeview, 'No selected item')
942 def __init__(self, widget):
943 self.abdialog = gtk.Dialog(title='About', parent=None, flags=0)
944 self.abdialog.set_has_separator(False)
945 self.abdialog.set_size_request(-1, 400)
947 self.info_lb = gtk.Label()
948 self.info_lb.set_line_wrap(True)
954 button = hildon.Button(fhsize, horbtn)
955 button.set_title('Description')
956 button.connect("clicked", self.show_info, 'description')
957 hbox1.pack_start(button, True, True, 0)
959 button = hildon.Button(fhsize, horbtn)
960 button.set_title('Credits')
961 button.connect("clicked", self.show_info, 'credits')
962 hbox1.pack_start(button, True, True, 0)
964 button = hildon.Button(fhsize, horbtn)
965 button.set_title('License')
966 button.connect("clicked", self.show_info, 'license')
967 hbox1.pack_start(button, True, True, 0)
969 button = hildon.Button(fhsize, horbtn)
970 button.set_title('Donate')
971 button.connect("clicked", self.show_info, 'donate')
972 hbox1.pack_start(button, True, True, 0)
974 button = hildon.Button(fhsize, horbtn)
975 button.set_title('Report ')
976 button.connect("clicked", self.show_info, 'report')
977 hbox1.pack_start(button, True, True, 0)
979 button = hildon.Button(fhsize, horbtn)
980 button.set_title(' Rate ')
981 button.connect("clicked", self.show_info, 'vote')
982 hbox1.pack_start(button, True, True, 0)
984 self.action_btn = hildon.Button(fhsize, horbtn)
985 self.image = gtk.Image()
987 self.show_info(None, 'description')
989 self.abdialog.vbox.pack_start(self.action_btn, False, False, 0)
990 self.abdialog.vbox.pack_start(self.image, False, False, 5)
991 self.abdialog.vbox.pack_start(self.info_lb, True, True, 0)
992 self.abdialog.vbox.pack_start(hbox1, False, False, 0)
994 self.abdialog.show_all()
995 self.action_btn.hide()
998 self.abdialog.destroy()
1000 def do_action(self, widget, action):
1003 self.abdialog.destroy()
1005 bus = dbus.SystemBus()
1006 proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
1007 iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
1009 if action == "donate":
1010 url = "http://stockthis.garage.maemo.org/donate.html"
1011 elif action == "report":
1012 url = "http://stockthis.garage.maemo.org/reporting.html"
1013 elif action == "vote":
1014 url = "http://maemo.org/downloads/product/stockthis"
1018 def show_info(self, widget, kind):
1019 if kind == 'license':
1020 self.action_btn.hide()
1022 info = """<small><b>StockThis</b> is free software. It's using a GPL version 2 license or at your election any later version.
1024 Logo by Daniel Martin Yerga.
1026 elif kind == 'credits':
1027 self.action_btn.hide()
1029 info = """<small><b>Written by</b> Daniel Martin Yerga (dyerga@gmail.com)
1031 <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>"""
1032 elif kind == 'description':
1033 self.action_btn.hide()
1035 info = """<b><big>StockThis 0.3</big></b>
1037 <i>StockThis is a stocks application for Maemo</i>
1040 stockthis.garage.maemo.org"""
1042 elif kind == 'donate':
1043 self.action_btn.show()
1045 self.action_btn.set_title('Make donation')
1047 self.action_btn.disconnect(self.id)
1048 self.id = self.action_btn.connect("clicked", self.do_action, "donate")
1049 info = """<small><b>StockThis</b> is a free (and gratis) software application.
1050 Developing good software takes time and hard work.
1052 <b>StockThis's author</b> develops the program in his spare time.
1053 If you like the program and it's helpful, consider donating a small amount of money.
1054 Donations are a great incentive and help the developer feel that the hard work is appreciated.</small>
1056 elif kind == 'report':
1057 self.action_btn.show()
1059 self.action_btn.set_title('Report bug')
1061 self.action_btn.disconnect(self.id)
1062 self.id = self.action_btn.connect("clicked", self.do_action, "report")
1063 info = """<small>StockThis is being improved thanks to bug reports that users have submitted. The author appreciates these reports.
1064 If the application is raising an error when you're using it, you have two choices to report this error:
1065 1) Send the log from the application menu (if there's an error in the log).
1066 2) Press the button and write a bug report with as much information as possible.</small>"""
1067 elif kind == 'vote':
1068 self.action_btn.show()
1070 self.image.set_from_file(imgdir + "maemoorg.png")
1071 self.action_btn.set_title('Rate StockThis')
1073 self.action_btn.disconnect(self.id)
1074 self.id = self.action_btn.connect("clicked", self.do_action, "vote")
1075 info = """<small>The downloads section in maemo.org has a nice system where you can rate applications.
1076 If you consider StockThis a good application (or a bad one too), you could rate it in maemo.org site.</small>"""
1078 self.info_lb.set_markup(info)
1083 def __init__(self, widget, logfile):
1085 dialog = gtk.Dialog(title='Log', parent=None)
1087 dialog.set_size_request(600, 350)
1089 parea = hildon.PannableArea()
1090 parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
1092 textview = hildon.TextView()
1093 textview.set_property("editable", False)
1094 textview.set_property("wrap-mode", gtk.WRAP_WORD)
1096 log = open(logfile, 'r')
1097 logtext = log.read()
1100 textview.get_buffer().set_text(logtext)
1103 dialog.vbox.pack_start(parea, True, True, 0)
1107 save_btn = hildon.Button(fhsize, horbtn)
1108 save_btn.set_title("Save")
1109 save_btn.connect('clicked', self.save, logfile, dialog)
1111 clear_btn = hildon.Button(fhsize, horbtn)
1112 clear_btn.set_title("Clear")
1113 clear_btn.connect('clicked', self.clear, textview, logfile)
1115 send_btn = hildon.Button(fhsize, horbtn)
1116 send_btn.set_title('Send')
1117 send_btn.connect('clicked', self.send, dialog, logfile)
1119 hbox.pack_start(save_btn, True, True, 0)
1120 hbox.pack_start(clear_btn, True, True, 0)
1121 hbox.pack_start(send_btn, True, True, 0)
1123 dialog.vbox.pack_start(hbox, False, False, 0)
1129 def show_filechooser(self, window, title, name, EXT):
1130 action = gtk.FILE_CHOOSER_ACTION_SAVE
1132 m = hildon.FileSystemModel()
1133 file_dialog = hildon.FileChooserDialog(window, action, m)
1134 file_dialog.set_title(title)
1136 file_dialog.set_current_name(name)
1137 HOME = os.path.expanduser("~")
1139 if os.path.exists(HOME + '/MyDocs/.documents'):
1140 file_dialog.set_current_folder(HOME + '/MyDocs/.documents')
1142 file_dialog.set_current_folder(HOME)
1144 file_dialog.set_default_response(gtk.RESPONSE_CANCEL)
1146 result = file_dialog.run()
1147 if result == gtk.RESPONSE_OK:
1148 namefile = file_dialog.get_filename()
1149 namefile, extension = os.path.splitext(namefile)
1150 namefile = namefile + "." + EXT
1153 file_dialog.destroy()
1158 def clear(self, widget, textview, logfile):
1159 textview.get_buffer().set_text('')
1160 f = open(logfile, 'w')
1163 def save(self, widget, logfile, dlg):
1165 filename = self.show_filechooser(dlg, "Save log file",
1166 "stockthis-log", "txt")
1172 shutil.copyfile(logfile, filename)
1173 stockspy.show_info_banner(widget, 'Log file saved')
1175 logger.exception("Saving log file")
1176 stockspy.show_info_banner(widget, 'Error saving the log file')
1178 def send(self, widget, dlg, logfile):
1179 sendtxt = ("You are going to send the log to the developers.\n"
1180 "This helps the developers to track problems with the application.\n"
1181 "It doesn't send any personal information (like passwords or similar).")
1183 dialog = hildon.hildon_note_new_confirmation(dlg, sendtxt)
1184 dialog.set_button_texts("Send", "Cancel")
1186 response = dialog.run()
1187 if response == gtk.RESPONSE_OK:
1188 self.do_pre_send(dlg, logfile)
1192 def do_pre_send(self, dlg, logfile):
1194 hildon.hildon_gtk_window_set_progress_indicator(dlg, 1)
1195 thread.start_new_thread(self._do_send, (dlg, logfile))
1197 def _do_send(self, dlg, logfile):
1198 import pycurl, shutil, random, commands
1201 for i in random.sample('abcdefghijkl123456789', 18):
1204 rnamepath = HOME + "/.stockthis/" + rname
1205 shutil.copyfile(logfile, rnamepath)
1207 gtkversion = "%s.%s.%s" % gtk.ver
1208 if os.path.exists("/etc/maemo_version"):
1209 mfile = open("/etc/maemo_version", 'r')
1210 maemoversion = mfile.read()
1215 opsystem = ' '.join(os.uname())
1216 pyversion = os.sys.version
1218 comm = ("awk '/Private_Dirty/{sum+=$2}END{print sum \"kB\"}'"
1219 " /proc/%s/smaps") % pid
1220 status, dirtymem = commands.getstatusoutput(comm)
1222 lfile = open(rnamepath, 'r')
1227 log = ("%s\nPython version: %s\nGtk version: %s\n"
1228 "Maemo version: %sOperating system: %s\n"
1229 "Dirty Memory: %s\nLog:\n%s") % (_version, pyversion, gtkversion,
1230 maemoversion, opsystem, dirtymem, log)
1232 lfile = open(rnamepath, 'w')
1236 url = "http://yerga.net/logs/uploader.php"
1237 data = [('uploadedfile', (pycurl.FORM_FILE, rnamepath)),]
1238 mycurl = pycurl.Curl()
1239 mycurl.setopt(pycurl.URL, url)
1240 mycurl.setopt(pycurl.HTTPPOST, data)
1244 os.remove(rnamepath)
1246 gtk.gdk.threads_enter()
1247 stockspy.show_info_banner(dlg, 'Log sent')
1248 gtk.gdk.threads_leave()
1249 hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1251 logger.exception("Sending log file")
1252 gtk.gdk.threads_enter()
1253 stockspy.show_info_banner(dlg, 'Error sending the log file')
1254 gtk.gdk.threads_leave()
1255 hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1258 if __name__ == "__main__":
1259 stockspy = StocksPy()
1260 gtk.gdk.threads_enter()
1262 gtk.gdk.threads_leave()