Versions Compared


  • This line was added.
  • This line was removed.
  • Formatting was changed.


Some time may be dedicated learning the basics (TkinterTkinterbook)

Application skeleton starts with:


Code Block
titleTreeview display
import ecflow  as ec
from threading import Thread
import Queue
import sys
    import Tkinter as tk
    import tkFont
except ImportError: # 3.x
    import tkinter as tk # Tkinter
    import tkinter.font as tkFont

import ttk

PROGRAM_NAME =  "ecflowview-treeview"
BUTTON_FONT  =  ('times', 12)
MONO_FONT    =  ('lucidatypewriter', 14, 'bold')
tree_columns = (# 'status', # 'since', 
    'path', "host", "id", "checked", "node")
colors = { 'complete': "yellow",
           'active': "green",
           'aborted': "red",
           'submitted': "turquoise",
           "shutdown": "pink",
           "halted": "violet",
           "queued": "lightblue",
           "unknown": "grey",
           "suspended": "orange" }
displays = dict()

running = True
PACE = 30

def guess_host(node, kind=None): 
    if node is None: return ""

    value = None
    for var in node.variables:
        name =
        if kind is None and name == "ECF_JOB_CMD":
            if "%WSHOST%" in var.value(): 
                return guess_host(node, "WSHOST")
            elif "%SCHOST%" in var.value(): 
                return guess_host(node, "SCHOST")
            elif "%HOST%" in var.value(): 
                return guess_host(node, "HOST")
            else: value = var.value()
        elif kind == "SCHOST" and name == "SCHOST":
            return var.value()
        elif kind == "WSHOST" and name == "WSHOST":
            return var.value()
        elif kind == "HOST" and name == "HOST":
            return var.value()
        else: pass
    if value is None and node.get_parent():
        return guess_host(node.get_parent(), kind)
    return "???"

def guess_qid(node, path=None): 
    if node is None: return ""
    if path is None and not isinstance(node, ec.Task): return ""

    if 0:
        content = "%s" % node
        for line in content.split("\n"): 
            print line
            if "edit ECF_JOB " in line:
                dummy, name, value = line.split()
                print line, dummy, name, value
                return value
    elif 0:
        for var in node.variables:
            if == "ECF_JOB": return var.value() + ".sub"
    elif path is None: 
        return guess_qid(node.get_parent(), node.get_abs_node_path())
    else: pass
    loc = None
    for var in node.variables:
        if == "ECF_HOME": loc = var.value() + path; break
    if loc is None and node.get_parent() is not None:
        return guess_qid(node.get_parent(), path)
    elif loc is None: return "???"
    import commands
    a = commands.getstatusoutput("ls -ltr %s*.sub | awk '{print $8}'" % loc)
    item  =a[1].split('\n')
    print path, item[0]
    fd = open(item[0])
    line = "???"
    for line in fd.readlines():
        if "has been submitted" in line:     
            full = line.split()
            if "llsubmit:" in line: return full[3]
            return full[2]
    print "# last: ", line
    return line

def guess_check(master, item, node, host, qid):
    qstat="/usr/local/apps/propbs/bin/qstat" # PBS
    llq="/usr/lpp/LoadL/full/bin/llq -f %id %jn %o %c %st %nh"
    import commands
    if "lxa" in host or "lxb" in host or host=="linux_cluster":
        print node, host, qid
        sge="/usr/local/apps/sge/sge6_2u5/bin/lx24-amd64/qstat -u emos -f"
        cmd = "ssh lxab 'SGE_ROOT=%s %s' | grep %d" % (SGE_ROOT, sge, int(qid))        
        a = commands.getstatusoutput(cmd)
        print cmd, a
        if a[0] != 0: return
        full = a[1].split()
        if len(full) > 6 and full[4]=='r':
            print "#YES %s is running since %s %s" % (node, full[5], full[6])
            master._tree.set(item, column="checked", value="%s" % full[6])
        else: print full
    elif "c2a" in host or "c2b" in host or "ecg" in host:
        print node, host, qid
        a = commands.getstatusoutput("rsh %s %s %s | grep %s" % (host, llq, qid, qid))
        print a
    elif "cc" in host:
        print node, host, qid
        a = commands.getstatusoutput("ssh %s qstat %s" % (host, qid))
        print a
    else: print node, host, qid

def textw(clnt, fullpath, kind, node=None):
    # create child window
    win = tk.Toplevel()
    # display message
    if kind == "variables":
        msg = ""
        for var in node.variables:
            msg += "%-20s: %s\n" %(, var.value())
        message = msg
    elif kind == "info":
        message = "%s" % node
    else: message = clnt.client.get_file(fullpath, kind)
    # text = tk.Label(win, text=message)

    win.title(fullpath + " - " + kind)
    s = tk.Scrollbar(win)
    T = tk.Text(win)

    s.pack(side=tk.RIGHT, fill=tk.Y)
    T.pack(side=tk.LEFT, fill=tk.Y)

    for i in message.split("\n"):
        T.insert(tk.END, i + "\n")
        T.yview(tk.MOVETO, 1.0)
    # ysb = ttk.Scrollbar(text, orien='vertical', 
    #                     command=text.yview)
    # xsb = ttk.Scrollbar(self, orien='horizontal', 
    #                     command=text.xview)
    # text.configure(yscrollcommand=ysb.set, 
    #                xscrollcommand=xsb.set)
    # text.pack()
    # quit child window and return to root window
    # the button is optional here, simply use the corner x of the child window
    # tk.Button(win, text='close', command=win.destroy).pack()

class PaceKeeper():

    def __init__(self, master, queue): 
        self.thr = Thread(target=self.process, 
               args=(queue, running))
        self._master = master
    def process(self, queue, running):
        import time
        if not running: self.thr.stop(); return

    def run(self): self.update()

    def update(self, verbose=False): 
        import time
        if 1: # while running:
            print time.clock()

class ContextMenu(object):
    def __init__(self, dad): = dad
        self.cmn = tk.Menu(dad, tearoff=0)
        self.cmn.add_command(label="Script", command=dad.script)
        self.cmn.add_command(label="Manual", command=dad.manual)
        self.cmn.add_command(label="Job",    command=dad.job)
        self.cmn.add_command(label="Output",    command=dad.jobout)
        self.cmn.add_command(label="Why",    command=dad.why)
        self.cmn.add_command(label="TimeLine",    command=dad.timeline)
        self.cmn.add_command(label="Variables",    command=dad.variables)
        self.cmn.add_command(label="Messages",     command=dad.messages)
class MenuBar(tk.Frame):
    def __init__(self, parent):
        tk.Frame.__init__(self, parent)
        self.__helpButton = self.__createHelp()

        self.__updateButton = tk.Button(self, text='Update',
                                        font= BUTTON_FONT,
                                        command= parent.update)

        self.__checkButton = tk.Button(self, text='Check',
                                        font= BUTTON_FONT,
                                        command= parent.check)

        self.__quitButton = tk.Button(
            self, text='Quit',
            font= BUTTON_FONT, command= parent.quit)

        col = 0
        self.__quitButton.grid(row=0, column=col)
        col += 1
        self.__updateButton.grid(row=0, column=col)
        col += 1
        self.__checkButton.grid(row=0, column=col)
        col += 1

        self.__helpButton.grid(row=0, column=col)
        col += 1

        timed = tk.Label(self, font=BUTTON_FONT,
                        textvariable= parent.last_update, )
        timed.grid(row=0, column=col)


    def __createHelp(self):
        mb = tk.Menubutton(self, font=BUTTON_FONT,
                        relief= tk.RAISED,
                        text= 'Help')
        menu = tk.Menu(mb)
        mb['menu'] = menu
        menu.add_command(command= self.__url,
                         label="confluence tutorial?")
        menu.add_command(command= self.__url2,
        return mb

    def __url2(self): self.__url(1)

    def __url(self, num=0):
        import os
        url = ""

        if num == 1: url = ""

        os.system("firefox " + url)

    def __createCheck(self, col):
        keys = colors.keys()
        for kind in sorted(keys):
            var = tk.IntVar()
            displays[kind] = var
            check = tk.Checkbutton(self, text= kind, variable= var,
                                onvalue= 1, offvalue= 0,)
            if kind in ("active", "aborted", "submitted", "unfold"):
            check.grid(row=0, column=col)
            col += 1
def sortby(tree, col, desc, node=''):
    data = []
    for item in tree.get_children(node):
        for kid in tree.get_children(item):
            data.append((tree.set(kid, col), kid, item))


    for idx, item in enumerate(data): 
        tree.move(item[1], item[2], idx)
        col, command= lambda col= col: sortby(tree, col, int(not desc)))

class Client(object):
    def __init__(self, host, port):
        self.client = ec.Client(host, port)   = host
        self.port   = port

    def sync(self, top, parent):
        load = self.defs()
        if load is None: print "empty server %s %d" %(, self.port)
        for item in load.suites:
            self.process(top, parent, item)

    def defs(self): return self.client.get_defs()

    def process(self, top, parent, item):
        status = "%s" % item.get_state()
        fullpath = item.get_abs_node_path()

        if not displays[status].get(): return

        unfold = displays["unfold"].get()
        if isinstance(item, ec.Task) or not unfold:
            name =
            if isinstance(item, ec.Task): 
                host = guess_host(item)
                checked = "nope"
                qid = guess_qid(item)
            else: host= ""; qid = ""; checked = ""
            if unfold: name = fullpath; fullpath = ""
            dad = top._tree.insert(parent, 'end', 
                                     values= (fullpath, 
                                     tags= status,
                                     text=name, open=unfold)
        else: dad = parent
        if 0:
            ilen = tkFont.Font().measure(fullpath)
            if ilen > 80: self._tree.column(1, width=ilen)
            else: pass
        if not isinstance(item, ec.Task):
            for kid in item.nodes: self.process(top, dad, kid)
        else: pass

class Application(tk.Frame):
    def __init__(self, master=None, client=None, queue=None):
        width = 768
        height = 480
        tk.Frame.__init__(self, master, width=width, height=height,)
        self.__queue = queue
        if client is None: 
            HOST = "vsms1"; 
            self.__client = [ ]
            PORT = 43333; self.__client.append(Client(HOST, PORT))
            PORT = 32222; self.__client.append(Client(HOST, PORT))
            PORT = 31415; self.__client.append(Client(HOST, PORT))
            print self.__client
        else: self.__client = [client ]
        # self.canvas = tk.Canvas(width=width, height=height, bg='black')

        self.last_update = tk.StringVar()

        self.cwn = None
        # self.canvas.after(50, self.check_queue)

    def createWidgets(self):
        self.__menuBar = MenuBar(self)
        row = 0
        self.__menuBar.grid(row=row, column=0, columnspan=3,
        row += 1
        self.__wins = dict()
        rowx = 1
        colx = 0
        if 1:
            self._tree = ttk.Treeview(self, columns=tree_columns,
                                       # justify="right",
                                       # show="headings" # path disappear

            ysb = ttk.Scrollbar(self, orien='vertical', 
            xsb = ttk.Scrollbar(self, orien='horizontal', 
                                  # justify="right",

            self._tree.grid(row=row, column=0,                                   
                             sticky='nsew', in_=self)
            ysb.grid(row=row, column=len(tree_columns)+1, sticky='wens')
            xsb.grid(sticky='ewns', columnspan=len(tree_columns),)
            self.grid_columnconfigure(0, weight=1,)
            self.grid_rowconfigure(row, weight=1)

            pos = 0
            for kind, color in colors.items():
                self._tree.tag_configure(kind, background=color)

            self._tree.bind("<Double-1>", self.OnDoubleClick)

            # create a popup menu
            self.cmn = ContextMenu(self)
            self._tree.bind("<Button-3>", self.popup)

            for col in tree_columns:
                    col, text=col.title(),
                    command= lambda c=col: sortby(self._tree, c, 0))
                width = tkFont.Font().measure(col.title())
                if col == "status":
                    ilen = tkFont.Font().measure(kind)
                    if ilen > width: width = ilen
                else: width = 80
                self._tree.column(col, width= width, stretch= True, anchor=tk.E)
                pos += 1
            self.pack(fill='both', expand=True)


    def info(self): return self.cmgen("info")
    def manual(self): return self.cmgen("manual")
    def script(self): return self.cmgen("script")
    def job(self): return self.cmgen("job")
    def jobout(self): return self.cmgen("jobout")
    def why(self): return self.cmgen("why")
    def timeline(self): return self.cmgen("timeline")
    def variables(self): return self.cmgen("variables")
    def messages(self): return self.cmgen("messages")

    def cmgen(self, kind="info"): 
        item = self.cwn
        full = self._tree.item(item,"values")
        path = self._tree.item(item,"text")
        print path, full, item
        print "#hello:" # , node.get_abs_node_path()
        upp = item; print  self._tree.parent(upp)
        while self._tree.parent(upp) != '': 
            upp = self._tree.parent(upp)
        idx = self._tree.index(upp)
        if path[0] == '/': fullpath = path
        else: fullpath = full[0]
        if idx < 0 or idx >= len(self.__client): return
        clnt = self.__client[idx]
        defs = clnt.defs()
        node = defs.find_abs_node(fullpath)
        if kind in ("script", "manual", "job", "output", "jobout"):            
            textw(clnt, fullpath, kind)
        elif kind in ("variables",):
            textw(clnt, fullpath, kind, node)
        elif kind in ("info"):
            textw(clnt, fullpath, kind, node)
            print "# not yet", node

    def popup(self, event):
        item = self._tree.identify('row',event.x,event.y)
        if item is None: print "???"; return
        print "context for", self._tree.item(item,"values"), \
        self.cwn = item, event.y_root)

    def OnDoubleClick(self, event):
        item = self._tree.identify('row',event.x,event.y)
        if item is None: print "???"; return
        print "you clicked on", self._tree.item(item,"values"), \

    def check_queue(self):
        except Queue.Empty: 
        else: self.update()
        self.canvas.after(50, self.check_queue)

    def unfold(self, item=None):
        if item is None:
            for item in self._tree.get_children(''):
        for kid in self._tree.get_children(item):

    def quit(self):
        global running
        running = False

    def check(self, item=None):
        if item is None:
            for item in self._tree.get_children(''): self.check(item)
            else: pass
            for kid in self._tree.get_children(item): self.check(kid)    
            full = self._tree.item(item, 'values'), 
            text = self._tree.item(item, 'text'), 
            if len(full[0]) > 3:
                full = full[0]
                if full[1] != "???":
                    unfold = displays["unfold"].get()            
                    if unfold: path = text
                    else: path = full[0]
                    guess_check(self, item, path, full[1], full[2])
    def process(self):
        for clt in self.__client:
            parent = self._tree.insert('', 'end', 
                                       text="%s@%s" % (, 
                                       open= True)
            clt.sync(self, parent)

    def update(self):
            import time
            now = time.localtime(time.time())
            hhmmss = "%02d:%02d:%02d" % (now.tm_hour, now.tm_min, now.tm_sec)
            for item in self._tree.get_children(''): self._tree.delete(item)

if __name__ == '__main__':
    if len(sys.argv) > 2:
        HOST = sys.argv[1]
        PORT = int(sys.argv[2])
        print "# " + PROGRAM_NAME + ": contact %s %d" % (HOST, PORT)
        client = Client(HOST, PORT)
    else: client = None
    queue = Queue.Queue()
    app = Application(client=client, queue=queue)

    PaceKeeper(app, queue)