more markets, search function, and a bunch of minor things
[stockthis] / stockthis.py
1 #!/usr/bin/env python
2 # -*- coding: UTF8 -*-
3 # Copyright (C) 2008 by Daniel Martin Yerga
4 # <dyerga@gmail.com>
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.
9 #
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.
14 #
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.
18 #
19 # StocksPy: Application to get stocks data from Yahoo Finance.
20 # Version 0.1
21 #
22
23 _version = "StockThis 0.3 beta1 rev1"
24
25 import urllib2
26 import gtk, gobject
27 import os
28 import hildon
29 import marketdata
30 import settings
31 import logging
32 import sys
33
34 import osso
35 osso_c = osso.Context("net.yerga.stockthis", "0.3", False)
36
37 #detect if is ran locally or not
38 runningpath = sys.path[0]
39
40 if '/usr/share' in runningpath:
41     runninglocally = False
42 else:
43     runninglocally = True
44
45 HOME = os.path.expanduser("~")
46
47 settingsdb, imgdir, configdir, logfile = \
48     settings.define_paths(runninglocally, HOME)
49
50
51 logger = logging.getLogger('st')
52 logging.basicConfig(filename=logfile,level=logging.ERROR, filemode='w')
53
54 DEBUG = True
55
56 if DEBUG:
57     #set the main logger to DEBUG
58     logger.setLevel(logging.DEBUG)
59
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)
68
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
74
75 allnames = []
76 allsymbols = []
77
78 gtk.gdk.threads_init()
79
80 class StocksPy:
81
82     def __init__(self):
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)
90
91         self.create_menu(self.window)
92
93         vbox = gtk.VBox()
94         toolbar = self.main_toolbar(False, False, None, '', '', True)
95
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,
101                     marketdata.idmain)
102         tv.set_model(inmodel)
103         self._tv_columns(tv)
104         parea.add(tv)
105
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)
109
110         self.window.add(vbox)
111         self.window.show_all()
112
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)
118         menu.append(button)
119         button = gtk.Button("Log")
120         button.connect("clicked", Log, logfile)
121         menu.append(button)
122         menu.show_all()
123
124     def show_instrument_view(self, widget, path, column, inmodel, names,
125         ids, mindex):
126         market = inmodel[path][0]
127         names = names[mindex.index(market)]
128         ids = ids[mindex.index(market)]
129
130         window = hildon.StackableWindow()
131         self.create_menu(window)
132         window.set_title("StockThis - " + inmodel[path][1])
133
134         vbox = gtk.VBox()
135         toolbar = self.main_toolbar(False, False, None, '', '', False)
136
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)
142         tv.set_model(model)
143         self._tv_columns(tv)
144         parea.add(tv)
145
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)
149
150         window.add(vbox)
151         window.show_all()
152
153     def horizontal_mov(self, parea, direction, initial_x, initial_y):
154         #direction = 2 right-to-left
155         #direction = 3 lefto-to-right
156
157         vadj = parea.get_vadjustment()
158         val = vadj.get_value()
159
160         if direction == 2:
161             if int(val)-2500 < 0:
162                  parea.scroll_to(-1, 0)
163             else:
164                 parea.scroll_to(-1, int(val)-2500)
165         elif direction == 3:
166             parea.scroll_to(-1, int(val)+3500)
167
168         #print val
169
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')
174
175         #Currencies and ETFs should show the list now -> view = True
176         #Other items show a new list with options
177         view = False
178         for i in marketdata.localids[(len(marketdata.localids)-2):]:
179             for j in i:
180                 if quote[0] == j:
181                     #print j
182                     view = True
183
184         if not view:
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)
190                 return
191             if quote[0] in marketdata.idotmarkets:
192                 self.show_instrument_view(widget, path, column, model,
193                                         marketdata.omnames,
194                                         marketdata.omsymbols,
195                                         marketdata.idotmarkets)
196                 return
197             if quote[0] in marketdata.ideumarkets:
198                 self.show_instrument_view(widget, path, column, model,
199                                         marketdata.eunames,
200                                         marketdata.eusymbols,
201                                         marketdata.ideumarkets)
202                 return
203             if quote[0] in marketdata.idusmarkets:
204                 self.show_instrument_view(widget, path, column, model,
205                                         marketdata.usnames,
206                                         marketdata.ussymbols,
207                                         marketdata.idusmarkets)
208                 return
209
210
211         win = hildon.StackableWindow()
212         self.create_menu(win)
213         win.set_title("StockThis - Quotes View - " + quote[1])
214
215         vbox = gtk.VBox()
216
217         ltitle = gtk.Label('')
218         ltitle.set_markup('<b><big>' + quote[1].replace('&', '') +
219                                  '</big></b>')
220         color = gtk.gdk.color_parse("#03A5FF")
221         ltitle.modify_fg(gtk.STATE_NORMAL, color)
222
223         parea = hildon.PannableArea()
224
225         vbox1 = gtk.VBox()
226
227         hbox = gtk.HBox()
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)
234
235         hbox = gtk.HBox()
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)
244
245         hbox = gtk.HBox()
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)
252
253         hbox = gtk.HBox()
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)
260
261         hbox = gtk.HBox()
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)
268
269         hbox = gtk.HBox()
270         button1 = hildon.PickerButton(fhsize, horbtn)
271         data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
272                 "900", "1000"]
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)
279
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)
284
285         hbox1 = gtk.HBox()
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)
291
292         hbox2 = gtk.HBox()
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)
298
299         hbox3 = gtk.HBox()
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)
305
306         if not portfolio:
307             vbox1.pack_start(hbox, False, False, 0)
308         else:
309             vbox1.pack_start(hbox1, True, True, 0)
310             vbox1.pack_start(hbox2, True, True, 0)
311             vbox1.pack_start(hbox3, True, True, 0)
312
313         parea.add_with_viewport(vbox1)
314
315         widgets = [win, ltitle, lprice, lchange,  lpercent, lvolume, l52whigh,
316                     l52wlow, lshares, holdingsvalue, dayvaluechange]
317
318         toolbar = self.main_toolbar(True, portfolio, widgets, quote[0], quote[1], False)
319
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)
325
326
327         win.add(vbox)
328         win.show_all()
329         self.show_data(quote[0], widgets, shares)
330
331     def get_shares_from_symbol(self, symbol):
332         shares = "0"
333         try:
334             portfolio_data = settings.load_portfolio(settingsdb)
335             for item in portfolio_data :
336                 if symbol in item:
337                     shares = item[2]
338             return shares
339         except:
340             logger.exception("Getting shares from symbol")
341             return shares
342
343     def add_to_portfolio(self, widget, button, symbol, name):
344         shares = button.get_value()
345
346         try:
347             portfolio = settings.load_portfolio(settingsdb)
348             index = "None"
349             for item in portfolio:
350                 if symbol in item:
351                     index = portfolio.index(item)
352
353             item = [symbol, name, shares, '-']
354
355             if index is "None":
356                 settings.insert_new_item_to_portfolio(settingsdb, item)
357             else:
358                 settings.delete_item_from_portfolio(settingsdb, symbol)
359                 settings.insert_new_item_to_portfolio(settingsdb, item)
360
361             self.show_info_banner(widget, "Added to portfolio")
362         except:
363             logger.exception("Adding to portfolio")
364             self.show_info_banner(widget, "Error adding to portfolio")
365
366
367     def create_selector(self, data, entry):
368         if entry:
369             selector = hildon.TouchSelectorEntry(text=True)
370         else:
371             selector = hildon.TouchSelector(text=True)
372         for i in range(len(data)):
373             selector.append_text(data[i])
374
375         return selector
376
377     def show_data(self, symbol, widgets, shares):
378         import thread
379         winprogind(widgets[0], 1)
380         thread.start_new_thread(self.get_data, (symbol, widgets, shares))
381
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
385
386         try:
387             data = yt.get_all(symbol)
388         except:
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>')
393
394         try:
395             ch_percent = \
396                     100.0 * float(data['change'])/(float(data['price']) - \
397                     float(data['change']))
398         except ValueError:
399             ch_percent = 0.0
400
401         lprice.set_label(data['price'])
402         lchange.set_label(data['change'])
403         lpercent.set_label('%6.2f %%' % ch_percent)
404
405         if '-' in data['change']:
406             color = gtk.gdk.color_parse("#FF0000")
407         else:
408             color = gtk.gdk.color_parse("#16EB78")
409
410         lpercent.modify_fg(gtk.STATE_NORMAL, color)
411         lchange.modify_fg(gtk.STATE_NORMAL, color)
412
413         lvolume.set_label(data['volume'])
414         l52whigh.set_label(data['52_week_high'])
415         l52wlow.set_label(data['52_week_low'])
416
417         try:
418             daychange = float(shares)*float(data['change'])
419         except ValueError:
420             daychange = 'N/A'
421         try:
422             holdvalue = float(shares)*float(data['price'])
423         except ValueError:
424             holdvalue = 'N/A'
425
426         dayvaluechange.set_label(str(daychange))
427         holdingsvalue.set_label(str(holdvalue))
428
429         winprogind(win, 0)
430
431     def refresh_stock_data(self, widget, portfolio, widgets, symbol):
432         if portfolio:
433             shares = self.get_shares_from_symbol(symbol)
434         else:
435             shares = "0"
436
437         self.show_data(symbol, widgets, shares)
438
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)
443
444         vbox = gtk.VBox()
445         toolbar = self.main_toolbar(False, True, None, '', '', False)
446
447         self.graphs_title = gtk.Label(name)
448         color = gtk.gdk.color_parse("#03A5FF")
449         self.graphs_title.modify_fg(gtk.STATE_NORMAL, color)
450
451         parea = hildon.PannableArea()
452
453         hbox = gtk.HBox()
454         hbox.set_homogeneous(True)
455
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)
460
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)
465
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)
470
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)
475
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)
480
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)
485
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)
490
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)
495
496         vbox1 = gtk.VBox()
497         vbox1.pack_start(hbox, False, False, 0)
498
499         self.graph = gtk.Image()
500         vbox1.pack_start(self.graph, True, True, 0)
501
502         parea.add_with_viewport(vbox1)
503
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)
509
510         win.add(vbox)
511         win.show_all()
512
513         self.show_graph(None, '1d', win, symbol)
514
515     def show_graph(self, widget, option, win, symbol):
516         import thread
517         winprogind(win, 1)
518         thread.start_new_thread(self.get_graph_data, (option, win, symbol))
519
520     def get_graph_data(self, option, win, symbol):
521         if option == '1d':
522             url = 'http://uk.ichart.yahoo.com/b?s=%s' % symbol
523         elif option == '5d':
524             url = 'http://uk.ichart.yahoo.com/w?s=%s' % symbol
525         elif option == '3m':
526             url = 'http://chart.finance.yahoo.com/c/3m/s/%s' % symbol.lower()
527         elif option == '6m':
528             url = 'http://chart.finance.yahoo.com/c/6m/s/%s' % symbol.lower()
529         elif option == '1y':
530             url = 'http://chart.finance.yahoo.com/c/1y/s/%s' % symbol.lower()
531         elif option == '2y':
532             url = 'http://chart.finance.yahoo.com/c/2y/s/%s' % symbol.lower()
533         elif option == '5y':
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()
537
538         try:
539             myimg = urllib2.urlopen(url)
540             imgdata = myimg.read()
541
542             pbl = gtk.gdk.PixbufLoader()
543             pbl.write(imgdata)
544
545             pbuf = pbl.get_pixbuf()
546             pbl.close()
547             self.graph.set_from_pixbuf(pbuf)
548             winprogind(win, 0)
549         except:
550             logger.exception("Getting graph data")
551             winprogind(win, 0)
552             self.graphs_title.set_label('Failed to get data')
553             self.graph.destroy()
554
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)
559
560         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
561         treeview.append_column(column)
562
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])
568         return lstore
569
570     def main_toolbar(self, quotesview, portfolio, widgets, symbol, name, initial):
571         toolbar = gtk.HBox()
572         #toolbar.set_homogeneous(True)
573
574         portfolio_btn = hildon.Button(fhsize, horbtn)
575         portfolio_btn.set_title("Portfolio")
576         portfolio_btn.connect("clicked", self.show_portfolio_view)
577
578         graph_btn = hildon.Button(fhsize, horbtn)
579         graph_btn.set_title("Graph")
580         graph_btn.connect("clicked", self.show_graph_view, symbol, name)
581
582         refresh_btn = hildon.Button(fhsize, horbtn)
583         refresh_btn.set_title("Refresh")
584         refresh_btn.connect("clicked", self.refresh_stock_data, portfolio,
585                             widgets, symbol)
586
587
588         stockiconspath = "/usr/share/icons/hicolor/48x48/hildon/"
589         info_btn = hildon.Button(fhsize, horbtn)
590         img = gtk.Image()
591         img.set_from_file(stockiconspath + "general_information.png")
592         info_btn.set_image(img)
593         info_btn.connect("clicked", self.show_app_information)
594
595         search_btn = hildon.Button(fhsize, horbtn)
596         img = gtk.Image()
597         img.set_from_file(stockiconspath + "general_search.png")
598         search_btn.set_image(img)
599         search_btn.connect("clicked", self.show_search_dialog)
600
601         if not portfolio:
602             toolbar.pack_start(portfolio_btn)
603             if not quotesview:
604                 toolbar.pack_start(info_btn, False, False, 0)
605         if quotesview:
606             toolbar.pack_start(graph_btn)
607             toolbar.pack_start(refresh_btn)
608
609         if initial:
610             toolbar.pack_start(search_btn, False, False, 0)
611
612         toolbar.show_all()
613
614         return toolbar
615
616     def show_search_dialog(self, widget):
617         dlg = gtk.Dialog(title='Search company', parent=None, flags=0)
618         dlg.set_has_separator(False)
619
620         entry = hildon.Entry(fhsize)
621         dlg.vbox.pack_start(entry, False, False, 0)
622
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)
627
628         dlg.show_all()
629         dlg.run()
630         dlg.destroy()
631
632
633     def do_search(self, widget, entry, dlg):
634         import thread
635         text = entry.get_text()
636         dlg.destroy()
637
638         winprogind(self.window, 1)
639         thread.start_new_thread(self._really_do_search, (text,))
640
641     def _really_do_search(self, text):
642
643         if allnames == []:
644             for market in marketdata.eunames:
645                 for company in market:
646                     if not company in allnames:
647                         allnames.append(company)
648
649             for market in marketdata.omnames:
650                 for company in market:
651                     if not company in allnames:
652                         allnames.append(company)
653
654             for market in marketdata.usnames:
655                 for company in market:
656                     if not company in allnames:
657                         allnames.append(company)
658
659         if allsymbols == []:
660             for market in marketdata.eusymbols:
661                 for company in market:
662                     if not company in allsymbols:
663                         allsymbols.append(company)
664
665             for market in marketdata.omsymbols:
666                 for company in market:
667                     if not company in allsymbols:
668                         allsymbols.append(company)
669
670             for market in marketdata.ussymbols:
671                 for company in market:
672                     if not company in allsymbols:
673                         allsymbols.append(company)
674
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])
680
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()
686             return
687
688         gtk.gdk.threads_enter()
689         self.show_search_screen(new_model, text)
690         gtk.gdk.threads_leave()
691         winprogind(self.window, 0)
692
693
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)
698
699         vbox = gtk.VBox()
700         toolbar = self.main_toolbar(False, False, None, '', '', False)
701
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)
706         tv.set_model(model)
707         self._tv_columns(tv)
708         parea.add(tv)
709
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)
713
714         window.add(vbox)
715         window.show_all()
716
717
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."))
726
727     def show_portfolio_view(self, widget):
728         data = settings.load_portfolio(settingsdb)
729
730         win = hildon.StackableWindow()
731         self.create_menu(win)
732         win.set_title("StockThis - Portfolio")
733
734         vbox = gtk.VBox()
735
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)
743         parea.add(tv)
744
745         hbox = gtk.HBox()
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)
750
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)
755
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)
760
761         vbox.pack_start(parea, True, True, 0)
762         vbox.pack_start(hbox, False, False, 0)
763         win.add(vbox)
764         win.show_all()
765
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)
769
770         button1 = hildon.PickerButton(fhsize, horbtn)
771         data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
772                 "900", "1000"]
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)
778
779         entry1 = hildon.Entry(fhsize)
780         entry1.set_placeholder("Name")
781         dlg.vbox.pack_start(entry1, False, False, 0)
782
783         entry2 = hildon.Entry(fhsize)
784         entry2.set_placeholder("Yahoo Finance symbol")
785         dlg.vbox.pack_start(entry2, False, False, 0)
786
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)
791
792         dlg.show_all()
793         dlg.run()
794         dlg.destroy()
795
796
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()
801
802         self.add_to_portfolio(widget, button, symbol, name)
803         dlg.destroy()
804
805         niter = self.portfolio_model.append()
806         self.portfolio_model.set(niter, 0, symbol, 1, name, 2, shares, 3, "-")
807
808     def remove_item(self, widget):
809         win = hildon.StackableWindow()
810         win.fullscreen()
811         toolbar = hildon.EditToolbar("Choose items to delete", "Delete")
812         win.set_edit_toolbar(toolbar)
813
814         vbox = gtk.VBox()
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)
821         parea.add(tv)
822
823         toolbar.connect("button-clicked", self.delete_from_portfolio, win, tv,
824                         selection)
825         toolbar.connect_object("arrow-clicked", gtk.Window.destroy, win)
826
827         vbox.pack_start(parea, True, True, 0)
828         win.add(vbox)
829         win.show_all()
830
831     def delete_from_portfolio(self, widget, win, tv, selection):
832         if not self.is_treeview_selected(tv):
833             return
834
835         conf = self.show_confirmation(win, "Delete items?")
836
837         if conf:
838             try:
839                 selmodel, selected = selection.get_selected_rows()
840                 iters = [selmodel.get_iter(path) for path in selected]
841                 for i in iters:
842                     symbol = selmodel.get_value(i, 0)
843                     settings.delete_item_from_portfolio(settingsdb, symbol)
844                     selmodel.remove(i)
845             except:
846                 logger.exception("Deleting item from portfolio")
847                 self.info_banner(widget, "Error deleting item")
848
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)
853
854         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
855         column.set_property("expand", True)
856         treeview.append_column(column)
857
858         column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
859         column.set_visible(False)
860         treeview.append_column(column)
861
862         column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
863         column.set_visible(False)
864         treeview.append_column(column)
865
866     def refresh_portfolio(self, widget, tv, win):
867         data = settings.load_portfolio(settingsdb)
868         import thread
869         winprogind(win, 1)
870         thread.start_new_thread(self._do_refresh_portfolio, (data, tv, win))
871
872     def _do_refresh_portfolio(self, data, tv, win):
873         for item in data:
874             item[3] = self.get_price(item[0])
875
876         self.portfolio_model = self._create_portfolio_model(data)
877         tv.set_model(self.portfolio_model)
878         winprogind(win, 0)
879
880     def get_price(self, symbol):
881         from ystockquote import ystockquote as yt
882         try:
883             price = yt.get_price(symbol)
884             return price
885         except:
886             logger.exception("Getting price from Yahoo")
887             return "N/A"
888
889     def _create_portfolio_model(self, data):
890         lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
891                                 gobject.TYPE_STRING, gobject.TYPE_STRING)
892         for item in data:
893             iter = lstore.append()
894             lstore.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3])
895         return lstore
896
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)
901
902         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
903         column.set_property("expand", True)
904         treeview.append_column(column)
905
906         column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
907         treeview.append_column(column)
908
909         column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
910         treeview.append_column(column)
911
912     def show_confirmation(self, window, msg):
913         dialog = hildon.hildon_note_new_confirmation(window, msg)
914         dialog.show_all()
915         result = dialog.run()
916         if result == gtk.RESPONSE_OK:
917             dialog.destroy()
918             return True
919
920         dialog.destroy()
921         return False
922
923     def show_information_note(self, window, msg):
924         dialog = hildon.hildon_note_new_information(window, msg)
925         dialog.show_all()
926         result = dialog.run()
927         dialog.destroy()
928
929     def show_info_banner(self, widget, msg):
930         hildon.hildon_banner_show_information(widget, 'qgn_note_infoprint', msg)
931
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')
936             return False
937         else:
938             return True
939
940 class About:
941
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)
946
947         self.info_lb = gtk.Label()
948         self.info_lb.set_line_wrap(True)
949
950         self.id = False
951
952         hbox1 = gtk.HBox()
953
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)
958
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)
963
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)
968
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)
973
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)
978
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)
983
984         self.action_btn = hildon.Button(fhsize, horbtn)
985         self.image = gtk.Image()
986
987         self.show_info(None, 'description')
988
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)
993
994         self.abdialog.show_all()
995         self.action_btn.hide()
996         self.image.hide()
997         self.abdialog.run()
998         self.abdialog.destroy()
999
1000     def do_action(self, widget, action):
1001         import dbus
1002
1003         self.abdialog.destroy()
1004
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')
1008
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"
1015
1016         iface.load_url(url)
1017
1018     def show_info(self, widget, kind):
1019         if kind == 'license':
1020             self.action_btn.hide()
1021             self.image.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.
1023
1024 Logo by Daniel Martin Yerga.
1025 </small>"""
1026         elif kind == 'credits':
1027             self.action_btn.hide()
1028             self.image.hide()
1029             info = """<small><b>Written by</b> Daniel Martin Yerga (dyerga@gmail.com)
1030
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()
1034             self.image.hide()
1035             info = """<b><big>StockThis 0.3</big></b>
1036
1037 <i>StockThis is a stocks application for Maemo</i>
1038
1039 <b>Web Page</b>:
1040 stockthis.garage.maemo.org"""
1041
1042         elif kind == 'donate':
1043             self.action_btn.show()
1044             self.image.hide()
1045             self.action_btn.set_title('Make donation')
1046             if self.id:
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.
1051
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>
1055 """
1056         elif kind == 'report':
1057             self.action_btn.show()
1058             self.image.hide()
1059             self.action_btn.set_title('Report bug')
1060             if self.id:
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()
1069             self.image.show()
1070             self.image.set_from_file(imgdir + "maemoorg.png")
1071             self.action_btn.set_title('Rate StockThis')
1072             if self.id:
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>"""
1077
1078         self.info_lb.set_markup(info)
1079
1080
1081 class Log:
1082
1083     def __init__(self, widget, logfile):
1084         #Log dialog UI
1085         dialog = gtk.Dialog(title='Log', parent=None)
1086
1087         dialog.set_size_request(600, 350)
1088
1089         parea = hildon.PannableArea()
1090         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
1091
1092         textview = hildon.TextView()
1093         textview.set_property("editable", False)
1094         textview.set_property("wrap-mode", gtk.WRAP_WORD)
1095
1096         log = open(logfile, 'r')
1097         logtext = log.read()
1098         log.close()
1099
1100         textview.get_buffer().set_text(logtext)
1101         parea.add(textview)
1102
1103         dialog.vbox.pack_start(parea, True, True, 0)
1104
1105         hbox = gtk.HBox()
1106
1107         save_btn = hildon.Button(fhsize, horbtn)
1108         save_btn.set_title("Save")
1109         save_btn.connect('clicked', self.save, logfile, dialog)
1110
1111         clear_btn = hildon.Button(fhsize, horbtn)
1112         clear_btn.set_title("Clear")
1113         clear_btn.connect('clicked', self.clear, textview, logfile)
1114
1115         send_btn = hildon.Button(fhsize, horbtn)
1116         send_btn.set_title('Send')
1117         send_btn.connect('clicked', self.send, dialog, logfile)
1118
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)
1122
1123         dialog.vbox.pack_start(hbox, False, False, 0)
1124
1125         dialog.show_all()
1126         dialog.run()
1127         dialog.destroy()
1128
1129     def show_filechooser(self, window, title, name, EXT):
1130         action = gtk.FILE_CHOOSER_ACTION_SAVE
1131
1132         m = hildon.FileSystemModel()
1133         file_dialog = hildon.FileChooserDialog(window, action, m)
1134         file_dialog.set_title(title)
1135
1136         file_dialog.set_current_name(name)
1137         HOME = os.path.expanduser("~")
1138
1139         if os.path.exists(HOME + '/MyDocs/.documents'):
1140             file_dialog.set_current_folder(HOME + '/MyDocs/.documents')
1141         else:
1142             file_dialog.set_current_folder(HOME)
1143
1144         file_dialog.set_default_response(gtk.RESPONSE_CANCEL)
1145
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
1151         else:
1152             namefile = None
1153         file_dialog.destroy()
1154
1155         return namefile
1156
1157
1158     def clear(self, widget, textview, logfile):
1159         textview.get_buffer().set_text('')
1160         f = open(logfile, 'w')
1161         f.close()
1162
1163     def save(self, widget, logfile, dlg):
1164         import shutil
1165         filename = self.show_filechooser(dlg, "Save log file",
1166                     "stockthis-log", "txt")
1167
1168         if not filename:
1169             return
1170
1171         try:
1172             shutil.copyfile(logfile, filename)
1173             stockspy.show_info_banner(widget, 'Log file saved')
1174         except:
1175             logger.exception("Saving log file")
1176             stockspy.show_info_banner(widget, 'Error saving the log file')
1177
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).")
1182
1183         dialog = hildon.hildon_note_new_confirmation(dlg, sendtxt)
1184         dialog.set_button_texts("Send", "Cancel")
1185         dialog.show_all()
1186         response = dialog.run()
1187         if response == gtk.RESPONSE_OK:
1188             self.do_pre_send(dlg, logfile)
1189
1190         dialog.destroy()
1191
1192     def do_pre_send(self, dlg, logfile):
1193         import thread
1194         hildon.hildon_gtk_window_set_progress_indicator(dlg, 1)
1195         thread.start_new_thread(self._do_send, (dlg, logfile))
1196
1197     def _do_send(self, dlg, logfile):
1198         import pycurl, shutil, random, commands
1199         try:
1200             rname = ''
1201             for i in random.sample('abcdefghijkl123456789', 18):
1202                 rname += i
1203
1204             rnamepath = HOME + "/.stockthis/" + rname
1205             shutil.copyfile(logfile, rnamepath)
1206
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()
1211                 mfile.close()
1212             else:
1213                 maemoversion = ''
1214
1215             opsystem = ' '.join(os.uname())
1216             pyversion = os.sys.version
1217             pid = os.getpid()
1218             comm = ("awk '/Private_Dirty/{sum+=$2}END{print sum \"kB\"}'"
1219             " /proc/%s/smaps") % pid
1220             status, dirtymem = commands.getstatusoutput(comm)
1221
1222             lfile = open(rnamepath, 'r')
1223             log = lfile.read()
1224             lfile.close()
1225
1226
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)
1231
1232             lfile = open(rnamepath, 'w')
1233             lfile.write(log)
1234             lfile.close()
1235
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)
1241
1242             mycurl.perform()
1243             mycurl.close()
1244             os.remove(rnamepath)
1245
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)
1250         except:
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)
1256
1257
1258 if __name__ == "__main__":
1259     stockspy = StocksPy()
1260     gtk.gdk.threads_enter()
1261     gtk.main()
1262     gtk.gdk.threads_leave()