#!/bin/env python3 # # Simple ttk treeview with split view, scrollbar, and # row of callback buttons import os import re import itertools import tkinter from tkinter import ttk #------------------------------------------------------ # Tree view used as a multi-column list box #------------------------------------------------------ class TreeViewSplit(ttk.Frame): def __init__(self, parent, fontsize=11, *args, **kwargs): ttk.Frame.__init__(self, parent, *args, **kwargs) s = ttk.Style() s.configure('normal.TLabel', font=('Helvetica', fontsize)) s.configure('title.TLabel', font=('Helvetica', fontsize, 'bold')) s.configure('normal.TButton', font=('Helvetica', fontsize), border = 3, relief = 'raised') s.configure('Treeview.Heading', font=('Helvetica', fontsize, 'bold')) s.configure('Treeview.Column', font=('Helvetica', fontsize)) self.fontsize = fontsize # Last item is a list of 2-item lists, each containing the name of a button # to place along the button bar at the bottom, and a callback function to # run when the button is pressed. def populate(self, title1="", item1list=[], title2="", item2list=[], buttons=[], height=10): self.item1list = item1list[:] self.item2list = item2list[:] columns = [0, 1] treeFrame = ttk.Frame(self) treeFrame.pack(side='top', padx=5, pady=5, fill='both', expand='true') scrollBar = ttk.Scrollbar(treeFrame) scrollBar.pack(side='right', fill='y') self.treeView = ttk.Treeview(treeFrame, selectmode='browse', columns=columns, height=height) self.treeView.pack(side='left', fill='both', expand='true') scrollBar.config(command=self.treeView.yview) self.treeView.config(yscrollcommand=scrollBar.set) self.treeView.column('#0', width=120, stretch='false') self.treeView.heading(0, text=title1, anchor='w') self.treeView.heading(1, text=title2, anchor='w') buttonFrame = ttk.Frame(self) buttonFrame.pack(side='bottom', fill='x') self.treeView.tag_configure('select',background='darkslategray',foreground='white') # Test type tags self.treeView.tag_configure('error', font=('Helvetica', self.fontsize - 1), foreground = 'red') self.treeView.tag_configure('clean', font=('Helvetica', self.fontsize - 1), foreground = 'green3') self.treeView.tag_configure('normal', font=('Helvetica', self.fontsize - 1), foreground = 'black') self.treeView.tag_configure('prep', font=('Helvetica', self.fontsize, 'bold italic'), foreground = 'black', anchor = 'center') self.treeView.tag_configure('header1', font=('Helvetica', self.fontsize, 'bold italic'), foreground = 'brown', anchor = 'center') self.treeView.tag_configure('header2', font=('Helvetica', self.fontsize - 1, 'bold'), foreground = 'blue', anchor = 'center') self.treeView.tag_configure('header3', font=('Helvetica', self.fontsize - 1, 'italic'), foreground = 'green4', anchor = 'center') self.treeView.tag_configure('header4', font=('Helvetica', self.fontsize - 1), foreground = 'purple', anchor = 'center') self.func_buttons = [] for button in buttons: func = button[2] # Each func_buttons entry is a list of two items; first is the # button widget, and the second is a boolean that is True if the # button is to be present always, False if the button is only # present when there are entries in the itemlists. self.func_buttons.append([ttk.Button(buttonFrame, text=button[0], style = 'normal.TButton', command = lambda func=func: self.func_callback(func)), button[1]]) self.selectcallback = None self.lastselected = None self.lasttag = None self.repopulate(item1list, item2list) def get_button(self, index): if index >= 0 and index < len(self.func_buttons): return self.func_buttons[index][0] else: return None def set_title(self, title): self.treeView.heading('#0', text=title, anchor='w') def repopulate(self, item1list=[], item2list=[]): # Remove all children of treeview self.treeView.delete(*self.treeView.get_children()) self.item1list = item1list[:] self.item2list = item2list[:] lines = max(len(self.item1list), len(self.item2list)) # Parse the information coming from comp.out. This is preferably # handled from inside netgen, but that requires development in netgen. # Note: A top-level group is denoted by an empty string. nested = [''] if lines > 0: # print("Create item ID 0 parent = ''") self.treeView.insert(nested[-1], 'end', text='-', iid='0', value=['Initialize', 'Initialize'], tags=['prep']) nested.append('0') tagstyle = 'header1' emptyrec = re.compile('^[\t ]*$') subrex = re.compile('Subcircuit summary') cktrex = re.compile('Circuit[\t ]+[12]:[\t ]+([^ \t]+)') netrex = re.compile('NET mismatches') devrex = re.compile('DEVICE mismatches') seprex = re.compile('-----') sumrex = re.compile('Netlists ') matchrex = re.compile('.*\*\*Mismatch\*\*') incircuit = False watchgroup = False groupnum = 0 for item1, item2, index in zip(self.item1list, self.item2list, range(lines)): cname1 = 'Layout' # Placeholder cname2 = 'Schematic' # Placeholder # Remove blank lines from the display lmatch = emptyrec.match(item1) if lmatch: lmatch = emptyrec.match(item2) if lmatch: continue index = str(index + 1) # Parse text to determine how to structure and display it. tagstyle = 'normal' nextnest = None lmatch = subrex.match(item1) if lmatch: nested = [''] # pop back to topmost level nextnest = index tagstyle = 'header1' incircuit = False watchgroup = False groupnum = 0 item1 = 'Layout compare' item2 = 'Schematic compare' else: lmatch = cktrex.match(item1) if lmatch and not incircuit: # Pick up circuit names and replace them in the title, then use them # for all following titles. cname1 = lmatch.group(1) lmatch = cktrex.match(item2) cname2 = lmatch.group(1) print("Circuit names " + cname1 + " " + cname2) # Rewrite title cktitem = self.treeView.item(nested[-1], values=[cname1 + ' compare', cname2 + ' compare']) nextnest = index tagstyle = 'header2' incircuit = True item1 = cname1 + ' Summary' item2 = cname2 + ' Summary' elif lmatch: continue else: lmatch = netrex.match(item1) if lmatch: if watchgroup: nested = nested[0:-1] nested = nested[0:-1] nextnest = index tagstyle = 'header2' groupnum = 1 watchgroup = True item1 = cname1 + ' Net mismatches' item2 = cname2 + ' Net mismatches' else: lmatch = devrex.match(item1) if lmatch: if watchgroup: nested = nested[0:-1] nested = nested[0:-1] nextnest = index tagstyle = 'header2' groupnum = 1 watchgroup = True item1 = cname1 + ' Device mismatches' item2 = cname2 + ' Device mismatches' else: lmatch = seprex.match(item1) if lmatch: if watchgroup: tagstyle = 'header3' item1 = 'Group ' + str(groupnum) item2 = 'Group ' + str(groupnum) if groupnum > 1: nested = nested[0:-1] groupnum += 1 nextnest = index watchgroup = False else: if groupnum > 0: watchgroup = True continue else: lmatch = sumrex.match(item1) if lmatch: if watchgroup: nested = nested[0:-1] nested = nested[0:-1] watchgroup = False tagstyle = 'header2' groupnum = 0 lmatch1 = matchrex.match(item1) lmatch2 = matchrex.match(item2) if lmatch1 or lmatch2: tagstyle='error' # print("Create item ID " + str(index) + " parent = " + str(nested[-1])) self.treeView.insert(nested[-1], 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) if nextnest: nested.append(nextnest) for button in self.func_buttons: button[0].pack_forget() if lines == 0: self.treeView.insert('', 'end', text='-', value=['(no items)', '(no items)']) for button in self.func_buttons: if button[1]: button[0].pack(side='left', padx = 5) else: for button in self.func_buttons: button[0].pack(side='left', padx = 5) # Special routine to pull in the JSON file data produced by netgen-1.5.72 def json_repopulate(self, lvsdata): # Remove all children of treeview self.treeView.delete(*self.treeView.get_children()) # Parse the information coming from comp.out. This is preferably # handled from inside netgen, but that requires development in netgen. # Note: A top-level group is denoted by an empty string. index = 0 errtotal = {} errtotal['net'] = 0 errtotal['netmatch'] = 0 errtotal['device'] = 0 errtotal['devmatch'] = 0 errtotal['property'] = 0 errtotal['pin'] = 0 ncells = len(lvsdata) for c in range(0, ncells): cellrec = lvsdata[c] if c == ncells - 1: topcell = True else: topcell = False errcell = {} errcell['net'] = 0 errcell['netmatch'] = 0 errcell['device'] = 0 errcell['devmatch'] = 0 errcell['property'] = 0 errcell['pin'] = 0; cname1 = 'Layout' cname2 = 'Schematic' # cellrec is a dictionary. Parse the cell summary, then failing nets, # devices, and properties, and finally pins. if 'name' in cellrec: names = cellrec['name'] cname1 = names[0] cname2 = names[1] else: # This is an error but don't halt the program because of it. cname1 = '(unknown layout)' cname2 = '(unknown schematic)' item1 = cname1 item2 = cname2 tagstyle = 'header1' index += 1 nest0 = index self.treeView.insert('', 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) if 'devices' in cellrec or 'nets' in cellrec: item1 = cname1 + " Summary" item2 = cname2 + " Summary" tagstyle = 'header2' index += 1 nest1 = index self.treeView.insert(nest0, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) if 'devices' in cellrec: item1 = cname1 + " Devices" item2 = cname2 + " Devices" tagstyle = 'header3' index += 1 nest2 = index self.treeView.insert(nest1, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) devices = cellrec['devices'] devlist = [val for pair in zip(devices[0], devices[1]) for val in pair] devpair = list(devlist[p:p + 2] for p in range(0, len(devlist), 2)) for dev in devpair: c1dev = dev[0] c2dev = dev[1] item1 = c1dev[0] + "(" + str(c1dev[1]) + ")" item2 = c2dev[0] + "(" + str(c2dev[1]) + ")" diffdevs = abs(c1dev[1] - c2dev[1]) if diffdevs == 0: tagstyle = 'normal' else: tagstyle = 'error' errcell['device'] += diffdevs if topcell: errtotal['device'] += diffdevs index += 1 nest2 = index self.treeView.insert(nest1, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) if 'nets' in cellrec: item1 = cname1 + " Nets" item2 = cname2 + " Nets" tagstyle = 'header3' index += 1 nest2 = index self.treeView.insert(nest1, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) nets = cellrec['nets'] item1 = nets[0] item2 = nets[1] diffnets = abs(nets[0] - nets[1]) if diffnets == 0: tagstyle = 'normal' else: tagstyle = 'error' errcell['net'] = diffnets if topcell: errtotal['net'] += diffnets index += 1 nest2 = index self.treeView.insert(nest1, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) if 'badnets' in cellrec: badnets = cellrec['badnets'] if len(badnets) > 0: item1 = cname1 + " Net Mismatches" item2 = cname2 + " Net Mismatches" tagstyle = 'header2' index += 1 nest1 = index self.treeView.insert(nest0, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) groupnum = 0 for group in badnets: groupc1 = group[0] groupc2 = group[1] nnets = len(groupc1) groupnum += 1 tagstyle = 'header3' index += 1 nest2 = index item1 = "Group " + str(groupnum) + ' (' + str(nnets) + ' nets)' self.treeView.insert(nest1, 'end', text=index, iid=index, value=[item1, item1], tags=[tagstyle]) tagstyle = 'error' errcell['netmatch'] += nnets if topcell: errtotal['netmatch'] += nnets for netnum in range(0, nnets): if netnum > 0: item1 = "" index += 1 nest3 = index self.treeView.insert(nest2, 'end', text=index, iid=index, value=[item1, item1], tags=[tagstyle]) net1 = groupc1[netnum] net2 = groupc2[netnum] tagstyle = 'header4' item1 = net1[0] item2 = net2[0] index += 1 nest3 = index self.treeView.insert(nest2, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) # Pad shorter device list to the length of the longer one netdevs = list(itertools.zip_longest(net1[1], net2[1])) for devpair in netdevs: devc1 = devpair[0] devc2 = devpair[1] tagstyle = 'normal' if devc1 and devc1[0] != "": item1 = devc1[0] + '/' + devc1[1] + ' = ' + str(devc1[2]) else: item1 = "" if devc2 and devc2[0] != "": item2 = devc2[0] + '/' + devc2[1] + ' = ' + str(devc2[2]) else: item2 = "" index += 1 nest3 = index self.treeView.insert(nest2, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) if 'badelements' in cellrec: badelements = cellrec['badelements'] if len(badelements) > 0: item1 = cname1 + " Device Mismatches" item2 = cname2 + " Device Mismatches" tagstyle = 'header2' index += 1 nest1 = index self.treeView.insert(nest0, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) groupnum = 0 for group in badelements: groupc1 = group[0] groupc2 = group[1] ndevs = len(groupc1) groupnum += 1 tagstyle = 'header3' index += 1 nest2 = index item1 = "Group " + str(groupnum) + ' (' + str(ndevs) + ' devices)' self.treeView.insert(nest1, 'end', text=index, iid=index, value=[item1, item1], tags=[tagstyle]) tagstyle = 'error' errcell['devmatch'] += ndevs if topcell: errtotal['devmatch'] += ndevs for elemnum in range(0, ndevs): if elemnum > 0: item1 = "" index += 1 nest3 = index self.treeView.insert(nest2, 'end', text=index, iid=index, value=[item1, item1], tags=[tagstyle]) elem1 = groupc1[elemnum] elem2 = groupc2[elemnum] tagstyle = 'header4' item1 = elem1[0] item2 = elem2[0] index += 1 nest3 = index self.treeView.insert(nest2, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) # Pad shorter pin list to the length of the longer one elempins = list(itertools.zip_longest(elem1[1], elem2[1])) for pinpair in elempins: pinc1 = pinpair[0] pinc2 = pinpair[1] tagstyle = 'normal' if pinc1 and pinc1[0] != "": item1 = pinc1[0] + ' = ' + str(pinc1[1]) else: item1 = "" if pinc2 and pinc2[0] != "": item2 = pinc2[0] + ' = ' + str(pinc2[1]) else: item2 = "" index += 1 nest3 = index self.treeView.insert(nest2, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) if 'properties' in cellrec: properties = cellrec['properties'] numproperr = len(properties) if numproperr > 0: item1 = cname1 + " Properties" item2 = cname2 + " Properties" tagstyle = 'header2' index += 1 nest1 = index self.treeView.insert(nest0, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) errcell['property'] = numproperr errtotal['property'] += numproperr for prop in properties: if prop != properties[0]: item1 = "" index += 1 nest2 = index self.treeView.insert(nest1, 'end', text=index, iid=index, value=[item1, item1], tags=[tagstyle]) propc1 = prop[0] propc2 = prop[1] tagstyle = 'header3' item1 = propc1[0] item2 = propc2[0] index += 1 nest2 = index self.treeView.insert(nest1, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) # Pad shorter property list to the length of the longer one elemprops = list(itertools.zip_longest(propc1[1], propc2[1])) for proppair in elemprops: perrc1 = proppair[0] perrc2 = proppair[1] tagstyle = 'normal' if perrc1 and perrc1[0] != "": item1 = perrc1[0] + ' = ' + str(perrc1[1]) else: item1 = "" if perrc2 and perrc2[0] != "": item2 = perrc2[0] + ' = ' + str(perrc2[1]) else: item2 = "" index += 1 nest2 = index self.treeView.insert(nest1, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) if 'pins' in cellrec: item1 = cname1 + " Pins" item2 = cname2 + " Pins" tagstyle = 'header2' index += 1 nest1 = index self.treeView.insert(nest0, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) pins = cellrec['pins'] pinlist = [val for pair in zip(pins[0], pins[1]) for val in pair] pinpair = list(pinlist[p:p + 2] for p in range(0, len(pinlist), 2)) for pin in pinpair: item1 = pin[0] item2 = pin[1] if item1.lower() == item2.lower(): tagstyle = 'header4' else: tagstyle = 'error' errcell['pin'] += 1 if topcell: errtotal['pin'] += 1 index += 1 nest2 = index self.treeView.insert(nest1, 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) allcellerror = errcell['net'] + errcell['device'] + errcell['property'] + errcell['pin'] + errcell['netmatch'] + errcell['devmatch'] if allcellerror > 0: item1 = 'Errors: Net = ' + str(errcell['net']) + ', Device = ' + str(errcell['device']) + ', Property = ' + str(errcell['property']) + ', Pin = ' + str(errcell['pin']) + ', Net match = ' + str(errcell['netmatch']) + ', Device match = ' + str(errcell['devmatch']) tagstyle = 'error' else: item1 = 'LVS Clean' tagstyle = 'clean' item2 = "" index += 1 nest0 = index self.treeView.insert('', 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) item1 = "Final LVS result:" item2 = "" tagstyle = 'header1' index += 1 nest0 = index self.treeView.insert('', 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) allerror = errtotal['net'] + errtotal['device'] + errtotal['property'] + errtotal['pin'] + errtotal['netmatch'] + errtotal['devmatch'] if allerror > 0: item1 = 'Errors: Net = ' + str(errtotal['net']) + ', Device = ' + str(errtotal['device']) + ', Property = ' + str(errtotal['property']) + ', Pin = ' + str(errtotal['pin']) + ', Net match = ' + str(errtotal['netmatch']) + ', Device match = ' + str(errtotal['devmatch']) tagstyle = 'error' else: item1 = 'LVS Clean' tagstyle = 'clean' item2 = "" index += 1 nest0 = index self.treeView.insert('', 'end', text=index, iid=index, value=[item1, item2], tags=[tagstyle]) for button in self.func_buttons: button[0].pack_forget() if index == 0: self.treeView.insert('', 'end', text='-', value=['(no items)', '(no items)']) for button in self.func_buttons: if button[1]: button[0].pack(side='left', padx = 5) else: for button in self.func_buttons: button[0].pack(side='left', padx = 5) # Return values from the treeview def getlist(self): return self.treeView.get_children() def func_callback(self, callback, event=None): callback(self.treeView.item(self.treeView.selection())) def bindselect(self, callback): self.selectcallback = callback def setselect(self, value): self.treeView.selection_set(value) def selected(self): value = self.treeView.item(self.treeView.selection()) if value['values']: return value else: return None