1df7d6c12c3702107059bc9443563920cc667ad5
[stockthis] / stockthis.py
1 #!/usr/bin/env python2.5
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 alpha1 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 logger.debug("test this log")
76
77 gtk.gdk.threads_init()
78
79 class StocksPy:
80
81     def __init__(self):
82         self.program = hildon.Program()
83         self.program.__init__()
84         gtk.set_application_name("StockThis")
85         self.window = hildon.StackableWindow()
86         self.window.set_default_size(800, 480)
87         self.program.add_window(self.window)
88         self.window.connect("destroy", gtk.main_quit)
89
90         self.create_menu(self.window)
91
92         vbox = gtk.VBox()
93         toolbar = self.main_toolbar(False, False, None, '', '')
94
95         parea = hildon.PannableArea()
96         tv = hildon.GtkTreeView(ui_normal)
97         inmodel = self.__create_model(marketdata.main, marketdata.idmain)
98         tv.connect("row-activated", self.show_instrument_view, inmodel,
99                     marketdata.localmarkets, marketdata.localids,
100                     marketdata.idmain)
101         tv.set_model(inmodel)
102         self._tv_columns(tv)
103         parea.add(tv)
104
105         vbox.pack_start(parea, True, True, 0)
106         vbox.pack_start(gtk.HSeparator(), False, False, 5)
107         vbox.pack_start(toolbar, False, False, 0)
108
109         self.window.add(vbox)
110         self.window.show_all()
111
112     def create_menu(self, window):
113         menu = hildon.AppMenu()
114         window.set_app_menu(menu)
115         button = gtk.Button("About")
116         button.connect("clicked", About)
117         menu.append(button)
118         button = gtk.Button("Log")
119         button.connect("clicked", Log, logfile)
120         menu.append(button)
121         menu.show_all()
122
123     def show_instrument_view(self, widget, path, column, inmodel, names,
124         ids, mindex):
125         market = inmodel[path][0]
126         names = names[mindex.index(market)]
127         ids = ids[mindex.index(market)]
128
129         window = hildon.StackableWindow()
130         self.create_menu(window)
131         window.set_title("StockThis - " + inmodel[path][1])
132
133         vbox = gtk.VBox()
134         toolbar = self.main_toolbar(False, False, None, '', '')
135
136         parea = hildon.PannableArea()
137         tv = hildon.GtkTreeView(ui_normal)
138         model = self.__create_model(names, ids)
139         tv.connect("row-activated", self.show_quotes_view, model, False)
140         tv.set_model(model)
141         self._tv_columns(tv)
142         parea.add(tv)
143
144         vbox.pack_start(parea, True, True, 0)
145         vbox.pack_start(gtk.HSeparator(), False, False, 5)
146         vbox.pack_start(toolbar, False, False, 0)
147
148         window.add(vbox)
149         window.show_all()
150
151     def show_quotes_view(self, widget, path, column, model, portfolio):
152         quote = model[path][0], model[path][1]
153         #print "quote:", quote[0]
154         #('EURUSD=X', 'EUR/USD')
155
156         #Currencies and ETFs should show the list now -> view = True
157         #Other items show a new list with options
158         view = False
159         for i in marketdata.localids[(len(marketdata.localids)-2):]:
160             for j in i:
161                 if quote[0] == j:
162                     #print j
163                     view = True
164
165         if not view:
166             if quote[0] in marketdata.idindexes:
167                 self.show_instrument_view(widget, path, column, model,
168                                         marketdata.wnamesindexes,
169                                         marketdata.widsindexes,
170                                         marketdata.idindexes)
171                 return
172             if quote[0] in marketdata.idotmarkets:
173                 self.show_instrument_view(widget, path, column, model,
174                                         marketdata.omnames,
175                                         marketdata.omsymbols,
176                                         marketdata.idotmarkets)
177                 return
178             if quote[0] in marketdata.ideumarkets:
179                 self.show_instrument_view(widget, path, column, model,
180                                         marketdata.eunames,
181                                         marketdata.eusymbols,
182                                         marketdata.ideumarkets)
183                 return
184             if quote[0] in marketdata.idusmarkets:
185                 self.show_instrument_view(widget, path, column, model,
186                                         marketdata.usnames,
187                                         marketdata.ussymbols,
188                                         marketdata.idusmarkets)
189                 return
190
191
192         win = hildon.StackableWindow()
193         self.create_menu(win)
194         win.set_title("StockThis - Quotes View - " + quote[1])
195
196         vbox = gtk.VBox()
197
198         ltitle = gtk.Label('')
199         ltitle.set_markup('<b><big>' + quote[1].replace('&', '') +
200                                  '</big></b>')
201         color = gtk.gdk.color_parse("#03A5FF")
202         ltitle.modify_fg(gtk.STATE_NORMAL, color)
203
204         parea = hildon.PannableArea()
205
206         vbox1 = gtk.VBox()
207
208         hbox = gtk.HBox()
209         label = gtk.Label('')
210         label.set_markup('<b><big>Price:</big></b>')
211         lprice = gtk.Label('')
212         hbox.pack_start(label, False, False, 20)
213         hbox.pack_start(lprice, False, False, 245)
214         vbox1.pack_start(hbox, True, True, 0)
215
216         hbox = gtk.HBox()
217         label = gtk.Label('')
218         label.set_markup('<b><big>Change:</big></b>')
219         lchange = gtk.Label('')
220         lpercent = gtk.Label('')
221         hbox.pack_start(label, False, False, 20)
222         hbox.pack_start(lchange, False, False, 205)
223         hbox.pack_start(lpercent, False, False, 0)
224         vbox1.pack_start(hbox, True, True, 0)
225
226         hbox = gtk.HBox()
227         label = gtk.Label('')
228         label.set_markup('<b><big>Volume:</big></b>')
229         lvolume = gtk.Label('')
230         hbox.pack_start(label, False, False, 20)
231         hbox.pack_start(lvolume, False, False, 207)
232         vbox1.pack_start(hbox, True, True, 0)
233
234         hbox = gtk.HBox()
235         label = gtk.Label('')
236         label.set_markup('<b><big>52 week high:</big></b>')
237         l52whigh = gtk.Label('')
238         hbox.pack_start(label, False, False, 20)
239         hbox.pack_start(l52whigh, False, False, 110)
240         vbox1.pack_start(hbox, True, True, 0)
241
242         hbox = gtk.HBox()
243         label = gtk.Label('')
244         label.set_markup('<b><big>52 week low:</big></b>')
245         l52wlow = gtk.Label('')
246         hbox.pack_start(label, False, False, 20)
247         hbox.pack_start(l52wlow, False, False, 125)
248         vbox1.pack_start(hbox, True, True, 0)
249
250         hbox = gtk.HBox()
251         button1 = hildon.PickerButton(fhsize, horbtn)
252         data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
253                 "900", "1000"]
254         selector = self.create_selector(data, True)
255         button1.set_selector(selector)
256         button1.set_title("Your shares")
257         shares = self.get_shares_from_symbol(quote[0])
258         button1.set_value(shares)
259         hbox.pack_start(button1, True, True, 0)
260
261         button = hildon.Button(fhsize, horbtn)
262         button.set_title("Add to Portfolio")
263         button.connect("clicked", self.add_to_portfolio, button1, quote[0], quote[1])
264         hbox.pack_start(button, True, True, 0)
265
266         hbox1 = gtk.HBox()
267         label = gtk.Label('')
268         label.set_markup('<b><big>Shares:</big></b>')
269         lshares = gtk.Label(shares)
270         hbox1.pack_start(label, False, False, 20)
271         hbox1.pack_start(lshares, False, False, 215)
272
273         hbox2 = gtk.HBox()
274         label = gtk.Label('')
275         label.set_markup('<b><big>Holdings Value:</big></b>')
276         holdingsvalue = gtk.Label("")
277         hbox2.pack_start(label, False, False, 20)
278         hbox2.pack_start(holdingsvalue, False, False, 85)
279
280         hbox3 = gtk.HBox()
281         label = gtk.Label('')
282         label.set_markup("<b><big>Day's Value Change:</big></b>")
283         dayvaluechange = gtk.Label("")
284         hbox3.pack_start(label, False, False, 20)
285         hbox3.pack_start(dayvaluechange, False, False, 10)
286
287         if not portfolio:
288             vbox1.pack_start(hbox, False, False, 0)
289         else:
290             vbox1.pack_start(hbox1, True, True, 0)
291             vbox1.pack_start(hbox2, True, True, 0)
292             vbox1.pack_start(hbox3, True, True, 0)
293
294         parea.add_with_viewport(vbox1)
295
296         widgets = [win, ltitle, lprice, lchange,  lpercent, lvolume, l52whigh,
297                     l52wlow, lshares, holdingsvalue, dayvaluechange]
298
299         toolbar = self.main_toolbar(True, portfolio, widgets, quote[0], quote[1])
300
301         vbox.pack_start(ltitle, False, False, 0)
302         vbox.pack_start(gtk.HSeparator(), False, False, 0)
303         vbox.pack_start(parea, True, True, 0)
304         vbox.pack_start(gtk.HSeparator(), False, False, 5)
305         vbox.pack_start(toolbar, False, False, 0)
306
307
308         win.add(vbox)
309         win.show_all()
310         self.show_data(quote[0], widgets, shares)
311
312     def get_shares_from_symbol(self, symbol):
313         shares = "0"
314         try:
315             portfolio_data = settings.load_portfolio(settingsdb)
316             for item in portfolio_data :
317                 if symbol in item:
318                     shares = item[2]
319             return shares
320         except:
321             logger.exception("Getting shares from symbol")
322             return shares
323
324     def add_to_portfolio(self, widget, button, symbol, name):
325         shares = button.get_value()
326
327         try:
328             portfolio = settings.load_portfolio(settingsdb)
329             index = "None"
330             for item in portfolio:
331                 if symbol in item:
332                     index = portfolio.index(item)
333
334             item = [symbol, name, shares, '-']
335
336             if index is "None":
337                 settings.insert_new_item_to_portfolio(settingsdb, item)
338             else:
339                 settings.delete_item_from_portfolio(settingsdb, symbol)
340                 settings.insert_new_item_to_portfolio(settingsdb, item)
341
342             self.show_info_banner(widget, "Added to portfolio")
343         except:
344             logger.exception("Adding to portfolio")
345             self.show_info_banner(widget, "Error adding to portfolio")
346
347
348     def create_selector(self, data, entry):
349         if entry:
350             selector = hildon.TouchSelectorEntry(text=True)
351         else:
352             selector = hildon.hildon_touch_selector_new_text()
353         for i in range(len(data)):
354             selector.append_text(data[i])
355
356         return selector
357
358     def show_data(self, symbol, widgets, shares):
359         import thread
360         winprogind(widgets[0], 1)
361         thread.start_new_thread(self.get_data, (symbol, widgets, shares))
362
363     def get_data(self, symbol, widgets, shares):
364         from ystockquote import ystockquote as yt
365         win, ltitle, lprice, lchange,  lpercent, lvolume, l52whigh, l52wlow, lshares, holdingsvalue, dayvaluechange = widgets
366
367         try:
368             data = yt.get_all(symbol)
369         except:
370             logger.exception("Getting data from Yahoo")
371             data = {'price': 'N/A', 'change': 'N/A', 'volume':'N/A',
372                     '52_week_high': 'N/A', '52_week_low': 'N/A'}
373             ltitle.set_markup('<b><big>Failed to get data</big></b>')
374
375         try:
376             ch_percent = \
377                     100.0 * float(data['change'])/(float(data['price']) - \
378                     float(data['change']))
379         except ValueError:
380             ch_percent = 0.0
381
382         lprice.set_label(data['price'])
383         lchange.set_label(data['change'])
384         lpercent.set_label('%6.2f %%' % ch_percent)
385
386         if '-' in data['change']:
387             color = gtk.gdk.color_parse("#FF0000")
388         else:
389             color = gtk.gdk.color_parse("#16EB78")
390
391         lpercent.modify_fg(gtk.STATE_NORMAL, color)
392         lchange.modify_fg(gtk.STATE_NORMAL, color)
393
394         lvolume.set_label(data['volume'])
395         l52whigh.set_label(data['52_week_high'])
396         l52wlow.set_label(data['52_week_low'])
397
398         try:
399             daychange = float(shares)*float(data['change'])
400         except ValueError:
401             daychange = 'N/A'
402         try:
403             holdvalue = float(shares)*float(data['price'])
404         except ValueError:
405             holdvalue = 'N/A'
406
407         dayvaluechange.set_label(str(daychange))
408         holdingsvalue.set_label(str(holdvalue))
409
410         winprogind(win, 0)
411
412     def refresh_stock_data(self, widget, portfolio, widgets, symbol):
413         if portfolio:
414             shares = self.get_shares_from_symbol(symbol)
415         else:
416             shares = "0"
417
418         self.show_data(symbol, widgets, shares)
419
420     def show_graph_view(self, widget, symbol, name):
421         win = hildon.StackableWindow()
422         self.create_menu(win)
423         win.set_title("StockThis - Graph View - " + name)
424
425         vbox = gtk.VBox()
426         toolbar = self.main_toolbar(False, True, None, '', '')
427
428         self.graphs_title = gtk.Label(name)
429         color = gtk.gdk.color_parse("#03A5FF")
430         self.graphs_title.modify_fg(gtk.STATE_NORMAL, color)
431
432         parea = hildon.PannableArea()
433
434         hbox = gtk.HBox()
435         hbox.set_homogeneous(True)
436
437         button = hildon.Button(fhsize, horbtn)
438         button.set_label('1d')
439         button.connect("clicked", self.show_graph, '1d', win, symbol)
440         hbox.pack_start(button)
441
442         button = hildon.Button(fhsize, horbtn)
443         button.set_label('5d')
444         button.connect("clicked", self.show_graph, '5d', win, symbol)
445         hbox.pack_start(button)
446
447         button = hildon.Button(fhsize, horbtn)
448         button.set_label('3m')
449         button.connect("clicked", self.show_graph, '3m', win, symbol)
450         hbox.pack_start(button)
451
452         button = hildon.Button(fhsize, horbtn)
453         button.set_label('6m')
454         button.connect("clicked", self.show_graph, '6m', win, symbol)
455         hbox.pack_start(button)
456
457         button = hildon.Button(fhsize, horbtn)
458         button.set_label('1y')
459         button.connect("clicked", self.show_graph, '1y', win, symbol)
460         hbox.pack_start(button)
461
462         button = hildon.Button(fhsize, horbtn)
463         button.set_label('2y')
464         button.connect("clicked", self.show_graph, '2y', win, symbol)
465         hbox.pack_start(button)
466
467         button = hildon.Button(fhsize, horbtn)
468         button.set_label('5y')
469         button.connect("clicked", self.show_graph, '5y', win, symbol)
470         hbox.pack_start(button)
471
472         button = hildon.Button(fhsize, horbtn)
473         button.set_label('Max')
474         button.connect("clicked", self.show_graph, 'max', win, symbol)
475         hbox.pack_start(button)
476
477         vbox1 = gtk.VBox()
478         vbox1.pack_start(hbox, False, False, 0)
479
480         self.graph = gtk.Image()
481         vbox1.pack_start(self.graph, True, True, 0)
482
483         parea.add_with_viewport(vbox1)
484
485         vbox.pack_start(self.graphs_title, False, False, 0)
486         vbox.pack_start(gtk.HSeparator(), False, False, 0)
487         vbox.pack_start(parea, True, True, 0)
488         vbox.pack_start(gtk.HSeparator(), False, False, 5)
489         vbox.pack_start(toolbar, False, False, 0)
490
491         win.add(vbox)
492         win.show_all()
493
494         self.show_graph(None, '1d', win, symbol)
495
496     def show_graph(self, widget, option, win, symbol):
497         import thread
498         winprogind(win, 1)
499         thread.start_new_thread(self.get_graph_data, (option, win, symbol))
500
501     def get_graph_data(self, option, win, symbol):
502         if option == '1d':
503             url = 'http://uk.ichart.yahoo.com/b?s=%s' % symbol
504         elif option == '5d':
505             url = 'http://uk.ichart.yahoo.com/w?s=%s' % symbol
506         elif option == '3m':
507             url = 'http://chart.finance.yahoo.com/c/3m/s/%s' % symbol.lower()
508         elif option == '6m':
509             url = 'http://chart.finance.yahoo.com/c/6m/s/%s' % symbol.lower()
510         elif option == '1y':
511             url = 'http://chart.finance.yahoo.com/c/1y/s/%s' % symbol.lower()
512         elif option == '2y':
513             url = 'http://chart.finance.yahoo.com/c/2y/s/%s' % symbol.lower()
514         elif option == '5y':
515             url = 'http://chart.finance.yahoo.com/c/5y/s/%s' % symbol.lower()
516         elif option == 'max':
517             url = 'http://chart.finance.yahoo.com/c/my/s/%s' % symbol.lower()
518
519         try:
520             myimg = urllib2.urlopen(url)
521             imgdata = myimg.read()
522
523             pbl = gtk.gdk.PixbufLoader()
524             pbl.write(imgdata)
525
526             pbuf = pbl.get_pixbuf()
527             pbl.close()
528             self.graph.set_from_pixbuf(pbuf)
529             winprogind(win, 0)
530         except:
531             logger.exception("Getting graph data")
532             winprogind(win, 0)
533             self.graphs_title.set_label('Failed to get data')
534             self.graph.destroy()
535
536     def _tv_columns(self, treeview):
537         column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
538         column.set_visible(False)
539         treeview.append_column(column)
540
541         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
542         treeview.append_column(column)
543
544     def __create_model(self, names, ids):
545         lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
546         for item in range(len(names)):
547             iter = lstore.append()
548             lstore.set(iter, 0, ids[item], 1, names[item])
549         return lstore
550
551     def main_toolbar(self, quotesview, portfolio, widgets, symbol, name):
552         toolbar = gtk.HBox()
553         toolbar.set_homogeneous(True)
554
555         portfolio_btn = hildon.Button(fhsize, horbtn)
556         portfolio_btn.set_title("Portfolio")
557         portfolio_btn.connect("clicked", self.show_portfolio_view)
558
559         graph_btn = hildon.Button(fhsize, horbtn)
560         graph_btn.set_title("Graph")
561         graph_btn.connect("clicked", self.show_graph_view, symbol, name)
562
563         refresh_btn = hildon.Button(fhsize, horbtn)
564         refresh_btn.set_title("Refresh")
565         refresh_btn.connect("clicked", self.refresh_stock_data, portfolio,
566                             widgets, symbol)
567
568         if not portfolio:
569             toolbar.pack_start(portfolio_btn)
570         if quotesview:
571             toolbar.pack_start(graph_btn)
572             toolbar.pack_start(refresh_btn)
573
574         toolbar.show_all()
575
576         return toolbar
577
578     def show_portfolio_view(self, widget):
579         data = settings.load_portfolio(settingsdb)
580
581         win = hildon.StackableWindow()
582         self.create_menu(win)
583         win.set_title("StockThis - Portfolio")
584
585         vbox = gtk.VBox()
586
587         parea = hildon.PannableArea()
588         tv = hildon.GtkTreeView(ui_normal)
589         tv.set_headers_visible(True)
590         self.portfolio_model = self._create_portfolio_model(data)
591         tv.connect("row-activated", self.show_quotes_view, self.portfolio_model, True)
592         tv.set_model(self.portfolio_model)
593         self._tv_portfolio_columns(tv)
594         parea.add(tv)
595
596         hbox = gtk.HBox()
597         button = hildon.Button(fhsize, horbtn)
598         button.set_title("Refresh All")
599         button.connect("clicked", self.refresh_portfolio, tv, win)
600         hbox.pack_start(button, True, True, 0)
601
602         button = hildon.Button(fhsize, horbtn)
603         button.set_title("Remove")
604         button.connect("clicked", self.remove_item)
605         hbox.pack_start(button, True, True, 0)
606
607         vbox.pack_start(parea, True, True, 0)
608         vbox.pack_start(hbox, False, False, 0)
609         win.add(vbox)
610         win.show_all()
611
612     def remove_item(self, widget):
613         win = hildon.StackableWindow()
614         win.fullscreen()
615         toolbar = hildon.EditToolbar("Choose items to delete", "Delete")
616         win.set_edit_toolbar(toolbar)
617
618         vbox = gtk.VBox()
619         parea = hildon.PannableArea()
620         tv = hildon.GtkTreeView(ui_edit)
621         selection = tv.get_selection()
622         selection.set_mode(gtk.SELECTION_MULTIPLE)
623         tv.set_model(self.portfolio_model)
624         self._tv_remove_portfolio_columns(tv)
625         parea.add(tv)
626
627         toolbar.connect("button-clicked", self.delete_from_portfolio, win, tv,
628                         selection)
629         toolbar.connect_object("arrow-clicked", gtk.Window.destroy, win)
630
631         vbox.pack_start(parea, True, True, 0)
632         win.add(vbox)
633         win.show_all()
634
635     def delete_from_portfolio(self, widget, win, tv, selection):
636         if not self.is_treeview_selected(tv):
637             return
638
639         conf = self.show_confirmation(win, "Delete items?")
640
641         if conf:
642             try:
643                 selmodel, selected = selection.get_selected_rows()
644                 iters = [selmodel.get_iter(path) for path in selected]
645                 for i in iters:
646                     symbol = selmodel.get_value(i, 0)
647                     settings.delete_item_from_portfolio(settingsdb, symbol)
648                     selmodel.remove(i)
649             except:
650                 logger.exception("Deleting item from portfolio")
651                 self.info_banner(widget, "Error deleting item")
652
653     def _tv_remove_portfolio_columns(self, treeview):
654         column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
655         column.set_visible(False)
656         treeview.append_column(column)
657
658         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
659         column.set_property("expand", True)
660         treeview.append_column(column)
661
662         column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
663         column.set_visible(False)
664         treeview.append_column(column)
665
666         column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
667         column.set_visible(False)
668         treeview.append_column(column)
669
670     def refresh_portfolio(self, widget, tv, win):
671         data = settings.load_portfolio(settingsdb)
672         import thread
673         winprogind(win, 1)
674         thread.start_new_thread(self._do_refresh_portfolio, (data, tv, win))
675
676     def _do_refresh_portfolio(self, data, tv, win):
677         for item in data:
678             item[3] = self.get_price(item[0])
679
680         self.portfolio_model = self._create_portfolio_model(data)
681         tv.set_model(self.portfolio_model)
682         winprogind(win, 0)
683
684     def get_price(self, symbol):
685         from ystockquote import ystockquote as yt
686         try:
687             price = yt.get_price(symbol)
688             return price
689         except:
690             logger.exception("Getting price from Yahoo")
691             return "N/A"
692
693     def _create_portfolio_model(self, data):
694         lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
695                                 gobject.TYPE_STRING, gobject.TYPE_STRING)
696         for item in data:
697             iter = lstore.append()
698             lstore.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3])
699         return lstore
700
701     def _tv_portfolio_columns(self, treeview):
702         column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
703         column.set_visible(False)
704         treeview.append_column(column)
705
706         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
707         column.set_property("expand", True)
708         treeview.append_column(column)
709
710         column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
711         treeview.append_column(column)
712
713         column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
714         treeview.append_column(column)
715
716     def show_confirmation(self, window, msg):
717         dialog = hildon.hildon_note_new_confirmation(window, msg)
718         dialog.show_all()
719         result = dialog.run()
720         if result == gtk.RESPONSE_OK:
721             dialog.destroy()
722             return True
723
724         dialog.destroy()
725         return False
726
727     def show_info_banner(self, widget, msg):
728         hildon.hildon_banner_show_information(widget, 'qgn_note_infoprint', msg)
729
730     def is_treeview_selected(self, treeview):
731         selection = treeview.get_selection()
732         if selection.count_selected_rows() == 0:
733             self.show_info_banner(treeview, 'No selected item')
734             return False
735         else:
736             return True
737
738 class About:
739
740     def __init__(self, widget):
741         dialog = gtk.Dialog(title='About', parent=None, flags=0)
742         dialog.set_has_separator(False)
743         dialog.set_size_request(-1, 400)
744
745         self.info_lb = gtk.Label()
746         self.info_lb.set_line_wrap(True)
747
748         self.id = False
749
750         hbox1 = gtk.HBox()
751
752         button = hildon.Button(fhsize, horbtn)
753         button.set_title('Description')
754         button.connect("clicked", self.show_info, 'description')
755         hbox1.pack_start(button, True, True, 0)
756
757         button = hildon.Button(fhsize, horbtn)
758         button.set_title('Credits')
759         button.connect("clicked", self.show_info, 'credits')
760         hbox1.pack_start(button, True, True, 0)
761
762         button = hildon.Button(fhsize, horbtn)
763         button.set_title('License')
764         button.connect("clicked", self.show_info, 'license')
765         hbox1.pack_start(button, True, True, 0)
766
767         button = hildon.Button(fhsize, horbtn)
768         button.set_title('Donate')
769         button.connect("clicked", self.show_info, 'donate')
770         hbox1.pack_start(button, True, True, 0)
771
772         button = hildon.Button(fhsize, horbtn)
773         button.set_title('Report ')
774         button.connect("clicked", self.show_info, 'report')
775         hbox1.pack_start(button, True, True, 0)
776
777         button = hildon.Button(fhsize, horbtn)
778         button.set_title(' Vote ')
779         button.connect("clicked", self.show_info, 'vote')
780         hbox1.pack_start(button, True, True, 0)
781
782         self.action_btn = hildon.Button(fhsize, horbtn)
783         self.image = gtk.Image()
784
785         self.show_info(None, 'description')
786
787         dialog.vbox.pack_start(self.action_btn, False, False, 0)
788         dialog.vbox.pack_start(self.image, False, False, 5)
789         dialog.vbox.pack_start(self.info_lb, True, True, 0)
790         dialog.vbox.pack_start(hbox1, False, False, 0)
791
792         dialog.show_all()
793         self.action_btn.hide()
794         self.image.hide()
795         dialog.run()
796         dialog.destroy()
797
798     def do_action(self, widget, action):
799         import webbrowser
800         if action == "donate":
801             url = "http://stockthis.garage.maemo.org/donate.html"
802         elif action == "report":
803             url = "http://stockthis.garage.maemo.org/reporting.html"
804         elif action == "vote":
805             url = "http://maemo.org/downloads/product/stockthis"
806         webbrowser.open_new(url)
807
808     def show_info(self, widget, kind):
809         if kind == 'license':
810             self.action_btn.hide()
811             self.image.hide()
812             info = """<small><b>StockThis</b> is free software. It's using a GPL version 2 license or at your election any later version.
813
814 Logo by Daniel Martin Yerga.
815 </small>"""
816         elif kind == 'credits':
817             self.action_btn.hide()
818             self.image.hide()
819             info = """<small><b>Written by</b> Daniel Martin Yerga (dyerga@gmail.com)
820
821 <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>"""
822         elif kind == 'description':
823             self.action_btn.hide()
824             self.image.hide()
825             info = """<b><big>StockThis 0.3</big></b>
826
827 <i>StockThis is a stocks application for Maemo</i>
828
829 <b>Web Page</b>:
830 stockthis.garage.maemo.org"""
831
832         elif kind == 'donate':
833             self.action_btn.show()
834             self.image.hide()
835             self.action_btn.set_title('I want donate')
836             if self.id:
837                 self.action_btn.disconnect(self.id)
838             self.id = self.action_btn.connect("clicked", self.do_action, "donate")
839             info = """<small><b>StockThis</b> is a free (and gratis) software application.
840 Developing good software takes time and hard work.
841
842 <b>StockThis's author</b> develops the program in him spare time.
843 If you like the program and it's helpful, consider donating a small amount of money.
844 Donations are a great incentive and help to feel that the hard work is appreciated.</small>
845 """
846
847         elif kind == 'report':
848             self.action_btn.show()
849             self.image.hide()
850             self.action_btn.set_title('Report bug')
851             if self.id:
852                 self.action_btn.disconnect(self.id)
853             self.id = self.action_btn.connect("clicked", self.do_action, "report")
854             info = """<small>StockThis is being improved thanks to bug reports. The author appreciates very much all these reports.
855 If the application is raising an error when you're using it, you have two choices to report this error:
856 1) Send the log from the application menu (if there's an error in the log).
857 2) Write a bug report in the bugtracker of StockThis with as much information as possible (especially the log from the menu).</small>"""
858
859         elif kind == 'vote':
860             self.action_btn.show()
861             self.image.show()
862             self.image.set_from_file(imgdir + "maemoorg.png")
863             self.action_btn.set_title('Vote for StockThis')
864             if self.id:
865                 self.action_btn.disconnect(self.id)
866             self.id = self.action_btn.connect("clicked", self.do_action, "vote")
867             info = """<small>The downloads section in maemo.org has a nice system where you can rate applications.
868 If you consider StockThis a good application (or a bad one too), you could rate it in maemo.org site.</small>"""
869
870         self.info_lb.set_markup(info)
871
872
873 class Log:
874
875     def __init__(self, widget, logfile):
876         #Log dialog UI
877         dialog = gtk.Dialog(title='Log', parent=None)
878
879         dialog.set_size_request(600, 350)
880
881         parea = hildon.PannableArea()
882         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
883
884         textview = hildon.TextView()
885         textview.set_property("editable", False)
886         textview.set_property("wrap-mode", gtk.WRAP_WORD)
887
888         log = open(logfile, 'r')
889         logtext = log.read()
890         log.close()
891
892         textview.get_buffer().set_text(logtext)
893         parea.add(textview)
894
895         dialog.vbox.pack_start(parea, True, True, 0)
896
897         hbox = gtk.HBox()
898
899         save_btn = hildon.Button(fhsize, horbtn)
900         save_btn.set_title("Save")
901         save_btn.connect('clicked', self.save, logfile, dialog)
902
903         clear_btn = hildon.Button(fhsize, horbtn)
904         clear_btn.set_title("Clear")
905         clear_btn.connect('clicked', self.clear, textview, logfile)
906
907         send_btn = hildon.Button(fhsize, horbtn)
908         send_btn.set_title('Send')
909         send_btn.connect('clicked', self.send, dialog, logfile)
910
911         hbox.pack_start(save_btn, True, True, 0)
912         hbox.pack_start(clear_btn, True, True, 0)
913         hbox.pack_start(send_btn, True, True, 0)
914
915         dialog.vbox.pack_start(hbox, False, False, 0)
916
917         dialog.show_all()
918         dialog.run()
919         dialog.destroy()
920
921     def show_filechooser(self, window, title, name, EXT):
922         action = gtk.FILE_CHOOSER_ACTION_SAVE
923
924         m = hildon.FileSystemModel()
925         file_dialog = hildon.FileChooserDialog(window, action, m)
926         file_dialog.set_title(title)
927
928         file_dialog.set_current_name(name)
929         HOME = os.path.expanduser("~")
930
931         if os.path.exists(HOME + '/MyDocs/.documents'):
932             file_dialog.set_current_folder(HOME + '/MyDocs/.documents')
933         else:
934             file_dialog.set_current_folder(HOME)
935
936         file_dialog.set_default_response(gtk.RESPONSE_CANCEL)
937
938         result = file_dialog.run()
939         if result == gtk.RESPONSE_OK:
940             namefile = file_dialog.get_filename()
941             namefile, extension = os.path.splitext(namefile)
942             namefile = namefile + "." + EXT
943         else:
944             namefile = None
945         file_dialog.destroy()
946
947         return namefile
948
949
950     def clear(self, widget, textview, logfile):
951         textview.get_buffer().set_text('')
952         f = open(logfile, 'w')
953         f.close()
954
955     def save(self, widget, logfile, dlg):
956         import shutil
957         filename = self.show_filechooser(dlg, "Save log file",
958                     "stockthis-log", "txt")
959
960         if not filename:
961             return
962
963         try:
964             shutil.copyfile(logfile, filename)
965             stockspy.show_info_banner(widget, 'Log file saved')
966         except:
967             logger.exception("Saving log file")
968             stockspy.show_info_banner(widget, 'Error saving the log file')
969
970     def send(self, widget, dlg, logfile):
971         import thread
972         hildon.hildon_gtk_window_set_progress_indicator(dlg, 1)
973         thread.start_new_thread(self._do_send, (dlg, logfile))
974
975     def _do_send(self, dlg, logfile):
976         import pycurl, shutil, random, commands
977         try:
978             rname = ''
979             for i in random.sample('abcdefghijkl123456789', 18):
980                 rname += i
981
982             rnamepath = HOME + "/.stockthis/" + rname
983             shutil.copyfile(logfile, rnamepath)
984
985             gtkversion = "%s.%s.%s" % gtk.ver
986             if os.path.exists("/etc/maemo_version"):
987                 mfile = open("/etc/maemo_version", 'r')
988                 maemoversion = mfile.read()
989                 mfile.close()
990             else:
991                 maemoversion = ''
992
993             opsystem = ' '.join(os.uname())
994             pyversion = os.sys.version
995             pid = os.getpid()
996             comm = ("awk '/Private_Dirty/{sum+=$2}END{print sum \"kB\"}'"
997             " /proc/%s/smaps") % pid
998             status, dirtymem = commands.getstatusoutput(comm)
999
1000             lfile = open(rnamepath, 'r')
1001             log = lfile.read()
1002             lfile.close()
1003
1004
1005             log = ("%s\nPython version: %s\nGtk version: %s\n"
1006             "Maemo version: %sOperating system: %s\n"
1007             "Dirty Memory: %s\nLog:\n%s") % (_version, pyversion, gtkversion,
1008             maemoversion, opsystem, dirtymem, log)
1009
1010             lfile = open(rnamepath, 'w')
1011             lfile.write(log)
1012             lfile.close()
1013
1014             url = "http://yerga.net/logs/uploader.php"
1015             data = [('uploadedfile', (pycurl.FORM_FILE, rnamepath)),]
1016             mycurl = pycurl.Curl()
1017             mycurl.setopt(pycurl.URL, url)
1018             mycurl.setopt(pycurl.HTTPPOST, data)
1019
1020             mycurl.perform()
1021             mycurl.close()
1022             os.remove(rnamepath)
1023
1024             gtk.gdk.threads_enter()
1025             stockspy.show_info_banner(dlg, 'Log sent')
1026             gtk.gdk.threads_leave()
1027             hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1028         except:
1029             logger.exception("Sending log file")
1030             gtk.gdk.threads_enter()
1031             stockspy.show_info_banner(dlg, 'Error sending the log file')
1032             gtk.gdk.threads_leave()
1033             hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1034
1035
1036 if __name__ == "__main__":
1037     stockspy = StocksPy()
1038     gtk.gdk.threads_enter()
1039     gtk.main()
1040     gtk.gdk.threads_leave()