new portfolio features, metal graphs, preparing for 0.4 release
[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.4 rev1"
23 VERSION = "0.4"
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 from portrait import FremantleRotation
35
36 import osso
37 osso_c = osso.Context("net.yerga.stockthis", "0.3", False)
38
39 #detect if is ran locally or not
40 runningpath = sys.path[0]
41
42 if '/opt/' in runningpath:
43     runninglocally = False
44 else:
45     runninglocally = True
46
47 HOME = os.path.expanduser("~")
48
49 settingsdb, imgdir, configdir, logfile = \
50     settings.define_paths(runninglocally, HOME)
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         self.show_info_banner(self.window,
117             ("StockThis uses your network connection to get data.\n"
118             "Be aware of the high costs that your network provider may apply."))
119
120     def create_menu(self, window):
121         menu = hildon.AppMenu()
122         window.set_app_menu(menu)
123         button = gtk.Button("About")
124         button.connect("clicked", About)
125         menu.append(button)
126         menu.show_all()
127
128     def show_instrument_view(self, widget, path, column, inmodel, names,
129         ids, mindex):
130         market = inmodel[path][0]
131         names = names[mindex.index(market)]
132         ids = ids[mindex.index(market)]
133
134         window = hildon.StackableWindow()
135         self.create_menu(window)
136         window.set_title(inmodel[path][1])
137
138         vbox = gtk.VBox()
139         toolbar = self.main_toolbar(False, False, None, '', '', False)
140
141         parea = hildon.PannableArea()
142         parea.connect("horizontal-movement", self.horizontal_mov)
143         tv = hildon.GtkTreeView(ui_normal)
144         model = self.__create_model(names, ids)
145
146         if market == 'metals':
147             tv.connect("row-activated", self.show_metal_graphs, model)
148         else:
149             tv.connect("row-activated", self.show_quotes_view, model, False)
150
151         tv.set_model(model)
152         self._tv_columns(tv)
153         parea.add(tv)
154
155         vbox.pack_start(parea, True, True, 0)
156         vbox.pack_start(gtk.HSeparator(), False, False, 5)
157         vbox.pack_start(toolbar, False, False, 0)
158
159         window.add(vbox)
160         window.show_all()
161
162     def horizontal_mov(self, parea, direction, initial_x, initial_y):
163         #direction = 2 right-to-left
164         #direction = 3 lefto-to-right
165
166         vadj = parea.get_vadjustment()
167         val = vadj.get_value()
168
169         if direction == 2:
170             if int(val)-2500 < 0:
171                  parea.scroll_to(-1, 0)
172             else:
173                 parea.scroll_to(-1, int(val)-2500)
174         elif direction == 3:
175             parea.scroll_to(-1, int(val)+3500)
176
177         #print val
178
179     def show_quotes_view(self, widget, path, column, model, portfolio):
180         quote = model[path][0], model[path][1]
181         #print "quote:", quote[0]
182         #('EURUSD=X', 'EUR/USD')
183
184         #Currencies and ETFs should show the list now -> view = True
185         #Other items show a new list with options
186         view = False
187         for i in marketdata.localids[(len(marketdata.localids)-2):]:
188             for j in i:
189                 if quote[0] == j:
190                     #print j
191                     view = True
192
193         if not view:
194             if quote[0] in marketdata.idindexes:
195                 self.show_instrument_view(widget, path, column, model,
196                                         marketdata.wnamesindexes,
197                                         marketdata.widsindexes,
198                                         marketdata.idindexes)
199                 return
200             if quote[0] in marketdata.idotmarkets:
201                 self.show_instrument_view(widget, path, column, model,
202                                         marketdata.omnames,
203                                         marketdata.omsymbols,
204                                         marketdata.idotmarkets)
205                 return
206             if quote[0] in marketdata.ideumarkets:
207                 self.show_instrument_view(widget, path, column, model,
208                                         marketdata.eunames,
209                                         marketdata.eusymbols,
210                                         marketdata.ideumarkets)
211                 return
212             if quote[0] in marketdata.idusmarkets:
213                 self.show_instrument_view(widget, path, column, model,
214                                         marketdata.usnames,
215                                         marketdata.ussymbols,
216                                         marketdata.idusmarkets)
217                 return
218
219
220         win = hildon.StackableWindow()
221         self.create_menu(win)
222         win.set_title(quote[1])
223
224         vbox = gtk.VBox()
225
226         ltitle = gtk.Label('')
227         ltitle.set_markup('<b><big>' + quote[1].replace('&', '') +
228                                  '</big></b>')
229         color = gtk.gdk.color_parse("#03A5FF")
230         ltitle.modify_fg(gtk.STATE_NORMAL, color)
231
232         parea = hildon.PannableArea()
233         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
234
235         vbox1 = gtk.VBox()
236
237         hbox = gtk.HBox()
238         label = gtk.Label('')
239         label.set_markup('<b><big>%39s:</big></b>' % '<u>Price</u>')
240         lprice = gtk.Label('')
241         hbox.pack_start(label, False, False, 0)
242         #if self.is_portrait():
243         hbox.pack_start(lprice, False, False, 25)
244         #else:
245         #    hbox.pack_start(lprice, False, False, 245)
246
247         vbox1.pack_start(hbox, True, True, 0)
248
249         hbox = gtk.HBox()
250         label = gtk.Label('')
251         label.set_markup('<b><big>%35s:</big></b>' % '<u>Change</u>')
252         lchange = gtk.Label('')
253         lpercent = gtk.Label('')
254         hbox.pack_start(label, False, False, 0)
255         #if self.is_portrait():
256         hbox.pack_start(lchange, False, False, 25)
257         hbox.pack_start(lpercent, False, False, 0)
258         #else:
259         #    hbox.pack_start(lchange, False, False, 205)
260         #    hbox.pack_start(lpercent, False, False, 0)
261
262         vbox1.pack_start(hbox, True, True, 0)
263
264         hbox = gtk.HBox()
265         label = gtk.Label('')
266         label.set_markup('<b><big>%35s:</big></b>' % '<u>Volume</u>')
267         lvolume = gtk.Label('')
268         hbox.pack_start(label, False, False, 0)
269         #if self.is_portrait():
270         hbox.pack_start(lvolume, False, False, 25)
271         #else:
272         #    hbox.pack_start(lvolume, False, False, 207)
273
274         vbox1.pack_start(hbox, True, True, 0)
275
276         hbox = gtk.HBox()
277         label = gtk.Label('')
278         label.set_markup('<b><big>%30s:</big></b>' % '<u>52 week high</u>')
279         l52whigh = gtk.Label('')
280         hbox.pack_start(label, False, False, 0)
281         #if self.is_portrait():
282         hbox.pack_start(l52whigh, False, False, 25)
283         #else:
284         #    hbox.pack_start(l52whigh, False, False, 125)
285
286         vbox1.pack_start(hbox, True, True, 0)
287
288         hbox = gtk.HBox()
289         label = gtk.Label('')
290         label.set_markup('<b><big>%30s:</big></b>' % '<u>52 week low</u>')
291         l52wlow = gtk.Label('')
292         hbox.pack_start(label, False, False, 0)
293         #if self.is_portrait():
294         hbox.pack_start(l52wlow, False, False, 26)
295         #else:
296         #    hbox.pack_start(l52wlow, False, False, 140)
297         vbox1.pack_start(hbox, True, True, 0)
298
299         #if self.is_portrait():
300         #    hbox = gtk.VBox()
301         #else:
302         hbox = gtk.HBox()
303         button1 = hildon.PickerButton(fhsize, horbtn)
304         data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
305                 "900", "1000"]
306         selector = self.create_selector(data, True)
307         button1.set_selector(selector)
308         button1.set_title("Your shares")
309         shares = self.get_shares_from_symbol(quote[0])
310         button1.set_value(shares)
311         hbox.pack_start(button1, True, True, 0)
312
313         button = hildon.Button(fhsize, horbtn)
314         button.set_title("Add to Portfolio")
315         button.connect("clicked", self.add_to_portfolio, button1, quote[0], quote[1])
316         hbox.pack_start(button, True, True, 0)
317
318         hbox1 = gtk.HBox()
319         label = gtk.Label('')
320         label.set_markup('<b><big>%37s:</big></b>' % '<u>Shares</u>')
321         lshares = gtk.Label(shares)
322         hbox1.pack_start(label, False, False, 0)
323         #if self.is_portrait():
324         hbox1.pack_start(lshares, False, False, 25)
325         #else:
326         #    hbox1.pack_start(lshares, False, False, 220)
327
328         hbox2 = gtk.HBox()
329         label = gtk.Label('')
330         label.set_markup('<b><big>%29s:</big></b>' % '<u>Holdings Value</u>')
331         holdingsvalue = gtk.Label("")
332         hbox2.pack_start(label, False, False, 0)
333         #if self.is_portrait():
334         hbox2.pack_start(holdingsvalue, False, False, 25)
335         #else:
336         #    hbox2.pack_start(holdingsvalue, False, False, 105)
337
338         hbox3 = gtk.HBox()
339         label = gtk.Label('')
340         label.set_markup("<b><big>%25s:</big></b>" % "<u>Day's Value Change</u>")
341         dayvaluechange = gtk.Label("")
342         hbox3.pack_start(label, False, False, 0)
343         #if self.is_portrait():
344         hbox3.pack_start(dayvaluechange, False, False, 25)
345         #else:
346         #    hbox3.pack_start(dayvaluechange, False, False, 45)
347
348         if not portfolio:
349             vbox1.pack_start(hbox, False, False, 0)
350         else:
351             vbox1.pack_start(hbox1, True, True, 0)
352             vbox1.pack_start(hbox2, True, True, 0)
353             vbox1.pack_start(hbox3, True, True, 0)
354
355         parea.add_with_viewport(vbox1)
356
357         widgets = [win, ltitle, lprice, lchange,  lpercent, lvolume, l52whigh,
358                     l52wlow, lshares, holdingsvalue, dayvaluechange]
359
360         toolbar = self.main_toolbar(True, portfolio, widgets, quote[0], quote[1], False)
361
362         vbox.pack_start(ltitle, False, False, 0)
363         vbox.pack_start(gtk.HSeparator(), False, False, 0)
364         vbox.pack_start(parea, True, True, 0)
365         vbox.pack_start(gtk.HSeparator(), False, False, 5)
366         vbox.pack_start(toolbar, False, False, 0)
367
368
369         win.add(vbox)
370         win.show_all()
371         self.show_data(quote[0], widgets, shares)
372
373     def is_portrait(self):
374         width = gtk.gdk.screen_width()
375         height = gtk.gdk.screen_height()
376         if width > height:
377             return False
378         else:
379             return True
380
381     def get_shares_from_symbol(self, symbol):
382         shares = "0"
383         try:
384             portfolio_data = settings.load_portfolio(settingsdb)
385             for item in portfolio_data :
386                 if symbol in item:
387                     shares = item[2]
388             return shares
389         except:
390             logger.exception("Getting shares from symbol: %s" % symbol)
391             return shares
392
393     def add_to_portfolio(self, widget, button, symbol, name):
394         shares = button.get_value()
395
396         try:
397             portfolio = settings.load_portfolio(settingsdb)
398             index = "None"
399             for item in portfolio:
400                 if symbol in item:
401                     index = portfolio.index(item)
402
403             item = [symbol, name, shares, '-']
404
405             if index is "None":
406                 settings.insert_new_item_to_portfolio(settingsdb, item)
407             else:
408                 settings.delete_item_from_portfolio(settingsdb, symbol)
409                 settings.insert_new_item_to_portfolio(settingsdb, item)
410
411             self.show_info_banner(widget, "Added to portfolio")
412         except:
413             logger.exception("Adding to portfolio: %s, %s" % (symbol, name))
414             self.show_info_banner(widget, "Error adding to portfolio")
415
416
417     def create_selector(self, data, entry):
418         if entry:
419             selector = hildon.TouchSelectorEntry(text=True)
420         else:
421             selector = hildon.TouchSelector(text=True)
422         for i in range(len(data)):
423             selector.append_text(data[i])
424
425         return selector
426
427     def show_data(self, symbol, widgets, shares):
428         import thread
429         winprogind(widgets[0], 1)
430         thread.start_new_thread(self.get_data, (symbol, widgets, shares))
431
432     def get_data(self, symbol, widgets, shares):
433         from ystockquote import ystockquote as yt
434         win, ltitle, lprice, lchange,  lpercent, lvolume, l52whigh, l52wlow, lshares, holdingsvalue, dayvaluechange = widgets
435
436         try:
437             data = yt.get_all(symbol)
438         except:
439             logger.exception("Getting data from Yahoo: %s" % symbol)
440             data = {'price': 'N/A', 'change': 'N/A', 'volume':'N/A',
441                     '52_week_high': 'N/A', '52_week_low': 'N/A'}
442             ltitle.set_markup('<b><big>Failed to get data</big></b>')
443
444         try:
445             ch_percent = \
446                     100.0 * float(data['change'])/(float(data['price']) - \
447                     float(data['change']))
448         except ValueError:
449             ch_percent = 0.0
450
451         lprice.set_label(data['price'])
452         lchange.set_label(data['change'])
453         lpercent.set_label('%6.2f %%' % ch_percent)
454
455         if '-' in data['change']:
456             color = gtk.gdk.color_parse("#FF0000")
457         else:
458             color = gtk.gdk.color_parse("#16EB78")
459
460         lpercent.modify_fg(gtk.STATE_NORMAL, color)
461         lchange.modify_fg(gtk.STATE_NORMAL, color)
462
463         lvolume.set_label(data['volume'])
464         l52whigh.set_label(data['52_week_high'])
465         l52wlow.set_label(data['52_week_low'])
466
467         try:
468             daychange = float(shares)*float(data['change'])
469         except ValueError:
470             daychange = 'N/A'
471         try:
472             holdvalue = float(shares)*float(data['price'])
473         except ValueError:
474             holdvalue = 'N/A'
475
476         dayvaluechange.set_label(str(daychange))
477         holdingsvalue.set_label(str(holdvalue))
478
479         winprogind(win, 0)
480
481     def refresh_stock_data(self, widget, portfolio, widgets, symbol):
482         if portfolio:
483             shares = self.get_shares_from_symbol(symbol)
484         else:
485             shares = "0"
486
487         self.show_data(symbol, widgets, shares)
488
489     def show_graph_view(self, widget, symbol, name):
490         win = hildon.StackableWindow()
491         self.create_menu(win)
492         win.set_title(name)
493
494         vbox = gtk.VBox()
495         toolbar = self.main_toolbar(False, True, None, '', '', False)
496
497         self.graphs_title = gtk.Label(name)
498         color = gtk.gdk.color_parse("#03A5FF")
499         self.graphs_title.modify_fg(gtk.STATE_NORMAL, color)
500
501         parea = hildon.PannableArea()
502         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
503
504         vbox1 = gtk.VBox()
505
506         hbox = gtk.HBox()
507         hbox.set_homogeneous(True)
508
509         button = hildon.Button(fhsize, horbtn)
510         button.set_label('1d')
511         button.connect("clicked", self.show_graph, '1d', win, symbol)
512         hbox.pack_start(button)
513
514         button = hildon.Button(fhsize, horbtn)
515         button.set_label('5d')
516         button.connect("clicked", self.show_graph, '5d', win, symbol)
517         hbox.pack_start(button)
518
519         button = hildon.Button(fhsize, horbtn)
520         button.set_label('3m')
521         button.connect("clicked", self.show_graph, '3m', win, symbol)
522         hbox.pack_start(button)
523
524         button = hildon.Button(fhsize, horbtn)
525         button.set_label('6m')
526         button.connect("clicked", self.show_graph, '6m', win, symbol)
527         hbox.pack_start(button)
528
529         vbox1.pack_start(hbox, False, False, 0)
530         hbox = gtk.HBox()
531         hbox.set_homogeneous(True)
532
533         button = hildon.Button(fhsize, horbtn)
534         button.set_label('1y')
535         button.connect("clicked", self.show_graph, '1y', win, symbol)
536         hbox.pack_start(button)
537
538         button = hildon.Button(fhsize, horbtn)
539         button.set_label('2y')
540         button.connect("clicked", self.show_graph, '2y', win, symbol)
541         hbox.pack_start(button)
542
543         button = hildon.Button(fhsize, horbtn)
544         button.set_label('5y')
545         button.connect("clicked", self.show_graph, '5y', win, symbol)
546         hbox.pack_start(button)
547
548         button = hildon.Button(fhsize, horbtn)
549         button.set_label('Max')
550         button.connect("clicked", self.show_graph, 'max', win, symbol)
551         hbox.pack_start(button)
552
553         vbox1.pack_start(hbox, False, False, 0)
554
555         self.graph = gtk.Image()
556         vbox1.pack_start(self.graph, True, True, 0)
557
558         parea.add_with_viewport(vbox1)
559
560         vbox.pack_start(self.graphs_title, False, False, 0)
561         vbox.pack_start(gtk.HSeparator(), False, False, 0)
562         vbox.pack_start(parea, True, True, 0)
563         vbox.pack_start(gtk.HSeparator(), False, False, 5)
564         vbox.pack_start(toolbar, False, False, 0)
565
566         win.add(vbox)
567         win.show_all()
568
569         self.show_graph(None, '1d', win, symbol)
570
571     def show_graph(self, widget, option, win, symbol):
572         import thread
573         winprogind(win, 1)
574         thread.start_new_thread(self.get_graph_data, (option, win, symbol))
575
576     def get_graph_data(self, option, win, symbol):
577         if option == '1d':
578             url = 'http://uk.ichart.yahoo.com/b?s=%s' % symbol
579         elif option == '5d':
580             url = 'http://uk.ichart.yahoo.com/w?s=%s' % symbol
581         elif option == '3m':
582             url = 'http://chart.finance.yahoo.com/c/3m/s/%s' % symbol.lower()
583         elif option == '6m':
584             url = 'http://chart.finance.yahoo.com/c/6m/s/%s' % symbol.lower()
585         elif option == '1y':
586             url = 'http://chart.finance.yahoo.com/c/1y/s/%s' % symbol.lower()
587         elif option == '2y':
588             url = 'http://chart.finance.yahoo.com/c/2y/s/%s' % symbol.lower()
589         elif option == '5y':
590             url = 'http://chart.finance.yahoo.com/c/5y/s/%s' % symbol.lower()
591         elif option == 'max':
592             url = 'http://chart.finance.yahoo.com/c/my/s/%s' % symbol.lower()
593
594         try:
595             myimg = urllib2.urlopen(url)
596             imgdata = myimg.read()
597
598             pbl = gtk.gdk.PixbufLoader()
599             pbl.write(imgdata)
600
601             pbuf = pbl.get_pixbuf()
602             pbuf = pbuf.scale_simple(475, 235, gtk.gdk.INTERP_TILES)
603             pbl.close()
604             self.graph.set_from_pixbuf(pbuf)
605             winprogind(win, 0)
606         except:
607             logger.exception("Getting graph data: %s" % url)
608             winprogind(win, 0)
609             self.graphs_title.set_label('Failed to get data')
610             self.graph.destroy()
611
612     def show_metal_graphs(self, widget, path, column, model):
613         metal = model[path][1]
614         metal_urls = self.get_metal_graphs_urls(metal)
615         options =  []
616         for i in metal_urls:
617             options.append(i[0])
618
619         win = hildon.StackableWindow()
620         #self.create_menu(win)
621         win.set_title(metal)
622
623         vbox = gtk.VBox()
624         #toolbar = self.main_toolbar(False, True, None, '', '', False)
625
626         self.metalgraph_title = gtk.Label(metal)
627         color = gtk.gdk.color_parse("#03A5FF")
628         self.metalgraph_title.modify_fg(gtk.STATE_NORMAL, color)
629
630         parea = hildon.PannableArea()
631         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
632
633         vbox1 = gtk.VBox()
634
635         hbox = gtk.HBox()
636         hbox.set_homogeneous(True)
637
638         button = hildon.Button(fhsize, horbtn)
639         button.set_label('live')
640         button.connect("clicked", self.show_metalgraph, 'live', win, metal_urls)
641         if 'live' in options:
642             hbox.pack_start(button)
643
644         button = hildon.Button(fhsize, horbtn)
645         button.set_label('30d')
646         button.connect("clicked", self.show_metalgraph, '30d', win, metal_urls)
647         if '30d' in options:
648             hbox.pack_start(button)
649
650         button = hildon.Button(fhsize, horbtn)
651         button.set_label('60d')
652         button.connect("clicked", self.show_metalgraph, '60d', win, metal_urls)
653         if '60d' in options:
654             hbox.pack_start(button)
655
656         button = hildon.Button(fhsize, horbtn)
657         button.set_label('6m')
658         button.connect("clicked", self.show_metalgraph, '6m', win, metal_urls)
659         if '6m' in options:
660             hbox.pack_start(button)
661
662         vbox1.pack_start(hbox, False, False, 0)
663         hbox = gtk.HBox()
664         hbox.set_homogeneous(True)
665
666         button = hildon.Button(fhsize, horbtn)
667         button.set_label('1y')
668         button.connect("clicked", self.show_metalgraph, '1y', win, metal_urls)
669         if '1y' in options:
670             hbox.pack_start(button)
671
672         button = hildon.Button(fhsize, horbtn)
673         button.set_label('5y')
674         button.connect("clicked", self.show_metalgraph, '5y', win, metal_urls)
675         if '5y' in options:
676             hbox.pack_start(button)
677
678         button = hildon.Button(fhsize, horbtn)
679         button.set_label('10y')
680         button.connect("clicked", self.show_metalgraph, '10y', win, metal_urls)
681         if '10y' in options:
682             hbox.pack_start(button)
683
684         vbox1.pack_start(hbox, False, False, 0)
685
686         self.metalgraph = gtk.Image()
687         vbox1.pack_start(self.metalgraph, True, True, 0)
688
689         parea.add_with_viewport(vbox1)
690
691         vbox.pack_start(self.metalgraph_title, False, False, 0)
692         vbox.pack_start(gtk.HSeparator(), False, False, 0)
693         vbox.pack_start(parea, True, True, 0)
694         vbox.pack_start(gtk.HSeparator(), False, False, 5)
695         #vbox.pack_start(toolbar, False, False, 0)
696
697         win.add(vbox)
698         win.show_all()
699
700         self.show_metalgraph(None, options[0], win, metal_urls)
701
702     def show_metalgraph(self, widget, option, win, metal_urls):
703         import thread
704         winprogind(win, 1)
705         thread.start_new_thread(self.get_metalgraph_data, (option, win, metal_urls))
706
707     def get_metalgraph_data(self, option, win, metal_urls):
708         for i in metal_urls:
709             if i[0] == option:
710                 print i[1]
711                 url = i[1]
712
713         try:
714             myimg = urllib2.urlopen(url)
715             imgdata = myimg.read()
716
717             pbl = gtk.gdk.PixbufLoader()
718             pbl.write(imgdata)
719
720
721             pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 475, 235)
722             pixbuf.fill(0xffffffff)
723
724
725             pbuf = pbl.get_pixbuf()
726             pbuf = pbuf.scale_simple(475, 235, gtk.gdk.INTERP_TILES)
727
728             pixbuf.composite(pbuf, 0, 0, pixbuf.props.width, pixbuf.props.height, 0, 0, 1.0, 1.0, gtk.gdk.INTERP_HYPER, 55)
729
730
731             pbl.close()
732             self.metalgraph.set_from_pixbuf(pbuf)
733             winprogind(win, 0)
734         except:
735             logger.exception("Getting graph data: %s" % url)
736             winprogind(win, 0)
737             self.metalgraph_title.set_label('Failed to get data')
738             self.metalgraph.destroy()
739
740     def get_metal_graphs_urls(self, metal):
741         metal_urls = []
742         liveurl = 'http://www.kitco.com/images/live/%s'
743         graphurl = 'http://www.kitco.com/LFgif/%s'
744         t24url = 'http://www.kitconet.com/charts/metals/base/%s'
745         if metal == 'Gold':
746             metal_urls.append(['live', liveurl % 'gold.gif'])
747             metal_urls.append(['30d', graphurl % 'au0030lnb.gif'])
748             metal_urls.append(['60d', graphurl % 'au0060lnb.gif'])
749             metal_urls.append(['6m', graphurl % 'au0182nyb.gif'])
750             metal_urls.append(['1y', graphurl % 'au0365nyb.gif'])
751             metal_urls.append(['5y', graphurl % 'au1825nyb.gif'])
752             metal_urls.append(['10y', graphurl % 'au3650nyb.gif'])
753         elif metal == 'Silver':
754             metal_urls.append(['live', liveurl % 'silver.gif'])
755             metal_urls.append(['30d', graphurl % 'ag0030lnb.gif'])
756             metal_urls.append(['60d', graphurl % 'ag0060lnb.gif'])
757             metal_urls.append(['6m', graphurl % 'ag0182nyb.gif'])
758             metal_urls.append(['1y', graphurl % 'ag0365nyb.gif'])
759             metal_urls.append(['5y', graphurl % 'ag1825nyb.gif'])
760             metal_urls.append(['10y', graphurl % 'ag3650nyb.gif'])
761         elif metal == 'Platinum':
762             metal_urls.append(['live', liveurl % 'plati.gif'])
763             metal_urls.append(['30d', graphurl % 'pt0030lnb.gif'])
764             metal_urls.append(['60d', graphurl % 'pt0060lnb.gif'])
765             metal_urls.append(['6m', graphurl % 'pt0182nyb.gif'])
766             metal_urls.append(['1y', graphurl % 'pt0365nyb.gif'])
767             metal_urls.append(['5y', graphurl % 'pt1825nyb.gif'])
768         elif metal == 'Palladium':
769             metal_urls.append(['live', liveurl % 'plad.gif'])
770             metal_urls.append(['30d', graphurl % 'pd0030lnb.gif'])
771             metal_urls.append(['60d', graphurl % 'pd0060lnb.gif'])
772             metal_urls.append(['6m', graphurl % 'pd0182nyb.gif'])
773             metal_urls.append(['1y', graphurl % 'pd0365nyb.gif'])
774             metal_urls.append(['5y', graphurl % 'pd1825nyb.gif'])
775         elif metal == 'Rhodium':
776             metal_urls.append(['30d', graphurl % 'rh0030lnb.gif'])
777             metal_urls.append(['60d', graphurl % 'rh0060lnb.gif'])
778             metal_urls.append(['6m', graphurl % 'rh0182lnb.gif'])
779             metal_urls.append(['1y', graphurl % 'rh0365lnb.gif'])
780             metal_urls.append(['5y', graphurl % 'rh1825lnb.gif'])
781         elif metal == 'Copper':
782             metal_urls.append(['live', t24url % 't24_cp450x275.gif'])
783         elif metal == 'Nickel':
784             metal_urls.append(['live', t24url % 't24_nk450x275.gif'])
785         elif metal == 'Aluminium':
786             metal_urls.append(['live', t24url % 't24_al450x275.gif'])
787         elif metal == 'Zinc':
788             metal_urls.append(['live', t24url % 't24_zc450x275.gif'])
789         elif metal == 'Lead':
790             metal_urls.append(['live', t24url % 't24_ld450x275.gif'])
791
792         return metal_urls
793
794     def _tv_columns(self, treeview):
795         column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
796         column.set_visible(False)
797         treeview.append_column(column)
798
799         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
800         treeview.append_column(column)
801
802     def __create_model(self, names, ids):
803         lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
804         for item in range(len(names)):
805             iter = lstore.append()
806             lstore.set(iter, 0, ids[item], 1, names[item])
807         return lstore
808
809     def main_toolbar(self, quotesview, portfolio, widgets, symbol, name, initial):
810         toolbar = gtk.HBox()
811         #toolbar.set_homogeneous(True)
812
813         portfolio_btn = hildon.Button(fhsize, horbtn)
814         portfolio_btn.set_title("Portfolio")
815         portfolio_btn.connect("clicked", self.show_portfolio_view)
816
817         graph_btn = hildon.Button(fhsize, horbtn)
818         graph_btn.set_title("Graph")
819         graph_btn.connect("clicked", self.show_graph_view, symbol, name)
820
821         refresh_btn = hildon.Button(fhsize, horbtn)
822         refresh_btn.set_title("Refresh")
823         refresh_btn.connect("clicked", self.refresh_stock_data, portfolio,
824                             widgets, symbol)
825
826
827         info_btn = hildon.Button(fhsize, horbtn)
828         img = gtk.image_new_from_icon_name("general_information", gtk.ICON_SIZE_SMALL_TOOLBAR)
829         info_btn.set_image(img)
830         info_btn.connect("clicked", self.show_app_information)
831
832         search_btn = hildon.Button(fhsize, horbtn)
833         img = gtk.image_new_from_icon_name("general_search", gtk.ICON_SIZE_SMALL_TOOLBAR)
834         search_btn.set_image(img)
835         search_btn.connect("clicked", self.show_search_dialog)
836
837         if not portfolio:
838             toolbar.pack_start(portfolio_btn)
839             if not quotesview:
840                 toolbar.pack_start(info_btn, False, False, 0)
841         if quotesview:
842             toolbar.pack_start(graph_btn)
843             toolbar.pack_start(refresh_btn)
844
845         if initial:
846             toolbar.pack_start(search_btn, False, False, 0)
847
848         toolbar.show_all()
849
850         return toolbar
851
852     def show_search_dialog(self, widget):
853         dlg = gtk.Dialog(title='Search company', parent=None, flags=0)
854         dlg.set_has_separator(False)
855
856         hbox = gtk.HBox()
857
858         entry = hildon.Entry(fhsize)
859         entry.connect("activate", self.do_search, entry, dlg)
860         hbox.pack_start(entry, True, True, 0)
861
862         button = hildon.Button(fhsize, horbtn)
863         img = gtk.image_new_from_icon_name("general_search", gtk.ICON_SIZE_SMALL_TOOLBAR)
864         button.set_image(img)
865         button.connect("clicked", self.do_search, entry, dlg)
866         hbox.pack_start(button, False, False, 0)
867
868         dlg.vbox.pack_start(hbox, False, False, 0)
869
870         dlg.show_all()
871         dlg.run()
872         dlg.destroy()
873
874
875     def do_search(self, widget, entry, dlg):
876         import thread
877         text = entry.get_text()
878         dlg.destroy()
879
880         winprogind(self.window, 1)
881         thread.start_new_thread(self._really_do_search, (text,))
882
883     def _really_do_search(self, text):
884
885         if allnames == []:
886             for market in marketdata.eunames:
887                 for company in market:
888                     allnames.append(company)
889
890             for market in marketdata.omnames:
891                 for company in market:
892                     allnames.append(company)
893
894             for market in marketdata.usnames:
895                 for company in market:
896                     allnames.append(company)
897
898         if allsymbols == []:
899             for market in marketdata.eusymbols:
900                 for company in market:
901                     allsymbols.append(company)
902
903             for market in marketdata.omsymbols:
904                 for company in market:
905                     allsymbols.append(company)
906
907             for market in marketdata.ussymbols:
908                 for company in market:
909                     allsymbols.append(company)
910
911         new_model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
912         for i in range(len(allnames)):
913             if text.lower() in allnames[i].lower():
914                 niter = new_model.append()
915                 #print allsymbols[i], allnames[i]
916                 #FIXME: repeated companies in the results list
917                 #print if allnames[i]
918                 #for j in new_model:
919                 #    if j[1] == allnames[i]:
920                 new_model.set(niter, 0, allsymbols[i], 1, allnames[i])
921
922         if len(new_model) == 0:
923             winprogind(self.window, 0)
924             gtk.gdk.threads_enter()
925             self.show_info_banner(self.window, "No items found for this search")
926             gtk.gdk.threads_leave()
927             return
928
929         gtk.gdk.threads_enter()
930         self.show_search_screen(new_model, text)
931         gtk.gdk.threads_leave()
932         winprogind(self.window, 0)
933
934
935     def show_search_screen(self, model, text):
936         window = hildon.StackableWindow()
937         self.create_menu(window)
938         window.set_title("Search for " + text)
939
940         vbox = gtk.VBox()
941         toolbar = self.main_toolbar(False, False, None, '', '', False)
942
943         parea = hildon.PannableArea()
944         parea.connect("horizontal-movement", self.horizontal_mov)
945         tv = hildon.GtkTreeView(ui_normal)
946         tv.connect("row-activated", self.show_quotes_view, model, False)
947         tv.set_model(model)
948         self._tv_columns(tv)
949         parea.add(tv)
950
951         vbox.pack_start(parea, True, True, 0)
952         vbox.pack_start(gtk.HSeparator(), False, False, 5)
953         vbox.pack_start(toolbar, False, False, 0)
954
955         window.add(vbox)
956         window.show_all()
957
958     def show_app_information(self, widget):
959         self.show_information_note(self.window, (
960         "The data is got from Yahoo! Finance.\n"
961         "It could be delayed or even wrong.\n"
962         "The author doesn't validate in any way this data and therefore he is not responsible for any damage that may occur.\n\n"
963         "You can scroll large list with gestures:\n"
964         "Left-to-right gesture: scroll down.\n"
965         "Right-to-left gesture: scroll up."))
966
967     def show_portfolio_view(self, widget):
968         data = settings.load_portfolio(settingsdb)
969         for item in data:
970             item.append('-')
971             item.append('-')
972
973         win = hildon.StackableWindow()
974         self.create_menu(win)
975         win.set_title("Portfolio")
976
977         vbox = gtk.VBox()
978
979         parea = hildon.PannableArea()
980         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
981         tv = hildon.GtkTreeView(ui_normal)
982         tv.set_headers_visible(True)
983         self.portfolio_model = self._create_portfolio_model(data)
984         self.quotes_id = tv.connect("row-activated", self.show_quotes_view,
985                                     self.portfolio_model, True)
986         tv.set_model(self.portfolio_model)
987         self._tv_portfolio_columns(tv)
988         parea.add(tv)
989
990         hbox = gtk.HBox()
991         button = hildon.Button(fhsize, horbtn)
992         button.set_title("Refresh All")
993         button.connect("clicked", self.refresh_portfolio, tv, win)
994         hbox.pack_start(button, True, True, 0)
995
996         button = hildon.Button(fhsize, horbtn)
997         button.set_title("Add manually")
998         button.connect("clicked", self.add_item_dlg, None, None)
999         hbox.pack_start(button, True, True, 0)
1000
1001         button = hildon.Button(fhsize, horbtn)
1002         button.set_title("Remove")
1003         button.connect("clicked", self.remove_item)
1004         hbox.pack_start(button, True, True, 0)
1005
1006         button = hildon.CheckButton(fhsize)
1007         button.set_label("Edit")
1008
1009         button.connect("toggled", self.edit_toggled, tv)
1010         hbox.pack_start(button, True, True, 0)
1011
1012
1013         vbox.pack_start(parea, True, True, 0)
1014         vbox.pack_start(hbox, False, False, 0)
1015         win.add(vbox)
1016         win.show_all()
1017
1018     def edit_toggled(self, widget, tv):
1019         if widget.get_active():
1020             tv.disconnect(self.quotes_id)
1021             self.edit_id = tv.connect("row-activated",
1022                             self.edit_portfolio_item, self.portfolio_model)
1023         else:
1024             tv.disconnect(self.edit_id)
1025             self.quotes_id = tv.connect("row-activated",
1026                             self.show_quotes_view, self.portfolio_model, True)
1027
1028     def edit_portfolio_item(self, widget, path, column, model):
1029         seliter = model.get_iter(path)
1030
1031         symbol = model[path][0]
1032         name = model[path][1]
1033         shares = model[path][2]
1034
1035         data = [shares, symbol, name]
1036         self.add_item_dlg(widget, data, seliter)
1037
1038     def add_item_dlg(self, widget, data, seliter):
1039         if data:
1040             shares = data[0]
1041             symbol = data[1]
1042             name = data[2]
1043             title = "Edit item from portfolio"
1044             btntext = "Edit"
1045             edit = True
1046         else:
1047             shares = "0"
1048             symbol = ""
1049             name = ""
1050             title = "Add to portfolio"
1051             btntext = "Add"
1052             edit = False
1053
1054         dlg = gtk.Dialog(title=title, parent=None, flags=0)
1055         dlg.set_has_separator(False)
1056
1057         button1 = hildon.PickerButton(fhsize, horbtn)
1058         data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
1059                 "900", "1000"]
1060         selector = self.create_selector(data, True)
1061         button1.set_selector(selector)
1062         button1.set_title("Your shares")
1063         button1.set_value(shares)
1064         dlg.vbox.pack_start(button1, False, False, 0)
1065
1066         entry1 = hildon.Entry(fhsize)
1067         entry1.set_text(name)
1068         entry2 = hildon.Entry(fhsize)
1069         entry2.set_text(symbol)
1070
1071         entry1.set_placeholder("Name")
1072         entry1.connect("activate", self.add_item, dlg, button1, entry1,
1073                         entry2, edit, seliter)
1074         dlg.vbox.pack_start(entry1, False, False, 0)
1075
1076         entry2.connect("activate", self.add_item, dlg, button1, entry1,
1077                         entry2, edit, seliter)
1078         entry2.set_placeholder("Yahoo Finance symbol")
1079         dlg.vbox.pack_start(entry2, False, False, 0)
1080
1081         button = hildon.Button(fhsize, horbtn)
1082         button.set_label(btntext)
1083         button.connect("clicked", self.add_item, dlg, button1, entry1,
1084                         entry2, edit, seliter)
1085         dlg.vbox.pack_start(button, False, False, 0)
1086
1087         dlg.show_all()
1088         dlg.run()
1089         dlg.destroy()
1090
1091     def add_item(self, widget, dlg, button, entry1, entry2, edit, seliter):
1092         symbol = entry2.get_text()
1093         name = entry1.get_text()
1094         shares = button.get_value()
1095
1096         if name == '' or symbol == '':
1097             self.show_info_banner(widget, "Must add the name and symbol")
1098             return
1099
1100         self.add_to_portfolio(widget, button, symbol, name)
1101         dlg.destroy()
1102
1103         if edit:
1104             self.portfolio_model.set(seliter, 0, symbol, 1, name, 2, shares,
1105                                        3, "-", 4, "-", 5, "-", 6, "green")
1106         else:
1107             niter = self.portfolio_model.append()
1108             self.portfolio_model.set(niter, 0, symbol, 1, name, 2, shares,
1109                                     3, "-", 4, "-", 5, "-", 6, "green")
1110
1111     def remove_item(self, widget):
1112         win = hildon.StackableWindow()
1113         win.fullscreen()
1114         toolbar = hildon.EditToolbar("Choose items to delete", "Delete")
1115         win.set_edit_toolbar(toolbar)
1116
1117         vbox = gtk.VBox()
1118         parea = hildon.PannableArea()
1119         tv = hildon.GtkTreeView(ui_edit)
1120         selection = tv.get_selection()
1121         selection.set_mode(gtk.SELECTION_MULTIPLE)
1122         tv.set_model(self.portfolio_model)
1123         self._tv_remove_portfolio_columns(tv)
1124         parea.add(tv)
1125
1126         toolbar.connect("button-clicked", self.delete_from_portfolio, win, tv,
1127                         selection)
1128         toolbar.connect_object("arrow-clicked", gtk.Window.destroy, win)
1129
1130         vbox.pack_start(parea, True, True, 0)
1131         win.add(vbox)
1132         win.show_all()
1133
1134     def delete_from_portfolio(self, widget, win, tv, selection):
1135         if not self.is_treeview_selected(tv):
1136             return
1137
1138         conf = self.show_confirmation(win, "Delete items?")
1139
1140         if conf:
1141             try:
1142                 selmodel, selected = selection.get_selected_rows()
1143                 iters = [selmodel.get_iter(path) for path in selected]
1144                 for i in iters:
1145                     symbol = selmodel.get_value(i, 0)
1146                     settings.delete_item_from_portfolio(settingsdb, symbol)
1147                     selmodel.remove(i)
1148             except:
1149                 logger.exception("Deleting item from portfolio")
1150                 self.info_banner(widget, "Error deleting item")
1151
1152     def _tv_remove_portfolio_columns(self, treeview):
1153         column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
1154         column.set_visible(False)
1155         treeview.append_column(column)
1156
1157         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
1158         column.set_property("expand", True)
1159         treeview.append_column(column)
1160
1161         column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
1162         column.set_visible(False)
1163         treeview.append_column(column)
1164
1165         column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
1166         column.set_visible(False)
1167         treeview.append_column(column)
1168
1169     def refresh_portfolio(self, widget, tv, win):
1170         data = settings.load_portfolio(settingsdb)
1171         for item in data:
1172             item.append('-')
1173             item.append('-')
1174         import thread
1175         winprogind(win, 1)
1176         thread.start_new_thread(self._do_refresh_portfolio, (data, tv, win))
1177
1178     def _do_refresh_portfolio(self, data, tv, win):
1179         print data
1180         for item in data:
1181             item[3], item[4] = self.get_portfolio_data(item[0])
1182             try:
1183                 ch_percent = \
1184                         100.0 * float(item[4])/(float(item[3]) - \
1185                         float(item[4]))
1186             except ValueError:
1187                 ch_percent = 0.0
1188
1189             item[5] = '%6.2f %%' % ch_percent
1190
1191
1192         print data
1193         self.portfolio_model = self._create_portfolio_model(data)
1194         tv.set_model(self.portfolio_model)
1195         winprogind(win, 0)
1196
1197     def get_portfolio_data(self, symbol):
1198         from ystockquote import ystockquote as yt
1199         try:
1200             data = yt.get_all(symbol)
1201             return data['price'], data['change']
1202         except:
1203             logger.exception("Getting price from Yahoo: %s" % symbol)
1204             return "-", "-"
1205
1206     def _create_portfolio_model(self, data):
1207         lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
1208                                 gobject.TYPE_STRING, gobject.TYPE_STRING,
1209                                 gobject.TYPE_STRING, gobject.TYPE_STRING,
1210                                 gobject.TYPE_STRING)
1211         for item in data:
1212             iter = lstore.append()
1213             if '+' in item[4]:
1214                 color = 'green'
1215             else:
1216                 color = 'red'
1217             lstore.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3],
1218                         4, item[4], 5, item[5], 6, color)
1219         return lstore
1220
1221     def _tv_portfolio_columns(self, treeview):
1222         column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
1223         column.set_visible(False)
1224         treeview.append_column(column)
1225
1226         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
1227         column.set_property("expand", True)
1228         treeview.append_column(column)
1229
1230         column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
1231         treeview.append_column(column)
1232
1233         column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
1234         treeview.append_column(column)
1235
1236         column = gtk.TreeViewColumn('Change', gtk.CellRendererText(), text=4)
1237         treeview.append_column(column)
1238
1239
1240         renderer = gtk.CellRendererText()
1241         renderer.set_property("foreground-set", True)
1242         column = gtk.TreeViewColumn('%', renderer, text=5, foreground=6)
1243         treeview.append_column(column)
1244
1245     def show_confirmation(self, window, msg):
1246         dialog = hildon.hildon_note_new_confirmation(window, msg)
1247         dialog.show_all()
1248         result = dialog.run()
1249         if result == gtk.RESPONSE_OK:
1250             dialog.destroy()
1251             return True
1252
1253         dialog.destroy()
1254         return False
1255
1256     def show_information_note(self, window, msg):
1257         dialog = hildon.hildon_note_new_information(window, msg)
1258         dialog.show_all()
1259         result = dialog.run()
1260         dialog.destroy()
1261
1262     def show_info_banner(self, widget, msg):
1263         hildon.hildon_banner_show_information(widget, 'qgn_note_infoprint', msg)
1264
1265     def is_treeview_selected(self, treeview):
1266         selection = treeview.get_selection()
1267         if selection.count_selected_rows() == 0:
1268             self.show_info_banner(treeview, 'No selected item')
1269             return False
1270         else:
1271             return True
1272
1273 class About:
1274
1275     def __init__(self, widget):
1276         self.abdialog = gtk.Dialog()
1277         self.abdialog.set_title("About StockThis")
1278
1279         notebook = gtk.Notebook()
1280         notebook.set_show_tabs(False)
1281         notebook.set_scrollable(False)
1282         notebook.set_show_border(False)
1283
1284         # Description page #
1285         vbox = gtk.VBox()
1286
1287         label = gtk.Label()
1288         label.set_markup("<b><big>StockThis %s</big></b>" % VERSION)
1289         vbox.pack_start(label, True, True, 0)
1290
1291         label = gtk.Label("Stocks application with big database")
1292         vbox.pack_start(label, True, True, 0)
1293
1294         label = gtk.Label("GNU General Public License")
1295         vbox.pack_start(label, True, True, 0)
1296
1297         url = "http://stockthis.garage.maemo.org"
1298         webbtn = gtk.LinkButton(url, "Web")
1299         vbox.pack_start(webbtn, True, True, 0)
1300         gtk.link_button_set_uri_hook(self.launch_browser)
1301
1302         notebook.append_page(vbox, gtk.Label())
1303
1304         # Credits page #
1305         vbox = gtk.VBox()
1306         textview = hildon.TextView()
1307         textview.set_cursor_visible(False)
1308         textview.set_wrap_mode(gtk.WRAP_WORD)
1309         text = "Written by Daniel Martin Yerga (dyerga@gmail.com)"
1310         textview.get_buffer().set_text(text)
1311
1312         parea = hildon.PannableArea()
1313         parea.add(textview)
1314
1315         vbox.pack_start(parea, True, True, 0)
1316         notebook.append_page(vbox, gtk.Label())
1317
1318
1319         # Donate page #
1320         vbox = gtk.VBox()
1321
1322         textview = hildon.TextView()
1323         textview.set_cursor_visible(False)
1324         textview.set_wrap_mode(gtk.WRAP_WORD)
1325         text = """StockThis is a free software application.
1326 Developing good software takes time and hard work.
1327 StockThis's author develops the program in his spare time.
1328 If you like the program and it's helpful, consider donating a small amount of money.
1329 Donations are a great incentive and help the developer feel that the hard work is appreciated.
1330 """
1331         textview.get_buffer().set_text(text)
1332
1333         parea = hildon.PannableArea()
1334         parea.add(textview)
1335
1336         button = hildon.Button(fhsize, horbtn)
1337         button.set_title("Make donation")
1338         url = "http://stockthis.garage.maemo.org/donate.html"
1339         button.connect("clicked", self.launch_browser, url)
1340         vbox.pack_start(button, False, False, 0)
1341         vbox.pack_start(parea, True, True, 0)
1342
1343         notebook.append_page(vbox, gtk.Label())
1344
1345         # Report page #
1346         vbox = gtk.VBox()
1347
1348         textview = hildon.TextView()
1349         textview.set_cursor_visible(False)
1350         textview.set_wrap_mode(gtk.WRAP_WORD)
1351         text = """StockThis is being improved thanks to bug reports that users have submitted. The author appreciates these reports.
1352 If the application is having an error when you're using it, you have two choices to report this error:
1353 1) Send the log from the button above (if there's an error in the log).
1354 2) Press the button and read how to report a bug."""
1355         textview.get_buffer().set_text(text)
1356
1357         parea = hildon.PannableArea()
1358         parea.add(textview)
1359
1360         hbox = gtk.HBox()
1361         hbox.set_homogeneous(True)
1362
1363         button = hildon.Button(fhsize, horbtn)
1364         button.set_title("Report error")
1365         url = "http://stockthis.garage.maemo.org/reporting.html"
1366         button.connect("clicked", self.launch_browser, url)
1367         hbox.pack_start(button, True, True, 0)
1368
1369         button = hildon.Button(fhsize, horbtn)
1370         button.set_title("Log")
1371         button.connect("clicked", self.on_show_log)
1372         hbox.pack_start(button, True, True, 0)
1373
1374         vbox.pack_start(hbox, False, False, 0)
1375         vbox.pack_start(parea, True, True, 0)
1376
1377         notebook.append_page(vbox, gtk.Label())
1378
1379         # Rate page #
1380         vbox = gtk.VBox()
1381
1382         textview = hildon.TextView()
1383         textview.set_cursor_visible(False)
1384         textview.set_wrap_mode(gtk.WRAP_WORD)
1385         text = """The downloads section in maemo.org has a nice system where you can rate applications.
1386 If you consider StockThis a good application (or a bad one too), you could rate it in maemo.org site."""
1387         textview.get_buffer().set_text(text)
1388
1389         button = hildon.Button(fhsize, horbtn)
1390         button.set_title("Rate StockThis")
1391         url = "http://maemo.org/downloads/product/Maemo5/stockthis"
1392         button.connect("clicked", self.launch_browser, url)
1393         image = gtk.Image()
1394         image.set_from_file(imgdir + "maemoorg.png")
1395         vbox.pack_start(button, False, False, 0)
1396         vbox.pack_start(image, False, False, 5)
1397         vbox.pack_start(textview, True, True, 0)
1398
1399         notebook.append_page(vbox, gtk.Label())
1400
1401         # Buttons #
1402         self.abdialog.vbox.pack_start(notebook, True, True, 0)
1403
1404         hbox = gtk.HBox()
1405
1406         descbutton = hildon.GtkRadioButton(fhsize)
1407         descbutton.set_mode(False)
1408         descbutton.set_active(True)
1409         descbutton.set_label('Description')
1410         descbutton.connect("toggled", self.change_tab, notebook, 0)
1411         hbox.pack_start(descbutton, True, True, 0)
1412
1413         button = hildon.GtkRadioButton(fhsize)
1414         button.set_mode(False)
1415         button.set_active(True)
1416         button.set_label('Credits')
1417         button.set_group(descbutton)
1418         button.connect("toggled", self.change_tab, notebook, 1)
1419         hbox.pack_start(button, True, True, 0)
1420
1421         button = hildon.GtkRadioButton(fhsize)
1422         button.set_mode(False)
1423         button.set_label('Donate')
1424         button.set_group(descbutton)
1425         button.connect("clicked", self.change_tab, notebook, 2)
1426         hbox.pack_start(button, True, True, 0)
1427
1428         button = hildon.GtkRadioButton(fhsize)
1429         button.set_mode(False)
1430         button.set_label('Report')
1431         button.set_group(descbutton)
1432         button.connect("clicked", self.change_tab, notebook, 3)
1433         hbox.pack_start(button, True, True, 0)
1434
1435         button = hildon.GtkRadioButton(fhsize)
1436         button.set_mode(False)
1437         button.set_label('Rate')
1438         button.set_group(descbutton)
1439         button.connect("clicked", self.change_tab, notebook, 4)
1440         hbox.pack_start(button, True, True, 0)
1441
1442         self.abdialog.vbox.pack_start(hbox, False, False, 0)
1443
1444         self.abdialog.show_all()
1445         self.abdialog.run()
1446         self.abdialog.destroy()
1447
1448     def change_tab(self, widget, notebook, number):
1449         notebook.set_current_page(number)
1450
1451     def launch_browser(self, widget, url):
1452         import dbus
1453         bus = dbus.SystemBus()
1454         proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
1455         iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
1456
1457         self.abdialog.destroy()
1458
1459         iface.open_new_window(url)
1460
1461     def on_show_log(self, widget):
1462         Log(widget, logfile)
1463
1464
1465 class Log:
1466
1467     def __init__(self, widget, logfile):
1468         #Log dialog UI
1469         dialog = gtk.Dialog(title='Log', parent=None)
1470
1471         dialog.set_size_request(600, 350)
1472
1473         parea = hildon.PannableArea()
1474         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
1475
1476         textview = hildon.TextView()
1477         textview.set_property("editable", False)
1478         textview.set_property("wrap-mode", gtk.WRAP_WORD)
1479
1480         log = open(logfile, 'r')
1481         logtext = log.read()
1482         log.close()
1483
1484         textview.get_buffer().set_text(logtext)
1485         parea.add(textview)
1486
1487         dialog.vbox.pack_start(parea, True, True, 0)
1488
1489         hbox = gtk.HBox()
1490
1491         save_btn = hildon.Button(fhsize, horbtn)
1492         save_btn.set_title("Save")
1493         save_btn.connect('clicked', self.save, logfile, dialog)
1494
1495         clear_btn = hildon.Button(fhsize, horbtn)
1496         clear_btn.set_title("Clear")
1497         clear_btn.connect('clicked', self.clear, textview, logfile)
1498
1499         send_btn = hildon.Button(fhsize, horbtn)
1500         send_btn.set_title('Send')
1501         send_btn.connect('clicked', self.send, dialog, logfile)
1502
1503         hbox.pack_start(save_btn, True, True, 0)
1504         hbox.pack_start(clear_btn, True, True, 0)
1505         hbox.pack_start(send_btn, True, True, 0)
1506
1507         dialog.vbox.pack_start(hbox, False, False, 0)
1508
1509         dialog.show_all()
1510         dialog.run()
1511         dialog.destroy()
1512
1513     def show_filechooser(self, window, title, name, EXT):
1514         action = gtk.FILE_CHOOSER_ACTION_SAVE
1515
1516         m = hildon.FileSystemModel()
1517         file_dialog = hildon.FileChooserDialog(window, action, m)
1518         file_dialog.set_title(title)
1519
1520         file_dialog.set_current_name(name)
1521         HOME = os.path.expanduser("~")
1522
1523         if os.path.exists(HOME + '/MyDocs/.documents'):
1524             file_dialog.set_current_folder(HOME + '/MyDocs/.documents')
1525         else:
1526             file_dialog.set_current_folder(HOME)
1527
1528         file_dialog.set_default_response(gtk.RESPONSE_CANCEL)
1529
1530         result = file_dialog.run()
1531         if result == gtk.RESPONSE_OK:
1532             namefile = file_dialog.get_filename()
1533             namefile, extension = os.path.splitext(namefile)
1534             namefile = namefile + "." + EXT
1535         else:
1536             namefile = None
1537         file_dialog.destroy()
1538
1539         return namefile
1540
1541
1542     def clear(self, widget, textview, logfile):
1543         textview.get_buffer().set_text('')
1544         f = open(logfile, 'w')
1545         f.close()
1546
1547     def save(self, widget, logfile, dlg):
1548         import shutil
1549         filename = self.show_filechooser(dlg, "Save log file",
1550                     "stockthis-log", "txt")
1551
1552         if not filename:
1553             return
1554
1555         try:
1556             shutil.copyfile(logfile, filename)
1557             stockspy.show_info_banner(widget, 'Log file saved')
1558         except:
1559             logger.exception("Saving log file")
1560             stockspy.show_info_banner(widget, 'Error saving the log file')
1561
1562     def send(self, widget, dlg, logfile):
1563         sendtxt = ("You are going to send the log to the developers.\n"
1564         "This helps the developers to track problems with the application.\n"
1565         "It doesn't send any personal information (like passwords or similar).")
1566
1567         dialog = hildon.hildon_note_new_confirmation(dlg, sendtxt)
1568         dialog.set_button_texts("Send", "Cancel")
1569         dialog.show_all()
1570         response = dialog.run()
1571         if response == gtk.RESPONSE_OK:
1572             self.do_pre_send(dlg, logfile)
1573
1574         dialog.destroy()
1575
1576     def do_pre_send(self, dlg, logfile):
1577         import thread
1578         hildon.hildon_gtk_window_set_progress_indicator(dlg, 1)
1579         thread.start_new_thread(self._do_send, (dlg, logfile))
1580
1581     def _do_send(self, dlg, logfile):
1582         import pycurl, shutil, random, commands
1583         try:
1584             rname = ''
1585             for i in random.sample('abcdefghijkl123456789', 18):
1586                 rname += i
1587
1588             rnamepath = HOME + "/.stockthis/" + rname
1589             shutil.copyfile(logfile, rnamepath)
1590
1591             gtkversion = "%s.%s.%s" % gtk.ver
1592             if os.path.exists("/etc/maemo_version"):
1593                 mfile = open("/etc/maemo_version", 'r')
1594                 maemoversion = mfile.read()
1595                 mfile.close()
1596             else:
1597                 maemoversion = ''
1598
1599             opsystem = ' '.join(os.uname())
1600             pyversion = os.sys.version
1601             pid = os.getpid()
1602             comm = ("awk '/Private_Dirty/{sum+=$2}END{print sum \"kB\"}'"
1603             " /proc/%s/smaps") % pid
1604             status, dirtymem = commands.getstatusoutput(comm)
1605
1606             lfile = open(rnamepath, 'r')
1607             log = lfile.read()
1608             lfile.close()
1609
1610
1611             log = ("%s\nPython version: %s\nGtk version: %s\n"
1612             "Maemo version: %sOperating system: %s\n"
1613             "Dirty Memory: %s\nLog:\n%s") % (_version, pyversion, gtkversion,
1614             maemoversion, opsystem, dirtymem, log)
1615
1616             lfile = open(rnamepath, 'w')
1617             lfile.write(log)
1618             lfile.close()
1619
1620             url = "http://yerga.net/logs/uploader.php"
1621             data = [('uploadedfile', (pycurl.FORM_FILE, rnamepath)),]
1622             mycurl = pycurl.Curl()
1623             mycurl.setopt(pycurl.URL, url)
1624             mycurl.setopt(pycurl.HTTPPOST, data)
1625
1626             mycurl.perform()
1627             mycurl.close()
1628             os.remove(rnamepath)
1629
1630             gtk.gdk.threads_enter()
1631             stockspy.show_info_banner(dlg, 'Log sent')
1632             gtk.gdk.threads_leave()
1633             hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1634         except:
1635             logger.exception("Sending log file")
1636             gtk.gdk.threads_enter()
1637             stockspy.show_info_banner(dlg, 'Error sending the log file')
1638             gtk.gdk.threads_leave()
1639             hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1640
1641
1642 if __name__ == "__main__":
1643     stockspy = StocksPy()
1644     gtk.gdk.threads_enter()
1645     gtk.main()
1646     gtk.gdk.threads_leave()