c2a8b016ebd8172b2d9ceced278553d4bdfea60f
[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 # StocksThis: Application to get stocks data from Yahoo Finance.
20 #
21
22 _version = "StockThis 0.3 beta1 rev1"
23
24 import urllib2
25 import gtk, gobject
26 import os
27 import hildon
28 import marketdata
29 import settings
30 import logging
31 import sys
32
33 from portrait import FremantleRotation
34
35 import osso
36 osso_c = osso.Context("net.yerga.stockthis", "0.3", False)
37
38 #detect if is ran locally or not
39 runningpath = sys.path[0]
40
41 if '/usr/share' in runningpath:
42     runninglocally = False
43 else:
44     runninglocally = True
45
46 HOME = os.path.expanduser("~")
47
48 settingsdb, imgdir, configdir, logfile = \
49     settings.define_paths(runninglocally, HOME)
50
51
52 logger = logging.getLogger('st')
53 logging.basicConfig(filename=logfile,level=logging.ERROR, filemode='w')
54
55 DEBUG = True
56
57 if DEBUG:
58     #set the main logger to DEBUG
59     logger.setLevel(logging.DEBUG)
60
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)
69
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
75
76 allnames = []
77 allsymbols = []
78
79 gtk.gdk.threads_init()
80
81 class StocksPy:
82
83     def __init__(self):
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)
91
92         FremantleRotation('StockThis', None, "0.3", 0)
93
94         self.create_menu(self.window)
95
96         vbox = gtk.VBox()
97         toolbar = self.main_toolbar(False, False, None, '', '', True)
98
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,
104                     marketdata.idmain)
105         tv.set_model(inmodel)
106         self._tv_columns(tv)
107         parea.add(tv)
108
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)
112
113         self.window.add(vbox)
114         self.window.show_all()
115
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)
121         menu.append(button)
122         button = gtk.Button("Log")
123         button.connect("clicked", Log, logfile)
124         menu.append(button)
125         menu.show_all()
126
127     def show_instrument_view(self, widget, path, column, inmodel, names,
128         ids, mindex):
129         market = inmodel[path][0]
130         names = names[mindex.index(market)]
131         ids = ids[mindex.index(market)]
132
133         window = hildon.StackableWindow()
134         self.create_menu(window)
135         window.set_title("StockThis - " + inmodel[path][1])
136
137         vbox = gtk.VBox()
138         toolbar = self.main_toolbar(False, False, None, '', '', False)
139
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)
145         tv.set_model(model)
146         self._tv_columns(tv)
147         parea.add(tv)
148
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)
152
153         window.add(vbox)
154         window.show_all()
155
156     def horizontal_mov(self, parea, direction, initial_x, initial_y):
157         #direction = 2 right-to-left
158         #direction = 3 lefto-to-right
159
160         vadj = parea.get_vadjustment()
161         val = vadj.get_value()
162
163         if direction == 2:
164             if int(val)-2500 < 0:
165                  parea.scroll_to(-1, 0)
166             else:
167                 parea.scroll_to(-1, int(val)-2500)
168         elif direction == 3:
169             parea.scroll_to(-1, int(val)+3500)
170
171         #print val
172
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')
177
178         #Currencies and ETFs should show the list now -> view = True
179         #Other items show a new list with options
180         view = False
181         for i in marketdata.localids[(len(marketdata.localids)-2):]:
182             for j in i:
183                 if quote[0] == j:
184                     #print j
185                     view = True
186
187         if not view:
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)
193                 return
194             if quote[0] in marketdata.idotmarkets:
195                 self.show_instrument_view(widget, path, column, model,
196                                         marketdata.omnames,
197                                         marketdata.omsymbols,
198                                         marketdata.idotmarkets)
199                 return
200             if quote[0] in marketdata.ideumarkets:
201                 self.show_instrument_view(widget, path, column, model,
202                                         marketdata.eunames,
203                                         marketdata.eusymbols,
204                                         marketdata.ideumarkets)
205                 return
206             if quote[0] in marketdata.idusmarkets:
207                 self.show_instrument_view(widget, path, column, model,
208                                         marketdata.usnames,
209                                         marketdata.ussymbols,
210                                         marketdata.idusmarkets)
211                 return
212
213
214         win = hildon.StackableWindow()
215         self.create_menu(win)
216         win.set_title("StockThis - Quotes View - " + quote[1])
217
218         vbox = gtk.VBox()
219
220         ltitle = gtk.Label('')
221         ltitle.set_markup('<b><big>' + quote[1].replace('&', '') +
222                                  '</big></b>')
223         color = gtk.gdk.color_parse("#03A5FF")
224         ltitle.modify_fg(gtk.STATE_NORMAL, color)
225
226         parea = hildon.PannableArea()
227         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
228
229         vbox1 = gtk.VBox()
230
231         hbox = gtk.HBox()
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)
238         #else:
239         #    hbox.pack_start(lprice, False, False, 245)
240
241         vbox1.pack_start(hbox, True, True, 0)
242
243         hbox = gtk.HBox()
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)
252         #else:
253         #    hbox.pack_start(lchange, False, False, 205)
254         #    hbox.pack_start(lpercent, False, False, 0)
255
256         vbox1.pack_start(hbox, True, True, 0)
257
258         hbox = gtk.HBox()
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)
265         #else:
266         #    hbox.pack_start(lvolume, False, False, 207)
267
268         vbox1.pack_start(hbox, True, True, 0)
269
270         hbox = gtk.HBox()
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)
277         #else:
278         #    hbox.pack_start(l52whigh, False, False, 125)
279
280         vbox1.pack_start(hbox, True, True, 0)
281
282         hbox = gtk.HBox()
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)
289         #else:
290         #    hbox.pack_start(l52wlow, False, False, 140)
291         vbox1.pack_start(hbox, True, True, 0)
292
293         #if self.is_portrait():
294         #    hbox = gtk.VBox()
295         #else:
296         hbox = gtk.HBox()
297         button1 = hildon.PickerButton(fhsize, horbtn)
298         data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
299                 "900", "1000"]
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)
306
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)
311
312         hbox1 = gtk.HBox()
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)
319         #else:
320         #    hbox1.pack_start(lshares, False, False, 220)
321
322         hbox2 = gtk.HBox()
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)
329         #else:
330         #    hbox2.pack_start(holdingsvalue, False, False, 105)
331
332         hbox3 = gtk.HBox()
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)
339         #else:
340         #    hbox3.pack_start(dayvaluechange, False, False, 45)
341
342         if not portfolio:
343             vbox1.pack_start(hbox, False, False, 0)
344         else:
345             vbox1.pack_start(hbox1, True, True, 0)
346             vbox1.pack_start(hbox2, True, True, 0)
347             vbox1.pack_start(hbox3, True, True, 0)
348
349         parea.add_with_viewport(vbox1)
350
351         widgets = [win, ltitle, lprice, lchange,  lpercent, lvolume, l52whigh,
352                     l52wlow, lshares, holdingsvalue, dayvaluechange]
353
354         toolbar = self.main_toolbar(True, portfolio, widgets, quote[0], quote[1], False)
355
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)
361
362
363         win.add(vbox)
364         win.show_all()
365         self.show_data(quote[0], widgets, shares)
366
367     def is_portrait(self):
368         width = gtk.gdk.screen_width()
369         height = gtk.gdk.screen_height()
370         if width > height:
371             return False
372         else:
373             return True
374
375     def get_shares_from_symbol(self, symbol):
376         shares = "0"
377         try:
378             portfolio_data = settings.load_portfolio(settingsdb)
379             for item in portfolio_data :
380                 if symbol in item:
381                     shares = item[2]
382             return shares
383         except:
384             logger.exception("Getting shares from symbol")
385             return shares
386
387     def add_to_portfolio(self, widget, button, symbol, name):
388         shares = button.get_value()
389
390         try:
391             portfolio = settings.load_portfolio(settingsdb)
392             index = "None"
393             for item in portfolio:
394                 if symbol in item:
395                     index = portfolio.index(item)
396
397             item = [symbol, name, shares, '-']
398
399             if index is "None":
400                 settings.insert_new_item_to_portfolio(settingsdb, item)
401             else:
402                 settings.delete_item_from_portfolio(settingsdb, symbol)
403                 settings.insert_new_item_to_portfolio(settingsdb, item)
404
405             self.show_info_banner(widget, "Added to portfolio")
406         except:
407             logger.exception("Adding to portfolio")
408             self.show_info_banner(widget, "Error adding to portfolio")
409
410
411     def create_selector(self, data, entry):
412         if entry:
413             selector = hildon.TouchSelectorEntry(text=True)
414         else:
415             selector = hildon.TouchSelector(text=True)
416         for i in range(len(data)):
417             selector.append_text(data[i])
418
419         return selector
420
421     def show_data(self, symbol, widgets, shares):
422         import thread
423         winprogind(widgets[0], 1)
424         thread.start_new_thread(self.get_data, (symbol, widgets, shares))
425
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
429
430         try:
431             data = yt.get_all(symbol)
432         except:
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>')
437
438         try:
439             ch_percent = \
440                     100.0 * float(data['change'])/(float(data['price']) - \
441                     float(data['change']))
442         except ValueError:
443             ch_percent = 0.0
444
445         lprice.set_label(data['price'])
446         lchange.set_label(data['change'])
447         lpercent.set_label('%6.2f %%' % ch_percent)
448
449         if '-' in data['change']:
450             color = gtk.gdk.color_parse("#FF0000")
451         else:
452             color = gtk.gdk.color_parse("#16EB78")
453
454         lpercent.modify_fg(gtk.STATE_NORMAL, color)
455         lchange.modify_fg(gtk.STATE_NORMAL, color)
456
457         lvolume.set_label(data['volume'])
458         l52whigh.set_label(data['52_week_high'])
459         l52wlow.set_label(data['52_week_low'])
460
461         try:
462             daychange = float(shares)*float(data['change'])
463         except ValueError:
464             daychange = 'N/A'
465         try:
466             holdvalue = float(shares)*float(data['price'])
467         except ValueError:
468             holdvalue = 'N/A'
469
470         dayvaluechange.set_label(str(daychange))
471         holdingsvalue.set_label(str(holdvalue))
472
473         winprogind(win, 0)
474
475     def refresh_stock_data(self, widget, portfolio, widgets, symbol):
476         if portfolio:
477             shares = self.get_shares_from_symbol(symbol)
478         else:
479             shares = "0"
480
481         self.show_data(symbol, widgets, shares)
482
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)
487
488         vbox = gtk.VBox()
489         toolbar = self.main_toolbar(False, True, None, '', '', False)
490
491         self.graphs_title = gtk.Label(name)
492         color = gtk.gdk.color_parse("#03A5FF")
493         self.graphs_title.modify_fg(gtk.STATE_NORMAL, color)
494
495         parea = hildon.PannableArea()
496         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
497
498         vbox1 = gtk.VBox()
499
500         hbox = gtk.HBox()
501         hbox.set_homogeneous(True)
502
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)
507
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)
512
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)
517
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)
522
523         vbox1.pack_start(hbox, False, False, 0)
524         hbox = gtk.HBox()
525         hbox.set_homogeneous(True)
526
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)
531
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)
536
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)
541
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)
546
547         vbox1.pack_start(hbox, False, False, 0)
548
549         self.graph = gtk.Image()
550         vbox1.pack_start(self.graph, True, True, 0)
551
552         parea.add_with_viewport(vbox1)
553
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)
559
560         win.add(vbox)
561         win.show_all()
562
563         self.show_graph(None, '1d', win, symbol)
564
565     def show_graph(self, widget, option, win, symbol):
566         import thread
567         winprogind(win, 1)
568         thread.start_new_thread(self.get_graph_data, (option, win, symbol))
569
570     def get_graph_data(self, option, win, symbol):
571         if option == '1d':
572             url = 'http://uk.ichart.yahoo.com/b?s=%s' % symbol
573         elif option == '5d':
574             url = 'http://uk.ichart.yahoo.com/w?s=%s' % symbol
575         elif option == '3m':
576             url = 'http://chart.finance.yahoo.com/c/3m/s/%s' % symbol.lower()
577         elif option == '6m':
578             url = 'http://chart.finance.yahoo.com/c/6m/s/%s' % symbol.lower()
579         elif option == '1y':
580             url = 'http://chart.finance.yahoo.com/c/1y/s/%s' % symbol.lower()
581         elif option == '2y':
582             url = 'http://chart.finance.yahoo.com/c/2y/s/%s' % symbol.lower()
583         elif option == '5y':
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()
587
588         try:
589             myimg = urllib2.urlopen(url)
590             imgdata = myimg.read()
591
592             pbl = gtk.gdk.PixbufLoader()
593             pbl.write(imgdata)
594
595             pbuf = pbl.get_pixbuf()
596             pbuf = pbuf.scale_simple(475, 235, gtk.gdk.INTERP_TILES)
597             pbl.close()
598             self.graph.set_from_pixbuf(pbuf)
599             winprogind(win, 0)
600         except:
601             logger.exception("Getting graph data")
602             winprogind(win, 0)
603             self.graphs_title.set_label('Failed to get data')
604             self.graph.destroy()
605
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)
610
611         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
612         treeview.append_column(column)
613
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])
619         return lstore
620
621     def main_toolbar(self, quotesview, portfolio, widgets, symbol, name, initial):
622         toolbar = gtk.HBox()
623         #toolbar.set_homogeneous(True)
624
625         portfolio_btn = hildon.Button(fhsize, horbtn)
626         portfolio_btn.set_title("Portfolio")
627         portfolio_btn.connect("clicked", self.show_portfolio_view)
628
629         graph_btn = hildon.Button(fhsize, horbtn)
630         graph_btn.set_title("Graph")
631         graph_btn.connect("clicked", self.show_graph_view, symbol, name)
632
633         refresh_btn = hildon.Button(fhsize, horbtn)
634         refresh_btn.set_title("Refresh")
635         refresh_btn.connect("clicked", self.refresh_stock_data, portfolio,
636                             widgets, symbol)
637
638
639         stockiconspath = "/usr/share/icons/hicolor/48x48/hildon/"
640         info_btn = hildon.Button(fhsize, horbtn)
641         img = gtk.Image()
642         img.set_from_file(stockiconspath + "general_information.png")
643         info_btn.set_image(img)
644         info_btn.connect("clicked", self.show_app_information)
645
646         search_btn = hildon.Button(fhsize, horbtn)
647         img = gtk.Image()
648         img.set_from_file(stockiconspath + "general_search.png")
649         search_btn.set_image(img)
650         search_btn.connect("clicked", self.show_search_dialog)
651
652         if not portfolio:
653             toolbar.pack_start(portfolio_btn)
654             if not quotesview:
655                 toolbar.pack_start(info_btn, False, False, 0)
656         if quotesview:
657             toolbar.pack_start(graph_btn)
658             toolbar.pack_start(refresh_btn)
659
660         if initial:
661             toolbar.pack_start(search_btn, False, False, 0)
662
663         toolbar.show_all()
664
665         return toolbar
666
667     def show_search_dialog(self, widget):
668         dlg = gtk.Dialog(title='Search company', parent=None, flags=0)
669         dlg.set_has_separator(False)
670
671         entry = hildon.Entry(fhsize)
672         dlg.vbox.pack_start(entry, False, False, 0)
673
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)
678
679         dlg.show_all()
680         dlg.run()
681         dlg.destroy()
682
683
684     def do_search(self, widget, entry, dlg):
685         import thread
686         text = entry.get_text()
687         dlg.destroy()
688
689         winprogind(self.window, 1)
690         thread.start_new_thread(self._really_do_search, (text,))
691
692     def _really_do_search(self, text):
693
694         if allnames == []:
695             for market in marketdata.eunames:
696                 for company in market:
697                     if not company in allnames:
698                         allnames.append(company)
699
700             for market in marketdata.omnames:
701                 for company in market:
702                     if not company in allnames:
703                         allnames.append(company)
704
705             for market in marketdata.usnames:
706                 for company in market:
707                     if not company in allnames:
708                         allnames.append(company)
709
710         if allsymbols == []:
711             for market in marketdata.eusymbols:
712                 for company in market:
713                     if not company in allsymbols:
714                         allsymbols.append(company)
715
716             for market in marketdata.omsymbols:
717                 for company in market:
718                     if not company in allsymbols:
719                         allsymbols.append(company)
720
721             for market in marketdata.ussymbols:
722                 for company in market:
723                     if not company in allsymbols:
724                         allsymbols.append(company)
725
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])
731
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()
737             return
738
739         gtk.gdk.threads_enter()
740         self.show_search_screen(new_model, text)
741         gtk.gdk.threads_leave()
742         winprogind(self.window, 0)
743
744
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)
749
750         vbox = gtk.VBox()
751         toolbar = self.main_toolbar(False, False, None, '', '', False)
752
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)
757         tv.set_model(model)
758         self._tv_columns(tv)
759         parea.add(tv)
760
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)
764
765         window.add(vbox)
766         window.show_all()
767
768
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."))
777
778     def show_portfolio_view(self, widget):
779         data = settings.load_portfolio(settingsdb)
780
781         win = hildon.StackableWindow()
782         self.create_menu(win)
783         win.set_title("StockThis - Portfolio")
784
785         vbox = gtk.VBox()
786
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)
795         parea.add(tv)
796
797         hbox = gtk.HBox()
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)
802
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)
807
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)
812
813         vbox.pack_start(parea, True, True, 0)
814         vbox.pack_start(hbox, False, False, 0)
815         win.add(vbox)
816         win.show_all()
817
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)
821
822         button1 = hildon.PickerButton(fhsize, horbtn)
823         data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
824                 "900", "1000"]
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)
830
831         entry1 = hildon.Entry(fhsize)
832         entry1.set_placeholder("Name")
833         dlg.vbox.pack_start(entry1, False, False, 0)
834
835         entry2 = hildon.Entry(fhsize)
836         entry2.set_placeholder("Yahoo Finance symbol")
837         dlg.vbox.pack_start(entry2, False, False, 0)
838
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)
843
844         dlg.show_all()
845         dlg.run()
846         dlg.destroy()
847
848
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()
853
854         self.add_to_portfolio(widget, button, symbol, name)
855         dlg.destroy()
856
857         niter = self.portfolio_model.append()
858         self.portfolio_model.set(niter, 0, symbol, 1, name, 2, shares, 3, "-")
859
860     def remove_item(self, widget):
861         win = hildon.StackableWindow()
862         win.fullscreen()
863         toolbar = hildon.EditToolbar("Choose items to delete", "Delete")
864         win.set_edit_toolbar(toolbar)
865
866         vbox = gtk.VBox()
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)
873         parea.add(tv)
874
875         toolbar.connect("button-clicked", self.delete_from_portfolio, win, tv,
876                         selection)
877         toolbar.connect_object("arrow-clicked", gtk.Window.destroy, win)
878
879         vbox.pack_start(parea, True, True, 0)
880         win.add(vbox)
881         win.show_all()
882
883     def delete_from_portfolio(self, widget, win, tv, selection):
884         if not self.is_treeview_selected(tv):
885             return
886
887         conf = self.show_confirmation(win, "Delete items?")
888
889         if conf:
890             try:
891                 selmodel, selected = selection.get_selected_rows()
892                 iters = [selmodel.get_iter(path) for path in selected]
893                 for i in iters:
894                     symbol = selmodel.get_value(i, 0)
895                     settings.delete_item_from_portfolio(settingsdb, symbol)
896                     selmodel.remove(i)
897             except:
898                 logger.exception("Deleting item from portfolio")
899                 self.info_banner(widget, "Error deleting item")
900
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)
905
906         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
907         column.set_property("expand", True)
908         treeview.append_column(column)
909
910         column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
911         column.set_visible(False)
912         treeview.append_column(column)
913
914         column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
915         column.set_visible(False)
916         treeview.append_column(column)
917
918     def refresh_portfolio(self, widget, tv, win):
919         data = settings.load_portfolio(settingsdb)
920         import thread
921         winprogind(win, 1)
922         thread.start_new_thread(self._do_refresh_portfolio, (data, tv, win))
923
924     def _do_refresh_portfolio(self, data, tv, win):
925         for item in data:
926             item[3] = self.get_price(item[0])
927
928         self.portfolio_model = self._create_portfolio_model(data)
929         tv.set_model(self.portfolio_model)
930         winprogind(win, 0)
931
932     def get_price(self, symbol):
933         from ystockquote import ystockquote as yt
934         try:
935             price = yt.get_price(symbol)
936             return price
937         except:
938             logger.exception("Getting price from Yahoo")
939             return "N/A"
940
941     def _create_portfolio_model(self, data):
942         lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
943                                 gobject.TYPE_STRING, gobject.TYPE_STRING)
944         for item in data:
945             iter = lstore.append()
946             lstore.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3])
947         return lstore
948
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)
953
954         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
955         column.set_property("expand", True)
956         treeview.append_column(column)
957
958         column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
959         treeview.append_column(column)
960
961         column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
962         treeview.append_column(column)
963
964     def show_confirmation(self, window, msg):
965         dialog = hildon.hildon_note_new_confirmation(window, msg)
966         dialog.show_all()
967         result = dialog.run()
968         if result == gtk.RESPONSE_OK:
969             dialog.destroy()
970             return True
971
972         dialog.destroy()
973         return False
974
975     def show_information_note(self, window, msg):
976         dialog = hildon.hildon_note_new_information(window, msg)
977         dialog.show_all()
978         result = dialog.run()
979         dialog.destroy()
980
981     def show_info_banner(self, widget, msg):
982         hildon.hildon_banner_show_information(widget, 'qgn_note_infoprint', msg)
983
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')
988             return False
989         else:
990             return True
991
992 class About:
993
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)
998
999         self.info_lb = gtk.Label()
1000         self.info_lb.set_line_wrap(True)
1001
1002         self.id = False
1003
1004         hbox1 = gtk.HBox()
1005
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)
1010
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)
1015
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)
1020
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)
1025
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)
1030
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)
1035
1036         self.action_btn = hildon.Button(fhsize, horbtn)
1037         self.image = gtk.Image()
1038
1039         self.show_info(None, 'description')
1040
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)
1045
1046         self.abdialog.show_all()
1047         self.action_btn.hide()
1048         self.image.hide()
1049         self.abdialog.run()
1050         self.abdialog.destroy()
1051
1052     def do_action(self, widget, action):
1053         import dbus
1054
1055         self.abdialog.destroy()
1056
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')
1060
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"
1067
1068         iface.load_url(url)
1069
1070     def show_info(self, widget, kind):
1071         if kind == 'license':
1072             self.action_btn.hide()
1073             self.image.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.
1075
1076 Logo by Daniel Martin Yerga.
1077 </small>"""
1078         elif kind == 'credits':
1079             self.action_btn.hide()
1080             self.image.hide()
1081             info = """<small><b>Written by</b> Daniel Martin Yerga (dyerga@gmail.com)
1082
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()
1086             self.image.hide()
1087             info = """<b><big>StockThis 0.3</big></b>
1088
1089 <i>StockThis is a stocks application for Maemo</i>
1090
1091 <b>Web Page</b>:
1092 stockthis.garage.maemo.org"""
1093
1094         elif kind == 'donate':
1095             self.action_btn.show()
1096             self.image.hide()
1097             self.action_btn.set_title('Make donation')
1098             if self.id:
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.
1103
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>
1107 """
1108         elif kind == 'report':
1109             self.action_btn.show()
1110             self.image.hide()
1111             self.action_btn.set_title('Report bug')
1112             if self.id:
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()
1121             self.image.show()
1122             self.image.set_from_file(imgdir + "maemoorg.png")
1123             self.action_btn.set_title('Rate StockThis')
1124             if self.id:
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>"""
1129
1130         self.info_lb.set_markup(info)
1131
1132
1133 class Log:
1134
1135     def __init__(self, widget, logfile):
1136         #Log dialog UI
1137         dialog = gtk.Dialog(title='Log', parent=None)
1138
1139         dialog.set_size_request(600, 350)
1140
1141         parea = hildon.PannableArea()
1142         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
1143
1144         textview = hildon.TextView()
1145         textview.set_property("editable", False)
1146         textview.set_property("wrap-mode", gtk.WRAP_WORD)
1147
1148         log = open(logfile, 'r')
1149         logtext = log.read()
1150         log.close()
1151
1152         textview.get_buffer().set_text(logtext)
1153         parea.add(textview)
1154
1155         dialog.vbox.pack_start(parea, True, True, 0)
1156
1157         hbox = gtk.HBox()
1158
1159         save_btn = hildon.Button(fhsize, horbtn)
1160         save_btn.set_title("Save")
1161         save_btn.connect('clicked', self.save, logfile, dialog)
1162
1163         clear_btn = hildon.Button(fhsize, horbtn)
1164         clear_btn.set_title("Clear")
1165         clear_btn.connect('clicked', self.clear, textview, logfile)
1166
1167         send_btn = hildon.Button(fhsize, horbtn)
1168         send_btn.set_title('Send')
1169         send_btn.connect('clicked', self.send, dialog, logfile)
1170
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)
1174
1175         dialog.vbox.pack_start(hbox, False, False, 0)
1176
1177         dialog.show_all()
1178         dialog.run()
1179         dialog.destroy()
1180
1181     def show_filechooser(self, window, title, name, EXT):
1182         action = gtk.FILE_CHOOSER_ACTION_SAVE
1183
1184         m = hildon.FileSystemModel()
1185         file_dialog = hildon.FileChooserDialog(window, action, m)
1186         file_dialog.set_title(title)
1187
1188         file_dialog.set_current_name(name)
1189         HOME = os.path.expanduser("~")
1190
1191         if os.path.exists(HOME + '/MyDocs/.documents'):
1192             file_dialog.set_current_folder(HOME + '/MyDocs/.documents')
1193         else:
1194             file_dialog.set_current_folder(HOME)
1195
1196         file_dialog.set_default_response(gtk.RESPONSE_CANCEL)
1197
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
1203         else:
1204             namefile = None
1205         file_dialog.destroy()
1206
1207         return namefile
1208
1209
1210     def clear(self, widget, textview, logfile):
1211         textview.get_buffer().set_text('')
1212         f = open(logfile, 'w')
1213         f.close()
1214
1215     def save(self, widget, logfile, dlg):
1216         import shutil
1217         filename = self.show_filechooser(dlg, "Save log file",
1218                     "stockthis-log", "txt")
1219
1220         if not filename:
1221             return
1222
1223         try:
1224             shutil.copyfile(logfile, filename)
1225             stockspy.show_info_banner(widget, 'Log file saved')
1226         except:
1227             logger.exception("Saving log file")
1228             stockspy.show_info_banner(widget, 'Error saving the log file')
1229
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).")
1234
1235         dialog = hildon.hildon_note_new_confirmation(dlg, sendtxt)
1236         dialog.set_button_texts("Send", "Cancel")
1237         dialog.show_all()
1238         response = dialog.run()
1239         if response == gtk.RESPONSE_OK:
1240             self.do_pre_send(dlg, logfile)
1241
1242         dialog.destroy()
1243
1244     def do_pre_send(self, dlg, logfile):
1245         import thread
1246         hildon.hildon_gtk_window_set_progress_indicator(dlg, 1)
1247         thread.start_new_thread(self._do_send, (dlg, logfile))
1248
1249     def _do_send(self, dlg, logfile):
1250         import pycurl, shutil, random, commands
1251         try:
1252             rname = ''
1253             for i in random.sample('abcdefghijkl123456789', 18):
1254                 rname += i
1255
1256             rnamepath = HOME + "/.stockthis/" + rname
1257             shutil.copyfile(logfile, rnamepath)
1258
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()
1263                 mfile.close()
1264             else:
1265                 maemoversion = ''
1266
1267             opsystem = ' '.join(os.uname())
1268             pyversion = os.sys.version
1269             pid = os.getpid()
1270             comm = ("awk '/Private_Dirty/{sum+=$2}END{print sum \"kB\"}'"
1271             " /proc/%s/smaps") % pid
1272             status, dirtymem = commands.getstatusoutput(comm)
1273
1274             lfile = open(rnamepath, 'r')
1275             log = lfile.read()
1276             lfile.close()
1277
1278
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)
1283
1284             lfile = open(rnamepath, 'w')
1285             lfile.write(log)
1286             lfile.close()
1287
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)
1293
1294             mycurl.perform()
1295             mycurl.close()
1296             os.remove(rnamepath)
1297
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)
1302         except:
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)
1308
1309
1310 if __name__ == "__main__":
1311     stockspy = StocksPy()
1312     gtk.gdk.threads_enter()
1313     gtk.main()
1314     gtk.gdk.threads_leave()