new portfolio features, metal graphs, preparing for 0.4 release
[stockthis] / stockthis.py
index 86fb395..7ca72b4 100644 (file)
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 #
-# StocksPy: Application to get stocks data from Yahoo Finance.
-# Version 0.1
+# StocksThis: Application to get stocks data from Yahoo Finance.
 #
 
-_version = "StockThis 0.3 beta1 rev1"
+_version = "StockThis 0.4 rev1"
+VERSION = "0.4"
 
 import urllib2
 import gtk, gobject
@@ -31,13 +31,15 @@ import settings
 import logging
 import sys
 
+from portrait import FremantleRotation
+
 import osso
 osso_c = osso.Context("net.yerga.stockthis", "0.3", False)
 
 #detect if is ran locally or not
 runningpath = sys.path[0]
 
-if '/usr/share' in runningpath:
+if '/opt/' in runningpath:
     runninglocally = False
 else:
     runninglocally = True
@@ -47,7 +49,6 @@ HOME = os.path.expanduser("~")
 settingsdb, imgdir, configdir, logfile = \
     settings.define_paths(runninglocally, HOME)
 
-
 logger = logging.getLogger('st')
 logging.basicConfig(filename=logfile,level=logging.ERROR, filemode='w')
 
@@ -88,6 +89,8 @@ class StocksPy:
         self.program.add_window(self.window)
         self.window.connect("destroy", gtk.main_quit)
 
+        FremantleRotation('StockThis', None, "0.3", 0)
+
         self.create_menu(self.window)
 
         vbox = gtk.VBox()
@@ -110,15 +113,16 @@ class StocksPy:
         self.window.add(vbox)
         self.window.show_all()
 
+        self.show_info_banner(self.window,
+            ("StockThis uses your network connection to get data.\n"
+            "Be aware of the high costs that your network provider may apply."))
+
     def create_menu(self, window):
         menu = hildon.AppMenu()
         window.set_app_menu(menu)
         button = gtk.Button("About")
         button.connect("clicked", About)
         menu.append(button)
-        button = gtk.Button("Log")
-        button.connect("clicked", Log, logfile)
-        menu.append(button)
         menu.show_all()
 
     def show_instrument_view(self, widget, path, column, inmodel, names,
@@ -129,7 +133,7 @@ class StocksPy:
 
         window = hildon.StackableWindow()
         self.create_menu(window)
-        window.set_title("StockThis - " + inmodel[path][1])
+        window.set_title(inmodel[path][1])
 
         vbox = gtk.VBox()
         toolbar = self.main_toolbar(False, False, None, '', '', False)
@@ -138,7 +142,12 @@ class StocksPy:
         parea.connect("horizontal-movement", self.horizontal_mov)
         tv = hildon.GtkTreeView(ui_normal)
         model = self.__create_model(names, ids)
-        tv.connect("row-activated", self.show_quotes_view, model, False)
+
+        if market == 'metals':
+            tv.connect("row-activated", self.show_metal_graphs, model)
+        else:
+            tv.connect("row-activated", self.show_quotes_view, model, False)
+
         tv.set_model(model)
         self._tv_columns(tv)
         parea.add(tv)
@@ -210,7 +219,7 @@ class StocksPy:
 
         win = hildon.StackableWindow()
         self.create_menu(win)
-        win.set_title("StockThis - Quotes View - " + quote[1])
+        win.set_title(quote[1])
 
         vbox = gtk.VBox()
 
@@ -221,51 +230,75 @@ class StocksPy:
         ltitle.modify_fg(gtk.STATE_NORMAL, color)
 
         parea = hildon.PannableArea()
+        parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
 
         vbox1 = gtk.VBox()
 
         hbox = gtk.HBox()
         label = gtk.Label('')
-        label.set_markup('<b><big>Price:</big></b>')
+        label.set_markup('<b><big>%39s:</big></b>' % '<u>Price</u>')
         lprice = gtk.Label('')
-        hbox.pack_start(label, False, False, 20)
-        hbox.pack_start(lprice, False, False, 245)
+        hbox.pack_start(label, False, False, 0)
+        #if self.is_portrait():
+        hbox.pack_start(lprice, False, False, 25)
+        #else:
+        #    hbox.pack_start(lprice, False, False, 245)
+
         vbox1.pack_start(hbox, True, True, 0)
 
         hbox = gtk.HBox()
         label = gtk.Label('')
-        label.set_markup('<b><big>Change:</big></b>')
+        label.set_markup('<b><big>%35s:</big></b>' % '<u>Change</u>')
         lchange = gtk.Label('')
         lpercent = gtk.Label('')
-        hbox.pack_start(label, False, False, 20)
-        hbox.pack_start(lchange, False, False, 205)
+        hbox.pack_start(label, False, False, 0)
+        #if self.is_portrait():
+        hbox.pack_start(lchange, False, False, 25)
         hbox.pack_start(lpercent, False, False, 0)
+        #else:
+        #    hbox.pack_start(lchange, False, False, 205)
+        #    hbox.pack_start(lpercent, False, False, 0)
+
         vbox1.pack_start(hbox, True, True, 0)
 
         hbox = gtk.HBox()
         label = gtk.Label('')
-        label.set_markup('<b><big>Volume:</big></b>')
+        label.set_markup('<b><big>%35s:</big></b>' % '<u>Volume</u>')
         lvolume = gtk.Label('')
-        hbox.pack_start(label, False, False, 20)
-        hbox.pack_start(lvolume, False, False, 207)
+        hbox.pack_start(label, False, False, 0)
+        #if self.is_portrait():
+        hbox.pack_start(lvolume, False, False, 25)
+        #else:
+        #    hbox.pack_start(lvolume, False, False, 207)
+
         vbox1.pack_start(hbox, True, True, 0)
 
         hbox = gtk.HBox()
         label = gtk.Label('')
-        label.set_markup('<b><big>52 week high:</big></b>')
+        label.set_markup('<b><big>%30s:</big></b>' % '<u>52 week high</u>')
         l52whigh = gtk.Label('')
-        hbox.pack_start(label, False, False, 20)
-        hbox.pack_start(l52whigh, False, False, 125)
+        hbox.pack_start(label, False, False, 0)
+        #if self.is_portrait():
+        hbox.pack_start(l52whigh, False, False, 25)
+        #else:
+        #    hbox.pack_start(l52whigh, False, False, 125)
+
         vbox1.pack_start(hbox, True, True, 0)
 
         hbox = gtk.HBox()
         label = gtk.Label('')
-        label.set_markup('<b><big>52 week low:</big></b>')
+        label.set_markup('<b><big>%30s:</big></b>' % '<u>52 week low</u>')
         l52wlow = gtk.Label('')
-        hbox.pack_start(label, False, False, 20)
-        hbox.pack_start(l52wlow, False, False, 140)
+        hbox.pack_start(label, False, False, 0)
+        #if self.is_portrait():
+        hbox.pack_start(l52wlow, False, False, 26)
+        #else:
+        #    hbox.pack_start(l52wlow, False, False, 140)
         vbox1.pack_start(hbox, True, True, 0)
 
+        #if self.is_portrait():
+        #    hbox = gtk.VBox()
+        #else:
         hbox = gtk.HBox()
         button1 = hildon.PickerButton(fhsize, horbtn)
         data = ["50", "100", "200", "300", "400", "500", "600", "700", "800",
@@ -284,24 +317,33 @@ class StocksPy:
 
         hbox1 = gtk.HBox()
         label = gtk.Label('')
-        label.set_markup('<b><big>Shares:</big></b>')
+        label.set_markup('<b><big>%37s:</big></b>' % '<u>Shares</u>')
         lshares = gtk.Label(shares)
-        hbox1.pack_start(label, False, False, 20)
-        hbox1.pack_start(lshares, False, False, 220)
+        hbox1.pack_start(label, False, False, 0)
+        #if self.is_portrait():
+        hbox1.pack_start(lshares, False, False, 25)
+        #else:
+        #    hbox1.pack_start(lshares, False, False, 220)
 
         hbox2 = gtk.HBox()
         label = gtk.Label('')
-        label.set_markup('<b><big>Holdings Value:</big></b>')
+        label.set_markup('<b><big>%29s:</big></b>' % '<u>Holdings Value</u>')
         holdingsvalue = gtk.Label("")
-        hbox2.pack_start(label, False, False, 20)
-        hbox2.pack_start(holdingsvalue, False, False, 105)
+        hbox2.pack_start(label, False, False, 0)
+        #if self.is_portrait():
+        hbox2.pack_start(holdingsvalue, False, False, 25)
+        #else:
+        #    hbox2.pack_start(holdingsvalue, False, False, 105)
 
         hbox3 = gtk.HBox()
         label = gtk.Label('')
-        label.set_markup("<b><big>Day's Value Change:</big></b>")
+        label.set_markup("<b><big>%25s:</big></b>" % "<u>Day's Value Change</u>")
         dayvaluechange = gtk.Label("")
-        hbox3.pack_start(label, False, False, 20)
-        hbox3.pack_start(dayvaluechange, False, False, 45)
+        hbox3.pack_start(label, False, False, 0)
+        #if self.is_portrait():
+        hbox3.pack_start(dayvaluechange, False, False, 25)
+        #else:
+        #    hbox3.pack_start(dayvaluechange, False, False, 45)
 
         if not portfolio:
             vbox1.pack_start(hbox, False, False, 0)
@@ -328,6 +370,14 @@ class StocksPy:
         win.show_all()
         self.show_data(quote[0], widgets, shares)
 
+    def is_portrait(self):
+        width = gtk.gdk.screen_width()
+        height = gtk.gdk.screen_height()
+        if width > height:
+            return False
+        else:
+            return True
+
     def get_shares_from_symbol(self, symbol):
         shares = "0"
         try:
@@ -337,7 +387,7 @@ class StocksPy:
                     shares = item[2]
             return shares
         except:
-            logger.exception("Getting shares from symbol")
+            logger.exception("Getting shares from symbol: %s" % symbol)
             return shares
 
     def add_to_portfolio(self, widget, button, symbol, name):
@@ -360,7 +410,7 @@ class StocksPy:
 
             self.show_info_banner(widget, "Added to portfolio")
         except:
-            logger.exception("Adding to portfolio")
+            logger.exception("Adding to portfolio: %s, %s" % (symbol, name))
             self.show_info_banner(widget, "Error adding to portfolio")
 
 
@@ -386,7 +436,7 @@ class StocksPy:
         try:
             data = yt.get_all(symbol)
         except:
-            logger.exception("Getting data from Yahoo")
+            logger.exception("Getting data from Yahoo: %s" % symbol)
             data = {'price': 'N/A', 'change': 'N/A', 'volume':'N/A',
                     '52_week_high': 'N/A', '52_week_low': 'N/A'}
             ltitle.set_markup('<b><big>Failed to get data</big></b>')
@@ -439,7 +489,7 @@ class StocksPy:
     def show_graph_view(self, widget, symbol, name):
         win = hildon.StackableWindow()
         self.create_menu(win)
-        win.set_title("StockThis - Graph View - " + name)
+        win.set_title(name)
 
         vbox = gtk.VBox()
         toolbar = self.main_toolbar(False, True, None, '', '', False)
@@ -449,6 +499,9 @@ class StocksPy:
         self.graphs_title.modify_fg(gtk.STATE_NORMAL, color)
 
         parea = hildon.PannableArea()
+        parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
+
+        vbox1 = gtk.VBox()
 
         hbox = gtk.HBox()
         hbox.set_homogeneous(True)
@@ -473,6 +526,10 @@ class StocksPy:
         button.connect("clicked", self.show_graph, '6m', win, symbol)
         hbox.pack_start(button)
 
+        vbox1.pack_start(hbox, False, False, 0)
+        hbox = gtk.HBox()
+        hbox.set_homogeneous(True)
+
         button = hildon.Button(fhsize, horbtn)
         button.set_label('1y')
         button.connect("clicked", self.show_graph, '1y', win, symbol)
@@ -493,7 +550,6 @@ class StocksPy:
         button.connect("clicked", self.show_graph, 'max', win, symbol)
         hbox.pack_start(button)
 
-        vbox1 = gtk.VBox()
         vbox1.pack_start(hbox, False, False, 0)
 
         self.graph = gtk.Image()
@@ -543,15 +599,198 @@ class StocksPy:
             pbl.write(imgdata)
 
             pbuf = pbl.get_pixbuf()
+            pbuf = pbuf.scale_simple(475, 235, gtk.gdk.INTERP_TILES)
             pbl.close()
             self.graph.set_from_pixbuf(pbuf)
             winprogind(win, 0)
         except:
-            logger.exception("Getting graph data")
+            logger.exception("Getting graph data: %s" % url)
             winprogind(win, 0)
             self.graphs_title.set_label('Failed to get data')
             self.graph.destroy()
 
+    def show_metal_graphs(self, widget, path, column, model):
+        metal = model[path][1]
+        metal_urls = self.get_metal_graphs_urls(metal)
+        options =  []
+        for i in metal_urls:
+            options.append(i[0])
+
+        win = hildon.StackableWindow()
+        #self.create_menu(win)
+        win.set_title(metal)
+
+        vbox = gtk.VBox()
+        #toolbar = self.main_toolbar(False, True, None, '', '', False)
+
+        self.metalgraph_title = gtk.Label(metal)
+        color = gtk.gdk.color_parse("#03A5FF")
+        self.metalgraph_title.modify_fg(gtk.STATE_NORMAL, color)
+
+        parea = hildon.PannableArea()
+        parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
+
+        vbox1 = gtk.VBox()
+
+        hbox = gtk.HBox()
+        hbox.set_homogeneous(True)
+
+        button = hildon.Button(fhsize, horbtn)
+        button.set_label('live')
+        button.connect("clicked", self.show_metalgraph, 'live', win, metal_urls)
+        if 'live' in options:
+            hbox.pack_start(button)
+
+        button = hildon.Button(fhsize, horbtn)
+        button.set_label('30d')
+        button.connect("clicked", self.show_metalgraph, '30d', win, metal_urls)
+        if '30d' in options:
+            hbox.pack_start(button)
+
+        button = hildon.Button(fhsize, horbtn)
+        button.set_label('60d')
+        button.connect("clicked", self.show_metalgraph, '60d', win, metal_urls)
+        if '60d' in options:
+            hbox.pack_start(button)
+
+        button = hildon.Button(fhsize, horbtn)
+        button.set_label('6m')
+        button.connect("clicked", self.show_metalgraph, '6m', win, metal_urls)
+        if '6m' in options:
+            hbox.pack_start(button)
+
+        vbox1.pack_start(hbox, False, False, 0)
+        hbox = gtk.HBox()
+        hbox.set_homogeneous(True)
+
+        button = hildon.Button(fhsize, horbtn)
+        button.set_label('1y')
+        button.connect("clicked", self.show_metalgraph, '1y', win, metal_urls)
+        if '1y' in options:
+            hbox.pack_start(button)
+
+        button = hildon.Button(fhsize, horbtn)
+        button.set_label('5y')
+        button.connect("clicked", self.show_metalgraph, '5y', win, metal_urls)
+        if '5y' in options:
+            hbox.pack_start(button)
+
+        button = hildon.Button(fhsize, horbtn)
+        button.set_label('10y')
+        button.connect("clicked", self.show_metalgraph, '10y', win, metal_urls)
+        if '10y' in options:
+            hbox.pack_start(button)
+
+        vbox1.pack_start(hbox, False, False, 0)
+
+        self.metalgraph = gtk.Image()
+        vbox1.pack_start(self.metalgraph, True, True, 0)
+
+        parea.add_with_viewport(vbox1)
+
+        vbox.pack_start(self.metalgraph_title, False, False, 0)
+        vbox.pack_start(gtk.HSeparator(), False, False, 0)
+        vbox.pack_start(parea, True, True, 0)
+        vbox.pack_start(gtk.HSeparator(), False, False, 5)
+        #vbox.pack_start(toolbar, False, False, 0)
+
+        win.add(vbox)
+        win.show_all()
+
+        self.show_metalgraph(None, options[0], win, metal_urls)
+
+    def show_metalgraph(self, widget, option, win, metal_urls):
+        import thread
+        winprogind(win, 1)
+        thread.start_new_thread(self.get_metalgraph_data, (option, win, metal_urls))
+
+    def get_metalgraph_data(self, option, win, metal_urls):
+        for i in metal_urls:
+            if i[0] == option:
+                print i[1]
+                url = i[1]
+
+        try:
+            myimg = urllib2.urlopen(url)
+            imgdata = myimg.read()
+
+            pbl = gtk.gdk.PixbufLoader()
+            pbl.write(imgdata)
+
+
+            pixbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, 475, 235)
+            pixbuf.fill(0xffffffff)
+
+
+            pbuf = pbl.get_pixbuf()
+            pbuf = pbuf.scale_simple(475, 235, gtk.gdk.INTERP_TILES)
+
+            pixbuf.composite(pbuf, 0, 0, pixbuf.props.width, pixbuf.props.height, 0, 0, 1.0, 1.0, gtk.gdk.INTERP_HYPER, 55)
+
+
+            pbl.close()
+            self.metalgraph.set_from_pixbuf(pbuf)
+            winprogind(win, 0)
+        except:
+            logger.exception("Getting graph data: %s" % url)
+            winprogind(win, 0)
+            self.metalgraph_title.set_label('Failed to get data')
+            self.metalgraph.destroy()
+
+    def get_metal_graphs_urls(self, metal):
+        metal_urls = []
+        liveurl = 'http://www.kitco.com/images/live/%s'
+        graphurl = 'http://www.kitco.com/LFgif/%s'
+        t24url = 'http://www.kitconet.com/charts/metals/base/%s'
+        if metal == 'Gold':
+            metal_urls.append(['live', liveurl % 'gold.gif'])
+            metal_urls.append(['30d', graphurl % 'au0030lnb.gif'])
+            metal_urls.append(['60d', graphurl % 'au0060lnb.gif'])
+            metal_urls.append(['6m', graphurl % 'au0182nyb.gif'])
+            metal_urls.append(['1y', graphurl % 'au0365nyb.gif'])
+            metal_urls.append(['5y', graphurl % 'au1825nyb.gif'])
+            metal_urls.append(['10y', graphurl % 'au3650nyb.gif'])
+        elif metal == 'Silver':
+            metal_urls.append(['live', liveurl % 'silver.gif'])
+            metal_urls.append(['30d', graphurl % 'ag0030lnb.gif'])
+            metal_urls.append(['60d', graphurl % 'ag0060lnb.gif'])
+            metal_urls.append(['6m', graphurl % 'ag0182nyb.gif'])
+            metal_urls.append(['1y', graphurl % 'ag0365nyb.gif'])
+            metal_urls.append(['5y', graphurl % 'ag1825nyb.gif'])
+            metal_urls.append(['10y', graphurl % 'ag3650nyb.gif'])
+        elif metal == 'Platinum':
+            metal_urls.append(['live', liveurl % 'plati.gif'])
+            metal_urls.append(['30d', graphurl % 'pt0030lnb.gif'])
+            metal_urls.append(['60d', graphurl % 'pt0060lnb.gif'])
+            metal_urls.append(['6m', graphurl % 'pt0182nyb.gif'])
+            metal_urls.append(['1y', graphurl % 'pt0365nyb.gif'])
+            metal_urls.append(['5y', graphurl % 'pt1825nyb.gif'])
+        elif metal == 'Palladium':
+            metal_urls.append(['live', liveurl % 'plad.gif'])
+            metal_urls.append(['30d', graphurl % 'pd0030lnb.gif'])
+            metal_urls.append(['60d', graphurl % 'pd0060lnb.gif'])
+            metal_urls.append(['6m', graphurl % 'pd0182nyb.gif'])
+            metal_urls.append(['1y', graphurl % 'pd0365nyb.gif'])
+            metal_urls.append(['5y', graphurl % 'pd1825nyb.gif'])
+        elif metal == 'Rhodium':
+            metal_urls.append(['30d', graphurl % 'rh0030lnb.gif'])
+            metal_urls.append(['60d', graphurl % 'rh0060lnb.gif'])
+            metal_urls.append(['6m', graphurl % 'rh0182lnb.gif'])
+            metal_urls.append(['1y', graphurl % 'rh0365lnb.gif'])
+            metal_urls.append(['5y', graphurl % 'rh1825lnb.gif'])
+        elif metal == 'Copper':
+            metal_urls.append(['live', t24url % 't24_cp450x275.gif'])
+        elif metal == 'Nickel':
+            metal_urls.append(['live', t24url % 't24_nk450x275.gif'])
+        elif metal == 'Aluminium':
+            metal_urls.append(['live', t24url % 't24_al450x275.gif'])
+        elif metal == 'Zinc':
+            metal_urls.append(['live', t24url % 't24_zc450x275.gif'])
+        elif metal == 'Lead':
+            metal_urls.append(['live', t24url % 't24_ld450x275.gif'])
+
+        return metal_urls
+
     def _tv_columns(self, treeview):
         column = gtk.TreeViewColumn('ID', gtk.CellRendererText(), text=0)
         column.set_visible(False)
@@ -585,16 +824,13 @@ class StocksPy:
                             widgets, symbol)
 
 
-        stockiconspath = "/usr/share/icons/hicolor/48x48/hildon/"
         info_btn = hildon.Button(fhsize, horbtn)
-        img = gtk.Image()
-        img.set_from_file(stockiconspath + "general_information.png")
+        img = gtk.image_new_from_icon_name("general_information", gtk.ICON_SIZE_SMALL_TOOLBAR)
         info_btn.set_image(img)
         info_btn.connect("clicked", self.show_app_information)
 
         search_btn = hildon.Button(fhsize, horbtn)
-        img = gtk.Image()
-        img.set_from_file(stockiconspath + "general_search.png")
+        img = gtk.image_new_from_icon_name("general_search", gtk.ICON_SIZE_SMALL_TOOLBAR)
         search_btn.set_image(img)
         search_btn.connect("clicked", self.show_search_dialog)
 
@@ -617,13 +853,19 @@ class StocksPy:
         dlg = gtk.Dialog(title='Search company', parent=None, flags=0)
         dlg.set_has_separator(False)
 
+        hbox = gtk.HBox()
+
         entry = hildon.Entry(fhsize)
-        dlg.vbox.pack_start(entry, False, False, 0)
+        entry.connect("activate", self.do_search, entry, dlg)
+        hbox.pack_start(entry, True, True, 0)
 
         button = hildon.Button(fhsize, horbtn)
-        button.set_label("Search")
+        img = gtk.image_new_from_icon_name("general_search", gtk.ICON_SIZE_SMALL_TOOLBAR)
+        button.set_image(img)
         button.connect("clicked", self.do_search, entry, dlg)
-        dlg.vbox.pack_start(button, False, False, 0)
+        hbox.pack_start(button, False, False, 0)
+
+        dlg.vbox.pack_start(hbox, False, False, 0)
 
         dlg.show_all()
         dlg.run()
@@ -643,39 +885,38 @@ class StocksPy:
         if allnames == []:
             for market in marketdata.eunames:
                 for company in market:
-                    if not company in allnames:
-                        allnames.append(company)
+                    allnames.append(company)
 
             for market in marketdata.omnames:
                 for company in market:
-                    if not company in allnames:
-                        allnames.append(company)
+                    allnames.append(company)
 
             for market in marketdata.usnames:
                 for company in market:
-                    if not company in allnames:
-                        allnames.append(company)
+                    allnames.append(company)
 
         if allsymbols == []:
             for market in marketdata.eusymbols:
                 for company in market:
-                    if not company in allsymbols:
-                        allsymbols.append(company)
+                    allsymbols.append(company)
 
             for market in marketdata.omsymbols:
                 for company in market:
-                    if not company in allsymbols:
-                        allsymbols.append(company)
+                    allsymbols.append(company)
 
             for market in marketdata.ussymbols:
                 for company in market:
-                    if not company in allsymbols:
-                        allsymbols.append(company)
+                    allsymbols.append(company)
 
         new_model = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
         for i in range(len(allnames)):
             if text.lower() in allnames[i].lower():
                 niter = new_model.append()
+                #print allsymbols[i], allnames[i]
+                #FIXME: repeated companies in the results list
+                #print if allnames[i]
+                #for j in new_model:
+                #    if j[1] == allnames[i]:
                 new_model.set(niter, 0, allsymbols[i], 1, allnames[i])
 
         if len(new_model) == 0:
@@ -694,7 +935,7 @@ class StocksPy:
     def show_search_screen(self, model, text):
         window = hildon.StackableWindow()
         self.create_menu(window)
-        window.set_title("StockThis - Search for " + text)
+        window.set_title("Search for " + text)
 
         vbox = gtk.VBox()
         toolbar = self.main_toolbar(False, False, None, '', '', False)
@@ -714,7 +955,6 @@ class StocksPy:
         window.add(vbox)
         window.show_all()
 
-
     def show_app_information(self, widget):
         self.show_information_note(self.window, (
         "The data is got from Yahoo! Finance.\n"
@@ -726,18 +966,23 @@ class StocksPy:
 
     def show_portfolio_view(self, widget):
         data = settings.load_portfolio(settingsdb)
+        for item in data:
+            item.append('-')
+            item.append('-')
 
         win = hildon.StackableWindow()
         self.create_menu(win)
-        win.set_title("StockThis - Portfolio")
+        win.set_title("Portfolio")
 
         vbox = gtk.VBox()
 
         parea = hildon.PannableArea()
+        parea.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
         tv = hildon.GtkTreeView(ui_normal)
         tv.set_headers_visible(True)
         self.portfolio_model = self._create_portfolio_model(data)
-        tv.connect("row-activated", self.show_quotes_view, self.portfolio_model, True)
+        self.quotes_id = tv.connect("row-activated", self.show_quotes_view,
+                                    self.portfolio_model, True)
         tv.set_model(self.portfolio_model)
         self._tv_portfolio_columns(tv)
         parea.add(tv)
@@ -750,7 +995,7 @@ class StocksPy:
 
         button = hildon.Button(fhsize, horbtn)
         button.set_title("Add manually")
-        button.connect("clicked", self.add_item_dlg)
+        button.connect("clicked", self.add_item_dlg, None, None)
         hbox.pack_start(button, True, True, 0)
 
         button = hildon.Button(fhsize, horbtn)
@@ -758,13 +1003,55 @@ class StocksPy:
         button.connect("clicked", self.remove_item)
         hbox.pack_start(button, True, True, 0)
 
+        button = hildon.CheckButton(fhsize)
+        button.set_label("Edit")
+
+        button.connect("toggled", self.edit_toggled, tv)
+        hbox.pack_start(button, True, True, 0)
+
+
         vbox.pack_start(parea, True, True, 0)
         vbox.pack_start(hbox, False, False, 0)
         win.add(vbox)
         win.show_all()
 
-    def add_item_dlg(self, widget):
-        dlg = gtk.Dialog(title='Add to portfolio', parent=None, flags=0)
+    def edit_toggled(self, widget, tv):
+        if widget.get_active():
+            tv.disconnect(self.quotes_id)
+            self.edit_id = tv.connect("row-activated",
+                            self.edit_portfolio_item, self.portfolio_model)
+        else:
+            tv.disconnect(self.edit_id)
+            self.quotes_id = tv.connect("row-activated",
+                            self.show_quotes_view, self.portfolio_model, True)
+
+    def edit_portfolio_item(self, widget, path, column, model):
+        seliter = model.get_iter(path)
+
+        symbol = model[path][0]
+        name = model[path][1]
+        shares = model[path][2]
+
+        data = [shares, symbol, name]
+        self.add_item_dlg(widget, data, seliter)
+
+    def add_item_dlg(self, widget, data, seliter):
+        if data:
+            shares = data[0]
+            symbol = data[1]
+            name = data[2]
+            title = "Edit item from portfolio"
+            btntext = "Edit"
+            edit = True
+        else:
+            shares = "0"
+            symbol = ""
+            name = ""
+            title = "Add to portfolio"
+            btntext = "Add"
+            edit = False
+
+        dlg = gtk.Dialog(title=title, parent=None, flags=0)
         dlg.set_has_separator(False)
 
         button1 = hildon.PickerButton(fhsize, horbtn)
@@ -773,37 +1060,53 @@ class StocksPy:
         selector = self.create_selector(data, True)
         button1.set_selector(selector)
         button1.set_title("Your shares")
-        button1.set_value("0")
+        button1.set_value(shares)
         dlg.vbox.pack_start(button1, False, False, 0)
 
         entry1 = hildon.Entry(fhsize)
+        entry1.set_text(name)
+        entry2 = hildon.Entry(fhsize)
+        entry2.set_text(symbol)
+
         entry1.set_placeholder("Name")
+        entry1.connect("activate", self.add_item, dlg, button1, entry1,
+                        entry2, edit, seliter)
         dlg.vbox.pack_start(entry1, False, False, 0)
 
-        entry2 = hildon.Entry(fhsize)
+        entry2.connect("activate", self.add_item, dlg, button1, entry1,
+                        entry2, edit, seliter)
         entry2.set_placeholder("Yahoo Finance symbol")
         dlg.vbox.pack_start(entry2, False, False, 0)
 
         button = hildon.Button(fhsize, horbtn)
-        button.set_label("Add")
-        button.connect("clicked", self.add_item, dlg, button1, entry1, entry2)
+        button.set_label(btntext)
+        button.connect("clicked", self.add_item, dlg, button1, entry1,
+                        entry2, edit, seliter)
         dlg.vbox.pack_start(button, False, False, 0)
 
         dlg.show_all()
         dlg.run()
         dlg.destroy()
 
-
-    def add_item(self, widget, dlg, button, entry1, entry2):
+    def add_item(self, widget, dlg, button, entry1, entry2, edit, seliter):
         symbol = entry2.get_text()
         name = entry1.get_text()
         shares = button.get_value()
 
+        if name == '' or symbol == '':
+            self.show_info_banner(widget, "Must add the name and symbol")
+            return
+
         self.add_to_portfolio(widget, button, symbol, name)
         dlg.destroy()
 
-        niter = self.portfolio_model.append()
-        self.portfolio_model.set(niter, 0, symbol, 1, name, 2, shares, 3, "-")
+        if edit:
+            self.portfolio_model.set(seliter, 0, symbol, 1, name, 2, shares,
+                                       3, "-", 4, "-", 5, "-", 6, "green")
+        else:
+            niter = self.portfolio_model.append()
+            self.portfolio_model.set(niter, 0, symbol, 1, name, 2, shares,
+                                    3, "-", 4, "-", 5, "-", 6, "green")
 
     def remove_item(self, widget):
         win = hildon.StackableWindow()
@@ -865,33 +1168,54 @@ class StocksPy:
 
     def refresh_portfolio(self, widget, tv, win):
         data = settings.load_portfolio(settingsdb)
+        for item in data:
+            item.append('-')
+            item.append('-')
         import thread
         winprogind(win, 1)
         thread.start_new_thread(self._do_refresh_portfolio, (data, tv, win))
 
     def _do_refresh_portfolio(self, data, tv, win):
+        print data
         for item in data:
-            item[3] = self.get_price(item[0])
+            item[3], item[4] = self.get_portfolio_data(item[0])
+            try:
+                ch_percent = \
+                        100.0 * float(item[4])/(float(item[3]) - \
+                        float(item[4]))
+            except ValueError:
+                ch_percent = 0.0
+
+            item[5] = '%6.2f %%' % ch_percent
 
+
+        print data
         self.portfolio_model = self._create_portfolio_model(data)
         tv.set_model(self.portfolio_model)
         winprogind(win, 0)
 
-    def get_price(self, symbol):
+    def get_portfolio_data(self, symbol):
         from ystockquote import ystockquote as yt
         try:
-            price = yt.get_price(symbol)
-            return price
+            data = yt.get_all(symbol)
+            return data['price'], data['change']
         except:
-            logger.exception("Getting price from Yahoo")
-            return "N/A"
+            logger.exception("Getting price from Yahoo: %s" % symbol)
+            return "-", "-"
 
     def _create_portfolio_model(self, data):
         lstore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING,
-                                gobject.TYPE_STRING, gobject.TYPE_STRING)
+                                gobject.TYPE_STRING, gobject.TYPE_STRING,
+                                gobject.TYPE_STRING, gobject.TYPE_STRING,
+                                gobject.TYPE_STRING)
         for item in data:
             iter = lstore.append()
-            lstore.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3])
+            if '+' in item[4]:
+                color = 'green'
+            else:
+                color = 'red'
+            lstore.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3],
+                        4, item[4], 5, item[5], 6, color)
         return lstore
 
     def _tv_portfolio_columns(self, treeview):
@@ -909,6 +1233,15 @@ class StocksPy:
         column = gtk.TreeViewColumn('Price', gtk.CellRendererText(), text=3)
         treeview.append_column(column)
 
+        column = gtk.TreeViewColumn('Change', gtk.CellRendererText(), text=4)
+        treeview.append_column(column)
+
+
+        renderer = gtk.CellRendererText()
+        renderer.set_property("foreground-set", True)
+        column = gtk.TreeViewColumn('%', renderer, text=5, foreground=6)
+        treeview.append_column(column)
+
     def show_confirmation(self, window, msg):
         dialog = hildon.hildon_note_new_confirmation(window, msg)
         dialog.show_all()
@@ -940,142 +1273,193 @@ class StocksPy:
 class About:
 
     def __init__(self, widget):
-        self.abdialog = gtk.Dialog(title='About', parent=None, flags=0)
-        self.abdialog.set_has_separator(False)
-        self.abdialog.set_size_request(-1, 400)
+        self.abdialog = gtk.Dialog()
+        self.abdialog.set_title("About StockThis")
 
-        self.info_lb = gtk.Label()
-        self.info_lb.set_line_wrap(True)
+        notebook = gtk.Notebook()
+        notebook.set_show_tabs(False)
+        notebook.set_scrollable(False)
+        notebook.set_show_border(False)
 
-        self.id = False
+        # Description page #
+        vbox = gtk.VBox()
 
-        hbox1 = gtk.HBox()
+        label = gtk.Label()
+        label.set_markup("<b><big>StockThis %s</big></b>" % VERSION)
+        vbox.pack_start(label, True, True, 0)
 
-        button = hildon.Button(fhsize, horbtn)
-        button.set_title('Description')
-        button.connect("clicked", self.show_info, 'description')
-        hbox1.pack_start(button, True, True, 0)
+        label = gtk.Label("Stocks application with big database")
+        vbox.pack_start(label, True, True, 0)
 
-        button = hildon.Button(fhsize, horbtn)
-        button.set_title('Credits')
-        button.connect("clicked", self.show_info, 'credits')
-        hbox1.pack_start(button, True, True, 0)
+        label = gtk.Label("GNU General Public License")
+        vbox.pack_start(label, True, True, 0)
+
+        url = "http://stockthis.garage.maemo.org"
+        webbtn = gtk.LinkButton(url, "Web")
+        vbox.pack_start(webbtn, True, True, 0)
+        gtk.link_button_set_uri_hook(self.launch_browser)
+
+        notebook.append_page(vbox, gtk.Label())
+
+        # Credits page #
+        vbox = gtk.VBox()
+        textview = hildon.TextView()
+        textview.set_cursor_visible(False)
+        textview.set_wrap_mode(gtk.WRAP_WORD)
+        text = "Written by Daniel Martin Yerga (dyerga@gmail.com)"
+        textview.get_buffer().set_text(text)
+
+        parea = hildon.PannableArea()
+        parea.add(textview)
+
+        vbox.pack_start(parea, True, True, 0)
+        notebook.append_page(vbox, gtk.Label())
+
+
+        # Donate page #
+        vbox = gtk.VBox()
+
+        textview = hildon.TextView()
+        textview.set_cursor_visible(False)
+        textview.set_wrap_mode(gtk.WRAP_WORD)
+        text = """StockThis is a free software application.
+Developing good software takes time and hard work.
+StockThis's author develops the program in his spare time.
+If you like the program and it's helpful, consider donating a small amount of money.
+Donations are a great incentive and help the developer feel that the hard work is appreciated.
+"""
+        textview.get_buffer().set_text(text)
+
+        parea = hildon.PannableArea()
+        parea.add(textview)
 
         button = hildon.Button(fhsize, horbtn)
-        button.set_title('License')
-        button.connect("clicked", self.show_info, 'license')
-        hbox1.pack_start(button, True, True, 0)
+        button.set_title("Make donation")
+        url = "http://stockthis.garage.maemo.org/donate.html"
+        button.connect("clicked", self.launch_browser, url)
+        vbox.pack_start(button, False, False, 0)
+        vbox.pack_start(parea, True, True, 0)
+
+        notebook.append_page(vbox, gtk.Label())
+
+        # Report page #
+        vbox = gtk.VBox()
+
+        textview = hildon.TextView()
+        textview.set_cursor_visible(False)
+        textview.set_wrap_mode(gtk.WRAP_WORD)
+        text = """StockThis is being improved thanks to bug reports that users have submitted. The author appreciates these reports.
+If the application is having an error when you're using it, you have two choices to report this error:
+1) Send the log from the button above (if there's an error in the log).
+2) Press the button and read how to report a bug."""
+        textview.get_buffer().set_text(text)
+
+        parea = hildon.PannableArea()
+        parea.add(textview)
+
+        hbox = gtk.HBox()
+        hbox.set_homogeneous(True)
 
         button = hildon.Button(fhsize, horbtn)
-        button.set_title('Donate')
-        button.connect("clicked", self.show_info, 'donate')
-        hbox1.pack_start(button, True, True, 0)
+        button.set_title("Report error")
+        url = "http://stockthis.garage.maemo.org/reporting.html"
+        button.connect("clicked", self.launch_browser, url)
+        hbox.pack_start(button, True, True, 0)
 
         button = hildon.Button(fhsize, horbtn)
-        button.set_title('Report ')
-        button.connect("clicked", self.show_info, 'report')
-        hbox1.pack_start(button, True, True, 0)
+        button.set_title("Log")
+        button.connect("clicked", self.on_show_log)
+        hbox.pack_start(button, True, True, 0)
+
+        vbox.pack_start(hbox, False, False, 0)
+        vbox.pack_start(parea, True, True, 0)
+
+        notebook.append_page(vbox, gtk.Label())
+
+        # Rate page #
+        vbox = gtk.VBox()
+
+        textview = hildon.TextView()
+        textview.set_cursor_visible(False)
+        textview.set_wrap_mode(gtk.WRAP_WORD)
+        text = """The downloads section in maemo.org has a nice system where you can rate applications.
+If you consider StockThis a good application (or a bad one too), you could rate it in maemo.org site."""
+        textview.get_buffer().set_text(text)
 
         button = hildon.Button(fhsize, horbtn)
-        button.set_title(' Rate ')
-        button.connect("clicked", self.show_info, 'vote')
-        hbox1.pack_start(button, True, True, 0)
+        button.set_title("Rate StockThis")
+        url = "http://maemo.org/downloads/product/Maemo5/stockthis"
+        button.connect("clicked", self.launch_browser, url)
+        image = gtk.Image()
+        image.set_from_file(imgdir + "maemoorg.png")
+        vbox.pack_start(button, False, False, 0)
+        vbox.pack_start(image, False, False, 5)
+        vbox.pack_start(textview, True, True, 0)
+
+        notebook.append_page(vbox, gtk.Label())
+
+        # Buttons #
+        self.abdialog.vbox.pack_start(notebook, True, True, 0)
+
+        hbox = gtk.HBox()
+
+        descbutton = hildon.GtkRadioButton(fhsize)
+        descbutton.set_mode(False)
+        descbutton.set_active(True)
+        descbutton.set_label('Description')
+        descbutton.connect("toggled", self.change_tab, notebook, 0)
+        hbox.pack_start(descbutton, True, True, 0)
+
+        button = hildon.GtkRadioButton(fhsize)
+        button.set_mode(False)
+        button.set_active(True)
+        button.set_label('Credits')
+        button.set_group(descbutton)
+        button.connect("toggled", self.change_tab, notebook, 1)
+        hbox.pack_start(button, True, True, 0)
+
+        button = hildon.GtkRadioButton(fhsize)
+        button.set_mode(False)
+        button.set_label('Donate')
+        button.set_group(descbutton)
+        button.connect("clicked", self.change_tab, notebook, 2)
+        hbox.pack_start(button, True, True, 0)
 
-        self.action_btn = hildon.Button(fhsize, horbtn)
-        self.image = gtk.Image()
+        button = hildon.GtkRadioButton(fhsize)
+        button.set_mode(False)
+        button.set_label('Report')
+        button.set_group(descbutton)
+        button.connect("clicked", self.change_tab, notebook, 3)
+        hbox.pack_start(button, True, True, 0)
 
-        self.show_info(None, 'description')
+        button = hildon.GtkRadioButton(fhsize)
+        button.set_mode(False)
+        button.set_label('Rate')
+        button.set_group(descbutton)
+        button.connect("clicked", self.change_tab, notebook, 4)
+        hbox.pack_start(button, True, True, 0)
 
-        self.abdialog.vbox.pack_start(self.action_btn, False, False, 0)
-        self.abdialog.vbox.pack_start(self.image, False, False, 5)
-        self.abdialog.vbox.pack_start(self.info_lb, True, True, 0)
-        self.abdialog.vbox.pack_start(hbox1, False, False, 0)
+        self.abdialog.vbox.pack_start(hbox, False, False, 0)
 
         self.abdialog.show_all()
-        self.action_btn.hide()
-        self.image.hide()
         self.abdialog.run()
         self.abdialog.destroy()
 
-    def do_action(self, widget, action):
-        import dbus
-
-        self.abdialog.destroy()
+    def change_tab(self, widget, notebook, number):
+        notebook.set_current_page(number)
 
+    def launch_browser(self, widget, url):
+        import dbus
         bus = dbus.SystemBus()
         proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
         iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
 
-        if action == "donate":
-            url = "http://stockthis.garage.maemo.org/donate.html"
-        elif action == "report":
-            url = "http://stockthis.garage.maemo.org/reporting.html"
-        elif action == "vote":
-            url = "http://maemo.org/downloads/product/stockthis"
-
-        iface.load_url(url)
-
-    def show_info(self, widget, kind):
-        if kind == 'license':
-            self.action_btn.hide()
-            self.image.hide()
-            info = """<small><b>StockThis</b> is free software. It's using a GPL version 2 license or at your election any later version.
-
-Logo by Daniel Martin Yerga.
-</small>"""
-        elif kind == 'credits':
-            self.action_btn.hide()
-            self.image.hide()
-            info = """<small><b>Written by</b> Daniel Martin Yerga (dyerga@gmail.com)
-
-<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>"""
-        elif kind == 'description':
-            self.action_btn.hide()
-            self.image.hide()
-            info = """<b><big>StockThis 0.3</big></b>
-
-<i>StockThis is a stocks application for Maemo</i>
-
-<b>Web Page</b>:
-stockthis.garage.maemo.org"""
-
-        elif kind == 'donate':
-            self.action_btn.show()
-            self.image.hide()
-            self.action_btn.set_title('Make donation')
-            if self.id:
-                self.action_btn.disconnect(self.id)
-            self.id = self.action_btn.connect("clicked", self.do_action, "donate")
-            info = """<small><b>StockThis</b> is a free (and gratis) software application.
-Developing good software takes time and hard work.
+        self.abdialog.destroy()
 
-<b>StockThis's author</b> develops the program in his spare time.
-If you like the program and it's helpful, consider donating a small amount of money.
-Donations are a great incentive and help the developer feel that the hard work is appreciated.</small>
-"""
-        elif kind == 'report':
-            self.action_btn.show()
-            self.image.hide()
-            self.action_btn.set_title('Report bug')
-            if self.id:
-                self.action_btn.disconnect(self.id)
-            self.id = self.action_btn.connect("clicked", self.do_action, "report")
-            info = """<small>StockThis is being improved thanks to bug reports that users have submitted. The author appreciates these reports.
-If the application is raising an error when you're using it, you have two choices to report this error:
-1) Send the log from the application menu (if there's an error in the log).
-2) Press the button and write a bug report with as much information as possible.</small>"""
-        elif kind == 'vote':
-            self.action_btn.show()
-            self.image.show()
-            self.image.set_from_file(imgdir + "maemoorg.png")
-            self.action_btn.set_title('Rate StockThis')
-            if self.id:
-                self.action_btn.disconnect(self.id)
-            self.id = self.action_btn.connect("clicked", self.do_action, "vote")
-            info = """<small>The downloads section in maemo.org has a nice system where you can rate applications.
-If you consider StockThis a good application (or a bad one too), you could rate it in maemo.org site.</small>"""
-
-        self.info_lb.set_markup(info)
+        iface.open_new_window(url)
+
+    def on_show_log(self, widget):
+        Log(widget, logfile)
 
 
 class Log: