#! python3
# mcb.pyw - Saves and loads pieces of text to the clipboard.
# Usage: py mcb.pyw save <keyword> - Saves clipboard to keyword.
#        py mcb.pyw <keyword> - Loads keyword to clipboard.
#        py mcb.pyw list - Loads all keywords to clipboard.
#        py mcb.pyw del <saved keyword> - Delete saved keyword.
#        py mcb.pyw del - Delete all saved keywords.

import shelve, pyperclip, sys

# shelve - Manage shelves of pickled objects.
# To summarize the interface (key is a string, data is an arbitrary object):
#
#         import shelve
#         d = shelve.open(filename) # open, with (g)dbm filename -- no suffix
#
#         d[key] = data   # store data at key (overwrites old data if
#                         # using an existing key)
#         data = d[key]   # retrieve a COPY of the data at key (raise
#                         # KeyError if no such key) -- NOTE that this
#                         # access returns a *copy* of the entry!
#         del d[key]      # delete data stored at key (raises KeyError
#                         # if no such key)
#         flag = key in d # true if the key exists
#         list = d.keys() # a list of all existing keys (slow!)
#
#         d.close()       # close it

mcbShelf = shelve.open('mcb')

# Save clipboard content.
# sys.argv[] 是用来获取命令行参数的，sys.argv[0] 表示代码本身文件路径，所以参数从 1 开始
# len(sys.argv) == 3 的意思是命令行有两个参数，且第一个参数为「save」忽略大小写
# 例如命令行格式为「py mcb.pyw save quiz」命令是「mcb.pyw」，参数一sys.argv[1] = save，参数二sys.argv[2] = quiz
if len(sys.argv) == 3 and sys.argv[1].lower() == 'save':
    # mcbShelf[quiz] = 'We'll have a quiz at the end of the show.'
    mcbShelf[sys.argv[2]] = pyperclip.paste()

if len(sys.argv) == 3 and sys.argv[1].lower() == 'del' and (sys.argv[2] in mcbShelf):
    del mcbShelf[sys.argv[2]]

# 一个参数的命令，如果参数为「list」输出所有 mcbShelf 的 key 到 clipboard
# 如果参数为 mcbShelf 中的一个 key，输出对应的 value 到 clipboard
elif len(sys.argv) == 2:
    # List keywords and load content.
    if sys.argv[1].lower() == 'list':
        pyperclip.copy(str(list(mcbShelf.keys())))
    elif sys.argv[1].lower() == 'del':
        for k in mcbShelf.keys():
            del mcbShelf[k]
    elif sys.argv[1] in mcbShelf:
        pyperclip.copy(mcbShelf[sys.argv[1]])
mcbShelf.close()


import sys
import time
import random
import os
import shutil
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from_dir = "G:/NATHAN"
class FileMovementHandler(FileSystemEventHandler):

    def on_created(self, event):
        print("{event.src_path} has just been created")

    def on_deleted(self, event):
        print("{event.src_path} has just been deleted")
    def on_modified(self, event):
        print("{event.src_path} has just been modified")
    def on_moved(self, event):
        print("{event.src_path} has just been moved")

event_handler = FileMovementHandler()
observer = Observer()
observer.schedule(event_handler, from_dir, recursive=True)
observer.start()
try:
    while True:
        time.sleep(2)
        print("running...")
except KeyboardInterrupt:
    print("stopped")
    observer.stop()


# -*- coding: big5 -*-
import clr                              # 汇入 clr
clr.AddReference("System")              # 加入参考 System
from System.Net import *                # 汇入 System.Net
from System.Text import *               # 汇入 System.Text
import cStringIO                        # 汇入 cStringIO

def gethtmldata(url="http://www.python.org/", enc="utf-8"):
        client = WebClient()            # 建立 WebClient 实体
        if enc == "utf-8":              # 设定编码
                myenc = Encoding.UTF8
        else:
                myenc = Encoding.GetEncoding(encode)
        client.Encoding = myenc         # 设定 URL 编码
        s = client.DownloadString(url)  # 取得 URL 指向的资料
        f = cStringIO.StringIO()        # 建立 StringIO 实体
        f.write(s)                      # 写出资料
        f.seek(0)                       # 搜寻位置返回开头
        return f                        # 回传 StringIO 物件


# -*- coding: utf-8 -*-

### CONFIGURATOR
# Clase que se encarga de salvar y cargar las configuraciones de valores de los PID y BIAS y demás parámetros.
#   Útil si se quieren cambiar valores de PID para diferentes lógicas de control.
#
# Muestra, refresca y mantiene los sliders de tuneo de PID, BIAS y altura/orientación objetivo.
#
# - rady

import json
import globales as gb
from datetime import datetime
import os
import cv2

class Panels():

    def __init__(self):
        self.create_trackbars()

    def attach_controller(self, controller):
        self.controller = controller

    def create_trackbars(self):
        """
            globals:
            KPx, KPy, KPz, KPangle, KIx, KIy, KIz, KIangle, KDx, KDy, KDz, KDangle
            throttle_middle, rudder_middle, aileron_middle, elevator_middle, correccion_gravedad, clamp_offset
        """

        # # color
        # cv2.namedWindow('color')
        # cv2.resizeWindow('color', 300, 120)
        # cv2.moveWindow('color', 200+640+400, 100+120)
        # cv2.createTrackbar('Z Target', 'color', 20, 50, self.nothing_target)
        # cv2.createTrackbar('A Target', 'color', 180, 360, self.nothing_target)

        # target
        cv2.namedWindow('target')
        cv2.resizeWindow('target', 300, 120)
        cv2.moveWindow('target', 200+640+400, 100)
        cv2.createTrackbar('Z Target', 'target', 40, 60, self.nothing_target)
        cv2.createTrackbar('A Target', 'target', 180, 360, self.nothing_target)

        # trackbars
        cv2.namedWindow('control')
        cv2.resizeWindow('control', 400, 900)
        cv2.moveWindow('control', 200+640, 100)
        cv2.createTrackbar('KPZ', 'control', gb.KPz, 20, self.nothing)
        cv2.createTrackbar('KIZ', 'control', int(gb.KIz * 100), 100, self.nothing)
        cv2.createTrackbar('KDZ', 'control', gb.KDz, 200, self.nothing)
        cv2.createTrackbar('BIASZ', 'control', gb.throttle_middle, 2000, self.nothing)
        cv2.createTrackbar('CG', 'control', gb.correccion_gravedad, 25, self.nothing)

        cv2.createTrackbar('Clamp', 'control', gb.clamp_offset, 500, self.nothing)
        cv2.createTrackbar('AUX1', 'control', gb.AUXILIAR1, 10, self.nothing)

        cv2.createTrackbar('KPZd', 'control', gb.KPzd, 20, self.nothing)
        cv2.createTrackbar('KIZd', 'control', int(gb.KIzd * 100), 100, self.nothing)
        cv2.createTrackbar('KDZd', 'control', gb.KDzd, 200, self.nothing)

        cv2.createTrackbar('KPX', 'control', int(gb.KPx * 100), 500, self.nothing)
        cv2.createTrackbar('KIX', 'control', int(gb.KIx * 100), 100, self.nothing)
        cv2.createTrackbar('KDX', 'control', gb.KDx, 200, self.nothing)
        cv2.createTrackbar('BIASAIL', 'control', gb.aileron_middle, 2000, self.nothing)

        cv2.createTrackbar('KPY', 'control', int(gb.KPy * 100), 500, self.nothing)
        cv2.createTrackbar('KIY', 'control', int(gb.KIy * 100), 100, self.nothing)
        cv2.createTrackbar('KDY', 'control', gb.KDy, 200, self.nothing)
        cv2.createTrackbar('BIASELE', 'control', gb.elevator_middle, 2000, self.nothing)

        cv2.createTrackbar('KPA', 'control', gb.KPangle, 20, self.nothing)
        cv2.createTrackbar('KIA', 'control', int(gb.KIangle * 100), 100, self.nothing)
        cv2.createTrackbar('KDA', 'control', gb.KDangle, 200, self.nothing)
        cv2.createTrackbar('BIASRUD', 'control', gb.rudder_middle, 2000, self.nothing)

    @staticmethod
    def refresh_biases_from_globals():
        cv2.setTrackbarPos('BIASAIL', 'control', gb.aileron_middle)
        cv2.setTrackbarPos('BIASELE', 'control', gb.elevator_middle)

    @staticmethod
    def refresh_sliders_from_globals():

        # trackbars
        cv2.setTrackbarPos('KPZ', 'control', gb.KPz)
        cv2.setTrackbarPos('KIZ', 'control', int(gb.KIz * 100))
        cv2.setTrackbarPos('KDZ', 'control', gb.KDz)
        cv2.setTrackbarPos('BIASZ', 'control', gb.throttle_middle)
        cv2.setTrackbarPos('CG', 'control', gb.correccion_gravedad)

        cv2.setTrackbarPos('KPZd', 'control', gb.KPzd)
        cv2.setTrackbarPos('KIZd', 'control', int(gb.KIzd * 100))
        cv2.setTrackbarPos('KDZd', 'control', gb.KDzd)

        cv2.setTrackbarPos('Clamp', 'control', gb.clamp_offset)
        cv2.setTrackbarPos('AUX1', 'control', gb.AUXILIAR1)

        cv2.setTrackbarPos('KPX', 'control', int(gb.KPx * 100))
        cv2.setTrackbarPos('KIX', 'control', int(gb.KIx * 100))
        cv2.setTrackbarPos('KDX', 'control', gb.KDx)
        cv2.setTrackbarPos('BIASAIL', 'control', gb.aileron_middle)

        cv2.setTrackbarPos('KPY', 'control', int(gb.KPy * 100))
        cv2.setTrackbarPos('KIY', 'control', int(gb.KIy * 100))
        cv2.setTrackbarPos('KDY', 'control', gb.KDy)
        cv2.setTrackbarPos('BIASELE', 'control', gb.elevator_middle)

        cv2.setTrackbarPos('KPA', 'control', gb.KPangle)
        cv2.setTrackbarPos('KIA', 'control', int(gb.KIangle * 100))
        cv2.setTrackbarPos('KDA', 'control', gb.KDangle)
        cv2.setTrackbarPos('BIASRUD', 'control', gb.rudder_middle)

    def nothing(self, value):
        gb.refresca_params_flag = True

    def nothing_target(self, value):
        pass

    def refresca_params(self):
        gb.refresca_params_flag = False

        gb.KPz = cv2.getTrackbarPos("KPZ", "control")
        gb.KIz = cv2.getTrackbarPos("KIZ", "control") / float(100)
        gb.KDz = cv2.getTrackbarPos("KDZ", "control")
        gb.throttle_middle = cv2.getTrackbarPos("BIASZ", "control")
        gb.correccion_gravedad = cv2.getTrackbarPos("CG", "control")

        ## PROV
        gb.KPzd = cv2.getTrackbarPos("KPZd", "control")
        gb.KIzd = cv2.getTrackbarPos("KIZd", "control") / float(100)
        gb.KDzd = cv2.getTrackbarPos("KDZd", "control")
        self.controller.spid_z.tunings = gb.KPz, gb.KIz, gb.KDz

        gb.clamp_offset = cv2.getTrackbarPos("Clamp", "control")
        gb.AUXILIAR1 = cv2.getTrackbarPos("AUX1", "control")

        gb.KPx = cv2.getTrackbarPos("KPX", "control") / float(100)
        gb.KIx = cv2.getTrackbarPos("KIX", "control") / float(100)
        gb.KDx = cv2.getTrackbarPos("KDX", "control")
        gb.aileron_middle = cv2.getTrackbarPos("BIASAIL", "control")

        gb.KPy = cv2.getTrackbarPos("KPY", "control") / float(100)
        gb.KIy = cv2.getTrackbarPos("KIY", "control") / float(100)
        gb.KDy = cv2.getTrackbarPos("KDY", "control")
        gb.elevator_middle = cv2.getTrackbarPos("BIASELE", "control")

        gb.KPangle = cv2.getTrackbarPos("KPA", "control")
        gb.KIangle = cv2.getTrackbarPos("KIA", "control") / float(100)
        gb.KDangle = cv2.getTrackbarPos("KDA", "control")
        gb.rudder_middle = cv2.getTrackbarPos("BIASRUD", "control")

class Configurator:

    # Gestiona sets de bias y params PID
    __instance = None

    def __new__(cls):
        if cls.__instance == None:
            cls.__instance = object.__new__(cls)

            # cls.__instance.initialize(pid_config)  # config inicial
        return cls.__instance

    def attach_controller(self, controller):
        self.panels.attach_controller(controller)

    def initialize(self, file_name):
        self.salvar_al_salir = False
        with open("config/config_pid/" + file_name, "r") as file:
            config_data = json.load(file)
        self.write_to_globals(config_data["pid"], config_data["bias"])
        gb.config_activa = file_name
        self.panels = Panels()

    def load_config_file(self, file_name):
        # Lee Config BIAS y PID
        with open("config/config_pid/" + file_name, "r") as file:
            config_data = json.load(file)
        self.write_to_globals(config_data["pid"], config_data["bias"])
        self.panels.refresh_sliders_from_globals()
        gb.config_activa = file_name
        print(file_name, "cargada a globales.")

    def modify_bias(self, bias_data, file_name):
        # define middle PPM values that make the drone hover
        if "throttle" in bias_data.keys() : gb.throttle_middle = bias_data["throttle"]  # up (+) and down (-)  # 1660 con corona de 4 gramos
        if "aileron" in bias_data.keys(): gb.aileron_middle = bias_data["aileron"]  # left (-) and right (+)
        if "elevator" in bias_data.keys(): gb.elevator_middle = bias_data["elevator"]  # forward (+) and backward (-)
        if "rudder" in bias_data.keys(): gb.rudder_middle = bias_data["rudder"]  # yaw left (-) and yaw right (+)
        if "correccion_gravedad" in bias_data.keys(): gb.correccion_gravedad = bias_data["correccion_gravedad"]
        self.panels.refresh_sliders_from_globals()
        gb.config_activa = file_name

    @staticmethod
    def write_to_globals(pid_data, bias_data):
        # define middle PPM values that make the drone hover
        gb.throttle_middle = bias_data["throttle"]  # up (+) and down (-)  # 1660 con corona de 4 gramos
        gb.aileron_middle = bias_data["aileron"]  # left (-) and right (+)
        gb.elevator_middle = bias_data["elevator"]  # forward (+) and backward (-)
        gb.rudder_middle = bias_data["rudder"]  # yaw left (-) and yaw right (+)
        gb.correccion_gravedad = bias_data["correccion_gravedad"]

        gb.clamp_offset = 400       # Todo: a piñoneti? Arreglar esto
        gb.AUXILIAR1 = 2            # Todo: a piñoneti? Arreglar esto

        # rady PID - pixeles (x-y) - cm (z) - degree (head)
        gb.KPx, gb.KPy, gb.KPz, gb.KPangle = pid_data["KPx"], pid_data["KPy"], pid_data["KPz"], pid_data["KPangle"]
        gb.KIx, gb.KIy, gb.KIz, gb.KIangle = pid_data["KIx"], pid_data["KIy"], pid_data["KIz"], pid_data["KIangle"]
        gb.KDx, gb.KDy, gb.KDz, gb.KDangle = pid_data["KDx"], pid_data["KDy"], pid_data["KDz"], pid_data["KDangle"]

        # mods PID bajada
        gb.KPzd, gb.KIzd, gb.KDzd = gb.KPz, gb.KIz, gb.KDz

    @staticmethod
    def save_config_file(file_name):
        config_data = dict()
        pid_data = {
            "KPx": gb.KPx, "KPy": gb.KPy, "KPz": gb.KPz, "KPangle": gb.KPangle,
            "KDx": gb.KDx, "KDy": gb.KDy, "KDz": gb.KDz, "KDangle": gb.KDangle,
            "KIx": gb.KIx, "KIy": gb.KIy, "KIz": gb.KIz, "KIangle": gb.KIangle,
        }
        bias_data = {
            "throttle": gb.throttle_middle, "rudder": gb.rudder_middle,
            "aileron": gb.aileron_middle, "elevator": gb.elevator_middle,
            "correccion_gravedad": gb.correccion_gravedad
        }
        config_data["pid"] = pid_data
        config_data["bias"] = bias_data

        with open('config/config_pid/' + file_name, 'w') as file:
            json.dump(config_data, file, sort_keys=True, indent=4)

    def save_to_config_activa(self):
        self.save_config_file(gb.config_activa)
        print("CONFIG SAVED!", gb.config_activa, "has been modified.")

    def load_config_activa(self):
        self.load_config_file(gb.config_activa)
        print(gb.config_activa, "config file LOADED!")

    def create_new_config_file(self):
        name = "{:%d_%H_%M}.json".format(datetime.now())
        self.save_config_file(name)
        print("NEW CONFIG FILE!", name, "has been created!")

    def select_another_config_file(self):
        file_list = os.listdir("config/config_pid")
        index = file_list.index(gb.config_activa) + 1
        if index >= len(file_list):
            index = 0
        gb.config_activa = file_list[index]
        print(gb.config_activa, "config file has been selected! Press P to Load.")

    # Chapuzas. TODO Meter en paneles.
    def config_target(self, x=None, y=None, z=None, a=None):
        if x:
            gb.xTarget = x
        if y:
            gb.yTarget = y
        if z:
            gb.zTarget = z
            cv2.setTrackbarPos('Z Target', 'target', z)
        if a:
            gb.angleTarget = a
            cv2.setTrackbarPos('A Target', 'target', a)


import sys
import os
import time
import json
from numpy import random
import speech_recognition as sr
from gtts import gTTS
from playsound import playsound
import special_commands

def load_json(file):
    with open(file, encoding='utf-8') as f:
        return json.loads(f.read())

def findResponse(commands, command):
    for key in commands.keys():
        if key.lower() in command.lower():
            return commands.get(key)
    return '-'

def listen(lang):
    r = sr.Recognizer()

    with sr.Microphone() as source:
        audio = r.listen(source)
        command = ''

        try:
            command = r.recognize_google(audio, language=lang)
        except sr.UnknownValueError:
            print('I don\'t understand')
        except sr.RequestError:
            print('Request error!')

        print(command)
        return command

def speak(text, lang):
    tts = gTTS(text, lang=lang.split('-')[0])
    file = f'tts-{time.time()}.mp3'
    tts.save(file)
    playsound(file)
    os.remove(file)

def response(commands, command, lang):
    found = findResponse(commands, command)

    if(found != "-"):
        if(type(found) == list):
            choise = random.choice(found)
            print(choise)
            speak(choise, lang)
        elif(type(found) == dict):
            unformatted = found.get('txt')
            function_name = found.get('fun').get('name')
            function_param = found.get('fun').get('param')
            fun = getattr(special_commands, function_name)
            r = fun(function_param) if function_param  else fun()
            text = unformatted.format(r)
            print(text)
            speak(text, lang)
    else:
        getattr(special_commands, 'searchInGoogle')(command)
        print(commands.get(found))
        speak(commands.get(found), lang)

def main(argv):
    lang = 'en-EN'

    if(len(argv) > 1):
        lang = str(argv[1])

    command_file = f'./commands/{lang}.json'
    commands = load_json(command_file)
    #print(commands)

    while True:
        print("...")
        command = listen(lang)
        response(commands, command, lang)

if __name__ == "__main__":
   main(sys.argv)


#!/usr/bin/env python3
#
#--------------------------------------------------------
# Project Manager GUI.
#
# This is a Python tkinter script that handles local
# project management.  Much of this involves the
# running of ng-spice for characterization, allowing
# the user to determine where a circuit is failing
# characterization;  and when the design passes local
# characterization, it may be submitted to the
# marketplace for official characterization.
#
#--------------------------------------------------------
# Written by Tim Edwards
# efabless, inc.
# September 9, 2016
# Version 1.0
#--------------------------------------------------------

import io
import re
import os
import sys
import copy
import json
import time
import signal
import select
import datetime
import contextlib
import subprocess
import faulthandler

import tkinter
from tkinter import ttk
from tkinter import filedialog

import tksimpledialog
import tooltip
from consoletext import ConsoleText
from helpwindow import HelpWindow
from failreport import FailReport
from textreport import TextReport
from editparam import EditParam
from settings import Settings
from simhints import SimHints

# User preferences file (if it exists)
prefsfile = '~/design/.profile/prefs.json'

# Application path (path where this script is located)
apps_path = os.path.realpath(os.path.dirname(__file__))

#------------------------------------------------------
# Simple dialog for confirming quit or upload
#------------------------------------------------------

class ConfirmDialog(tksimpledialog.Dialog):
    def body(self, master, warning, seed):
        ttk.Label(master, text=warning, wraplength=500).grid(row = 0, columnspan = 2, sticky = 'wns')
        return self

    def apply(self):
        return 'okay'

#------------------------------------------------------
# Simple dialog with no "OK" button (can only cancel)
#------------------------------------------------------

class PuntDialog(tksimpledialog.Dialog):
    def body(self, master, warning, seed):
        if warning:
            ttk.Label(master, text=warning, wraplength=500).grid(row = 0, columnspan = 2, sticky = 'wns')
        return self

    def buttonbox(self):
        # Add button box with "Cancel" only.
        box = ttk.Frame(self.obox)
        w = ttk.Button(box, text="Cancel", width=10, command=self.cancel)
        w.pack(side='left', padx=5, pady=5)
        self.bind("<Escape>", self.cancel)
        box.pack(fill='x', expand='true')

    def apply(self):
        return 'okay'

#------------------------------------------------------
# Main class for this application
#------------------------------------------------------

class CACECharacterize(ttk.Frame):
    """local characterization GUI."""

    def __init__(self, parent, *args, **kwargs):
        ttk.Frame.__init__(self, parent, *args, **kwargs)
        self.root = parent
        self.init_gui()
        parent.protocol("WM_DELETE_WINDOW", self.on_quit)

    def on_quit(self):
        """Exits program."""
        if not self.check_saved():
            warning = 'Warning:  Simulation results have not been saved.'
            confirm = ConfirmDialog(self, warning).result
            if not confirm == 'okay':
                print('Quit canceled.')
                return
        if self.logfile:
            self.logfile.close()
        quit()

    def on_mousewheel(self, event):
        if event.num == 5:
            self.datasheet_viewer.yview_scroll(1, "units")
        elif event.num == 4:
            self.datasheet_viewer.yview_scroll(-1, "units")

    def init_gui(self):
        """Builds GUI."""
        global prefsfile

        message = []
        fontsize = 11

        # Read user preferences file, get default font size from it.
        prefspath = os.path.expanduser(prefsfile)
        if os.path.exists(prefspath):
            with open(prefspath, 'r') as f:
                self.prefs = json.load(f)
            if 'fontsize' in self.prefs:
                fontsize = self.prefs['fontsize']
        else:
            self.prefs = {}

        s = ttk.Style()

        available_themes = s.theme_names()
        s.theme_use(available_themes[0])

        s.configure('bg.TFrame', background='gray40')
        s.configure('italic.TLabel', font=('Helvetica', fontsize, 'italic'))
        s.configure('title.TLabel', font=('Helvetica', fontsize, 'bold italic'),
			foreground = 'brown', anchor = 'center')
        s.configure('normal.TLabel', font=('Helvetica', fontsize))
        s.configure('red.TLabel', font=('Helvetica', fontsize), foreground = 'red')
        s.configure('green.TLabel', font=('Helvetica', fontsize), foreground = 'green3')
        s.configure('blue.TLabel', font=('Helvetica', fontsize), foreground = 'blue')
        s.configure('hlight.TLabel', font=('Helvetica', fontsize), background='gray93')
        s.configure('rhlight.TLabel', font=('Helvetica', fontsize), foreground = 'red',
			background='gray93')
        s.configure('ghlight.TLabel', font=('Helvetica', fontsize), foreground = 'green3',
			background='gray93')
        s.configure('blue.TLabel', font=('Helvetica', fontsize), foreground = 'blue')
        s.configure('blue.TMenubutton', font=('Helvetica', fontsize), foreground = 'blue',
			border = 3, relief = 'raised')
        s.configure('normal.TButton', font=('Helvetica', fontsize),
			border = 3, relief = 'raised')
        s.configure('red.TButton', font=('Helvetica', fontsize), foreground = 'red',
			border = 3, relief = 'raised')
        s.configure('green.TButton', font=('Helvetica', fontsize), foreground = 'green3',
			border = 3, relief = 'raised')
        s.configure('hlight.TButton', font=('Helvetica', fontsize),
			border = 3, relief = 'raised', background='gray93')
        s.configure('rhlight.TButton', font=('Helvetica', fontsize), foreground = 'red',
			border = 3, relief = 'raised', background='gray93')
        s.configure('ghlight.TButton', font=('Helvetica', fontsize), foreground = 'green3',
			border = 3, relief = 'raised', background='gray93')
        s.configure('blue.TButton', font=('Helvetica', fontsize), foreground = 'blue',
			border = 3, relief = 'raised')
        s.configure('redtitle.TButton', font=('Helvetica', fontsize, 'bold italic'),
			foreground = 'red', border = 3, relief = 'raised')
        s.configure('bluetitle.TButton', font=('Helvetica', fontsize, 'bold italic'),
			foreground = 'blue', border = 3, relief = 'raised')

        # Create the help window
        self.help = HelpWindow(self, fontsize = fontsize)

        with io.StringIO() as buf, contextlib.redirect_stdout(buf):
            self.help.add_pages_from_file(apps_path + '/characterize_help.txt')
            message = buf.getvalue()

        # Set the help display to the first page
        self.help.page(0)

        # Create the failure report window
        self.failreport = FailReport(self, fontsize = fontsize)

        # LVS results get a text window of results
        self.textreport = TextReport(self, fontsize = fontsize)

        # Create the settings window
        self.settings = Settings(self, fontsize = fontsize, callback = self.callback)

        # Create the simulation hints window
        self.simhints = SimHints(self, fontsize = fontsize)

        # Create the edit parameter window
        self.editparam = EditParam(self, fontsize = fontsize)

        # Variables used by option menus and other stuff
        self.origin = tkinter.StringVar(self)
        self.cur_project = tkinter.StringVar(self)
        self.cur_datasheet = "(no selection)"
        self.datatop = {}
        self.status = {}
        self.caceproc = None
        self.logfile = None

        # Root window title
        self.root.title('Characterization')
        self.root.option_add('*tearOff', 'FALSE')
        self.pack(side = 'top', fill = 'both', expand = 'true')

        pane = tkinter.PanedWindow(self, orient = 'vertical', sashrelief='groove', sashwidth=6)
        pane.pack(side = 'top', fill = 'both', expand = 'true')
        self.toppane = ttk.Frame(pane)
        self.botpane = ttk.Frame(pane)

        # Get username
        if 'username' in self.prefs:
            username = self.prefs['username']
        else:
            username = os.environ['USER']

        # Label with the user
        self.toppane.title_frame = ttk.Frame(self.toppane)
        self.toppane.title_frame.grid(column = 0, row=0, sticky = 'nswe')

        self.toppane.title_frame.title = ttk.Label(self.toppane.title_frame, text='User:', style = 'red.TLabel')
        self.toppane.title_frame.user = ttk.Label(self.toppane.title_frame, text=username, style = 'blue.TLabel')

        self.toppane.title_frame.title.grid(column=0, row=0, ipadx = 5)
        self.toppane.title_frame.user.grid(column=1, row=0, ipadx = 5)

        #---------------------------------------------
        ttk.Separator(self.toppane, orient='horizontal').grid(column = 0, row = 1, sticky = 'nswe')
        #---------------------------------------------

        self.toppane.title2_frame = ttk.Frame(self.toppane)
        self.toppane.title2_frame.grid(column = 0, row = 2, sticky = 'nswe')
        self.toppane.title2_frame.datasheet_label = ttk.Label(self.toppane.title2_frame, text="Datasheet:",
		style = 'normal.TLabel')
        self.toppane.title2_frame.datasheet_label.grid(column=0, row=0, ipadx = 5)

        # New datasheet select button
        self.toppane.title2_frame.datasheet_select = ttk.Button(self.toppane.title2_frame,
		text=self.cur_datasheet, style='normal.TButton', command=self.choose_datasheet)
        self.toppane.title2_frame.datasheet_select.grid(column=1, row=0, ipadx = 5)

        tooltip.ToolTip(self.toppane.title2_frame.datasheet_select,
			text = "Select new datasheet file")

        # Show path to datasheet
        self.toppane.title2_frame.path_label = ttk.Label(self.toppane.title2_frame, text=self.cur_datasheet,
		style = 'normal.TLabel')
        self.toppane.title2_frame.path_label.grid(column=2, row=0, ipadx = 5, padx = 10)

        # Spacer in middle moves selection button to right
        self.toppane.title2_frame.sep_label = ttk.Label(self.toppane.title2_frame, text=' ',
		style = 'normal.TLabel')
        self.toppane.title2_frame.sep_label.grid(column=3, row=0, ipadx = 5, padx = 10)
        self.toppane.title2_frame.columnconfigure(3, weight = 1)
        self.toppane.title2_frame.rowconfigure(0, weight=0)

        # Selection for origin of netlist
        self.toppane.title2_frame.origin_label = ttk.Label(self.toppane.title2_frame,
		text='Netlist from:', style = 'normal.TLabel')
        self.toppane.title2_frame.origin_label.grid(column=4, row=0, ipadx = 5, padx = 10)

        self.origin.set('Schematic Capture')
        self.toppane.title2_frame.origin_select = ttk.OptionMenu(self.toppane.title2_frame,
		self.origin, 'Schematic Capture', 'Schematic Capture', 'Layout Extracted',
		style='blue.TMenubutton', command=self.load_results)
        self.toppane.title2_frame.origin_select.grid(column=5, row=0, ipadx = 5)

        #---------------------------------------------
        ttk.Separator(self.toppane, orient='horizontal').grid(column = 0, row = 3, sticky = 'news')
        #---------------------------------------------

        # Datasheet information goes here when datasheet is loaded.
        self.mframe = ttk.Frame(self.toppane)
        self.mframe.grid(column = 0, row = 4, sticky = 'news')

        # Row 4 (mframe) is expandable, the other rows are not.
        self.toppane.rowconfigure(0, weight = 0)
        self.toppane.rowconfigure(1, weight = 0)
        self.toppane.rowconfigure(2, weight = 0)
        self.toppane.rowconfigure(3, weight = 0)
        self.toppane.rowconfigure(4, weight = 1)
        self.toppane.columnconfigure(0, weight = 1)

        #---------------------------------------------
        # ttk.Separator(self, orient='horizontal').grid(column=0, row=5, sticky='ew')
        #---------------------------------------------

        # Add a text window below the datasheet to capture output.  Redirect
        # print statements to it.

        self.botpane.console = ttk.Frame(self.botpane)
        self.botpane.console.pack(side = 'top', fill = 'both', expand = 'true')

        self.text_box = ConsoleText(self.botpane.console, wrap='word', height = 4)
        self.text_box.pack(side='left', fill='both', expand='true')
        console_scrollbar = ttk.Scrollbar(self.botpane.console)
        console_scrollbar.pack(side='right', fill='y')
        # attach console to scrollbar
        self.text_box.config(yscrollcommand = console_scrollbar.set)
        console_scrollbar.config(command = self.text_box.yview)

        # Add button bar at the bottom of the window
        self.bbar = ttk.Frame(self.botpane)
        self.bbar.pack(side = 'top', fill = 'x')
        # Progress bar expands with the window, buttons don't
        self.bbar.columnconfigure(6, weight = 1)

        # Define the "quit" button and action
        self.bbar.quit_button = ttk.Button(self.bbar, text='Close', command=self.on_quit,
		style = 'normal.TButton')
        self.bbar.quit_button.grid(column=0, row=0, padx = 5)

        # Define the save button
        self.bbar.save_button = ttk.Button(self.bbar, text='Save', command=self.save_results,
		style = 'normal.TButton')
        self.bbar.save_button.grid(column=1, row=0, padx = 5)

        # Define the save-as button
        self.bbar.saveas_button = ttk.Button(self.bbar, text='Save As', command=self.save_manual,
		style = 'normal.TButton')

	# Also a load button
        self.bbar.load_button = ttk.Button(self.bbar, text='Load', command=self.load_manual,
		style = 'normal.TButton')

        # Define help button
        self.bbar.help_button = ttk.Button(self.bbar, text='Help', command=self.help.open,
		style = 'normal.TButton')
        self.bbar.help_button.grid(column = 4, row = 0, padx = 5)

        # Define settings button
        self.bbar.settings_button = ttk.Button(self.bbar, text='Settings',
		command=self.settings.open, style = 'normal.TButton')
        self.bbar.settings_button.grid(column = 5, row = 0, padx = 5)

        # Define upload action
        self.bbar.upload_button = ttk.Button(self.bbar, text='Submit', state = 'enabled',
		command=self.upload_to_marketplace, style = 'normal.TButton')
        # "Submit" button remains unplaced;  upload may be done from the web side. . .
        # self.bbar.upload_button.grid(column = 8, row = 0, padx = 5, sticky = 'ens')

        tooltip.ToolTip(self.bbar.quit_button, text = "Exit characterization tool")
        tooltip.ToolTip(self.bbar.save_button, text = "Save current characterization state")
        tooltip.ToolTip(self.bbar.saveas_button, text = "Save current characterization state")
        tooltip.ToolTip(self.bbar.load_button, text = "Load characterization state from file")
        tooltip.ToolTip(self.bbar.help_button, text = "Start help tool")
        tooltip.ToolTip(self.bbar.settings_button, text = "Manage characterization tool settings")
        tooltip.ToolTip(self.bbar.upload_button, text = "Submit completed design to Marketplace")

        # Inside frame with main electrical parameter display and scrollbar
        # To make the frame scrollable, it must be a frame inside a canvas.
        self.datasheet_viewer = tkinter.Canvas(self.mframe)
        self.datasheet_viewer.grid(row = 0, column = 0, sticky = 'nsew')
        self.datasheet_viewer.dframe = ttk.Frame(self.datasheet_viewer,
			style='bg.TFrame')
        # Place the frame in the canvas
        self.datasheet_viewer.create_window((0,0),
			window=self.datasheet_viewer.dframe,
			anchor="nw", tags="self.frame")

        # Make sure the main window resizes, not the scrollbars.
        self.mframe.rowconfigure(0, weight = 1)
        self.mframe.columnconfigure(0, weight = 1)
        # X scrollbar for datasheet viewer
        main_xscrollbar = ttk.Scrollbar(self.mframe, orient = 'horizontal')
        main_xscrollbar.grid(row = 1, column = 0, sticky = 'nsew')
        # Y scrollbar for datasheet viewer
        main_yscrollbar = ttk.Scrollbar(self.mframe, orient = 'vertical')
        main_yscrollbar.grid(row = 0, column = 1, sticky = 'nsew')
        # Attach console to scrollbars
        self.datasheet_viewer.config(xscrollcommand = main_xscrollbar.set)
        main_xscrollbar.config(command = self.datasheet_viewer.xview)
        self.datasheet_viewer.config(yscrollcommand = main_yscrollbar.set)
        main_yscrollbar.config(command = self.datasheet_viewer.yview)

        # Make sure that scrollwheel pans window
        self.datasheet_viewer.bind_all("<Button-4>", self.on_mousewheel)
        self.datasheet_viewer.bind_all("<Button-5>", self.on_mousewheel)

        # Set up configure callback
        self.datasheet_viewer.dframe.bind("<Configure>", self.frame_configure)

        # Add the panes once the internal geometry is known
        pane.add(self.toppane)
        pane.add(self.botpane)
        pane.paneconfig(self.toppane, stretch='first')

        # Initialize variables
        self.sims_to_go = []

        # Capture time of start to compare against the annotated
        # output file timestamp.
        self.starttime = time.time()

        # Redirect stdout and stderr to the console as the last thing to do. . .
        # Otherwise errors in the GUI get sucked into the void.
        self.stdout = sys.stdout
        self.stderr = sys.stderr
        sys.stdout = ConsoleText.StdoutRedirector(self.text_box)
        sys.stderr = ConsoleText.StderrRedirector(self.text_box)

        if message:
            print(message)

    def frame_configure(self, event):
        self.update_idletasks()
        self.datasheet_viewer.configure(scrollregion=self.datasheet_viewer.bbox("all"))

    def logstart(self):
        # Start a logfile (or append to it, if it already exists)
        # Disabled by default, as it can get very large.
        # Can be enabled from Settings.
        if self.settings.get_log() == True:
            dataroot = os.path.splitext(self.cur_datasheet)[0]
            if not self.logfile:
                self.logfile = open(dataroot + '.log', 'a')

                # Print some initial information to the logfile.
                self.logprint('-------------------------')
                self.logprint('Starting new log file ' + datetime.datetime.now().strftime('%c'),
				doflush=True)

    def logstop(self):
        if self.logfile:
            self.logprint('-------------------------', doflush=True)
            self.logfile.close()
            self.logfile = []

    def logprint(self, message, doflush=False):
        if self.logfile:
            self.logfile.buffer.write(message.encode('utf-8'))
            self.logfile.buffer.write('\n'.encode('utf-8'))
            if doflush:
                self.logfile.flush()

    def set_datasheet(self, datasheet):
        if self.logfile:
            self.logprint('end of log.')
            self.logprint('-------------------------', doflush=True)
            self.logfile.close()
            self.logfile = None

        if not os.path.isfile(datasheet):
            print('Error:  File ' + datasheet + ' not found.')
            return

        [dspath, dsname] = os.path.split(datasheet)
        # Read the datasheet
        with open(datasheet) as ifile:
            try:
                datatop = json.load(ifile)
            except json.decoder.JSONDecodeError as e:
                print("Error:  Parse error reading JSON file " + datasheet + ':')
                print(str(e))
                return
            else:
                # 'request-hash' set to '.' for local simulation
                datatop['request-hash'] = '.'
        try:
            dsheet = datatop['data-sheet']
        except KeyError:
            print("Error:  JSON file is not a datasheet!\n")
        else:
            self.datatop = datatop
            self.cur_datasheet = datasheet
            self.create_datasheet_view()
            self.toppane.title2_frame.datasheet_select.configure(text=dsname)
            self.toppane.title2_frame.path_label.configure(text=datasheet)

            # Determine if there is a saved, annotated JSON file that is
            # more recent than the netlist used for simulation.
            self.load_results()

        # Attempt to set the datasheet viewer width to the interior width
        # but do not set it larger than the available desktop.
        self.update_idletasks()
        widthnow = self.datasheet_viewer.winfo_width()
        width = self.datasheet_viewer.dframe.winfo_width()
        screen_width = self.root.winfo_screenwidth()
        if width > widthnow:
            if width < screen_width - 10:
                self.datasheet_viewer.configure(width=width)
            else:
                self.datasheet_viewer.configure(width=screen_width - 10)
        elif widthnow > screen_width:
            self.datasheet_viewer.configure(width=screen_width - 10)
        elif widthnow > width:
            self.datasheet_viewer.configure(width=width)

        # Likewise for the height, up to 3/5 of the desktop height.
        height = self.datasheet_viewer.dframe.winfo_height()
        heightnow = self.datasheet_viewer.winfo_height()
        screen_height = self.root.winfo_screenheight()
        if height > heightnow:
            if height < screen_height * 0.6:
                self.datasheet_viewer.configure(height=height)
            else:
                self.datasheet_viewer.configure(height=screen_height * 0.6)
        elif heightnow > screen_height:
            self.datasheet_viewer.configure(height=screen_height - 10)
        elif heightnow > height:
            self.datasheet_viewer.configure(height=height)

    def choose_datasheet(self):
        datasheet = filedialog.askopenfilename(multiple = False,
			initialdir = os.path.expanduser('~/design'),
			filetypes = (("JSON File", "*.json"),("All Files","*.*")),
			title = "Find a datasheet.")
        if datasheet != '':
            self.set_datasheet(datasheet)

    def cancel_upload(self):
        # Post a cancelation message to CACE.  CACE responds by setting the
        # status to 'canceled'.  The watchprogress procedure is responsible for
        # returning the button to 'Submit' when the characterization finishes
        # or is canceled.
        dspath = os.path.split(self.cur_datasheet)[0]
        datasheet = os.path.split(self.cur_datasheet)[1]
        designname = os.path.splitext(datasheet)[0]
        print('Cancel characterization of ' + designname + ' (' + dspath + ' )')
        subprocess.run([apps_path + '/cace_design_upload.py', '-cancel',
                        dspath])
        self.removeprogress()
        self.bbar.upload_button.configure(text='Submit', state = 'enabled',
			command=self.upload_to_marketplace,
			style = 'normal.TButton')
        # Delete the remote status file.
        dsdir = dspath + '/ngspice/char'
        statusname = dsdir + '/remote_status.json'
        if os.path.exists(statusname):
            os.remove(statusname)

    def progress_bar_setup(self, dspath):
        # Create the progress bar at the bottom of the window to indicate
        # the status of a challenge submission.

        # Disable the Submit button
        self.bbar.upload_button.configure(state='disabled')

        # Start progress bar watchclock
        dsdir = dspath + '/ngspice/char'
        statusname = dsdir + '/remote_status.json'
        if os.path.exists(statusname):
            statbuf = os.stat(statusname)
            mtime = statbuf.st_mtime
        else:
            if os.path.exists(dsdir):
                # Write a simple status
                status = {'message': 'not started', 'total': '0', 'completed': '0'}
                with open(statusname, 'w') as f:
                    json.dump(status, f)
            mtime = 0
        # Create a TTK progress bar widget in the buttonbar.
        self.bbar.progress_label = ttk.Label(self.bbar, text="Characterization: ",
		style = 'normal.TLabel')
        self.bbar.progress_label.grid(column=4, row=0, ipadx = 5)

        self.bbar.progress_message = ttk.Label(self.bbar, text="(not started)",
		style = 'blue.TLabel')
        self.bbar.progress_message.grid(column=5, row=0, ipadx = 5)
        self.bbar.progress = ttk.Progressbar(self.bbar,
			orient='horizontal', mode='determinate')
        self.bbar.progress.grid(column = 6, row = 0, padx = 5, sticky = 'nsew')
        self.bbar.progress_text = ttk.Label(self.bbar, text="0/0",
		style = 'blue.TLabel')
        self.bbar.progress_text.grid(column=7, row=0, ipadx = 5)

        # Start the timer to watch the progress
        self.watchprogress(statusname, mtime, 1)

    def check_ongoing_upload(self):
        # Determine if an upload is ongoing when the characterization tool is
        # started.  If so, immediately go to the 'characterization running'
        # state with progress bar.
        dspath = os.path.split(self.cur_datasheet)[0]
        datasheet = os.path.split(self.cur_datasheet)[1]
        designname = os.path.splitext(datasheet)[0]
        dsdir = dspath + '/ngspice/char'
        statusname = dsdir + '/remote_status.json'
        if os.path.exists(statusname):
            with open(statusname, 'r') as f:
                status = json.load(f)
                if 'message' in status:
                    if status['message'] == 'in progress':
                        print('Design characterization in progress for ' + designname + ' (' + dspath + ' )')
                        self.progress_bar_setup(dspath)
                else:
                    print("No message in status file")

    def upload_to_marketplace(self):
        dspath = os.path.split(self.cur_datasheet)[0]
        datasheet = os.path.split(self.cur_datasheet)[1]
        dsheet = self.datatop['data-sheet']
        designname = dsheet['ip-name']

        # Make sure a netlist has been generated.
        if self.sim_param('check') == False:
            print('No netlist was generated, cannot submit!')
            return

        # For diagnostic purposes, place all of the characterization tool
        # settings into datatop['settings'] when uploading to remote CACE.
        runtime_settings = {}
        runtime_settings['force-regenerate'] = self.settings.get_force()
        runtime_settings['edit-all-params'] = self.settings.get_edit()
        runtime_settings['keep-files'] = self.settings.get_keep()
        runtime_settings['make-plots'] = self.settings.get_plot()
        runtime_settings['submit-test-mode'] = self.settings.get_test()
        runtime_settings['submit-as-schematic'] = self.settings.get_schem()
        runtime_settings['submit-failing'] = self.settings.get_submitfailed()
        runtime_settings['log-output'] = self.settings.get_log()

        # Write out runtime settings as a JSON file
        with open(dspath + '/settings.json', 'w') as file:
            json.dump(runtime_settings, file, indent = 4)

        warning = ''
        must_confirm = False
        if self.settings.get_schem() == True:
            # If a layout exists but "submit as schematic" was chosen, then
            # flag a warning and insist on confirmation.
            if os.path.exists(dspath + '/mag/' + designname + '.mag'):
                warning += 'Warning: layout exists but only schematic has been selected for submission'
                must_confirm = True
            else:
                print('No layout in ' + dspath + '/mag/' + designname + '.mag')
                print('Schematic only submission selection is not needed.')
        else:
            # Likewise, check if schematic netlist results are showing but a layout
            # exists, which means that the existing results are not the ones that are
            # going to be tested.
            if self.origin.get() == 'Schematic Capture':
                if os.path.exists(dspath + '/mag/' + designname + '.mag'):
                    warning += 'Warning: schematic results are shown but remote CACE will be run on layout results.'
                    must_confirm = True

        # Make a check to see if all simulations have been made and passed.  If so,
        # then just do the upload.  If not, then generate a warning dialog and
        # require the user to respond to force an upload in spite of an incomplete
        # simulation.  Give dire warnings if any simulation has failed.

        failures = 0
        missed = 0
        for param in dsheet['electrical-params']:
            if 'max' in param:
                pmax = param['max']
                if not 'value' in pmax:
                    missed += 1
                elif 'score' in pmax:
                    if pmax['score'] == 'fail':
                        failures += 1
            if 'min' in param:
                pmin = param['min']
                if not 'value' in pmin:
                    missed += 1
                elif 'score' in pmin:
                    if pmin['score'] == 'fail':
                        failures += 1

        if missed > 0:
            if must_confirm == True:
                warning += '\n'
            warning += 'Warning:  Not all critical parameters have been simulated.'
        if missed > 0 and failures > 0:
            warning += '\n'
        if failures > 0:
            warning += 'Dire Warning:  This design has errors on critical parameters!'

        # Require confirmation
        if missed > 0 or failures > 0:
            must_confirm = True

        if must_confirm:
            if self.settings.get_submitfailed() == True:
                confirm = ConfirmDialog(self, warning).result
            else:
                confirm = PuntDialog(self, warning).result
            if not confirm == 'okay':
                print('Upload canceled.')
                return
            print('Upload selected')

        # Save hints in file in spice/ directory.
        hintlist = []
        for eparam in dsheet['electrical-params']:
            if not 'editable' in eparam:
                if 'hints' in eparam:
                    hintlist.append(eparam['hints'])
                else:
                    # Must have a placeholder
                    hintlist.append({})
        if hintlist:
            hfilename = dspath + '/hints.json'
            with open(hfilename, 'w') as hfile:
                json.dump(hintlist, hfile, indent = 4)

        print('Uploading design ' + designname + ' (' + dspath + ' )')
        print('to marketplace and submitting for characterization.')
        if not self.settings.get_test():
            self.progress_bar_setup(dspath)
            self.update_idletasks()
        subprocess.run([apps_path + '/cace_design_upload.py', dspath])

        # Remove the settings file
        os.remove(dspath + '/settings.json')
        os.remove(dspath + '/hints.json')

    def removeprogress(self):
        # Remove the progress bar.  This is left up for a second after
        # completion or cancelation so that the final message has time
        # to be seen.
        try:
            self.bbar.progress_label.destroy()
            self.bbar.progress_message.destroy()
            self.bbar.progress.destroy()
            self.bbar.progress_text.destroy()
        except:
            pass

    def watchprogress(self, filename, filemtime, timeout):
        new_timeout = timeout + 1 if timeout > 0 else 0
        # 2 minute timeout for startup (note that all simulation files have to be
        # made during this period.
        if new_timeout == 120:
            self.cancel_upload()
            return

        # If file does not exist, then keep checking at 2 second intervals.
        if not os.path.exists(filename):
            self.after(2000, lambda: self.watchprogress(filename, filemtime, new_timeout))
            return

        # If filename file is modified, then update progress bar;
        # otherwise, restart the clock.
        statbuf = os.stat(filename)
        if statbuf.st_mtime > filemtime:
            self.after(250)	# Otherwise can catch file while it's incomplete. . .
            if self.update_progress(filename) == True:
                self.after(1000, lambda: self.watchprogress(filename, filemtime, 0))
            else:
                # Remove the progress bar when done, after letting the final
                # message display for a second.
                self.after(1500, self.removeprogress)
                # And return the button to "Submit" and in an enabled state.
                self.bbar.upload_button.configure(text='Submit', state = 'enabled',
				command=self.upload_to_marketplace,
				style = 'normal.TButton')
        else:
            self.after(1000, lambda: self.watchprogress(filename, filemtime, new_timeout))

    def update_progress(self, filename):
        # On first update, button changes from "Submit" to "Cancel"
        # This ensures that the 'remote_status.json' file has been sent
        # from the CACE with the hash value needed for the CACE to identify
        # the right simulation and cancel it.
        if self.bbar.upload_button.configure('text')[-1] == 'Submit':
            self.bbar.upload_button.configure(text='Cancel', state = 'enabled',
			command=self.cancel_upload, style = 'red.TButton')

        if not os.path.exists(filename):
            return False

        # Update the progress bar during an CACE simulation run.
        # Read the status file
        try:
            with open(filename, 'r') as f:
                status = json.load(f)
        except (PermissionError, FileNotFoundError):
            # For a very short time the user does not have ownership of
            # the file and the read will fail.  This is a rare case, so
            # just punt until the next cycle.
            return True

        if 'message' in status:
            self.bbar.progress_message.configure(text = status['message'])

        try:
            total = int(status['total'])
        except:
            total = 0
        else:
            self.bbar.progress.configure(maximum = total)

        try:
            completed = int(status['completed'])
        except:
            completed = 0
        else:
            self.bbar.progress.configure(value = completed)

        self.bbar.progress_text.configure(text = str(completed) + '/' + str(total))
        if completed > 0 and completed == total:
            print('Notice:  Design completed.')
            print('The CACE server has finished characterizing the design.')
            print('Go to the efabless marketplace to view submission.')
            return False
        elif status['message'] == 'canceled':
            print('Notice:  Design characterization was canceled.')
            return False
        else:
            return True

    def topfilter(self, line):
        # Check output for ubiquitous "Reference value" lines and remove them.
        # This happens before logging both to the file and to the console.
        refrex = re.compile('Reference value')
        rmatch = refrex.match(line)
        if not rmatch:
            return line
        else:
            return None

    def spicefilter(self, line):
        # Check for the alarmist 'tran simulation interrupted' message and remove it.
        # Check for error or warning and print as stderr or stdout accordingly.
        intrex = re.compile('tran simulation interrupted')
        warnrex = re.compile('.*warning', re.IGNORECASE)
        errrex = re.compile('.*error', re.IGNORECASE)

        imatch = intrex.match(line)
        if not imatch:
            ematch = errrex.match(line)
            wmatch = warnrex.match(line)
            if ematch or wmatch:
                print(line, file=sys.stderr)
            else:
                print(line, file=sys.stdout)

    def printwarn(self, output):
        # Check output for warning or error
        if not output:
            return 0

        warnrex = re.compile('.*warning', re.IGNORECASE)
        errrex = re.compile('.*error', re.IGNORECASE)

        errors = 0
        outlines = output.splitlines()
        for line in outlines:
            try:
                wmatch = warnrex.match(line)
            except TypeError:
                line = line.decode('utf-8')
                wmatch = warnrex.match(line)
            ematch = errrex.match(line)
            if ematch:
                errors += 1
            if ematch or wmatch:
                print(line)
        return errors

    def sim_all(self):
        if self.caceproc:
            # Failsafe
            if self.caceproc.poll() != None:
                self.caceproc = None
            else:
                print('Simulation in progress must finish first.')
                return

        # Create netlist if necessary, check for valid result
        if self.sim_param('check') == False:
            return

        # Simulate all of the electrical parameters in turn
        self.sims_to_go = []
        for puniq in self.status:
            self.sims_to_go.append(puniq)

        # Start first sim
        if len(self.sims_to_go) > 0:
            puniq = self.sims_to_go[0]
            self.sims_to_go = self.sims_to_go[1:]
            self.sim_param(puniq)

        # Button now stops the simulations
        self.allsimbutton.configure(style = 'redtitle.TButton', text='Stop Simulations',
		command=self.stop_sims)

    def stop_sims(self):
        # Make sure there will be no more simulations
        self.sims_to_go = []
        if not self.caceproc:
            print("No simulation running.")
            return
        self.caceproc.terminate()
        # Use communicate(), not wait() , on piped processes to avoid deadlock.
        try:
            self.caceproc.communicate(timeout=10)
        except subprocess.TimeoutExpired:
            self.caceproc.kill()
            self.caceproc.communicate()
            print("CACE process killed.")
        else:
            print("CACE process exited.")
        # Let watchdog timer see that caceproc is gone and reset the button.

    def edit_param(self, param):
        # Edit the conditions under which the parameter is tested.
        if ('editable' in param and param['editable'] == True) or self.settings.get_edit() == True:
            self.editparam.populate(param)
            self.editparam.open()
        else:
            print('Parameter is not editable')

    def copy_param(self, param):
        # Make a copy of the parameter (for editing)
        newparam = param.copy()
        # Make the copied parameter editable
        newparam['editable'] = True
        # Append this to the electrical parameter list after the item being copied
        if 'display' in param:
            newparam['display'] = param['display'] + ' (copy)'
        datatop = self.datatop
        dsheet = datatop['data-sheet']
        eparams = dsheet['electrical-params']
        eidx = eparams.index(param)
        eparams.insert(eidx + 1, newparam)
        self.create_datasheet_view()

    def delete_param(self, param):
        # Remove an electrical parameter from the datasheet.  This is only
        # allowed if the parameter has been copied from another and so does
        # not belong to the original set of parameters.
        datatop = self.datatop
        dsheet = datatop['data-sheet']
        eparams = dsheet['electrical-params']
        eidx = eparams.index(param)
        eparams.pop(eidx)
        self.create_datasheet_view()

    def add_hints(self, param, simbutton):
        # Raise hints window and configure appropriately for the parameter.
        # Fill in any existing hints.
        self.simhints.populate(param, simbutton)
        self.simhints.open()

    def sim_param(self, method):
        if self.caceproc:
            # Failsafe
            if self.caceproc.poll() != None:
                self.caceproc = None
            else:
                print('Simulation in progress, queued for simulation.')
                if not method in self.sims_to_go:
                    self.sims_to_go.append(method)
                return False

        # Get basic values for datasheet and ip-name

        dspath = os.path.split(self.cur_datasheet)[0]
        dsheet = self.datatop['data-sheet']
        dname = dsheet['ip-name']

        # Open log file, if specified
        self.logstart()

        # Check for whether the netlist is specified to come from schematic
        # or layout.  Add a record to the datasheet depending on whether
        # the netlist is from layout or extracted.  The settings window has
        # a checkbox to force submitting as a schematic even if layout exists.

        if self.origin.get() == 'Schematic Capture':
            dsheet['netlist-source'] = 'schematic'
        else:
            dsheet['netlist-source'] = 'layout'

        if self.settings.get_force() == True:
            dsheet['regenerate'] = 'force'

        basemethod = method.split('.')[0]
        if basemethod == 'check':	# used by submit to ensure netlist exists
            return True

        if basemethod == 'physical':
            print('Checking ' + method.split('.')[1])
        else:
            print('Simulating method = ' + basemethod)
        self.stat_label = self.status[method]
        self.stat_label.configure(text='(in progress)', style='blue.TLabel')
        # Update status now
        self.update_idletasks()

        if dspath == '':
            dspath = '.'

        print('Datasheet directory is = ' + dspath + '\n')

        # Instead of using the original datasheet, use the one in memory so that
        # it accumulates results.  A "save" button will update the original.
        if not os.path.isdir(dspath + '/ngspice'):
            os.makedirs(dspath + '/ngspice')
        dsdir = dspath + '/ngspice'
        if not os.path.isdir(dsdir):
            os.makedirs(dsdir)
        with open(dsdir + '/datasheet.json', 'w') as file:
            json.dump(self.datatop, file, indent = 4)
        # As soon as we call CACE, we will be watching the status of file
        # datasheet_anno.  So create it if it does not exist, else attempting
        # to stat a nonexistant file will cause the 1st simulation to fail.
        if not os.path.exists(dsdir + '/datasheet_anno.json'):
            open(dsdir + '/datasheet_anno.json', 'a').close()
        # Call cace_gensim with full set of options
        # First argument is the root directory
        # (Diagnostic)
        design_path = dspath + '/spice'

        print('Calling cace_gensim.py ' + dspath +
			' -local -method=' + method)

        modetext = ['-local']
        if self.settings.get_keep() == True:
            print(' -keep ')
            modetext.append('-keep')

        if self.settings.get_plot() == True:
            print(' -plot ')
            modetext.append('-plot')

        print(' -simdir=' + dsdir + ' -datasheetdir=' + dsdir + ' -designdir=' + design_path)
        print(' -layoutdir=' + dspath + '/mag' + ' -testbenchdir=' + dspath + '/testbench')
        print(' -datasheet=datasheet.json')

        self.caceproc = subprocess.Popen([apps_path + '/cace_gensim.py', dspath,
			*modetext,
			'-method=' + method,  # Call local mode w/method
			'-simdir=' + dsdir,
			'-datasheetdir=' + dsdir,
			'-designdir=' + design_path,
			'-layoutdir=' + dspath + '/mag',
			'-testbenchdir=' + dspath + '/testbench',
			'-datasheet=datasheet.json'],
          		stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=0)

        # Simulation finishes on its own time.  Use watchdog to handle.
        # Note that python "watchdog" is threaded, and tkinter is not thread-safe.
        # So watchdog is done with a simple timer loop.
        statbuf = os.stat(dsdir + '/datasheet.json')
        checktime = statbuf.st_mtime

        filename = dsdir + '/datasheet_anno.json'
        statbuf = os.stat(filename)
        self.watchclock(filename, statbuf.st_mtime, checktime)

    def watchclock(self, filename, filemtime, checktime):
        # In case simulations cleared while watchclock was pending
        if self.caceproc == None:
            return
        # Poll cace_gensim to see if it finished
        cace_status = self.caceproc.poll()
        if cace_status != None:
            try:
                output = self.caceproc.communicate(timeout=1)
            except ValueError:
                print("CACE gensim forced stop, status " + str(cace_status))
            else:
                outlines = output[0]
                errlines = output[1]
                for line in outlines.splitlines():
                    print(line.decode('utf-8'))
                for line in errlines.splitlines():
                    print(line.decode('utf-8'))
                print("CACE gensim exited with status " + str(cace_status))
        else:
            n = 0
            while True:
                self.update_idletasks()
                # Attempt to avoid infinite loop, unsure of the cause.
                n += 1
                if n > 100:
                    n = 0
                    cace_status = self.caceproc.poll()
                    if cace_status != None:
                        break
                    self.logprint("100 lines of output", doflush=True)
                    # Something went wrong.  Kill the process.
                    # self.stop_sims()
                sresult = select.select([self.caceproc.stdout, self.caceproc.stderr], [], [], 0)[0]
                if self.caceproc.stdout in sresult:
                    outstring = self.caceproc.stdout.readline().decode().strip()
                    self.logprint(outstring, doflush=True)
                    print(outstring)
                elif self.caceproc.stderr in sresult:
                    # ngspice passes back simulation time on stderr.  This ends in \r but no
                    # newline.  '\r' ends the transmission, so return.
                    # errstring = self.topfilter(self.caceproc.stderr.readline().decode().strip())
                    # if errstring:
                    #     self.logprint(errstring, doflush=True)
                    #     # Recast everything that isn't an error back into stdout.
                    #     self.spicefilter(errstring)
                    ochar = str(self.caceproc.stderr.read(1).decode())
                    if ochar == '\r':
                        print('')
                        break
                    else:
                        print(ochar, end='')
                else:
                    break

        # If filename file is modified, then call annotate;  otherwise, restart the clock.
        statbuf = os.stat(filename)
        if (statbuf.st_mtime > filemtime) or (cace_status != None):
            if cace_status != None:
                self.caceproc = None
            else:
                # Re-run to catch last output.
                self.after(500, lambda: self.watchclock(filename, statbuf.st_mtime, checktime))
                return
            if cace_status != 0:
                print('Errors encountered in simulation.')
                self.logprint('Errors in simulation, CACE status = ' + str(cace_status), doflush=True)
            self.annotate('anno', checktime)
            if len(self.sims_to_go) > 0:
                puniq = self.sims_to_go[0]
                self.sims_to_go = self.sims_to_go[1:]
                self.sim_param(puniq)
            else:
                # Button goes back to original text and command
                self.allsimbutton.configure(style = 'bluetitle.TButton',
				text='Simulate All', command = self.sim_all)
        elif not self.caceproc:
            # Process terminated by "stop"
            # Button goes back to original text and command
            self.allsimbutton.configure(style = 'bluetitle.TButton',
			text='Simulate All', command = self.sim_all)
            # Just redraw everthing so that the "(in progress)" message goes away.
            self.annotate('anno', checktime)
        else:
            self.after(500, lambda: self.watchclock(filename, filemtime, checktime))

    def clear_results(self, dsheet):
        # Remove results from the window by clearing parameter results
        paramstodo = []
        if 'electrical-params' in dsheet:
            paramstodo.extend(dsheet['electrical-params'])
        if 'physical-params' in dsheet:
            paramstodo.extend(dsheet['physical-params'])

        for param in paramstodo:
            # Fill frame with electrical parameter information
            if 'max' in param:
                maxrec = param['max']
                if 'value' in maxrec:
                    maxrec.pop('value')
                if 'score' in maxrec:
                    maxrec.pop('score')
            if 'typ' in param:
                typrec = param['typ']
                if 'value' in typrec:
                    typrec.pop('value')
                if 'score' in typrec:
                    typrec.pop('score')
            if 'min' in param:
                minrec = param['min']
                if 'value' in minrec:
                    minrec.pop('value')
                if 'score' in minrec:
                    minrec.pop('score')
            if 'results' in param:
                param.pop('results')

            if 'plot' in param:
                plotrec = param['plot']
                if 'status' in plotrec:
                    plotrec.pop('status')

        # Regenerate datasheet view
        self.create_datasheet_view()

    def annotate(self, suffix, checktime):
        # Pull results back from datasheet_anno.json.  Do NOT load this
        # file if it predates the unannotated datasheet (that indicates
        # simulator failure, and no results).
        dspath = os.path.split(self.cur_datasheet)[0]
        if dspath == '':
            dspath = '.'
        dsdir = dspath + '/ngspice'
        anno = dsdir + '/datasheet_' + suffix + '.json'
        unanno = dsdir + '/datasheet.json'

        if os.path.exists(anno):
            statbuf = os.stat(anno)
            mtimea = statbuf.st_mtime
            if checktime >= mtimea:
                # print('original = ' + str(checktime) + ' annotated = ' + str(mtimea))
                print('Error in simulation, no update to results.', file=sys.stderr)
            elif statbuf.st_size == 0:
                print('Error in simulation, no results.', file=sys.stderr)
            else:
                with open(anno, 'r') as file:
                    self.datatop = json.load(file)
        else:
            print('Error in simulation, no update to results.', file=sys.stderr)

        # Regenerate datasheet view
        self.create_datasheet_view()

        # Close log file, if it was enabled in the settings
        self.logstop()

    def save_results(self):
        # Write datasheet_save with all the locally processed results.
        dspath = os.path.split(self.cur_datasheet)[0]
        dsdir = dspath + '/ngspice'

        if self.origin.get() == 'Layout Extracted':
            jsonfile = dsdir + '/datasheet_lsave.json'
        else:
            jsonfile = dsdir + '/datasheet_save.json'

        with open(jsonfile, 'w') as ofile:
            json.dump(self.datatop, ofile, indent = 4)
        self.last_save = os.path.getmtime(jsonfile)

        # Create copy of datasheet without result data.  This is
        # the file appropriate to insert into the IP catalog
        # metadata JSON file.

        datacopy = copy.copy(self.datatop)
        dsheet = datacopy['data-sheet']
        if 'electrical-params' in dsheet:
            for eparam in dsheet['electrical-params']:
                if 'results' in eparam:
                    eparam.pop('results')

        datacopy.pop('request-hash')
        jsonfile = dsdir + '/datasheet_compact.json'
        with open(jsonfile, 'w') as ofile:
            json.dump(datacopy, ofile, indent = 4)

        print('Characterization results saved.')

    def check_saved(self):
        # Check if there is a file 'datasheet_save' and if it is more
        # recent than 'datasheet_anno'.  If so, return True, else False.

        [dspath, dsname] = os.path.split(self.cur_datasheet)
        dsdir = dspath + '/ngspice'

        if self.origin.get() == 'Layout Extracted':
            savefile = dsdir + '/datasheet_lsave.json'
        else:
            savefile = dsdir + '/datasheet_save.json'

        annofile = dsdir + '/datasheet_anno.json'
        if os.path.exists(annofile):
            annotime = os.path.getmtime(annofile)

            # If nothing has been updated since the characterization
            # tool was started, then there is no new information to save.
            if annotime < self.starttime:
                return True

            if os.path.exists(savefile):
                savetime = os.path.getmtime(savefile)
                # return True if (savetime > annotime) else False
                if savetime > annotime:
                    print("Save is more recent than sim, so no need to save.")
                    return True
                else:
                    print("Sim is more recent than save, so need to save.")
                    return False
            else:
                # There is a datasheet_anno file but no datasheet_save,
	        # so there are necessarily unsaved results.
                print("no datasheet_save, so any results have not been saved.")
                return False
        else:
            # There is no datasheet_anno file, so datasheet_save
            # is either current or there have been no simulations.
            print("no datasheet_anno, so there are no results to save.")
            return True

    def callback(self):
        # Check for manual load/save-as status from settings window (callback
        # when the settings window is closed).
        if self.settings.get_loadsave() == True:
            self.bbar.saveas_button.grid(column=2, row=0, padx = 5)
            self.bbar.load_button.grid(column=3, row=0, padx = 5)
        else:
            self.bbar.saveas_button.grid_forget()
            self.bbar.load_button.grid_forget()

    def save_manual(self, value={}):
        dspath = self.cur_datasheet
        # Set initialdir to the project where cur_datasheet is located
        dsparent = os.path.split(dspath)[0]

        datasheet = filedialog.asksaveasfilename(multiple = False,
			initialdir = dsparent,
			confirmoverwrite = True,
			defaultextension = ".json",
			filetypes = (("JSON File", "*.json"),("All Files","*.*")),
			title = "Select filename for saved datasheet.")
        with open(datasheet, 'w') as ofile:
            json.dump(self.datatop, ofile, indent = 4)

    def load_manual(self, value={}):
        dspath = self.cur_datasheet
        # Set initialdir to the project where cur_datasheet is located
        dsparent = os.path.split(dspath)[0]

        datasheet = filedialog.askopenfilename(multiple = False,
			initialdir = dsparent,
			filetypes = (("JSON File", "*.json"),("All Files","*.*")),
			title = "Find a datasheet.")
        if datasheet != '':
            try:
                with open(datasheet, 'r') as file:
                    self.datatop = json.load(file)
            except:
                print('Error in file, no update to results.', file=sys.stderr)

            else:
                # Regenerate datasheet view
                self.create_datasheet_view()

    def load_results(self, value={}):
        # Check if datasheet_save exists and is more recent than the
        # latest design netlist.  If so, load it;  otherwise, not.
        # NOTE:  Name of .spice file comes from the project 'ip-name'
        # in the datasheet.

        [dspath, dsname] = os.path.split(self.cur_datasheet)
        try:
            dsheet = self.datatop['data-sheet']
        except KeyError:
            return

        if dspath == '':
            dspath = '.'

        dsroot = dsheet['ip-name']

        # Remove any existing results from the datasheet records
        self.clear_results(dsheet)

        # Also must be more recent than datasheet
        jtime = os.path.getmtime(self.cur_datasheet)

        # dsroot = os.path.splitext(dsname)[0]

        dsdir = dspath + '/spice'

        if not os.path.exists(dsdir):
            print('Error:  Cannot find directory spice/ in path ' + dspath)

        if self.origin.get() == 'Layout Extracted':
            spifile = dsdir + '/pex/' + dsroot + '.spice'
            savesuffix = 'lsave'
        else:
            spifile = dsdir + '/' + dsroot + '.spice'
            savesuffix = 'save'

        dsdir = dspath + '/ngspice'
        savefile = dsdir + '/datasheet_' + savesuffix + '.json'

        if os.path.exists(savefile):
            savetime = os.path.getmtime(savefile)

        if os.path.exists(spifile):
            spitime = os.path.getmtime(spifile)

            if os.path.exists(savefile):
                if (savetime > spitime and savetime > jtime):
                    self.annotate(savesuffix, 0)
                    print('Characterization results loaded.')
                    # print('(' + savefile + ' timestamp = ' + str(savetime) + '; ' + self.cur_datasheet + ' timestamp = ' + str(jtime))
                else:
                    print('Saved datasheet is out-of-date, not loading')
            else:
                print('Datasheet file ' + savefile)
                print('No saved datasheet file, nothing to pre-load')
        else:
            print('No netlist file ' + spifile + '!')

        # Remove outdated datasheet.json and datasheet_anno.json to prevent
        # them from overwriting characterization document entries

        if os.path.exists(savefile):
            if savetime < jtime:
                print('Removing outdated save file ' + savefile)
                os.remove(savefile)

        savefile = dsdir + '/datasheet_anno.json'
        if os.path.exists(savefile):
            savetime = os.path.getmtime(savefile)
            if savetime < jtime:
                print('Removing outdated results file ' + savefile)
                os.remove(savefile)

        savefile = dsdir + '/datasheet.json'
        if os.path.exists(savefile):
            savetime = os.path.getmtime(savefile)
            if savetime < jtime:
                print('Removing outdated results file ' + savefile)
                os.remove(savefile)

    def create_datasheet_view(self):
        dframe = self.datasheet_viewer.dframe

        # Destroy the existing datasheet frame contents (if any)
        for widget in dframe.winfo_children():
            widget.destroy()
        self.status = {}	# Clear dictionary

        dsheet = self.datatop['data-sheet']
        if 'global-conditions' in dsheet:
            globcond = dsheet['global-conditions']
        else:
            globcond = []

        # Add basic information at the top

        n = 0
        dframe.cframe = ttk.Frame(dframe)
        dframe.cframe.grid(column = 0, row = n, sticky='ewns', columnspan = 10)

        dframe.cframe.plabel = ttk.Label(dframe.cframe, text = 'Project IP name:',
			style = 'italic.TLabel')
        dframe.cframe.plabel.grid(column = 0, row = n, sticky='ewns', ipadx = 5)
        dframe.cframe.pname = ttk.Label(dframe.cframe, text = dsheet['ip-name'],
			style = 'normal.TLabel')
        dframe.cframe.pname.grid(column = 1, row = n, sticky='ewns', ipadx = 5)
        dframe.cframe.fname = ttk.Label(dframe.cframe, text = dsheet['foundry'],
			style = 'normal.TLabel')
        dframe.cframe.fname.grid(column = 2, row = n, sticky='ewns', ipadx = 5)
        dframe.cframe.fname = ttk.Label(dframe.cframe, text = dsheet['node'],
			style = 'normal.TLabel')
        dframe.cframe.fname.grid(column = 3, row = n, sticky='ewns', ipadx = 5)
        if 'decription' in dsheet:
            dframe.cframe.pdesc = ttk.Label(dframe.cframe, text = dsheet['description'],
			style = 'normal.TLabel')
            dframe.cframe.pdesc.grid(column = 4, row = n, sticky='ewns', ipadx = 5)

        if 'UID' in self.datatop:
            n += 1
            dframe.cframe.ulabel = ttk.Label(dframe.cframe, text = 'UID:',
			style = 'italic.TLabel')
            dframe.cframe.ulabel.grid(column = 0, row = n, sticky='ewns', ipadx = 5)
            dframe.cframe.uname = ttk.Label(dframe.cframe, text = self.datatop['UID'],
			style = 'normal.TLabel')
            dframe.cframe.uname.grid(column = 1, row = n, columnspan = 5, sticky='ewns', ipadx = 5)

        n = 1
        ttk.Separator(dframe, orient='horizontal').grid(column=0, row=n, sticky='ewns', columnspan=10)

        # Title block
        n += 1
        dframe.desc_title = ttk.Label(dframe, text = 'Parameter', style = 'title.TLabel')
        dframe.desc_title.grid(column = 0, row = n, sticky='ewns')
        dframe.method_title = ttk.Label(dframe, text = 'Method', style = 'title.TLabel')
        dframe.method_title.grid(column = 1, row = n, sticky='ewns')
        dframe.min_title = ttk.Label(dframe, text = 'Min', style = 'title.TLabel')
        dframe.min_title.grid(column = 2, row = n, sticky='ewns', columnspan = 2)
        dframe.typ_title = ttk.Label(dframe, text = 'Typ', style = 'title.TLabel')
        dframe.typ_title.grid(column = 4, row = n, sticky='ewns', columnspan = 2)
        dframe.max_title = ttk.Label(dframe, text = 'Max', style = 'title.TLabel')
        dframe.max_title.grid(column = 6, row = n, sticky='ewns', columnspan = 2)
        dframe.stat_title = ttk.Label(dframe, text = 'Status', style = 'title.TLabel')
        dframe.stat_title.grid(column = 8, row = n, sticky='ewns')

        if not self.sims_to_go:
            self.allsimbutton = ttk.Button(dframe, text='Simulate All',
			style = 'bluetitle.TButton', command = self.sim_all)
        else:
            self.allsimbutton = ttk.Button(dframe, text='Stop Simulations',
			style = 'redtitle.TButton', command = self.stop_sims)
        self.allsimbutton.grid(column = 9, row=n, sticky='ewns')

        tooltip.ToolTip(self.allsimbutton, text = "Simulate all electrical parameters")

        # Make all columns equally expandable
        for i in range(10):
            dframe.columnconfigure(i, weight = 1)

        # Parse the file for electrical parameters
        n += 1
        binrex = re.compile(r'([0-9]*)\'([bodh])', re.IGNORECASE)
        paramstodo = []
        if 'electrical-params' in dsheet:
            paramstodo.extend(dsheet['electrical-params'])
        if 'physical-params' in dsheet:
            paramstodo.extend(dsheet['physical-params'])

        if self.origin.get() == 'Schematic Capture':
            isschem = True
        else:
            isschem = False

        for param in paramstodo:
            # Fill frame with electrical parameter information
            if 'method' in param:
                p = param['method']
                puniq = p + '.0'
                if puniq in self.status:
                    # This method was used before, so give it a unique identifier
                    j = 1
                    while True:
                        puniq = p + '.' + str(j)
                        if puniq not in self.status:
                            break
                        else:
                            j += 1
                else:
                    j = 0
                paramtype = 'electrical'
            else:
                paramtype = 'physical'
                p = param['condition']
                puniq = paramtype + '.' + p
                j = 0

            if 'editable' in param and param['editable'] == True:
                normlabel   = 'hlight.TLabel'
                redlabel    = 'rhlight.TLabel'
                greenlabel  = 'ghlight.TLabel'
                normbutton  = 'hlight.TButton'
                redbutton   = 'rhlight.TButton'
                greenbutton = 'ghlight.TButton'
            else:
                normlabel   = 'normal.TLabel'
                redlabel    = 'red.TLabel'
                greenlabel  = 'green.TLabel'
                normbutton  = 'normal.TButton'
                redbutton   = 'red.TButton'
                greenbutton = 'green.TButton'

            if 'display' in param:
                dtext = param['display']
            else:
                dtext = p

            # Special handling:  Change LVS_errors to "device check" when using
            # schematic netlist.
            if paramtype == 'physical':
                if isschem:
                    if p == 'LVS_errors':
                        dtext = 'Invalid device check'

            dframe.description = ttk.Label(dframe, text = dtext, style = normlabel)

            dframe.description.grid(column = 0, row=n, sticky='ewns')
            dframe.method = ttk.Label(dframe, text = p, style = normlabel)
            dframe.method.grid(column = 1, row=n, sticky='ewns')
            if 'plot' in param:
                status_style = normlabel
                dframe.plots = ttk.Frame(dframe)
                dframe.plots.grid(column = 2, row=n, columnspan = 6, sticky='ewns')
                plotrec = param['plot']
                if 'status' in plotrec:
                    status_value = plotrec['status']
                else:
                    status_value = '(not checked)'
                dframe_plot = ttk.Label(dframe.plots, text=plotrec['filename'],
				style = normlabel)
                dframe_plot.grid(column = j, row = n, sticky='ewns')
            else:
                # For schematic capture, mark physical parameters that can't and won't be
                # checked as "not applicable".
                status_value = '(not checked)'
                if paramtype == 'physical':
                    if isschem:
                       if p == 'area' or p == 'width' or p == 'height' or p == 'DRC_errors':
                           status_value = '(N/A)'

                if 'min' in param:
                    status_style = normlabel
                    pmin = param['min']
                    if 'target' in pmin:
                        if 'unit' in param and not binrex.match(param['unit']):
                            targettext = pmin['target'] + ' ' + param['unit']
                        else:
                            targettext = pmin['target']
                        # Hack for use of min to change method of scoring
                        if not 'penalty' in pmin or pmin['penalty'] != '0':
                            dframe.min = ttk.Label(dframe, text=targettext, style = normlabel)
                        else:
                            dframe.min = ttk.Label(dframe, text='(no limit)', style = normlabel)
                    else:
                        dframe.min = ttk.Label(dframe, text='(no limit)', style = normlabel)
                    if 'score' in pmin:
                        if pmin['score'] != 'fail':
                            status_style = greenlabel
                            if status_value != 'fail':
                                status_value = 'pass'
                        else:
                            status_style = redlabel
                            status_value = 'fail'
                    if 'value' in pmin:
                        if 'unit' in param and not binrex.match(param['unit']):
                            valuetext = pmin['value'] + ' ' + param['unit']
                        else:
                            valuetext = pmin['value']
                        dframe.value = ttk.Label(dframe, text=valuetext, style=status_style)
                        dframe.value.grid(column = 3, row=n, sticky='ewns')
                else:
                    dframe.min = ttk.Label(dframe, text='(no limit)', style = normlabel)
                dframe.min.grid(column = 2, row=n, sticky='ewns')
                if 'typ' in param:
                    status_style = normlabel
                    ptyp = param['typ']
                    if 'target' in ptyp:
                        if 'unit' in param and not binrex.match(param['unit']):
                            targettext = ptyp['target'] + ' ' + param['unit']
                        else:
                            targettext = ptyp['target']
                        dframe.typ = ttk.Label(dframe, text=targettext, style = normlabel)
                    else:
                        dframe.typ = ttk.Label(dframe, text='(no target)', style = normlabel)
                    if 'score' in ptyp:
                        # Note:  You can't fail a "typ" score, but there is only one "Status",
                        # so if it is a "fail", it must remain a "fail".
                        if ptyp['score'] != 'fail':
                            status_style = greenlabel
                            if status_value != 'fail':
                                status_value = 'pass'
                        else:
                            status_style = redlabel
                            status_value = 'fail'
                    if 'value' in ptyp:
                        if 'unit' in param and not binrex.match(param['unit']):
                            valuetext = ptyp['value'] + ' ' + param['unit']
                        else:
                            valuetext = ptyp['value']
                        dframe.value = ttk.Label(dframe, text=valuetext, style=status_style)
                        dframe.value.grid(column = 5, row=n, sticky='ewns')
                else:
                    dframe.typ = ttk.Label(dframe, text='(no target)', style = normlabel)
                dframe.typ.grid(column = 4, row=n, sticky='ewns')
                if 'max' in param:
                    status_style = normlabel
                    pmax = param['max']
                    if 'target' in pmax:
                        if 'unit' in param and not binrex.match(param['unit']):
                            targettext = pmax['target'] + ' ' + param['unit']
                        else:
                            targettext = pmax['target']
                        # Hack for use of max to change method of scoring
                        if not 'penalty' in pmax or pmax['penalty'] != '0':
                            dframe.max = ttk.Label(dframe, text=targettext, style = normlabel)
                        else:
                            dframe.max = ttk.Label(dframe, text='(no limit)', style = normlabel)
                    else:
                        dframe.max = ttk.Label(dframe, text='(no limit)', style = normlabel)
                    if 'score' in pmax:
                        if pmax['score'] != 'fail':
                            status_style = greenlabel
                            if status_value != 'fail':
                                status_value = 'pass'
                        else:
                            status_style = redlabel
                            status_value = 'fail'
                    if 'value' in pmax:
                        if 'unit' in param and not binrex.match(param['unit']):
                            valuetext = pmax['value'] + ' ' + param['unit']
                        else:
                            valuetext = pmax['value']
                        dframe.value = ttk.Label(dframe, text=valuetext, style=status_style)
                        dframe.value.grid(column = 7, row=n, sticky='ewns')
                else:
                    dframe.max = ttk.Label(dframe, text='(no limit)', style = normlabel)
                dframe.max.grid(column = 6, row=n, sticky='ewns')

            if paramtype == 'electrical':
                if 'hints' in param:
                    simtext = '\u2022Simulate'
                else:
                    simtext = 'Simulate'
            else:
                simtext = 'Check'

            simbutton = ttk.Menubutton(dframe, text=simtext, style = normbutton)

            # Generate pull-down menu on Simulate button.  Most items apply
            # only to electrical parameters (at least for now)
            simmenu = tkinter.Menu(simbutton)
            simmenu.add_command(label='Run',
			command = lambda puniq=puniq: self.sim_param(puniq))
            simmenu.add_command(label='Stop', command = self.stop_sims)
            if paramtype == 'electrical':
                simmenu.add_command(label='Hints',
			command = lambda param=param, simbutton=simbutton: self.add_hints(param, simbutton))
                simmenu.add_command(label='Edit',
			command = lambda param=param: self.edit_param(param))
                simmenu.add_command(label='Copy',
			command = lambda param=param: self.copy_param(param))
                if 'editable' in param and param['editable'] == True:
                    simmenu.add_command(label='Delete',
				command = lambda param=param: self.delete_param(param))

            # Attach the menu to the button
            simbutton.config(menu=simmenu)

            # simbutton = ttk.Button(dframe, text=simtext, style = normbutton)
            #		command = lambda puniq=puniq: self.sim_param(puniq))

            simbutton.grid(column = 9, row=n, sticky='ewns')

            if paramtype == 'electrical':
                tooltip.ToolTip(simbutton, text = "Simulate one electrical parameter")
            else:
                tooltip.ToolTip(simbutton, text = "Check one physical parameter")

            # If 'pass', then just display message.  If 'fail', then create a button that
            # opens and configures the failure report window.
            if status_value == '(not checked)':
                bstyle=normbutton
                stat_label = ttk.Label(dframe, text=status_value, style=bstyle)
            else:
                if status_value == 'fail':
                    bstyle=redbutton
                else:
                    bstyle=greenbutton
                if paramtype == 'electrical':
                    stat_label = ttk.Button(dframe, text=status_value, style=bstyle,
				command = lambda param=param, globcond=globcond:
				self.failreport.display(param, globcond,
				self.cur_datasheet))
                elif p == 'LVS_errors':
                    dspath = os.path.split(self.cur_datasheet)[0]
                    datasheet = os.path.split(self.cur_datasheet)[1]
                    dsheet = self.datatop['data-sheet']
                    designname = dsheet['ip-name']
                    if self.origin.get() == 'Schematic Capture':
                        lvs_file = dspath + '/mag/precheck.log'
                    else:
                        lvs_file = dspath + '/mag/comp.out'
                    if not os.path.exists(lvs_file):
                        if os.path.exists(dspath + '/mag/precheck.log'):
                            lvs_file = dspath + '/mag/precheck.log'
                        elif os.path.exists(dspath + '/mag/comp.out'):
                            lvs_file = dspath + '/mag/comp.out'

                    stat_label = ttk.Button(dframe, text=status_value, style=bstyle,
				command = lambda lvs_file=lvs_file: self.textreport.display(lvs_file))
                else:
                    stat_label = ttk.Label(dframe, text=status_value, style=bstyle)
                tooltip.ToolTip(stat_label,
			text = "Show detail view of simulation conditions and results")
            stat_label.grid(column = 8, row=n, sticky='ewns')
            self.status[puniq] = stat_label
            n += 1

        for child in dframe.winfo_children():
            child.grid_configure(ipadx = 5, ipady = 1, padx = 2, pady = 2)

        # Check if a design submission and characterization may be in progress.
        # If so, add the progress bar at the bottom.
        self.check_ongoing_upload()

if __name__ == '__main__':
    faulthandler.register(signal.SIGUSR2)
    options = []
    arguments = []
    for item in sys.argv[1:]:
        if item.find('-', 0) == 0:
            options.append(item)
        else:
            arguments.append(item)

    root = tkinter.Tk()
    app = CACECharacterize(root)
    if arguments:
        print('Calling set_datasheet with argument ' + arguments[0])
        app.set_datasheet(arguments[0])

    root.mainloop()


"""General Utility Functions to be used throughout the package"""

from typing import Union

from requests.structures import CaseInsensitiveDict

def clean_headers(headers: Union[dict, CaseInsensitiveDict]):
    """Cleans the headers to hide sensitive information in logs.
    Originally defined in canvasapi/util.py:
    https://github.com/ucfopen/canvasapi/blob/develop/canvasapi/util.py#L228

    Args:
        headers (dict): The headers to sanitize

    Returns:
        _type_: _description_
    """
    cleaned_headers = headers.copy()

    authorization_header = headers.get("Authorization")
    if authorization_header:
        sanitized = "****" + authorization_header[-4:]
        cleaned_headers["Authorization"] = sanitized

    return cleaned_headers