new portfolio features, metal graphs, preparing for 0.4 release
[stockthis] / stockthis-diablo.py
1 #!/usr/bin/env python
2 # -*- coding: UTF8 -*-
3 # Copyright (C) 2008 by Daniel Martin Yerga
4 # <dyerga@gmail.com>
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software
17 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18 #
19 # StocksPy: Application to get stocks data from Yahoo Finance.
20 # Version 0.1
21 #
22
23 import urllib2
24 import gtk, gobject
25 try:
26     import hildon
27     HILDON = True
28 except:
29     HILDON = False
30
31 try:
32     import osso
33     OSSO = True
34     osso_c = osso.Context("net.yerga.stockthis", "0.2", False)
35 except:
36     OSSO = False
37
38 from marketdata import markets, idmarket, localmarkets, localids
39
40 #TODO: detect if running in Fremantle
41 FREMANTLE=True
42
43 #detect if is ran locally or not
44 import sys
45 runningpath = sys.path[0]
46
47 if '/usr/share' in runningpath:
48     running_locally = False
49 else:
50     running_locally = True
51
52 if running_locally:
53     imgdir = 'pixmaps/'
54 else:
55     imgdir = '/usr/share/stockthis/pixmaps/'
56
57 loading_img = imgdir + 'loading.gif'
58
59 gtk.gdk.threads_init()
60
61 class StocksPy:
62
63     def __init__(self):
64         if HILDON:
65             self.program = hildon.Program()
66             self.program.__init__()
67             gtk.set_application_name('')
68             if FREMANTLE:
69                 self.window = hildon.StackableWindow()
70             else:
71                 self.window = hildon.Window()
72             self.program.add_window(self.window)
73         else:
74             self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
75
76         self.window.set_default_size(800, 480)
77         self.window.set_title('StockThis')
78         self.window.connect("destroy", gtk.main_quit)
79         self.window.connect("key-press-event", self.on_key_press)
80         self.window.connect("window-state-event", self.on_window_state_change)
81         self.window_in_fullscreen = False
82
83         if HILDON:
84             if FREMANTLE:
85                 menu = hildon.AppMenu()
86                 self.window.set_app_menu(menu)
87                 about_menu = gtk.Button("About")
88                 about_menu.connect("clicked", self.on_about)
89             else:
90                 menu = gtk.Menu()
91                 self.window.set_menu(menu)
92                 about_menu = gtk.MenuItem("About")
93                 about_menu.set_size_request(-1, 55)
94                 about_menu.connect("activate", self.on_about)
95
96             menu.append(about_menu)
97             menu.show_all()
98         else:
99             #TODO: create a gtk.menubar
100             pass
101
102
103         vbox = gtk.VBox()
104
105         self.toolbar = gtk.HBox()
106         self.toolbar.set_property("no-show-all", True)
107         self.toolbar.hide()
108         self.toolbar.set_homogeneous(True)
109         self.toolbar.set_size_request(-1, 55)
110
111         self.markets_btn = gtk.Button('Markets')
112         self.markets_btn.connect("clicked", self.create_markets_view)
113         self.markets_btn.set_property("can_focus", False)
114
115         self.components_btn = gtk.Button('Instruments')
116         self.components_btn.connect("clicked", self.show_components_view)
117         self.components_btn.set_property("can_focus", False)
118
119         self.graph_btn = gtk.Button('Graph')
120         self.graph_btn.connect("clicked", self.create_graphs_view)
121         self.graph_btn.set_property("can_focus", False)
122
123         self.refresh_btn = gtk.Button('Refresh')
124         self.refresh_btn.connect("clicked", self.refresh_stock_data)
125         self.refresh_btn.set_property("can_focus", False)
126
127         self.quotes_btn = gtk.Button('Quote')
128         self.quotes_btn.connect("clicked", self.show_quotes_view)
129         self.quotes_btn.set_property("can_focus", False)
130
131         self.toolbar.pack_start(self.markets_btn)
132         self.toolbar.pack_start(self.components_btn)
133         self.toolbar.pack_start(self.graph_btn)
134         self.toolbar.pack_start(self.quotes_btn)
135         self.toolbar.pack_start(self.refresh_btn)
136
137         self.mainbox = gtk.VBox()
138
139
140         if FREMANTLE:
141             self.swin = hildon.PannableArea()
142         else:
143             self.swin = gtk.ScrolledWindow()
144             if HILDON:
145                 hildon.hildon_helper_set_thumb_scrollbar(self.swin, True)
146             self.swin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
147         self.swin.add_with_viewport(self.mainbox)
148
149         vbox.pack_start(self.swin, True, True, 0)
150         vbox.pack_start(gtk.HSeparator(), False, False, 5)
151         vbox.pack_start(self.toolbar, False, False, 0)
152
153         self.create_markets_view(self.mainbox)
154         self.window.add(vbox)
155
156         self.window.show_all()
157
158     def create_markets_view(self, widget):
159         names = markets
160         ids = idmarket
161         self.toolbar.hide()
162
163         actual_view = self.mainbox.get_children()
164         if len(actual_view) > 0:
165             for a_widget in actual_view:
166                 a_widget.destroy()
167
168         self.marketsbox = gtk.VBox()
169         lnames = len(names)
170
171         for i in range(lnames):
172             button = gtk.Button(names[i])
173             button.connect("clicked", self.create_components_view, ids[i])
174             button.set_size_request(-1, 65)
175             button.set_property("can_focus", False)
176             self.marketsbox.pack_start(button, True, True, 2)
177
178         self.mainbox.pack_start(self.marketsbox, True, True, 2)
179         self.mainbox.show_all()
180
181     def show_components_view(self, widget):
182         kind = self.market_id
183         self.create_components_view(widget, kind)
184
185     def create_components_view(self, widget, kind):
186         actual_view = self.mainbox.get_children()
187         for i in actual_view:
188             i.destroy()
189
190         self.market_id = kind
191         self.toolbar.show()
192         self.components_btn.hide()
193         self.markets_btn.show()
194         self.refresh_btn.hide()
195         self.graph_btn.hide()
196         self.quotes_btn.hide()
197
198         names = localmarkets[idmarket.index(kind)]
199         ids = localids[idmarket.index(kind)]
200
201         self.compbox = gtk.VBox()
202
203         self.components_trv = gtk.TreeView()
204         self.components_trv.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_HORIZONTAL)
205
206         if not FREMANTLE:
207             selection = self.components_trv.get_selection()
208             selection.connect("changed", self.changed_selection, ids)
209         else:
210             self.components_trv.connect("row-activated", self.select_component, ids)
211
212         self.components_trv.set_rubber_banding(True)
213         self.components_trv.set_headers_visible(False)
214         self.components_model = self.__create_components_model()
215         self.components_trv.set_model(self.components_model)
216         self._components_trv_columns(self.components_trv)
217         self.add_initial_components(names, ids)
218
219         self.compbox.pack_start(self.components_trv, True, True, 0)
220
221         self.mainbox.pack_start(self.compbox, True, True, 0)
222         self.mainbox.show_all()
223
224     def select_component(self, widget, path, column, ids):
225         self.create_quotes_view(widget,self.components_model[path][1] )
226
227     def changed_selection(self, widget, ids):
228         n, i, m = self.get_selected_from_treeview(self.components_trv, self.components_model, 1)
229         if n is not None:
230             self.create_quotes_view(widget, n)
231
232     def __create_components_model(self):
233         lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
234         return lstore
235
236     def _components_trv_columns(self, treeview):
237         renderer = gtk.CellRendererText()
238         renderer.set_property('scale', 1.3)
239         #renderer.set_property("background", 'lightgray')
240         renderer.set_property("xpad", 15)
241         column = gtk.TreeViewColumn('Name', renderer, text=0)
242         column.set_expand(True)
243         treeview.append_column(column)
244
245         renderer = gtk.CellRendererText()
246         column = gtk.TreeViewColumn('IDS', renderer, text=1)
247         column.set_visible(False)
248         treeview.append_column(column)
249
250     def add_initial_components(self, kind, ids):
251         for i in range(len(kind)):
252             niter = self.components_model.append()
253             self.components_model.set(niter, 0, kind[i], 1, ids[i])
254
255     def get_selected_from_treeview(self, treeview, model, setting):
256         selection = treeview.get_selection()
257         selected_model, selected_iter = selection.get_selected()
258         if selected_iter:
259             selected_name = model.get_value(selected_iter, setting)
260         else:
261             selected_name = None
262         return selected_name, selected_iter, selected_model
263
264     def show_quotes_view(self, widget):
265         kind = self.stocks_id
266         self.create_quotes_view(widget, kind)
267
268     def create_quotes_view(self, widget, kind):
269         self.stocks_id = kind
270         self.toolbar.show()
271         self.components_btn.show()
272         self.markets_btn.show()
273         self.refresh_btn.show()
274         self.graph_btn.show()
275         self.quotes_btn.hide()
276
277         #actual_view = self.mainbox.get_children()
278         #actual_view[0].destroy()
279
280         try:
281             self.graphbox.destroy()
282         except:
283             pass
284
285         for i in range(len(localids)):
286             if kind in localids[i]:
287                 ind1, ind2 = i, localids[i].index(kind)
288
289         self.quotesbox = gtk.VBox()
290
291         self.titlelbl = gtk.Label('')
292         self.titlelbl.set_markup('<b><big>'+localmarkets[ind1][ind2].replace('&', '')+'</big></b>')
293         color = gtk.gdk.color_parse("#03A5FF")
294         self.titlelbl.modify_fg(gtk.STATE_NORMAL, color)
295
296         self.databox = gtk.VBox()
297
298         self.imagebox = gtk.VBox()
299         self.dataimg = gtk.Image()
300         self.imagebox.pack_start(self.dataimg)
301
302         self.show_data(kind)
303
304         hbox1 = gtk.HBox()
305
306         label2 = gtk.Label('')
307         label2.set_markup('<b><big>Price:</big></b>')
308         self.lprice = gtk.Label('')
309
310         hbox1.pack_start(label2, False, False, 50)
311         hbox1.pack_start(self.lprice, False, False, 185)
312
313         hbox2 = gtk.HBox()
314
315         label4 = gtk.Label('')
316         label4.set_markup('<b><big>Change:</big></b>')
317         self.lchange = gtk.Label('')
318         self.lpercent = gtk.Label('')
319
320         hbox2.pack_start(label4, False, False, 50)
321         hbox2.pack_start(self.lchange  , False, False, 145)
322         hbox2.pack_start(self.lpercent, False, False, 0)
323
324         hbox3 = gtk.HBox()
325
326         label7 = gtk.Label('')
327         label7.set_markup('<b><big>Volume:</big></b>')
328         self.lvolume = gtk.Label('')
329
330         hbox3.pack_start(label7, False, False, 50)
331         hbox3.pack_start(self.lvolume  , False, False, 145)
332
333         hbox4 = gtk.HBox()
334
335         label9 = gtk.Label('')
336         label9.set_markup('<b><big>52 week high:</big></b>')
337         self.l52whigh = gtk.Label('')
338
339         hbox4.pack_start(label9, False, False, 50)
340         hbox4.pack_start(self.l52whigh , False, False, 55)
341
342         hbox5 = gtk.HBox()
343
344         label11 = gtk.Label('')
345         label11.set_markup('<b><big>52 week low:</big></b>')
346         self.l52wlow = gtk.Label('')
347
348         hbox5.pack_start(label11, False, False, 50)
349         hbox5.pack_start(self.l52wlow , False, False, 70)
350
351         self.databox.pack_start(hbox1, True, True, 0)
352         self.databox.pack_start(hbox2, True, True, 0)
353         self.databox.pack_start(hbox3, True, True, 0)
354         self.databox.pack_start(hbox4, True, True, 0)
355         self.databox.pack_start(hbox5, True, True, 0)
356
357         self.quotesbox.pack_start(self.titlelbl, False, False, 0)
358         self.quotesbox.pack_start(gtk.HSeparator(), False, False, 0)
359         self.quotesbox.pack_start(self.databox, True, True, 0)
360         self.quotesbox.pack_start(self.imagebox, True, True, 0)
361
362         self.mainbox.pack_start(self.quotesbox, True, True, 0)
363         self.mainbox.show_all()
364         self.compbox.hide()
365         self.databox.hide()
366
367     def show_data(self, kind):
368         import thread
369         self.dataimg.set_from_file(loading_img)
370         self.imagebox.show()
371         self.databox.hide()
372         thread.start_new_thread(self.get_data, (kind,))
373
374     def get_data(self, kind):
375         self.graph_btn.show()
376         self.refresh_btn.show()
377         self.quotes_btn.hide()
378
379         from ystockquote import ystockquote as yt
380         try:
381             data = yt.get_all(kind)
382         except:
383             print 'Failed to get internet data'
384             data = {'price': 'N/A', 'change': 'N/A', 'volume':'N/A',
385                     '52_week_high': 'N/A', '52_week_low': 'N/A'}
386             self.titlelbl.set_markup('<b><big>Failed to get data</big></b>')
387
388         try:
389             ch_percent = 100.0 * float(data['change'])/(float(data['price'])-float(data['change']))
390         except ValueError:
391             ch_percent = 0.0
392
393         self.lprice.set_label(data['price'])
394         self.lchange.set_label(data['change'])
395         self.lpercent.set_label('%6.2f %%' % ch_percent)
396
397         if '-' in data['change']:
398             color = gtk.gdk.color_parse("#FF0000")
399         else:
400             color = gtk.gdk.color_parse("#16EB78")
401
402         self.lpercent.modify_fg(gtk.STATE_NORMAL, color)
403         self.lchange.modify_fg(gtk.STATE_NORMAL, color)
404
405         self.lvolume.set_label(data['volume'])
406         self.l52whigh.set_label(data['52_week_high'])
407         self.l52wlow.set_label(data['52_week_low'])
408
409         self.databox.show_all()
410         self.imagebox.hide()
411
412     def refresh_stock_data(self, widget):
413         self.show_data(self.stocks_id)
414
415     def create_graphs_view(self, widget):
416         self.graph_btn.hide()
417         self.refresh_btn.hide()
418         self.quotes_btn.show()
419
420         self.graph = gtk.Image()
421
422         kind = self.stocks_id
423
424         self.show_graph(None, '1d', kind)
425
426         for i in range(len(localids)):
427             if kind in localids[i]:
428                 ind1, ind2 = i, localids[i].index(kind)
429
430         #self.mainbox.destroy()
431         #self.mainbox = gtk.VBox()
432         #self.swin.add_with_viewport(self.mainbox)
433
434         actual_view = self.mainbox.get_children()
435         for a_widget in actual_view:
436             a_widget.destroy()
437
438         self.graphbox = gtk.VBox()
439
440         self.graphs_title = gtk.Label(localmarkets[ind1][ind2])
441         color = gtk.gdk.color_parse("#03A5FF")
442         self.graphs_title.modify_fg(gtk.STATE_NORMAL, color)
443
444         hbox1 = gtk.HBox()
445         hbox1.set_size_request(-1, 55)
446         hbox1.set_homogeneous(True)
447
448         self.btn1d = gtk.Button('1d')
449         self.btn1d.set_property("can_focus", False)
450         self.btn1d.connect("clicked", self.show_graph, '1d', kind)
451         self.btn5d = gtk.Button('5d')
452         self.btn5d.set_property("can_focus", False)
453         self.btn5d.connect("clicked", self.show_graph, '5d', kind)
454         self.btn3m = gtk.Button('3m')
455         self.btn3m.set_property("can_focus", False)
456         self.btn3m.connect("clicked", self.show_graph, '3m', kind)
457         self.btn6m = gtk.Button('6m')
458         self.btn6m.set_property("can_focus", False)
459         self.btn6m.connect("clicked", self.show_graph, '6m', kind)
460         self.btn1y = gtk.Button('1y')
461         self.btn1y.set_property("can_focus", False)
462         self.btn1y.connect("clicked", self.show_graph, '1y', kind)
463         self.btn2y = gtk.Button('2y')
464         self.btn2y.set_property("can_focus", False)
465         self.btn2y.connect("clicked", self.show_graph, '2y', kind)
466         self.btn5y = gtk.Button('5y')
467         self.btn5y.set_property("can_focus", False)
468         self.btn5y.connect("clicked", self.show_graph, '5y', kind)
469         self.btnmax = gtk.Button('max')
470         self.btnmax.set_property("can_focus", False)
471         self.btnmax.connect("clicked", self.show_graph, 'max', kind)
472
473         hbox1.pack_start(self.btn1d)
474         hbox1.pack_start(self.btn5d)
475         hbox1.pack_start(self.btn3m)
476         hbox1.pack_start(self.btn6m)
477         hbox1.pack_start(self.btn1y)
478         hbox1.pack_start(self.btn2y)
479         hbox1.pack_start(self.btn5y)
480         hbox1.pack_start(self.btnmax)
481
482         self.graphbox.pack_start(self.graphs_title, False, False, 2)
483         self.graphbox.pack_start(hbox1, False, False, 0)
484         self.graphbox.pack_start(self.graph, True, True, 5)
485
486         self.mainbox.pack_start(self.graphbox, False, False, 2)
487         self.mainbox.show_all()
488
489     def show_graph(self, widget, option, kind):
490         import thread
491         self.graph.set_from_file(loading_img)
492         thread.start_new_thread(self.get_graph_data, (option, kind))
493
494     def get_graph_data(self, option, kind):
495         if option == '1d':
496             url = 'http://uk.ichart.yahoo.com/b?s=%s' % kind
497         elif option == '5d':
498             url = 'http://uk.ichart.yahoo.com/w?s=%s' % kind
499         elif option == '3m':
500             url = 'http://chart.finance.yahoo.com/c/3m/s/%s' % kind.lower()
501         elif option == '6m':
502             url = 'http://chart.finance.yahoo.com/c/6m/s/%s' % kind.lower()
503         elif option == '1y':
504             url = 'http://chart.finance.yahoo.com/c/1y/s/%s' % kind.lower()
505         elif option == '2y':
506             url = 'http://chart.finance.yahoo.com/c/2y/s/%s' % kind.lower()
507         elif option == '5y':
508             url = 'http://chart.finance.yahoo.com/c/5y/s/%s' % kind.lower()
509         elif option == 'max':
510             url = 'http://chart.finance.yahoo.com/c/my/s/%s' % kind.lower()
511
512         try:
513             myimg = urllib2.urlopen(url)
514             imgdata=myimg.read()
515
516             pbl = gtk.gdk.PixbufLoader()
517             pbl.write(imgdata)
518
519             pbuf = pbl.get_pixbuf()
520             pbl.close()
521             self.graph.set_from_pixbuf(pbuf)
522         except:
523             print 'no internet data'
524             self.graphs_title.set_label('Failed to get data')
525             self.graph.destroy()
526
527
528     #Functions for fullscreen
529     def on_window_state_change(self, widget, event, *args):
530         if event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN:
531             self.window_in_fullscreen = True
532         else:
533             self.window_in_fullscreen = False
534
535     #F6 fullscreen, F7 bigger font, F8 smaller font
536     def on_key_press(self, widget, event, *args):
537         if event.keyval == gtk.keysyms.F6:
538             if self.window_in_fullscreen:
539                 self.window.unfullscreen ()
540             else:
541                 self.window.fullscreen ()
542
543     def on_about(self, widget):
544         dialog = gtk.AboutDialog()
545         dialog.set_name("StockThis")
546         dialog.set_version("0.2")
547         dialog.set_copyright("Copyright © 2009")
548         dialog.set_website("http://stockthis.garage.maemo.org")
549         dialog.set_authors(["Daniel Martin Yerga <dyerga@gmail.com>"])
550         logo = gtk.gdk.pixbuf_new_from_file(imgdir + "stockthis.png")
551         dialog.set_logo(logo)
552         dialog.set_license("This program is released under the GNU\nGeneral Public License. Please visit \nhttp://www.gnu.org/copyleft/gpl.html\nfor details.")
553         dialog.set_artists(["Logo by Daniel Martin Yerga"])
554         dialog.run()
555         dialog.destroy()
556
557 if __name__ == "__main__":
558     stockspy = StocksPy()
559     gtk.gdk.threads_enter()
560     gtk.main()
561     gtk.gdk.threads_leave()