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