settings saved with backup, and bugfixed 6077
[stockthis] / stockthis.py
1 #!/usr/bin/env python
2 # -*- coding: UTF8 -*-
3 # Copyright (C) 2008 by Daniel Martin Yerga
4 # <dyerga@gmail.com>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 #
19 # StocksThis: Application to get stocks data from Yahoo Finance.
20 #
21
22 _version = "StockThis 0.3 rev1"
23
24 import urllib2
25 import gtk, gobject
26 import os
27 import hildon
28 import marketdata
29 import settings
30 import logging
31 import sys
32
33 from portrait import FremantleRotation
34
35 import osso
36 osso_c = osso.Context("net.yerga.stockthis", "0.3", False)
37
38 #detect if is ran locally or not
39 runningpath = sys.path[0]
40
41 if '/opt/' in runningpath:
42     runninglocally = False
43 else:
44     runninglocally = True
45
46 HOME = os.path.expanduser("~")
47
48 settingsdb, imgdir, configdir, logfile = \
49     settings.define_paths(runninglocally, HOME)
50
51 logger = logging.getLogger('st')
52 logging.basicConfig(filename=logfile,level=logging.ERROR, filemode='w')
53
54 DEBUG = True
55
56 if DEBUG:
57     #set the main logger to DEBUG
58     logger.setLevel(logging.DEBUG)
59
60     #Create a handler for console debug
61     console = logging.StreamHandler()
62     console.setLevel(logging.DEBUG)
63     # set a format which is simpler for console use
64     formatter = logging.Formatter('%(name)-12s: %(levelname)-8s %(message)s')
65     # tell the handler to use this format
66     console.setFormatter(formatter)
67     logging.getLogger('').addHandler(console)
68
69 fhsize = gtk.HILDON_SIZE_FINGER_HEIGHT
70 horbtn = hildon.BUTTON_ARRANGEMENT_HORIZONTAL
71 ui_normal = gtk.HILDON_UI_MODE_NORMAL
72 ui_edit = gtk.HILDON_UI_MODE_EDIT
73 winprogind = hildon.hildon_gtk_window_set_progress_indicator
74
75 allnames = []
76 allsymbols = []
77
78 gtk.gdk.threads_init()
79
80 class StocksPy:
81
82     def __init__(self):
83         self.program = hildon.Program()
84         self.program.__init__()
85         gtk.set_application_name("StockThis")
86         self.window = hildon.StackableWindow()
87         self.window.set_default_size(800, 480)
88         self.program.add_window(self.window)
89         self.window.connect("destroy", gtk.main_quit)
90
91         FremantleRotation('StockThis', None, "0.3", 0)
92
93         self.create_menu(self.window)
94
95         vbox = gtk.VBox()
96         toolbar = self.main_toolbar(False, False, None, '', '', True)
97
98         parea = hildon.PannableArea()
99         tv = hildon.GtkTreeView(ui_normal)
100         inmodel = self.__create_model(marketdata.main, marketdata.idmain)
101         tv.connect("row-activated", self.show_instrument_view, inmodel,
102                     marketdata.localmarkets, marketdata.localids,
103                     marketdata.idmain)
104         tv.set_model(inmodel)
105         self._tv_columns(tv)
106         parea.add(tv)
107
108         vbox.pack_start(parea, True, True, 0)
109         vbox.pack_start(gtk.HSeparator(), False, False, 5)
110         vbox.pack_start(toolbar, False, False, 0)
111
112         self.window.add(vbox)
113         self.window.show_all()
114
115         self.show_info_banner(self.window,
116             ("StockThis uses your network connection to get data.\n"
117             "Be aware of the high costs that your network provider may apply."))
118
119     def create_menu(self, window):
120         menu = hildon.AppMenu()
121         window.set_app_menu(menu)
122         button = gtk.Button("About")
123         button.connect("clicked", About)
124         menu.append(button)
125         button = gtk.Button("Log")
126         button.connect("clicked", Log, logfile)
127         menu.append(button)
128         menu.show_all()
129
130     def show_instrument_view(self, widget, path, column, inmodel, names,
131         ids, mindex):
132         market = inmodel[path][0]
133         names = names[mindex.index(market)]
134         ids = ids[mindex.index(market)]
135
136         window = hildon.StackableWindow()
137         self.create_menu(window)
138         window.set_title(inmodel[path][1])
139
140         vbox = gtk.VBox()
141         toolbar = self.main_toolbar(False, False, None, '', '', False)
142
143         parea = hildon.PannableArea()
144         parea.connect("horizontal-movement", self.horizontal_mov)
145         tv = hildon.GtkTreeView(ui_normal)
146         model = self.__create_model(names, ids)
147         tv.connect("row-activated", self.show_quotes_view, model, False)
148         tv.set_model(model)
149         self._tv_columns(tv)
150         parea.add(tv)
151
152         vbox.pack_start(parea, True, True, 0)
153         vbox.pack_start(gtk.HSeparator(), False, False, 5)
154         vbox.pack_start(toolbar, False, False, 0)
155
156         window.add(vbox)
157         window.show_all()
158
159     def horizontal_mov(self, parea, direction, initial_x, initial_y):
160         #direction = 2 right-to-left
161         #direction = 3 lefto-to-right
162
163         vadj = parea.get_vadjustment()
164         val = vadj.get_value()
165
166         if direction == 2:
167             if int(val)-2500 < 0:
168                  parea.scroll_to(-1, 0)
169             else:
170                 parea.scroll_to(-1, int(val)-2500)
171         elif direction == 3:
172             parea.scroll_to(-1, int(val)+3500)
173
174         #print val
175
176     def show_quotes_view(self, widget, path, column, model, portfolio):
177         quote = model[path][0], model[path][1]
178         #print "quote:", quote[0]
179         #('EURUSD=X', 'EUR/USD')
180
181         #Currencies and ETFs should show the list now -> view = True
182         #Other items show a new list with options
183         view = False
184         for i in marketdata.localids[(len(marketdata.localids)-2):]:
185             for j in i:
186                 if quote[0] == j:
187                     #print j
188                     view = True
189
190         if not view:
191             if quote[0] in marketdata.idindexes:
192                 self.show_instrument_view(widget, path, column, model,
193                                         marketdata.wnamesindexes,
194                                         marketdata.widsindexes,
195                                         marketdata.idindexes)
196                 return
197             if quote[0] in marketdata.idotmarkets:
198                 self.show_instrument_view(widget, path, column, model,
199                                         marketdata.omnames,
200                                         marketdata.omsymbols,
201                                         marketdata.idotmarkets)
202                 return
203             if quote[0] in marketdata.ideumarkets:
204                 self.show_instrument_view(widget, path, column, model,
205                                         marketdata.eunames,
206                                         marketdata.eusymbols,
207                                         marketdata.ideumarkets)
208                 return
209             if quote[0] in marketdata.idusmarkets:
210                 self.show_instrument_view(widget, path, column, model,
211                                         marketdata.usnames,
212                                         marketdata.ussymbols,
213                                         marketdata.idusmarkets)
214                 return
215
216
217         win = hildon.StackableWindow()
218         self.create_menu(win)
219         win.set_title(quote[1])
220
221         vbox = gtk.VBox()
222
223         ltitle = gtk.Label('')
224         ltitle.set_markup('<b><big>' + quote[1].replace('&', '') +
225                                  '</big></b>')
226         color = gtk.gdk.color_parse("#03A5FF")
227         ltitle.modify_fg(gtk.STATE_NORMAL, color)
228
229         parea = hildon.PannableArea()
230         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
231
232         vbox1 = gtk.VBox()
233
234         hbox = gtk.HBox()
235         label = gtk.Label('')
236         label.set_markup('<b><big>%39s:</big></b>' % '<u>Price</u>')
237         lprice = gtk.Label('')
238         hbox.pack_start(label, False, False, 0)
239         #if self.is_portrait():
240         hbox.pack_start(lprice, False, False, 25)
241         #else:
242         #    hbox.pack_start(lprice, False, False, 245)
243
244         vbox1.pack_start(hbox, True, True, 0)
245
246         hbox = gtk.HBox()
247         label = gtk.Label('')
248         label.set_markup('<b><big>%35s:</big></b>' % '<u>Change</u>')
249         lchange = gtk.Label('')
250         lpercent = gtk.Label('')
251         hbox.pack_start(label, False, False, 0)
252         #if self.is_portrait():
253         hbox.pack_start(lchange, False, False, 25)
254         hbox.pack_start(lpercent, False, False, 0)
255         #else:
256         #    hbox.pack_start(lchange, False, False, 205)
257         #    hbox.pack_start(lpercent, False, False, 0)
258
259         vbox1.pack_start(hbox, True, True, 0)
260
261         hbox = gtk.HBox()
262         label = gtk.Label('')
263         label.set_markup('<b><big>%35s:</big></b>' % '<u>Volume</u>')
264         lvolume = gtk.Label('')
265         hbox.pack_start(label, False, False, 0)
266         #if self.is_portrait():
267         hbox.pack_start(lvolume, False, False, 25)
268         #else:
269         #    hbox.pack_start(lvolume, False, False, 207)
270
271         vbox1.pack_start(hbox, True, True, 0)
272
273         hbox = gtk.HBox()
274         label = gtk.Label('')
275         label.set_markup('<b><big>%30s:</big></b>' % '<u>52 week high</u>')
276         l52whigh = gtk.Label('')
277         hbox.pack_start(label, False, False, 0)
278         #if self.is_portrait():
279         hbox.pack_start(l52whigh, False, False, 25)
280         #else:
281         #    hbox.pack_start(l52whigh, False, False, 125)
282
283         vbox1.pack_start(hbox, True, True, 0)
284
285         hbox = gtk.HBox()
286         label = gtk.Label('')
287         label.set_markup('<b><big>%30s:</big></b>' % '<u>52 week low</u>')
288         l52wlow = gtk.Label('')
289         hbox.pack_start(label, False, False, 0)
290         #if self.is_portrait():
291         hbox.pack_start(l52wlow, False, False, 26)
292         #else:
293         #    hbox.pack_start(l52wlow, False, False, 140)
294         vbox1.pack_start(hbox, True, True, 0)
295
296         #if self.is_portrait():
297         #    hbox = gtk.VBox()
298         #else:
299         hbox = gtk.HBox()
300         button1 = hildon.PickerButton(fhsize, horbtn)
301         data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
302                 "900", "1000"]
303         selector = self.create_selector(data, True)
304         button1.set_selector(selector)
305         button1.set_title("Your shares")
306         shares = self.get_shares_from_symbol(quote[0])
307         button1.set_value(shares)
308         hbox.pack_start(button1, True, True, 0)
309
310         button = hildon.Button(fhsize, horbtn)
311         button.set_title("Add to Portfolio")
312         button.connect("clicked", self.add_to_portfolio, button1, quote[0], quote[1])
313         hbox.pack_start(button, True, True, 0)
314
315         hbox1 = gtk.HBox()
316         label = gtk.Label('')
317         label.set_markup('<b><big>%37s:</big></b>' % '<u>Shares</u>')
318         lshares = gtk.Label(shares)
319         hbox1.pack_start(label, False, False, 0)
320         #if self.is_portrait():
321         hbox1.pack_start(lshares, False, False, 25)
322         #else:
323         #    hbox1.pack_start(lshares, False, False, 220)
324
325         hbox2 = gtk.HBox()
326         label = gtk.Label('')
327         label.set_markup('<b><big>%29s:</big></b>' % '<u>Holdings Value</u>')
328         holdingsvalue = gtk.Label("")
329         hbox2.pack_start(label, False, False, 0)
330         #if self.is_portrait():
331         hbox2.pack_start(holdingsvalue, False, False, 25)
332         #else:
333         #    hbox2.pack_start(holdingsvalue, False, False, 105)
334
335         hbox3 = gtk.HBox()
336         label = gtk.Label('')
337         label.set_markup("<b><big>%25s:</big></b>" % "<u>Day's Value Change</u>")
338         dayvaluechange = gtk.Label("")
339         hbox3.pack_start(label, False, False, 0)
340         #if self.is_portrait():
341         hbox3.pack_start(dayvaluechange, False, False, 25)
342         #else:
343         #    hbox3.pack_start(dayvaluechange, False, False, 45)
344
345         if not portfolio:
346             vbox1.pack_start(hbox, False, False, 0)
347         else:
348             vbox1.pack_start(hbox1, True, True, 0)
349             vbox1.pack_start(hbox2, True, True, 0)
350             vbox1.pack_start(hbox3, True, True, 0)
351
352         parea.add_with_viewport(vbox1)
353
354         widgets = [win, ltitle, lprice, lchange,  lpercent, lvolume, l52whigh,
355                     l52wlow, lshares, holdingsvalue, dayvaluechange]
356
357         toolbar = self.main_toolbar(True, portfolio, widgets, quote[0], quote[1], False)
358
359         vbox.pack_start(ltitle, False, False, 0)
360         vbox.pack_start(gtk.HSeparator(), False, False, 0)
361         vbox.pack_start(parea, True, True, 0)
362         vbox.pack_start(gtk.HSeparator(), False, False, 5)
363         vbox.pack_start(toolbar, False, False, 0)
364
365
366         win.add(vbox)
367         win.show_all()
368         self.show_data(quote[0], widgets, shares)
369
370     def is_portrait(self):
371         width = gtk.gdk.screen_width()
372         height = gtk.gdk.screen_height()
373         if width > height:
374             return False
375         else:
376             return True
377
378     def get_shares_from_symbol(self, symbol):
379         shares = "0"
380         try:
381             portfolio_data = settings.load_portfolio(settingsdb)
382             for item in portfolio_data :
383                 if symbol in item:
384                     shares = item[2]
385             return shares
386         except:
387             logger.exception("Getting shares from symbol: %s" % symbol)
388             return shares
389
390     def add_to_portfolio(self, widget, button, symbol, name):
391         shares = button.get_value()
392
393         try:
394             portfolio = settings.load_portfolio(settingsdb)
395             index = "None"
396             for item in portfolio:
397                 if symbol in item:
398                     index = portfolio.index(item)
399
400             item = [symbol, name, shares, '-']
401
402             if index is "None":
403                 settings.insert_new_item_to_portfolio(settingsdb, item)
404             else:
405                 settings.delete_item_from_portfolio(settingsdb, symbol)
406                 settings.insert_new_item_to_portfolio(settingsdb, item)
407
408             self.show_info_banner(widget, "Added to portfolio")
409         except:
410             logger.exception("Adding to portfolio: %s, %s" % (symbol, name))
411             self.show_info_banner(widget, "Error adding to portfolio")
412
413
414     def create_selector(self, data, entry):
415         if entry:
416             selector = hildon.TouchSelectorEntry(text=True)
417         else:
418             selector = hildon.TouchSelector(text=True)
419         for i in range(len(data)):
420             selector.append_text(data[i])
421
422         return selector
423
424     def show_data(self, symbol, widgets, shares):
425         import thread
426         winprogind(widgets[0], 1)
427         thread.start_new_thread(self.get_data, (symbol, widgets, shares))
428
429     def get_data(self, symbol, widgets, shares):
430         from ystockquote import ystockquote as yt
431         win, ltitle, lprice, lchange,  lpercent, lvolume, l52whigh, l52wlow, lshares, holdingsvalue, dayvaluechange = widgets
432
433         try:
434             data = yt.get_all(symbol)
435         except:
436             logger.exception("Getting data from Yahoo: %s" % symbol)
437             data = {'price': 'N/A', 'change': 'N/A', 'volume':'N/A',
438                     '52_week_high': 'N/A', '52_week_low': 'N/A'}
439             ltitle.set_markup('<b><big>Failed to get data</big></b>')
440
441         try:
442             ch_percent = \
443                     100.0 * float(data['change'])/(float(data['price']) - \
444                     float(data['change']))
445         except ValueError:
446             ch_percent = 0.0
447
448         lprice.set_label(data['price'])
449         lchange.set_label(data['change'])
450         lpercent.set_label('%6.2f %%' % ch_percent)
451
452         if '-' in data['change']:
453             color = gtk.gdk.color_parse("#FF0000")
454         else:
455             color = gtk.gdk.color_parse("#16EB78")
456
457         lpercent.modify_fg(gtk.STATE_NORMAL, color)
458         lchange.modify_fg(gtk.STATE_NORMAL, color)
459
460         lvolume.set_label(data['volume'])
461         l52whigh.set_label(data['52_week_high'])
462         l52wlow.set_label(data['52_week_low'])
463
464         try:
465             daychange = float(shares)*float(data['change'])
466         except ValueError:
467             daychange = 'N/A'
468         try:
469             holdvalue = float(shares)*float(data['price'])
470         except ValueError:
471             holdvalue = 'N/A'
472
473         dayvaluechange.set_label(str(daychange))
474         holdingsvalue.set_label(str(holdvalue))
475
476         winprogind(win, 0)
477
478     def refresh_stock_data(self, widget, portfolio, widgets, symbol):
479         if portfolio:
480             shares = self.get_shares_from_symbol(symbol)
481         else:
482             shares = "0"
483
484         self.show_data(symbol, widgets, shares)
485
486     def show_graph_view(self, widget, symbol, name):
487         win = hildon.StackableWindow()
488         self.create_menu(win)
489         win.set_title(name)
490
491         vbox = gtk.VBox()
492         toolbar = self.main_toolbar(False, True, None, '', '', False)
493
494         self.graphs_title = gtk.Label(name)
495         color = gtk.gdk.color_parse("#03A5FF")
496         self.graphs_title.modify_fg(gtk.STATE_NORMAL, color)
497
498         parea = hildon.PannableArea()
499         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
500
501         vbox1 = gtk.VBox()
502
503         hbox = gtk.HBox()
504         hbox.set_homogeneous(True)
505
506         button = hildon.Button(fhsize, horbtn)
507         button.set_label('1d')
508         button.connect("clicked", self.show_graph, '1d', win, symbol)
509         hbox.pack_start(button)
510
511         button = hildon.Button(fhsize, horbtn)
512         button.set_label('5d')
513         button.connect("clicked", self.show_graph, '5d', win, symbol)
514         hbox.pack_start(button)
515
516         button = hildon.Button(fhsize, horbtn)
517         button.set_label('3m')
518         button.connect("clicked", self.show_graph, '3m', win, symbol)
519         hbox.pack_start(button)
520
521         button = hildon.Button(fhsize, horbtn)
522         button.set_label('6m')
523         button.connect("clicked", self.show_graph, '6m', win, symbol)
524         hbox.pack_start(button)
525
526         vbox1.pack_start(hbox, False, False, 0)
527         hbox = gtk.HBox()
528         hbox.set_homogeneous(True)
529
530         button = hildon.Button(fhsize, horbtn)
531         button.set_label('1y')
532         button.connect("clicked", self.show_graph, '1y', win, symbol)
533         hbox.pack_start(button)
534
535         button = hildon.Button(fhsize, horbtn)
536         button.set_label('2y')
537         button.connect("clicked", self.show_graph, '2y', win, symbol)
538         hbox.pack_start(button)
539
540         button = hildon.Button(fhsize, horbtn)
541         button.set_label('5y')
542         button.connect("clicked", self.show_graph, '5y', win, symbol)
543         hbox.pack_start(button)
544
545         button = hildon.Button(fhsize, horbtn)
546         button.set_label('Max')
547         button.connect("clicked", self.show_graph, 'max', win, symbol)
548         hbox.pack_start(button)
549
550         vbox1.pack_start(hbox, False, False, 0)
551
552         self.graph = gtk.Image()
553         vbox1.pack_start(self.graph, True, True, 0)
554
555         parea.add_with_viewport(vbox1)
556
557         vbox.pack_start(self.graphs_title, False, False, 0)
558         vbox.pack_start(gtk.HSeparator(), False, False, 0)
559         vbox.pack_start(parea, True, True, 0)
560         vbox.pack_start(gtk.HSeparator(), False, False, 5)
561         vbox.pack_start(toolbar, False, False, 0)
562
563         win.add(vbox)
564         win.show_all()
565
566         self.show_graph(None, '1d', win, symbol)
567
568     def show_graph(self, widget, option, win, symbol):
569         import thread
570         winprogind(win, 1)
571         thread.start_new_thread(self.get_graph_data, (option, win, symbol))
572
573     def get_graph_data(self, option, win, symbol):
574         if option == '1d':
575             url = 'http://uk.ichart.yahoo.com/b?s=%s' % symbol
576         elif option == '5d':
577             url = 'http://uk.ichart.yahoo.com/w?s=%s' % symbol
578         elif option == '3m':
579             url = 'http://chart.finance.yahoo.com/c/3m/s/%s' % symbol.lower()
580         elif option == '6m':
581             url = 'http://chart.finance.yahoo.com/c/6m/s/%s' % symbol.lower()
582         elif option == '1y':
583             url = 'http://chart.finance.yahoo.com/c/1y/s/%s' % symbol.lower()
584         elif option == '2y':
585             url = 'http://chart.finance.yahoo.com/c/2y/s/%s' % symbol.lower()
586         elif option == '5y':
587             url = 'http://chart.finance.yahoo.com/c/5y/s/%s' % symbol.lower()
588         elif option == 'max':
589             url = 'http://chart.finance.yahoo.com/c/my/s/%s' % symbol.lower()
590
591         try:
592             myimg = urllib2.urlopen(url)
593             imgdata = myimg.read()
594
595             pbl = gtk.gdk.PixbufLoader()
596             pbl.write(imgdata)
597
598             pbuf = pbl.get_pixbuf()
599             pbuf = pbuf.scale_simple(475, 235, gtk.gdk.INTERP_TILES)
600             pbl.close()
601             self.graph.set_from_pixbuf(pbuf)
602             winprogind(win, 0)
603         except:
604             logger.exception("Getting graph data: %s" % url)
605             winprogind(win, 0)
606             self.graphs_title.set_label('Failed to get data')
607             self.graph.destroy()
608
609     def _tv_columns(self, treeview):
610         column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
611         column.set_visible(False)
612         treeview.append_column(column)
613
614         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
615         treeview.append_column(column)
616
617     def __create_model(self, names, ids):
618         lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
619         for item in range(len(names)):
620             iter = lstore.append()
621             lstore.set(iter, 0, ids[item], 1, names[item])
622         return lstore
623
624     def main_toolbar(self, quotesview, portfolio, widgets, symbol, name, initial):
625         toolbar = gtk.HBox()
626         #toolbar.set_homogeneous(True)
627
628         portfolio_btn = hildon.Button(fhsize, horbtn)
629         portfolio_btn.set_title("Portfolio")
630         portfolio_btn.connect("clicked", self.show_portfolio_view)
631
632         graph_btn = hildon.Button(fhsize, horbtn)
633         graph_btn.set_title("Graph")
634         graph_btn.connect("clicked", self.show_graph_view, symbol, name)
635
636         refresh_btn = hildon.Button(fhsize, horbtn)
637         refresh_btn.set_title("Refresh")
638         refresh_btn.connect("clicked", self.refresh_stock_data, portfolio,
639                             widgets, symbol)
640
641
642         stockiconspath = "/usr/share/icons/hicolor/48x48/hildon/"
643         info_btn = hildon.Button(fhsize, horbtn)
644         img = gtk.Image()
645         img.set_from_file(stockiconspath + "general_information.png")
646         info_btn.set_image(img)
647         info_btn.connect("clicked", self.show_app_information)
648
649         search_btn = hildon.Button(fhsize, horbtn)
650         img = gtk.Image()
651         img.set_from_file(stockiconspath + "general_search.png")
652         search_btn.set_image(img)
653         search_btn.connect("clicked", self.show_search_dialog)
654
655         if not portfolio:
656             toolbar.pack_start(portfolio_btn)
657             if not quotesview:
658                 toolbar.pack_start(info_btn, False, False, 0)
659         if quotesview:
660             toolbar.pack_start(graph_btn)
661             toolbar.pack_start(refresh_btn)
662
663         if initial:
664             toolbar.pack_start(search_btn, False, False, 0)
665
666         toolbar.show_all()
667
668         return toolbar
669
670     def show_search_dialog(self, widget):
671         dlg = gtk.Dialog(title='Search company', parent=None, flags=0)
672         dlg.set_has_separator(False)
673
674         entry = hildon.Entry(fhsize)
675         dlg.vbox.pack_start(entry, False, False, 0)
676
677         button = hildon.Button(fhsize, horbtn)
678         button.set_label("Search")
679         button.connect("clicked", self.do_search, entry, dlg)
680         dlg.vbox.pack_start(button, False, False, 0)
681
682         dlg.show_all()
683         dlg.run()
684         dlg.destroy()
685
686
687     def do_search(self, widget, entry, dlg):
688         import thread
689         text = entry.get_text()
690         dlg.destroy()
691
692         winprogind(self.window, 1)
693         thread.start_new_thread(self._really_do_search, (text,))
694
695     def _really_do_search(self, text):
696
697         if allnames == []:
698             for market in marketdata.eunames:
699                 for company in market:
700                     allnames.append(company)
701
702             for market in marketdata.omnames:
703                 for company in market:
704                     allnames.append(company)
705
706             for market in marketdata.usnames:
707                 for company in market:
708                     allnames.append(company)
709
710         if allsymbols == []:
711             for market in marketdata.eusymbols:
712                 for company in market:
713                     allsymbols.append(company)
714
715             for market in marketdata.omsymbols:
716                 for company in market:
717                     allsymbols.append(company)
718
719             for market in marketdata.ussymbols:
720                 for company in market:
721                     allsymbols.append(company)
722
723         new_model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
724         for i in range(len(allnames)):
725             if text.lower() in allnames[i].lower():
726                 niter = new_model.append()
727                 #print allsymbols[i], allnames[i]
728                 #FIXME: repeated companies in the results list
729                 #print if allnames[i]
730                 #for j in new_model:
731                 #    if j[1] == allnames[i]:
732                 new_model.set(niter, 0, allsymbols[i], 1, allnames[i])
733
734         if len(new_model) == 0:
735             winprogind(self.window, 0)
736             gtk.gdk.threads_enter()
737             self.show_info_banner(self.window, "No items found for this search")
738             gtk.gdk.threads_leave()
739             return
740
741         gtk.gdk.threads_enter()
742         self.show_search_screen(new_model, text)
743         gtk.gdk.threads_leave()
744         winprogind(self.window, 0)
745
746
747     def show_search_screen(self, model, text):
748         window = hildon.StackableWindow()
749         self.create_menu(window)
750         window.set_title("Search for " + text)
751
752         vbox = gtk.VBox()
753         toolbar = self.main_toolbar(False, False, None, '', '', False)
754
755         parea = hildon.PannableArea()
756         parea.connect("horizontal-movement", self.horizontal_mov)
757         tv = hildon.GtkTreeView(ui_normal)
758         tv.connect("row-activated", self.show_quotes_view, model, False)
759         tv.set_model(model)
760         self._tv_columns(tv)
761         parea.add(tv)
762
763         vbox.pack_start(parea, True, True, 0)
764         vbox.pack_start(gtk.HSeparator(), False, False, 5)
765         vbox.pack_start(toolbar, False, False, 0)
766
767         window.add(vbox)
768         window.show_all()
769
770
771     def show_app_information(self, widget):
772         self.show_information_note(self.window, (
773         "The data is got from Yahoo! Finance.\n"
774         "It could be delayed or even wrong.\n"
775         "The author doesn't validate in any way this data and therefore he is not responsible for any damage that may occur.\n\n"
776         "You can scroll large list with gestures:\n"
777         "Left-to-right gesture: scroll down.\n"
778         "Right-to-left gesture: scroll up."))
779
780     def show_portfolio_view(self, widget):
781         data = settings.load_portfolio(settingsdb)
782
783         win = hildon.StackableWindow()
784         self.create_menu(win)
785         win.set_title("Portfolio")
786
787         vbox = gtk.VBox()
788
789         parea = hildon.PannableArea()
790         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
791         tv = hildon.GtkTreeView(ui_normal)
792         tv.set_headers_visible(True)
793         self.portfolio_model = self._create_portfolio_model(data)
794         tv.connect("row-activated", self.show_quotes_view, self.portfolio_model, True)
795         tv.set_model(self.portfolio_model)
796         self._tv_portfolio_columns(tv)
797         parea.add(tv)
798
799         hbox = gtk.HBox()
800         button = hildon.Button(fhsize, horbtn)
801         button.set_title("Refresh All")
802         button.connect("clicked", self.refresh_portfolio, tv, win)
803         hbox.pack_start(button, True, True, 0)
804
805         button = hildon.Button(fhsize, horbtn)
806         button.set_title("Add manually")
807         button.connect("clicked", self.add_item_dlg)
808         hbox.pack_start(button, True, True, 0)
809
810         button = hildon.Button(fhsize, horbtn)
811         button.set_title("Remove")
812         button.connect("clicked", self.remove_item)
813         hbox.pack_start(button, True, True, 0)
814
815         vbox.pack_start(parea, True, True, 0)
816         vbox.pack_start(hbox, False, False, 0)
817         win.add(vbox)
818         win.show_all()
819
820     def add_item_dlg(self, widget):
821         dlg = gtk.Dialog(title='Add to portfolio', parent=None, flags=0)
822         dlg.set_has_separator(False)
823
824         button1 = hildon.PickerButton(fhsize, horbtn)
825         data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
826                 "900", "1000"]
827         selector = self.create_selector(data, True)
828         button1.set_selector(selector)
829         button1.set_title("Your shares")
830         button1.set_value("0")
831         dlg.vbox.pack_start(button1, False, False, 0)
832
833         entry1 = hildon.Entry(fhsize)
834         entry1.set_placeholder("Name")
835         dlg.vbox.pack_start(entry1, False, False, 0)
836
837         entry2 = hildon.Entry(fhsize)
838         entry2.set_placeholder("Yahoo Finance symbol")
839         dlg.vbox.pack_start(entry2, False, False, 0)
840
841         button = hildon.Button(fhsize, horbtn)
842         button.set_label("Add")
843         button.connect("clicked", self.add_item, dlg, button1, entry1, entry2)
844         dlg.vbox.pack_start(button, False, False, 0)
845
846         dlg.show_all()
847         dlg.run()
848         dlg.destroy()
849
850
851     def add_item(self, widget, dlg, button, entry1, entry2):
852         symbol = entry2.get_text()
853         name = entry1.get_text()
854         shares = button.get_value()
855
856         if name == '' or symbol == '':
857             self.show_info_banner(widget, "Must add the name and symbol")
858             return
859
860         self.add_to_portfolio(widget, button, symbol, name)
861         dlg.destroy()
862
863         niter = self.portfolio_model.append()
864         self.portfolio_model.set(niter, 0, symbol, 1, name, 2, shares, 3, "-")
865
866     def remove_item(self, widget):
867         win = hildon.StackableWindow()
868         win.fullscreen()
869         toolbar = hildon.EditToolbar("Choose items to delete", "Delete")
870         win.set_edit_toolbar(toolbar)
871
872         vbox = gtk.VBox()
873         parea = hildon.PannableArea()
874         tv = hildon.GtkTreeView(ui_edit)
875         selection = tv.get_selection()
876         selection.set_mode(gtk.SELECTION_MULTIPLE)
877         tv.set_model(self.portfolio_model)
878         self._tv_remove_portfolio_columns(tv)
879         parea.add(tv)
880
881         toolbar.connect("button-clicked", self.delete_from_portfolio, win, tv,
882                         selection)
883         toolbar.connect_object("arrow-clicked", gtk.Window.destroy, win)
884
885         vbox.pack_start(parea, True, True, 0)
886         win.add(vbox)
887         win.show_all()
888
889     def delete_from_portfolio(self, widget, win, tv, selection):
890         if not self.is_treeview_selected(tv):
891             return
892
893         conf = self.show_confirmation(win, "Delete items?")
894
895         if conf:
896             try:
897                 selmodel, selected = selection.get_selected_rows()
898                 iters = [selmodel.get_iter(path) for path in selected]
899                 for i in iters:
900                     symbol = selmodel.get_value(i, 0)
901                     settings.delete_item_from_portfolio(settingsdb, symbol)
902                     selmodel.remove(i)
903             except:
904                 logger.exception("Deleting item from portfolio")
905                 self.info_banner(widget, "Error deleting item")
906
907     def _tv_remove_portfolio_columns(self, treeview):
908         column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
909         column.set_visible(False)
910         treeview.append_column(column)
911
912         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
913         column.set_property("expand", True)
914         treeview.append_column(column)
915
916         column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
917         column.set_visible(False)
918         treeview.append_column(column)
919
920         column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
921         column.set_visible(False)
922         treeview.append_column(column)
923
924     def refresh_portfolio(self, widget, tv, win):
925         data = settings.load_portfolio(settingsdb)
926         import thread
927         winprogind(win, 1)
928         thread.start_new_thread(self._do_refresh_portfolio, (data, tv, win))
929
930     def _do_refresh_portfolio(self, data, tv, win):
931         for item in data:
932             item[3] = self.get_price(item[0])
933
934         self.portfolio_model = self._create_portfolio_model(data)
935         tv.set_model(self.portfolio_model)
936         winprogind(win, 0)
937
938     def get_price(self, symbol):
939         from ystockquote import ystockquote as yt
940         try:
941             price = yt.get_price(symbol)
942             return price
943         except:
944             logger.exception("Getting price from Yahoo: %s" % symbol)
945             return "N/A"
946
947     def _create_portfolio_model(self, data):
948         lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
949                                 gobject.TYPE_STRING, gobject.TYPE_STRING)
950         for item in data:
951             iter = lstore.append()
952             lstore.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3])
953         return lstore
954
955     def _tv_portfolio_columns(self, treeview):
956         column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
957         column.set_visible(False)
958         treeview.append_column(column)
959
960         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
961         column.set_property("expand", True)
962         treeview.append_column(column)
963
964         column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
965         treeview.append_column(column)
966
967         column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
968         treeview.append_column(column)
969
970     def show_confirmation(self, window, msg):
971         dialog = hildon.hildon_note_new_confirmation(window, msg)
972         dialog.show_all()
973         result = dialog.run()
974         if result == gtk.RESPONSE_OK:
975             dialog.destroy()
976             return True
977
978         dialog.destroy()
979         return False
980
981     def show_information_note(self, window, msg):
982         dialog = hildon.hildon_note_new_information(window, msg)
983         dialog.show_all()
984         result = dialog.run()
985         dialog.destroy()
986
987     def show_info_banner(self, widget, msg):
988         hildon.hildon_banner_show_information(widget, 'qgn_note_infoprint', msg)
989
990     def is_treeview_selected(self, treeview):
991         selection = treeview.get_selection()
992         if selection.count_selected_rows() == 0:
993             self.show_info_banner(treeview, 'No selected item')
994             return False
995         else:
996             return True
997
998 class About:
999
1000     def __init__(self, widget):
1001         self.abdialog = gtk.Dialog(title='About', parent=None, flags=0)
1002         self.abdialog.set_has_separator(False)
1003         self.abdialog.set_size_request(-1, 400)
1004
1005         self.info_lb = gtk.Label()
1006         self.info_lb.set_line_wrap(True)
1007
1008         self.id = False
1009
1010         hbox1 = gtk.HBox()
1011
1012         button = hildon.Button(fhsize, horbtn)
1013         button.set_title('Description')
1014         button.connect("clicked", self.show_info, 'description')
1015         hbox1.pack_start(button, True, True, 0)
1016
1017         button = hildon.Button(fhsize, horbtn)
1018         button.set_title('Credits')
1019         button.connect("clicked", self.show_info, 'credits')
1020         hbox1.pack_start(button, True, True, 0)
1021
1022         button = hildon.Button(fhsize, horbtn)
1023         button.set_title('License')
1024         button.connect("clicked", self.show_info, 'license')
1025         hbox1.pack_start(button, True, True, 0)
1026
1027         button = hildon.Button(fhsize, horbtn)
1028         button.set_title('Donate')
1029         button.connect("clicked", self.show_info, 'donate')
1030         hbox1.pack_start(button, True, True, 0)
1031
1032         button = hildon.Button(fhsize, horbtn)
1033         button.set_title('Report ')
1034         button.connect("clicked", self.show_info, 'report')
1035         hbox1.pack_start(button, True, True, 0)
1036
1037         button = hildon.Button(fhsize, horbtn)
1038         button.set_title(' Rate ')
1039         button.connect("clicked", self.show_info, 'vote')
1040         hbox1.pack_start(button, True, True, 0)
1041
1042         self.action_btn = hildon.Button(fhsize, horbtn)
1043         self.image = gtk.Image()
1044
1045         self.show_info(None, 'description')
1046
1047         self.abdialog.vbox.pack_start(self.action_btn, False, False, 0)
1048         self.abdialog.vbox.pack_start(self.image, False, False, 5)
1049         self.abdialog.vbox.pack_start(self.info_lb, True, True, 0)
1050         self.abdialog.vbox.pack_start(hbox1, False, False, 0)
1051
1052         self.abdialog.show_all()
1053         self.action_btn.hide()
1054         self.image.hide()
1055         self.abdialog.run()
1056         self.abdialog.destroy()
1057
1058     def do_action(self, widget, action):
1059         import dbus
1060
1061         self.abdialog.destroy()
1062
1063         bus = dbus.SystemBus()
1064         proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
1065         iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
1066
1067         if action == "donate":
1068             url = "http://stockthis.garage.maemo.org/donate.html"
1069         elif action == "report":
1070             url = "http://stockthis.garage.maemo.org/reporting.html"
1071         elif action == "vote":
1072             url = "http://maemo.org/downloads/product/Maemo5/stockthis"
1073
1074         iface.open_new_window(url)
1075
1076     def show_info(self, widget, kind):
1077         if kind == 'license':
1078             self.action_btn.hide()
1079             self.image.hide()
1080             info = """<small><b>StockThis</b> is free software. It's using a GPL version 2 license or at your election any later version.
1081
1082 Logo by Daniel Martin Yerga.
1083 </small>"""
1084         elif kind == 'credits':
1085             self.action_btn.hide()
1086             self.image.hide()
1087             info = """<small><b>Written by</b> Daniel Martin Yerga (dyerga@gmail.com)
1088
1089 <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>"""
1090         elif kind == 'description':
1091             self.action_btn.hide()
1092             self.image.hide()
1093             info = """<b><big>StockThis 0.3</big></b>
1094
1095 <i>StockThis is a stocks application for Maemo</i>
1096
1097 <b>Web Page</b>:
1098 stockthis.garage.maemo.org"""
1099
1100         elif kind == 'donate':
1101             self.action_btn.show()
1102             self.image.hide()
1103             self.action_btn.set_title('Make donation')
1104             if self.id:
1105                 self.action_btn.disconnect(self.id)
1106             self.id = self.action_btn.connect("clicked", self.do_action, "donate")
1107             info = """<small><b>StockThis</b> is a free (and gratis) software application.
1108 Developing good software takes time and hard work.
1109
1110 <b>StockThis's author</b> develops the program in his spare time.
1111 If you like the program and it's helpful, consider donating a small amount of money.
1112 Donations are a great incentive and help the developer feel that the hard work is appreciated.</small>
1113 """
1114         elif kind == 'report':
1115             self.action_btn.show()
1116             self.image.hide()
1117             self.action_btn.set_title('Report bug')
1118             if self.id:
1119                 self.action_btn.disconnect(self.id)
1120             self.id = self.action_btn.connect("clicked", self.do_action, "report")
1121             info = """<small>StockThis is being improved thanks to bug reports that users have submitted. The author appreciates these reports.
1122 If the application is raising an error when you're using it, you have two choices to report this error:
1123 1) Send the log from the application menu (if there's an error in the log).
1124 2) Press the button and write a bug report with as much information as possible.</small>"""
1125         elif kind == 'vote':
1126             self.action_btn.show()
1127             self.image.show()
1128             self.image.set_from_file(imgdir + "maemoorg.png")
1129             self.action_btn.set_title('Rate StockThis')
1130             if self.id:
1131                 self.action_btn.disconnect(self.id)
1132             self.id = self.action_btn.connect("clicked", self.do_action, "vote")
1133             info = """<small>The downloads section in maemo.org has a nice system where you can rate applications.
1134 If you consider StockThis a good application (or a bad one too), you could rate it in maemo.org site.</small>"""
1135
1136         self.info_lb.set_markup(info)
1137
1138
1139 class Log:
1140
1141     def __init__(self, widget, logfile):
1142         #Log dialog UI
1143         dialog = gtk.Dialog(title='Log', parent=None)
1144
1145         dialog.set_size_request(600, 350)
1146
1147         parea = hildon.PannableArea()
1148         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
1149
1150         textview = hildon.TextView()
1151         textview.set_property("editable", False)
1152         textview.set_property("wrap-mode", gtk.WRAP_WORD)
1153
1154         log = open(logfile, 'r')
1155         logtext = log.read()
1156         log.close()
1157
1158         textview.get_buffer().set_text(logtext)
1159         parea.add(textview)
1160
1161         dialog.vbox.pack_start(parea, True, True, 0)
1162
1163         hbox = gtk.HBox()
1164
1165         save_btn = hildon.Button(fhsize, horbtn)
1166         save_btn.set_title("Save")
1167         save_btn.connect('clicked', self.save, logfile, dialog)
1168
1169         clear_btn = hildon.Button(fhsize, horbtn)
1170         clear_btn.set_title("Clear")
1171         clear_btn.connect('clicked', self.clear, textview, logfile)
1172
1173         send_btn = hildon.Button(fhsize, horbtn)
1174         send_btn.set_title('Send')
1175         send_btn.connect('clicked', self.send, dialog, logfile)
1176
1177         hbox.pack_start(save_btn, True, True, 0)
1178         hbox.pack_start(clear_btn, True, True, 0)
1179         hbox.pack_start(send_btn, True, True, 0)
1180
1181         dialog.vbox.pack_start(hbox, False, False, 0)
1182
1183         dialog.show_all()
1184         dialog.run()
1185         dialog.destroy()
1186
1187     def show_filechooser(self, window, title, name, EXT):
1188         action = gtk.FILE_CHOOSER_ACTION_SAVE
1189
1190         m = hildon.FileSystemModel()
1191         file_dialog = hildon.FileChooserDialog(window, action, m)
1192         file_dialog.set_title(title)
1193
1194         file_dialog.set_current_name(name)
1195         HOME = os.path.expanduser("~")
1196
1197         if os.path.exists(HOME + '/MyDocs/.documents'):
1198             file_dialog.set_current_folder(HOME + '/MyDocs/.documents')
1199         else:
1200             file_dialog.set_current_folder(HOME)
1201
1202         file_dialog.set_default_response(gtk.RESPONSE_CANCEL)
1203
1204         result = file_dialog.run()
1205         if result == gtk.RESPONSE_OK:
1206             namefile = file_dialog.get_filename()
1207             namefile, extension = os.path.splitext(namefile)
1208             namefile = namefile + "." + EXT
1209         else:
1210             namefile = None
1211         file_dialog.destroy()
1212
1213         return namefile
1214
1215
1216     def clear(self, widget, textview, logfile):
1217         textview.get_buffer().set_text('')
1218         f = open(logfile, 'w')
1219         f.close()
1220
1221     def save(self, widget, logfile, dlg):
1222         import shutil
1223         filename = self.show_filechooser(dlg, "Save log file",
1224                     "stockthis-log", "txt")
1225
1226         if not filename:
1227             return
1228
1229         try:
1230             shutil.copyfile(logfile, filename)
1231             stockspy.show_info_banner(widget, 'Log file saved')
1232         except:
1233             logger.exception("Saving log file")
1234             stockspy.show_info_banner(widget, 'Error saving the log file')
1235
1236     def send(self, widget, dlg, logfile):
1237         sendtxt = ("You are going to send the log to the developers.\n"
1238         "This helps the developers to track problems with the application.\n"
1239         "It doesn't send any personal information (like passwords or similar).")
1240
1241         dialog = hildon.hildon_note_new_confirmation(dlg, sendtxt)
1242         dialog.set_button_texts("Send", "Cancel")
1243         dialog.show_all()
1244         response = dialog.run()
1245         if response == gtk.RESPONSE_OK:
1246             self.do_pre_send(dlg, logfile)
1247
1248         dialog.destroy()
1249
1250     def do_pre_send(self, dlg, logfile):
1251         import thread
1252         hildon.hildon_gtk_window_set_progress_indicator(dlg, 1)
1253         thread.start_new_thread(self._do_send, (dlg, logfile))
1254
1255     def _do_send(self, dlg, logfile):
1256         import pycurl, shutil, random, commands
1257         try:
1258             rname = ''
1259             for i in random.sample('abcdefghijkl123456789', 18):
1260                 rname += i
1261
1262             rnamepath = HOME + "/.stockthis/" + rname
1263             shutil.copyfile(logfile, rnamepath)
1264
1265             gtkversion = "%s.%s.%s" % gtk.ver
1266             if os.path.exists("/etc/maemo_version"):
1267                 mfile = open("/etc/maemo_version", 'r')
1268                 maemoversion = mfile.read()
1269                 mfile.close()
1270             else:
1271                 maemoversion = ''
1272
1273             opsystem = ' '.join(os.uname())
1274             pyversion = os.sys.version
1275             pid = os.getpid()
1276             comm = ("awk '/Private_Dirty/{sum+=$2}END{print sum \"kB\"}'"
1277             " /proc/%s/smaps") % pid
1278             status, dirtymem = commands.getstatusoutput(comm)
1279
1280             lfile = open(rnamepath, 'r')
1281             log = lfile.read()
1282             lfile.close()
1283
1284
1285             log = ("%s\nPython version: %s\nGtk version: %s\n"
1286             "Maemo version: %sOperating system: %s\n"
1287             "Dirty Memory: %s\nLog:\n%s") % (_version, pyversion, gtkversion,
1288             maemoversion, opsystem, dirtymem, log)
1289
1290             lfile = open(rnamepath, 'w')
1291             lfile.write(log)
1292             lfile.close()
1293
1294             url = "http://yerga.net/logs/uploader.php"
1295             data = [('uploadedfile', (pycurl.FORM_FILE, rnamepath)),]
1296             mycurl = pycurl.Curl()
1297             mycurl.setopt(pycurl.URL, url)
1298             mycurl.setopt(pycurl.HTTPPOST, data)
1299
1300             mycurl.perform()
1301             mycurl.close()
1302             os.remove(rnamepath)
1303
1304             gtk.gdk.threads_enter()
1305             stockspy.show_info_banner(dlg, 'Log sent')
1306             gtk.gdk.threads_leave()
1307             hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1308         except:
1309             logger.exception("Sending log file")
1310             gtk.gdk.threads_enter()
1311             stockspy.show_info_banner(dlg, 'Error sending the log file')
1312             gtk.gdk.threads_leave()
1313             hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1314
1315
1316 if __name__ == "__main__":
1317     stockspy = StocksPy()
1318     gtk.gdk.threads_enter()
1319     gtk.main()
1320     gtk.gdk.threads_leave()