pushing last changes for 0.3 version
[stockthis] / stockthis.py
1 #!/usr/bin/env python
2 # -*- coding: UTF8 -*-
3 # Copyright (C) 2008 by Daniel Martin Yerga
4 # <dyerga@gmail.com>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 #
19 # StocksThis: Application to get stocks data from Yahoo Finance.
20 #
21
22 _version = "StockThis 0.3 beta1 rev1"
23
24 import urllib2
25 import gtk, gobject
26 import os
27 import hildon
28 import marketdata
29 import settings
30 import logging
31 import sys
32
33 from portrait import FremantleRotation
34
35 import osso
36 osso_c = osso.Context("net.yerga.stockthis", "0.3", False)
37
38 #detect if is ran locally or not
39 runningpath = sys.path[0]
40
41 if '/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         self.add_to_portfolio(widget, button, symbol, name)
857         dlg.destroy()
858
859         niter = self.portfolio_model.append()
860         self.portfolio_model.set(niter, 0, symbol, 1, name, 2, shares, 3, "-")
861
862     def remove_item(self, widget):
863         win = hildon.StackableWindow()
864         win.fullscreen()
865         toolbar = hildon.EditToolbar("Choose items to delete", "Delete")
866         win.set_edit_toolbar(toolbar)
867
868         vbox = gtk.VBox()
869         parea = hildon.PannableArea()
870         tv = hildon.GtkTreeView(ui_edit)
871         selection = tv.get_selection()
872         selection.set_mode(gtk.SELECTION_MULTIPLE)
873         tv.set_model(self.portfolio_model)
874         self._tv_remove_portfolio_columns(tv)
875         parea.add(tv)
876
877         toolbar.connect("button-clicked", self.delete_from_portfolio, win, tv,
878                         selection)
879         toolbar.connect_object("arrow-clicked", gtk.Window.destroy, win)
880
881         vbox.pack_start(parea, True, True, 0)
882         win.add(vbox)
883         win.show_all()
884
885     def delete_from_portfolio(self, widget, win, tv, selection):
886         if not self.is_treeview_selected(tv):
887             return
888
889         conf = self.show_confirmation(win, "Delete items?")
890
891         if conf:
892             try:
893                 selmodel, selected = selection.get_selected_rows()
894                 iters = [selmodel.get_iter(path) for path in selected]
895                 for i in iters:
896                     symbol = selmodel.get_value(i, 0)
897                     settings.delete_item_from_portfolio(settingsdb, symbol)
898                     selmodel.remove(i)
899             except:
900                 logger.exception("Deleting item from portfolio")
901                 self.info_banner(widget, "Error deleting item")
902
903     def _tv_remove_portfolio_columns(self, treeview):
904         column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
905         column.set_visible(False)
906         treeview.append_column(column)
907
908         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
909         column.set_property("expand", True)
910         treeview.append_column(column)
911
912         column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
913         column.set_visible(False)
914         treeview.append_column(column)
915
916         column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
917         column.set_visible(False)
918         treeview.append_column(column)
919
920     def refresh_portfolio(self, widget, tv, win):
921         data = settings.load_portfolio(settingsdb)
922         import thread
923         winprogind(win, 1)
924         thread.start_new_thread(self._do_refresh_portfolio, (data, tv, win))
925
926     def _do_refresh_portfolio(self, data, tv, win):
927         for item in data:
928             item[3] = self.get_price(item[0])
929
930         self.portfolio_model = self._create_portfolio_model(data)
931         tv.set_model(self.portfolio_model)
932         winprogind(win, 0)
933
934     def get_price(self, symbol):
935         from ystockquote import ystockquote as yt
936         try:
937             price = yt.get_price(symbol)
938             return price
939         except:
940             logger.exception("Getting price from Yahoo: %s" % symbol)
941             return "N/A"
942
943     def _create_portfolio_model(self, data):
944         lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
945                                 gobject.TYPE_STRING, gobject.TYPE_STRING)
946         for item in data:
947             iter = lstore.append()
948             lstore.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3])
949         return lstore
950
951     def _tv_portfolio_columns(self, treeview):
952         column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
953         column.set_visible(False)
954         treeview.append_column(column)
955
956         column = gtk.TreeViewColumn('Name', gtk.CellRendererText(), text=1)
957         column.set_property("expand", True)
958         treeview.append_column(column)
959
960         column = gtk.TreeViewColumn('Shares', gtk.CellRendererText(), text=2)
961         treeview.append_column(column)
962
963         column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
964         treeview.append_column(column)
965
966     def show_confirmation(self, window, msg):
967         dialog = hildon.hildon_note_new_confirmation(window, msg)
968         dialog.show_all()
969         result = dialog.run()
970         if result == gtk.RESPONSE_OK:
971             dialog.destroy()
972             return True
973
974         dialog.destroy()
975         return False
976
977     def show_information_note(self, window, msg):
978         dialog = hildon.hildon_note_new_information(window, msg)
979         dialog.show_all()
980         result = dialog.run()
981         dialog.destroy()
982
983     def show_info_banner(self, widget, msg):
984         hildon.hildon_banner_show_information(widget, 'qgn_note_infoprint', msg)
985
986     def is_treeview_selected(self, treeview):
987         selection = treeview.get_selection()
988         if selection.count_selected_rows() == 0:
989             self.show_info_banner(treeview, 'No selected item')
990             return False
991         else:
992             return True
993
994 class About:
995
996     def __init__(self, widget):
997         self.abdialog = gtk.Dialog(title='About', parent=None, flags=0)
998         self.abdialog.set_has_separator(False)
999         self.abdialog.set_size_request(-1, 400)
1000
1001         self.info_lb = gtk.Label()
1002         self.info_lb.set_line_wrap(True)
1003
1004         self.id = False
1005
1006         hbox1 = gtk.HBox()
1007
1008         button = hildon.Button(fhsize, horbtn)
1009         button.set_title('Description')
1010         button.connect("clicked", self.show_info, 'description')
1011         hbox1.pack_start(button, True, True, 0)
1012
1013         button = hildon.Button(fhsize, horbtn)
1014         button.set_title('Credits')
1015         button.connect("clicked", self.show_info, 'credits')
1016         hbox1.pack_start(button, True, True, 0)
1017
1018         button = hildon.Button(fhsize, horbtn)
1019         button.set_title('License')
1020         button.connect("clicked", self.show_info, 'license')
1021         hbox1.pack_start(button, True, True, 0)
1022
1023         button = hildon.Button(fhsize, horbtn)
1024         button.set_title('Donate')
1025         button.connect("clicked", self.show_info, 'donate')
1026         hbox1.pack_start(button, True, True, 0)
1027
1028         button = hildon.Button(fhsize, horbtn)
1029         button.set_title('Report ')
1030         button.connect("clicked", self.show_info, 'report')
1031         hbox1.pack_start(button, True, True, 0)
1032
1033         button = hildon.Button(fhsize, horbtn)
1034         button.set_title(' Rate ')
1035         button.connect("clicked", self.show_info, 'vote')
1036         hbox1.pack_start(button, True, True, 0)
1037
1038         self.action_btn = hildon.Button(fhsize, horbtn)
1039         self.image = gtk.Image()
1040
1041         self.show_info(None, 'description')
1042
1043         self.abdialog.vbox.pack_start(self.action_btn, False, False, 0)
1044         self.abdialog.vbox.pack_start(self.image, False, False, 5)
1045         self.abdialog.vbox.pack_start(self.info_lb, True, True, 0)
1046         self.abdialog.vbox.pack_start(hbox1, False, False, 0)
1047
1048         self.abdialog.show_all()
1049         self.action_btn.hide()
1050         self.image.hide()
1051         self.abdialog.run()
1052         self.abdialog.destroy()
1053
1054     def do_action(self, widget, action):
1055         import dbus
1056
1057         self.abdialog.destroy()
1058
1059         bus = dbus.SystemBus()
1060         proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
1061         iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
1062
1063         if action == "donate":
1064             url = "http://stockthis.garage.maemo.org/donate.html"
1065         elif action == "report":
1066             url = "http://stockthis.garage.maemo.org/reporting.html"
1067         elif action == "vote":
1068             url = "http://maemo.org/downloads/product/stockthis"
1069
1070         iface.open_new_window(url)
1071
1072     def show_info(self, widget, kind):
1073         if kind == 'license':
1074             self.action_btn.hide()
1075             self.image.hide()
1076             info = """<small><b>StockThis</b> is free software. It's using a GPL version 2 license or at your election any later version.
1077
1078 Logo by Daniel Martin Yerga.
1079 </small>"""
1080         elif kind == 'credits':
1081             self.action_btn.hide()
1082             self.image.hide()
1083             info = """<small><b>Written by</b> Daniel Martin Yerga (dyerga@gmail.com)
1084
1085 <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>"""
1086         elif kind == 'description':
1087             self.action_btn.hide()
1088             self.image.hide()
1089             info = """<b><big>StockThis 0.3</big></b>
1090
1091 <i>StockThis is a stocks application for Maemo</i>
1092
1093 <b>Web Page</b>:
1094 stockthis.garage.maemo.org"""
1095
1096         elif kind == 'donate':
1097             self.action_btn.show()
1098             self.image.hide()
1099             self.action_btn.set_title('Make donation')
1100             if self.id:
1101                 self.action_btn.disconnect(self.id)
1102             self.id = self.action_btn.connect("clicked", self.do_action, "donate")
1103             info = """<small><b>StockThis</b> is a free (and gratis) software application.
1104 Developing good software takes time and hard work.
1105
1106 <b>StockThis's author</b> develops the program in his spare time.
1107 If you like the program and it's helpful, consider donating a small amount of money.
1108 Donations are a great incentive and help the developer feel that the hard work is appreciated.</small>
1109 """
1110         elif kind == 'report':
1111             self.action_btn.show()
1112             self.image.hide()
1113             self.action_btn.set_title('Report bug')
1114             if self.id:
1115                 self.action_btn.disconnect(self.id)
1116             self.id = self.action_btn.connect("clicked", self.do_action, "report")
1117             info = """<small>StockThis is being improved thanks to bug reports that users have submitted. The author appreciates these reports.
1118 If the application is raising an error when you're using it, you have two choices to report this error:
1119 1) Send the log from the application menu (if there's an error in the log).
1120 2) Press the button and write a bug report with as much information as possible.</small>"""
1121         elif kind == 'vote':
1122             self.action_btn.show()
1123             self.image.show()
1124             self.image.set_from_file(imgdir + "maemoorg.png")
1125             self.action_btn.set_title('Rate StockThis')
1126             if self.id:
1127                 self.action_btn.disconnect(self.id)
1128             self.id = self.action_btn.connect("clicked", self.do_action, "vote")
1129             info = """<small>The downloads section in maemo.org has a nice system where you can rate applications.
1130 If you consider StockThis a good application (or a bad one too), you could rate it in maemo.org site.</small>"""
1131
1132         self.info_lb.set_markup(info)
1133
1134
1135 class Log:
1136
1137     def __init__(self, widget, logfile):
1138         #Log dialog UI
1139         dialog = gtk.Dialog(title='Log', parent=None)
1140
1141         dialog.set_size_request(600, 350)
1142
1143         parea = hildon.PannableArea()
1144         parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
1145
1146         textview = hildon.TextView()
1147         textview.set_property("editable", False)
1148         textview.set_property("wrap-mode", gtk.WRAP_WORD)
1149
1150         log = open(logfile, 'r')
1151         logtext = log.read()
1152         log.close()
1153
1154         textview.get_buffer().set_text(logtext)
1155         parea.add(textview)
1156
1157         dialog.vbox.pack_start(parea, True, True, 0)
1158
1159         hbox = gtk.HBox()
1160
1161         save_btn = hildon.Button(fhsize, horbtn)
1162         save_btn.set_title("Save")
1163         save_btn.connect('clicked', self.save, logfile, dialog)
1164
1165         clear_btn = hildon.Button(fhsize, horbtn)
1166         clear_btn.set_title("Clear")
1167         clear_btn.connect('clicked', self.clear, textview, logfile)
1168
1169         send_btn = hildon.Button(fhsize, horbtn)
1170         send_btn.set_title('Send')
1171         send_btn.connect('clicked', self.send, dialog, logfile)
1172
1173         hbox.pack_start(save_btn, True, True, 0)
1174         hbox.pack_start(clear_btn, True, True, 0)
1175         hbox.pack_start(send_btn, True, True, 0)
1176
1177         dialog.vbox.pack_start(hbox, False, False, 0)
1178
1179         dialog.show_all()
1180         dialog.run()
1181         dialog.destroy()
1182
1183     def show_filechooser(self, window, title, name, EXT):
1184         action = gtk.FILE_CHOOSER_ACTION_SAVE
1185
1186         m = hildon.FileSystemModel()
1187         file_dialog = hildon.FileChooserDialog(window, action, m)
1188         file_dialog.set_title(title)
1189
1190         file_dialog.set_current_name(name)
1191         HOME = os.path.expanduser("~")
1192
1193         if os.path.exists(HOME + '/MyDocs/.documents'):
1194             file_dialog.set_current_folder(HOME + '/MyDocs/.documents')
1195         else:
1196             file_dialog.set_current_folder(HOME)
1197
1198         file_dialog.set_default_response(gtk.RESPONSE_CANCEL)
1199
1200         result = file_dialog.run()
1201         if result == gtk.RESPONSE_OK:
1202             namefile = file_dialog.get_filename()
1203             namefile, extension = os.path.splitext(namefile)
1204             namefile = namefile + "." + EXT
1205         else:
1206             namefile = None
1207         file_dialog.destroy()
1208
1209         return namefile
1210
1211
1212     def clear(self, widget, textview, logfile):
1213         textview.get_buffer().set_text('')
1214         f = open(logfile, 'w')
1215         f.close()
1216
1217     def save(self, widget, logfile, dlg):
1218         import shutil
1219         filename = self.show_filechooser(dlg, "Save log file",
1220                     "stockthis-log", "txt")
1221
1222         if not filename:
1223             return
1224
1225         try:
1226             shutil.copyfile(logfile, filename)
1227             stockspy.show_info_banner(widget, 'Log file saved')
1228         except:
1229             logger.exception("Saving log file")
1230             stockspy.show_info_banner(widget, 'Error saving the log file')
1231
1232     def send(self, widget, dlg, logfile):
1233         sendtxt = ("You are going to send the log to the developers.\n"
1234         "This helps the developers to track problems with the application.\n"
1235         "It doesn't send any personal information (like passwords or similar).")
1236
1237         dialog = hildon.hildon_note_new_confirmation(dlg, sendtxt)
1238         dialog.set_button_texts("Send", "Cancel")
1239         dialog.show_all()
1240         response = dialog.run()
1241         if response == gtk.RESPONSE_OK:
1242             self.do_pre_send(dlg, logfile)
1243
1244         dialog.destroy()
1245
1246     def do_pre_send(self, dlg, logfile):
1247         import thread
1248         hildon.hildon_gtk_window_set_progress_indicator(dlg, 1)
1249         thread.start_new_thread(self._do_send, (dlg, logfile))
1250
1251     def _do_send(self, dlg, logfile):
1252         import pycurl, shutil, random, commands
1253         try:
1254             rname = ''
1255             for i in random.sample('abcdefghijkl123456789', 18):
1256                 rname += i
1257
1258             rnamepath = HOME + "/.stockthis/" + rname
1259             shutil.copyfile(logfile, rnamepath)
1260
1261             gtkversion = "%s.%s.%s" % gtk.ver
1262             if os.path.exists("/etc/maemo_version"):
1263                 mfile = open("/etc/maemo_version", 'r')
1264                 maemoversion = mfile.read()
1265                 mfile.close()
1266             else:
1267                 maemoversion = ''
1268
1269             opsystem = ' '.join(os.uname())
1270             pyversion = os.sys.version
1271             pid = os.getpid()
1272             comm = ("awk '/Private_Dirty/{sum+=$2}END{print sum \"kB\"}'"
1273             " /proc/%s/smaps") % pid
1274             status, dirtymem = commands.getstatusoutput(comm)
1275
1276             lfile = open(rnamepath, 'r')
1277             log = lfile.read()
1278             lfile.close()
1279
1280
1281             log = ("%s\nPython version: %s\nGtk version: %s\n"
1282             "Maemo version: %sOperating system: %s\n"
1283             "Dirty Memory: %s\nLog:\n%s") % (_version, pyversion, gtkversion,
1284             maemoversion, opsystem, dirtymem, log)
1285
1286             lfile = open(rnamepath, 'w')
1287             lfile.write(log)
1288             lfile.close()
1289
1290             url = "http://yerga.net/logs/uploader.php"
1291             data = [('uploadedfile', (pycurl.FORM_FILE, rnamepath)),]
1292             mycurl = pycurl.Curl()
1293             mycurl.setopt(pycurl.URL, url)
1294             mycurl.setopt(pycurl.HTTPPOST, data)
1295
1296             mycurl.perform()
1297             mycurl.close()
1298             os.remove(rnamepath)
1299
1300             gtk.gdk.threads_enter()
1301             stockspy.show_info_banner(dlg, 'Log sent')
1302             gtk.gdk.threads_leave()
1303             hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1304         except:
1305             logger.exception("Sending log file")
1306             gtk.gdk.threads_enter()
1307             stockspy.show_info_banner(dlg, 'Error sending the log file')
1308             gtk.gdk.threads_leave()
1309             hildon.hildon_gtk_window_set_progress_indicator(dlg, 0)
1310
1311
1312 if __name__ == "__main__":
1313     stockspy = StocksPy()
1314     gtk.gdk.threads_enter()
1315     gtk.main()
1316     gtk.gdk.threads_leave()