# -*-mode: tcl; fill-column: 75; tab-width: 8; coding: iso-latin-1-unix -*-
#
#	$Id$
#

"""Wrapper functions for the TkTable widget.
"""

import Tkinter

__version__ = "0.1"


class ArrayVar:
    _names = {}
    def __init__(self, master=Tkinter._default_master):
        self._tk = master.tk
        self._name = "PYARRAY_%08X" % id(self)
        self._names[self._name] = self
        self._tk.eval("array set %s [ ]" % self._name)

    def __del__(self):
        del self._arrays[self._name]
        self._tk.eval("array unset %s" % self._name)

    def __str__(self):
        return self._name

    def __repr__(self):
        return '<%s @ 0x%08X>' % (self.__class__.__name__, id(self))

    def _coords(self, coords):
        if type(coords) is not tuple:
            ValueError("Bad item coordinates %s: must be <row>,<col>" % str(coords))
        return int(coords[0]), int(coords[1])

    def __getitem__(self, coords):
        row, col = self._coords(coords)
        return self._tk.eval("return $%s(%d,%d)" % (self._name, row, col))

    def __setitem__(self, coords, value):
        row, col = self._coords(coords)
        self._tk.eval("set %s(%d,%d) {%s}" % (self._name, row, col, value))
        

class Table(Tkinter.Widget):
    """Table(master, ...) -> widget

    Create a tk table widget.
    """
    def __init__(self, master, cnf={}, **kw):
        # Load dynamic extension.
        master.tk.eval("package require Tktable")
        
        # Validate variable config option.
        try:
            if not isinstance(cnf["variable"], ArrayVar):
                raise ValueError("-variable argument must by an ArrayVar")
            cnf["variable"] = str(cnf["variable"])
        
        except KeyError: pass
        except ValueError: raise

        try:
            if not isinstance(kw["variable"], ArrayVar):
                raise ValueError("-variable argument must be an ArrayVar")
            kw["variable"] = str(kw["variable"])

        except KeyError: pass
        except ValueError: raise
            
        Tkinter.Widget.__init__(self, master, "table", cnf, kw)

    # Index positions.
    _indexes = { "active":0, "anchor":0, "end":0,
                 "origin":0, "topleft":0, "bottomright":0 }

    def _cell_index(self, index):
        "Check the form of the cell index, to validate it."
        # If it's one of the predefined index positions, then it's OK.
        if index in _indexes: return index

        # If it's sequence of length 2, then it's OK.
        try:
            return "%d,%d" % (index[0], index[1])
        except IndexError:
            raise ValueError("Bad table index '%s': must be sequence of length 2" % str(index))

        # Check it it's a string of the form "@x,y":
        if index[:0] == '@' and index.find(",") > 0:
            return index

        raise ValueError("Bad table index '%s': must be active, anchor, end, origin, topleft, bottomright, @x,y, or <row>,<col>" % index)

    def activate(self, index):
        "Activate a certain cell."
        index = self._cell_index(index)
        self.tk.call(self._w, "activate", index)

    def bbox(self, start, end):
        "Return bounding box for specified cells, in pixel X,Y,width,height"
        start = self._cell_index(start)
        end = self._cell_index(end)
        box = self.tk.call(self._w, "bbox", start, end).split()
        box = map(float, box)
        return tuple(box)

    def border(self, subcmd, x, y, *options):
        r = self.tk.call(self._w, "border", subcmd, x, y)
        r.replace("{}", "")
        return tuple(r.split())

    def border_mark(self, x, y):
        r = self.tk.call(self._w, "border", "mark", x, y)
        r.replace("{}", "")
        return tuple(r.split())

    def border_dragto(self, x, y):
        self.tk.call(self._w, "border", "dragto", x, y)

    def clear(self, option, *cells):
        first = last = None
        if len(cells) == 1: first = self._cell_index(cells[0])
        if len(cells) > 1: last = self._cell_index(cells[1])
        self.tk.call(self._w, "clear", option, first, last)

    def clear_cache(self, *cells): self.clear("cache", *cells)
        
    def clear_sizes(self, *cells): self.clear("sizes", *cells)

    def clear_tags(self, *cells): self.clear("tags", *cells)

    def clear_all(self, *cells): self.clear("all", *cells)

    def curselection(self, value=None):
        "Query current selected cells, or set their value."
        if value is None:
            row, col = self.tk.call(self._w, "curselection")
            return int(row), int(col)
        self.tk.call(self._w, "curselection", value)

    def curvalue(self, value=None):
        "Set or get value of active cell."
        if value is None:
            return self.tk.call(self._w, "curvalue")
        self.tk.call(self._w, "curvalue", value)

    def delete(self, option, *args):
        "Delete something."
        self.tk.call(self._w, "delete", option, *args)

    def delete_active(self, indexes):
        "Delete text from active cell."
        index1 = index2 = None
        if len(indexes) > 1:
            self.tk.call(self._w. "delete", "active", indexes[0], indexes[1])
        else:
            self.tk.call(self._w, "delete", "active", indexes[0])

    def delete_rowscols(self, what, index1, index2, count, switches):
        "Delete either rows or columns."
        cmd = [ self._w, "delete", what ]
        cmd.extend(["-%s" % K for K in switches])
        if index1 is not None: cmd.append(self._cell_index(index1))
        if index2 is not None: cmd.append(self._cell_index(index2))
        if count is not None: cmd.append(count)
        self.tk.call(*cmd)

    def delete_rows(self, index1, index2=None, count=1, **kw):
        self.delete_rowscols("rows", index1, index2, count, kw)

    def delete_cols(self, index1, index2=None, count=1, **kw):
        self.delete_rowscols("cols", index1, index2, count, kw)

    def get(self, *cells):
        index1 = index2 = None
        if len(cells) > 0: index1 = self._cell_index(cells[0])
        if len(cells) > 1: index2 = self._cell_index(cells[1])
        R = self.tk.call(self._w, "get", index1, index2).split()
        for i,r in zip(range(len(R)),R):
            if r == "{}": R[i] = ""
        return R

    def height(self, *values):
        if not values:
            # Return all rows with a height set.
            R = self.tk.call(self._w, "height")
            pairs = []
            while R:
                left = R.find("{")
                right = R.find("}")
                pairs.append(R[left:right].split())
                R = R[right:].strip()

            return [ (int(row),int(height)) for row,height in R ]
        
        if len(values) == 1:
            # Return height of specified row.
            return int(self.tk.call(self._w, "height", values[0]))

        # Set heights for various rows.
        cmd = [ self._w, "height" ]
        for i in range(len(values)/2):
            cmd.append(values[2*i])     # row
            cmd.append(values[2*i+1])   # height
        self.tk.call(*cmd)

    def hidden(self, index1=None, index2=None):
        if not index1 and not index2:
            # Return all hidden cells.
            R = self.tk.call(self._w, "hidden").split()
            R = [ index.split(",") for index in R ]
            return [ (int(row), int(col)) for row,col in R ]

        if index1 and not index2:
            # Return spanning cell covering this index.
            index1 = self._cell_index(index1)
            cell = self.tk.call(self._w, "hidden", index1)
            if cell: cell = tuple(map(int, cell.split(",")))
            else: cell = None
            return cell

        if index1 and index2:
            # Return 0 or 1 if there's a spanning cell covering the range.
            index1 = self._cell_index(index1)
            index2 = self._cell_index(index2)
            return int(self.tk.call(self._w, "hidden", index1, index2))

        raise ValueError("Bad arguments: must specify start or start and finish")

    def icursor(self, *pos):
        "Set position of insertion cursor in the active cell."
        if not pos: pos = None
        return self.tk.call(self._w, "icursor", pos)

    def index(self, index, *rowcol):
        "Return the integer cell coordinate corresponding to the cell 'index'."
        index = self._cell_index(index)
        if rowcol:
            return int(self.tk.call(self._w, "index", index, rowcol[0]))
        R = self.tk.call(self,_w, "index", index).split(",")
        return int(R[0]), int(R[1])

    def insert(self, options, *args):
        "Insert various things in the table."
        self.tk.call(self._w, "insert", options, *args)

    def insert_active(self, index, value):
        "Insert chars in the active cell."
        self.tk.call(self._w, "insert", "active", index, value)

    def insert_rowcols(self, what, index, count, switches):
        "Insert rows or columns."
        cmd = [ self._w, "insert", what ]
        cmd.extend(switches.keys())
        cmd.append(self._cell_index(index))
        if count is not None: cmd.append(count)
        self.tk.call(*cmd)

    def insert_rows(self, index, count=1, **kw):
        "Insert rows at the index."
        self.insert_rowscols("rows", index, count, kw)

    def insert_cols(self, index, count=1, **kw):
        "Insert cols at the index."
        self.insert_rowscols("cols", index, count, kw)

    def reread(self):
        "Reread the contents of the active cell."
        self.tk.call(self._w, "reread")

    def scan(self, option, *args):
        "Implement scanning on the table."
        self.tk.call(self._w, "scan", option, *args)

    def scan_mark(self, x, y):
        self.tk.call(self._w, "scan", "mark", x, y)

    def scan_dragto(self, x, y):
        self.tk.call(self._w, "scan", "dragto", x, y)

    def see(self, index):
        "Adjust view in the table to see the specified cell."
        index = self._cell_index(index)
        self.tk.call(self._w, "see", index)

    def selection(self, option, *args):
        "Command used to adjust the selection within the table."
        self.tk.call(self._w, "selection", option, *args)

    def selection_anchor(self, index):
        "Set the selection anchor to the cell given by index."
        index = self._cell_index(index)
        self.tk.call(self._w, "selection", "anchor", index)

    def selection_clear(self, index1, index2=None):
        "Remove the specified cell or range of cells from the selection."
        index1 = self._cell_index(index1)
        if index2:
            index = self._cell_index(index2)
            self.tk.call(self._w, "selection", "clear", index1, index2)
        else:
            self.tk.call(self._w, "selection", "clear", index1)

    def selection_includes(self, index):
        "Returns 1 if the selection includes the given index."
        index = self._cell_index(index)
        ok = self.tk.call(self._w, "selection", "includes", index)
        return int(ok)

    def selection_set(self, first, last=None):
        "Selects all of the cells in the range first-last, inclusive."
        first = self._cell_index(first)
        if last:
            last = self._cell_index(last)
            self.tk.call(self._w, "selection", "set", first, last)
        else:
            self.tk.call(self._w, "selection", "set", first)

    def spans(self, *args):
        "Query or set spanning cells."
        if not args:
            # return list of all cell spans.
            R = []
            spans = self.tk.call(self._w, "spans").split()
            for i in range(len(spans)/2):
                start = tuple(map(int, spans[2*i].split(",")))
                finish = tuple(map(int, spans[2*i+1].split(",")))
                R.append((start,finish))
            return R

        if len(args) == 1:
            # Return span of given index.
            index = self._cell_index(args[0])
            span = self.tk.call(self._w, "spans", index).split(",")
            return int(span[0]), int(span[1])

        if len(args) > 1:
            # Set spans of given indexes.
            cmd = [ self._w, "spans" ]
            for i in range(len(args)/2):
                cmd.append(self._cell_index(args[2*i]))
                cmd.append(self._cell_index(args[2*i+1]))
            self.tk.call(*cmd)

    def tag(self, option, tag, *args):
        "Query or set tags."
        self.tk.call(self._w, "tag", option, tag, *args)

    def tag_celltag(self, tag, *cells):
        "Query or set cell tags."
        if not cells:
            # Return list of cells having the tag.
            cells = self.tk.call(self._w, "tag", "celltag", tag).split()
            cells = [ tuple(map(int,rc.split(","))) for rc in cells ]
            return cells

        # Set a certain tag to a list of cells.
        cells = [ self._cell_index(index) for index in cells ]
        self.tk.call(self._w, "tag", "celltag", tag, *cells)

    def tag_cget(self, tag, option):
        "Query option of a tag."
        return self.tk.call(self._w, "tag", "cget", tag)

    def tag_coltag(self, tag, *cols):
        "Query or set column tags."
        if not cols:
            # Query which cols have a certain tag.
            cols = self.tk.call(self._w, "tag", "coltag", tag).split()
            return map(int, cols)

        # Set a list of columns to a tag.
        self.tk.call(self._w, "tag", "coltag", tag, *cols)

    def tag_configure(self, tag, **kw):
        "Configure a tag."
        opt = [  ]
        for k,v in kw.items():
            opt.append("-%s" % k)
            if type(v) in (tuple,list):
                opt.extend(v)
            else:
                opt.append(v)
        self.tk.call(self._w, "tag", "configure", tag, *opt)

    def tag_delete(self, *tags):
        "Delete (one or more) tags."
        if tags:
            self.tk.call(self._w, "tag", "delete", *tags)

    def tag_exists(self, tag):
        "Returns boolean true if tag exists."
        return int( self.tk.call(self._w, "tag", "exists", tag) )

    def tag_includes(self, tag, index):
        "Returns true if tag is included in cell 'index'"
        index = self._cell_index(index)
        return int( self.tk.call(self._w, "tag", "includes", tag, index) )

    def tag_lower(self, *tags):
        "Lower priority of a tag."
        self.tk.call(self._w, "tag", "lower", *tags)

    def tag_names(self):
        "Returns a list of defined tags."
        return self.tk.call(self._w, "tag", "names").split()

    def tag_raise(self, *tags):
        "Raise priority of a tag."
        self.tk.call(self._w, "tag", "raise", *tags)

    def tag_rowtag(self, tag, *rows):
        "Query or set row tags."
        if not rows:
            # Query which rows have the tag.
            rows = self.tk.call(self._w, "tag", "rowtag", tag).split()
            return map(int, rows)

        # Define rows to have the tag.
        self.tk.call(self._w, "tag", "rowtag", *rows)

    def validate(self, index):
        "Validate a cell."
        index = self._cell_index(index)
        return int( self.tk.call(self._w, "validate", index) )

    def version(self):
        "Return version of TkTable widget."
        return float( self.tk.call(self._w, "version") )

    def window(self, option, *args):
        "Commands for embedding windows in cells."
        self.tk.call(self._w, "window", option, *args)

    def window_cget(self, index, option):
        "Query cell's window configuration."
        index = self._cell_index(index)
        conf = self.tk.call(self._w, "window", "cget", index, option)
        if conf.find("no window at index") > -1:
            raise AttributeError(conf)
        return conf

    def window_configure(self, index, *options):
        "Query or set window configuration."
        pass

    def window_create(self, index, widget):
        "Embed a widget in a window."
        self.tk.call(self._w, "window", "configure", "-create", widget._w)

    def window_delete(self, *cells):
        "Destroy embedded widgets."
        if cells:
            cells = [ self._cell_index(c) for c in cells ]
            self.tk.call(self._w, "window", "delete", *cells)

    def window_names(self):
        "Return a list of cells with embedded widgets."
        cells = self.tk.call(self._w, "window", "names").split()
        cells = [ tuple(map(int, cell.split(","))) for cell in cells ]
        return cells
    

    
