gtktools.py :  » Chart-Report » Matplotlib » matplotlib-0.99.1.1 » lib » mpl_toolkits » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Chart Report » Matplotlib 
Matplotlib » matplotlib 0.99.1.1 » lib » mpl_toolkits » gtktools.py
"""

Some gtk specific tools and widgets

   * rec2gtk          : put record array in GTK treeview - requires gtk

Example usage

    import matplotlib.mlab as mlab
    import mpl_toolkits.gtktools as gtktools

    r = mlab.csv2rec('somefile.csv', checkrows=0)

    formatd = dict(
        weight = mlab.FormatFloat(2),
        change = mlab.FormatPercent(2),
        cost   = mlab.FormatThousands(2),
        )


    exceltools.rec2excel(r, 'test.xls', formatd=formatd)
    mlab.rec2csv(r, 'test.csv', formatd=formatd)


    import gtk
    scroll = gtktools.rec2gtk(r, formatd=formatd)
    win = gtk.Window()
    win.set_size_request(600,800)
    win.add(scroll)
    win.show_all()
    gtk.main()

"""
import copy
import gtk, gobject
import numpy as npy
import matplotlib.cbook as cbook
import matplotlib.mlab as mlab


def error_message(msg, parent=None, title=None):
    """
    create an error message dialog with string msg.  Optionally set
    the parent widget and dialog title
    """

    dialog = gtk.MessageDialog(
        parent         = None,
        type           = gtk.MESSAGE_ERROR,
        buttons        = gtk.BUTTONS_OK,
        message_format = msg)
    if parent is not None:
        dialog.set_transient_for(parent)
    if title is not None:
        dialog.set_title(title)
    else:
        dialog.set_title('Error!')
    dialog.show()
    dialog.run()
    dialog.destroy()
    return None

def simple_message(msg, parent=None, title=None):
    """
    create a simple message dialog with string msg.  Optionally set
    the parent widget and dialog title
    """
    dialog = gtk.MessageDialog(
        parent         = None,
        type           = gtk.MESSAGE_INFO,
        buttons        = gtk.BUTTONS_OK,
        message_format = msg)
    if parent is not None:
        dialog.set_transient_for(parent)
    if title is not None:
        dialog.set_title(title)
    dialog.show()
    dialog.run()
    dialog.destroy()
    return None


def gtkformat_factory(format, colnum):
    """
    copy the format, perform any overrides, and attach an gtk style attrs


    xalign = 0.
    cell = None

    """
    if format is None: return None
    format = copy.copy(format)
    format.xalign = 0.
    format.cell = None

    def negative_red_cell(column, cell, model, thisiter):
        val = model.get_value(thisiter, colnum)
        try: val = float(val)
        except: cell.set_property('foreground', 'black')
        else:
            if val<0:
                cell.set_property('foreground', 'red')
            else:
                cell.set_property('foreground', 'black')


    if isinstance(format, mlab.FormatFloat) or isinstance(format, mlab.FormatInt):
        format.cell = negative_red_cell
        format.xalign = 1.
    elif isinstance(format, mlab.FormatDate):
        format.xalign = 1.
    return format



class SortedStringsScrolledWindow(gtk.ScrolledWindow):
    """
    A simple treeview/liststore assuming all columns are strings.
    Supports ascending/descending sort by clicking on column header
    """

    def __init__(self, colheaders, formatterd=None):
        """
        xalignd if not None, is a dict mapping col header to xalignent (default 1)

        formatterd if not None, is a dict mapping col header to a ColumnFormatter
        """


        gtk.ScrolledWindow.__init__(self)
        self.colheaders = colheaders
        self.seq = None # not initialized with accts
        self.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        self.set_policy(gtk.POLICY_AUTOMATIC,
                        gtk.POLICY_AUTOMATIC)

        types = [gobject.TYPE_STRING] * len(colheaders)
        model = self.model = gtk.ListStore(*types)


        treeview = gtk.TreeView(self.model)
        treeview.show()
        treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
        treeview.set_rules_hint(True)


        class Clicked:
            def __init__(self, parent, i):
                self.parent = parent
                self.i = i
                self.num = 0

            def __call__(self, column):
                ind = []
                dsu = []
                for rownum, thisiter in enumerate(self.parent.iters):
                    val = model.get_value(thisiter, self.i)
                    try: val = float(val.strip().rstrip('%'))
                    except ValueError: pass
                    if npy.isnan(val): val = npy.inf # force nan to sort uniquely
                    dsu.append((val, rownum))
                dsu.sort()
                if not self.num%2: dsu.reverse()

                vals, otherind = zip(*dsu)
                ind.extend(otherind)

                self.parent.model.reorder(ind)
                newiters = []
                for i in ind:
                    newiters.append(self.parent.iters[i])
                self.parent.iters = newiters[:]
                for i, thisiter in enumerate(self.parent.iters):
                    key = tuple([self.parent.model.get_value(thisiter, j) for j in range(len(colheaders))])
                    self.parent.rownumd[i] = key

                self.num+=1


        if formatterd is None:
            formatterd = dict()

        formatterd = formatterd.copy()

        for i, header in enumerate(colheaders):
            renderer = gtk.CellRendererText()
            if header not in formatterd:
                formatterd[header] = ColumnFormatter()
            formatter = formatterd[header]

            column = gtk.TreeViewColumn(header, renderer, text=i)
            renderer.set_property('xalign', formatter.xalign)
            renderer.set_property('editable', True)
            renderer.connect("edited", self.position_edited, i)
            column.connect('clicked', Clicked(self, i))
            column.set_property('clickable', True)

            if formatter.cell is not None:
                column.set_cell_data_func(renderer, formatter.cell)

            treeview.append_column(column)



        self.formatterd = formatterd
        self.lastcol = column
        self.add(treeview)
        self.treeview = treeview
        self.clear()

    def position_edited(self, renderer, path, newtext, position):
        #print path, position
        self.model[path][position] = newtext

    def clear(self):
        self.iterd = dict()
        self.iters = []        # an ordered list of iters
        self.rownumd = dict()  # a map from rownum -> symbol
        self.model.clear()
        self.datad = dict()


    def flat(self, row):
        seq = []
        for i,val in enumerate(row):
            formatter = self.formatterd.get(self.colheaders[i])
            seq.extend([i,formatter.tostr(val)])
        return seq

    def __delete_selected(self, *unused): # untested


        keyd = dict([(thisiter, key) for key, thisiter in self.iterd.values()])
        for row in self.get_selected():
            key = tuple(row)
            thisiter = self.iterd[key]
            self.model.remove(thisiter)
            del self.datad[key]
            del self.iterd[key]
            self.iters.remove(thisiter)

        for i, thisiter in enumerate(self.iters):
            self.rownumd[i] = keyd[thisiter]



    def delete_row(self, row):
        key = tuple(row)
        thisiter = self.iterd[key]
        self.model.remove(thisiter)


        del self.datad[key]
        del self.iterd[key]
        self.rownumd[len(self.iters)] = key
        self.iters.remove(thisiter)

        for rownum, thiskey in self.rownumd.items():
            if thiskey==key: del self.rownumd[rownum]

    def add_row(self, row):
        thisiter = self.model.append()
        self.model.set(thisiter, *self.flat(row))
        key = tuple(row)
        self.datad[key] = row
        self.iterd[key] = thisiter
        self.rownumd[len(self.iters)] = key
        self.iters.append(thisiter)

    def update_row(self, rownum, newrow):
        key = self.rownumd[rownum]
        thisiter = self.iterd[key]
        newkey = tuple(newrow)

        self.rownumd[rownum] = newkey
        del self.datad[key]
        del self.iterd[key]
        self.datad[newkey] = newrow
        self.iterd[newkey] = thisiter


        self.model.set(thisiter, *self.flat(newrow))

    def get_row(self, rownum):
        key = self.rownumd[rownum]
        return self.datad[key]

    def get_selected(self):
        selected = []
        def foreach(model, path, iter, selected):
            selected.append(model.get_value(iter, 0))

        self.treeview.get_selection().selected_foreach(foreach, selected)
        return selected



def rec2gtk(r, formatd=None, rownum=0, autowin=True):
    """
    formatd is a dictionary mapping dtype name -> mlab.Format instances

    This function creates a SortedStringsScrolledWindow (derived
    from gtk.ScrolledWindow) and returns it.  if autowin is True,
    a gtk.Window is created, attached to the
    SortedStringsScrolledWindow instance, shown and returned.  If
    autowin=False, the caller is responsible for adding the
    SortedStringsScrolledWindow instance to a gtk widget and
    showing it.
    """



    if formatd is None:
        formatd = dict()

    formats = []
    for i, name in enumerate(r.dtype.names):
        dt = r.dtype[name]
        format = formatd.get(name)
        if format is None:
            format = mlab.defaultformatd.get(dt.type, mlab.FormatObj())
        #print 'gtk fmt factory', i, name, format, type(format)
        format = gtkformat_factory(format, i)
        formatd[name] = format


    colheaders = r.dtype.names
    scroll = SortedStringsScrolledWindow(colheaders, formatd)

    ind = npy.arange(len(r.dtype.names))
    for row in r:
        scroll.add_row(row)


    if autowin:
        win = gtk.Window()
        win.set_default_size(800,600)
        #win.set_geometry_hints(scroll)
        win.add(scroll)
        win.show_all()
        scroll.win = win

    return scroll


class RecListStore(gtk.ListStore):
    """
    A liststore as a model of an editable record array.

    attributes:

     * r - the record array with the edited values

     * formatd - the list of mlab.FormatObj instances, with gtk attachments

     * stringd - a dict mapping dtype names to a list of valid strings for the combo drop downs

     * callbacks - a matplotlib.cbook.CallbackRegistry.  Connect to the cell_changed with

        def mycallback(liststore, rownum, colname, oldval, newval):
           print 'verify: old=%s, new=%s, rec=%s'%(oldval, newval, liststore.r[rownum][colname])

        cid = liststore.callbacks.connect('cell_changed', mycallback)

        """
    def __init__(self, r, formatd=None, stringd=None):
        """
        r is a numpy record array

        formatd is a dict mapping dtype name to mlab.FormatObj instances

        stringd, if not None, is a dict mapping dtype names to a list of
        valid strings for a combo drop down editor
        """

        if stringd is None:
            stringd = dict()

        if formatd is None:
            formatd = mlab.get_formatd(r)

        self.stringd = stringd
        self.callbacks = cbook.CallbackRegistry(['cell_changed'])

        self.r = r

        self.headers = r.dtype.names
        self.formats = [gtkformat_factory(formatd.get(name, mlab.FormatObj()),i)
                        for i,name in enumerate(self.headers)]

        # use the gtk attached versions
        self.formatd = formatd = dict(zip(self.headers, self.formats))
        types = []
        for format in self.formats:
            if isinstance(format, mlab.FormatBool):
                types.append(gobject.TYPE_BOOLEAN)
            else:
                types.append(gobject.TYPE_STRING)

        self.combod = dict()
        if len(stringd):
            types.extend([gobject.TYPE_INT]*len(stringd))

            keys = stringd.keys()
            keys.sort()

            valid = set(r.dtype.names)
            for ikey, key in enumerate(keys):
                assert(key in valid)
                combostore = gtk.ListStore(gobject.TYPE_STRING)
                for s in stringd[key]:
                    combostore.append([s])
                self.combod[key] = combostore, len(self.headers)+ikey


        gtk.ListStore.__init__(self, *types)

        for row in r:
            vals = []
            for formatter, val in zip(self.formats, row):
                if isinstance(formatter, mlab.FormatBool):
                    vals.append(val)
                else:
                    vals.append(formatter.tostr(val))
            if len(stringd):
                # todo, get correct index here?
                vals.extend([0]*len(stringd))
            self.append(vals)


    def position_edited(self, renderer, path, newtext, position):

        position = int(position)
        format = self.formats[position]

        rownum = int(path)
        colname = self.headers[position]
        oldval = self.r[rownum][colname]
        try: newval = format.fromstr(newtext)
        except ValueError:
            msg = cbook.exception_to_str('Error converting "%s"'%newtext)
            error_message(msg, title='Error')
            return
        self.r[rownum][colname] = newval

        self[path][position] = format.tostr(newval)


        self.callbacks.process('cell_changed', self, rownum, colname, oldval, newval)

    def position_toggled(self, cellrenderer, path, position):
        position = int(position)
        format = self.formats[position]

        newval = not cellrenderer.get_active()

        rownum = int(path)
        colname = self.headers[position]
        oldval = self.r[rownum][colname]
        self.r[rownum][colname] = newval

        self[path][position] = newval

        self.callbacks.process('cell_changed', self, rownum, colname, oldval, newval)





class RecTreeView(gtk.TreeView):
    """
    An editable tree view widget for record arrays
    """
    def __init__(self, recliststore, constant=None):
        """
        build a gtk.TreeView to edit a RecListStore

        constant, if not None, is a list of dtype names which are not editable
        """
        self.recliststore = recliststore

        gtk.TreeView.__init__(self, recliststore)

        combostrings = set(recliststore.stringd.keys())


        if constant is None:
            constant = []

        constant = set(constant)

        for i, header in enumerate(recliststore.headers):
            formatter = recliststore.formatd[header]
            coltype =  recliststore.get_column_type(i)

            if coltype==gobject.TYPE_BOOLEAN:
                renderer = gtk.CellRendererToggle()
                if header not in constant:
                    renderer.connect("toggled", recliststore.position_toggled, i)
                    renderer.set_property('activatable', True)

            elif header in combostrings:
                 renderer = gtk.CellRendererCombo()
                 renderer.connect("edited", recliststore.position_edited, i)
                 combostore, listind = recliststore.combod[header]
                 renderer.set_property("model", combostore)
                 renderer.set_property('editable', True)
            else:
                renderer = gtk.CellRendererText()
                if header not in constant:
                    renderer.connect("edited", recliststore.position_edited, i)
                    renderer.set_property('editable', True)


                if formatter is not None:
                    renderer.set_property('xalign', formatter.xalign)



            tvcol = gtk.TreeViewColumn(header)
            self.append_column(tvcol)
            tvcol.pack_start(renderer, True)

            if coltype == gobject.TYPE_STRING:
                tvcol.add_attribute(renderer, 'text', i)
                if header in combostrings:
                    combostore, listind = recliststore.combod[header]
                    tvcol.add_attribute(renderer, 'text-column', listind)
            elif coltype == gobject.TYPE_BOOLEAN:
                tvcol.add_attribute(renderer, 'active', i)


            if formatter is not None and formatter.cell is not None:
                tvcol.set_cell_data_func(renderer, formatter.cell)




        self.connect("button-release-event", self.on_selection_changed)
        #self.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_BOTH)

        self.get_selection().set_mode(gtk.SELECTION_BROWSE)
        self.get_selection().set_select_function(self.on_select)


    def on_select(self, *args):
        return False

    def on_selection_changed(self, *args):
        (path, col) = self.get_cursor()
        ren = col.get_cell_renderers()[0]
        if isinstance(ren, gtk.CellRendererText):
            self.set_cursor_on_cell(path, col, ren, start_editing=True)

def edit_recarray(r, formatd=None, stringd=None, constant=None, autowin=True):
    """
    create a RecListStore and RecTreeView and return them.

    If autowin is True, create a gtk.Window, insert the treeview into
    it, and return it (return value will be (liststore, treeview, win)

    See RecListStore and RecTreeView for a description of the keyword args
    """

    liststore = RecListStore(r, formatd=formatd, stringd=stringd)
    treeview = RecTreeView(liststore, constant=constant)

    if autowin:
        win = gtk.Window()
        win.add(treeview)
        win.show_all()
        return liststore, treeview, win
    else:
        return liststore, treeview




if __name__=='__main__':

    import datetime
    import gtk
    import numpy as np
    import matplotlib.mlab as mlab
    N = 10
    today = datetime.date.today()
    dates = [today+datetime.timedelta(days=i) for i in range(N)] # datetimes
    weekdays = [d.strftime('%a') for d in dates]                 # strings
    gains = np.random.randn(N)                                   # floats
    prices = np.random.rand(N)*1e7                               # big numbers
    up = gains>0                                                 # bools
    clientid = range(N)                                          # ints

    r = np.rec.fromarrays([clientid, dates, weekdays, gains, prices, up],
                          names='clientid,date,weekdays,gains,prices,up')

    # some custom formatters
    formatd = mlab.get_formatd(r)
    formatd['date'] = mlab.FormatDate('%Y-%m-%d')
    formatd['prices'] = mlab.FormatMillions(precision=1)
    formatd['gain'] = mlab.FormatPercent(precision=2)

    # use a drop down combo for weekdays
    stringd = dict(weekdays=['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'])
    constant = ['clientid']   # block editing of this field


    liststore = RecListStore(r, formatd=formatd, stringd=stringd)
    treeview = RecTreeView(liststore, constant=constant)

    def mycallback(liststore, rownum, colname, oldval, newval):
        print 'verify: old=%s, new=%s, rec=%s'%(oldval, newval, liststore.r[rownum][colname])

    liststore.callbacks.connect('cell_changed', mycallback)

    win = gtk.Window()
    win.set_title('with full customization')
    win.add(treeview)
    win.show_all()

    # or you just use the defaults
    r2 = r.copy()
    ls, tv, win2 = edit_recarray(r2)
    win2.set_title('with all defaults')

    gtk.main()

www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.