Added more features to the import dialog and treeview now displays all subprojects recursively
diff --git a/common/project_manager.py b/common/project_manager.py
index e8c0aa4..c186d17 100755
--- a/common/project_manager.py
+++ b/common/project_manager.py
@@ -277,7 +277,7 @@
         #TODO: Replace with PREFIX
         for pdkdir_lr in glob.glob('/usr/share/pdk/*/libs.tech/'):
             pdkdir = os.path.split( os.path.split( pdkdir_lr )[0])[0]    # discard final .../libs.tech/
-            (foundry, foundry_name, node, desc, status) = OpenGalaxyManager.pdkdir2fnd( pdkdir )
+            (foundry, foundry_name, node, desc, status) = ProjectManager.pdkdir2fnd( pdkdir )
             if not foundry or not node:
                 continue
             key = foundry + '/' + node
@@ -582,46 +582,144 @@
 #------------------------------------------------------
 
 class ImportDialog(tksimpledialog.Dialog):
-    def body(self, master, warning, seed):
+    def body(self, master, warning, seed, parent_pdk, parent_path, project_dir):
+        self.badrex1 = re.compile("^\.")
+        self.badrex2 = re.compile(".*[/ \t\n\\\><\*\?].*")
+        
+        self.projectpath = ""
+        self.project_pdkdir = ""
+        self.foundry = ""
+        self.node = ""
+        self.parentpdk = parent_pdk
+        self.parentpath = parent_path
+        self.projectdir = project_dir #folder that contains all projects
+        
         if warning:
             ttk.Label(master, text=warning).grid(row = 0, columnspan = 2, sticky = 'wns')
         ttk.Label(master, text="Enter new project name:").grid(row = 1, column = 0)
-        self.nentry = ttk.Entry(master)
+        
+        self.entry_v = tkinter.StringVar()
+        
+        self.nentry = ttk.Entry(master, textvariable = self.entry_v)
         self.nentry.grid(row = 1, column = 1, sticky = 'ewns')
-        self.projectpath = ""
+        
+        self.entry_v.trace('w', self.text_validate)
+        
+
         ttk.Button(master,
                         text = "Choose Project...",
                         command = self.browseFiles).grid(row = 3, column = 0)
                         
-        self.pathlabel = ttk.Label(master, text = ("No project selected" if self.projectpath =="" else self.projectpath), style = 'brown.TLabel', wraplength=250)
+        self.pathlabel = ttk.Label(master, text = ("No project selected" if self.projectpath =="" else self.projectpath), style = 'red.TLabel', wraplength=250)
         
         self.pathlabel.grid(row = 3, column = 1)
         
-
+        ttk.Label(master, text="Foundry/node:").grid(row = 4, column = 0)
+        
+        self.pdklabel = ttk.Label(master, text="N/A", style = 'red.TLabel')
+        self.pdklabel.grid(row = 4, column = 1)
+        
+        self.importoption = tkinter.StringVar()
+        
+        self.importoption.set(("copy" if parent_pdk!='' else "link"))
+        
+        self.linkbutton = ttk.Radiobutton(master, text="Make symbolic link", variable=self.importoption, value="link")
+        self.linkbutton.grid(row = 5, column = 0)
+        ttk.Radiobutton(master, text="Copy project", variable=self.importoption, value="copy").grid(row = 5, column = 1)
+        
+        self.error_label = ttk.Label(master, text="", style = 'red.TLabel', wraplength=300)
+        self.error_label.grid(row = 6, column = 0, columnspan = 2)
+        
+        self.entry_error = ttk.Label(master, text="", style = 'red.TLabel', wraplength=300)
+        self.entry_error.grid(row = 2, column = 0, columnspan = 2)
+        
         return self.nentry
     
+    def text_validate(self, *args):
+        newname = self.entry_v.get()
+        projectpath = ''
+        if self.parentpath!='':
+            projectpath = self.parentpath + '/subcells/' + newname
+        else:
+            projectpath = self.projectdir + '/' + newname
+             
+        if ProjectManager.blacklisted( newname):
+            self.entry_error.configure(text = newname + ' is not allowed for a project name.')
+        elif newname == "":
+            self.entry_error.configure(text = "")
+        elif self.badrex1.match(newname):
+            self.entry_error.configure(text =  'project name may not start with "."')
+        elif self.badrex2.match(newname):
+            self.entry_error.configure(text = 'project name contains illegal characters or whitespace.')
+        elif os.path.exists(projectpath):
+            self.entry_error.configure(text = newname + ' is already a project name.')
+        else:
+            self.entry_error.configure(text = '')
+            return True
+        return False
+        
+    def validate(self, *args):
+        return self.text_validate(self) and self.pdk_validate(self)
+        
     def browseFiles(self):
         initialdir = "~/"
         if os.path.isdir(self.projectpath):
             initialdir = os.path.split(self.projectpath)[0]
             
-        selected_dir = filedialog.askdirectory(initialdir = initialdir, title = "Select a Project",)
+        selected_dir = filedialog.askdirectory(initialdir = initialdir, title = "Select a Project to Import",)
                                           
         if os.path.isdir(str(selected_dir)):
+            self.error_label.configure(text = '')
+            self.linkbutton.configure(state="normal")
+            
             self.projectpath = selected_dir
-            self.pathlabel.configure(text=self.projectpath)
+            self.pathlabel.configure(text=self.projectpath, style = 'blue.TLabel')
             # Change label contents
             if (self.nentry.get() == ''):
                 self.nentry.insert(0, os.path.split(self.projectpath)[1])
                 
+            self.pdk_validate(self)
+    
+    def pdk_validate(self, *args):
+        if not os.path.exists(self.projectpath):
+            self.error_label.configure(text = 'Invalid directory')
+            return False
+            
+        #Find project pdk
+        if os.path.exists(self.projectpath + '/.config/techdir') or os.path.exists(self.projectpath + '/.ef-config/techdir'):
+            self.project_pdkdir = os.path.realpath(self.projectpath + ProjectManager.config_path( self.projectpath) + '/techdir')
+            self.foundry, foundry_name, self.node, desc, status = ProjectManager.pdkdir2fnd( self.project_pdkdir )
+        else:
+            if not os.path.exists(self.projectpath + '/info.yaml'):
+                self.error_label.configure(text = self.projectpath + ' does not contain an info.yaml file.')
+                self.project_pdkdir = ""
+                self.foundry = ""
+                self.node = ""
+            else:                    
+                self.project_pdkdir, self.foundry, self.node = ProjectManager.get_import_pdk( self.projectpath)
+            
+        if self.project_pdkdir == "":
+            self.pdklabel.configure(text="Not found", style='red.TLabel')
+            return False
+        else:
+            if (self.parentpdk!="" and self.parentpdk != self.foundry + '/' + self.node):
+                self.importoption.set("copy")
+                self.linkbutton.configure(state="disabled")
+                self.error_label.configure(text = 'Warning: Parent project uses '+self.parentpdk+' instead of '+self.foundry + '/' + self.node+'. The imported project will be copied and cleaned.')
+            self.pdklabel.configure(text=self.foundry + '/' + self.node, style='blue.TLabel')     
+            return True
+        
+    
     def apply(self):
-        return self.nentry.get(), self.projectpath
+        return self.nentry.get(), self.project_pdkdir, self.projectpath, self.importoption.get()
+    
+    
         
 #------------------------------------------------------
 # Project Manager class
 #------------------------------------------------------
 
-class OpenGalaxyManager(ttk.Frame):
+class ProjectManager(ttk.Frame):
     """Project Management GUI."""
 
     def __init__(self, parent, *args, **kwargs):
@@ -1072,8 +1170,9 @@
             tooltip.ToolTip(self.toppane.appbar.layout_button, text="Start 'KLayout' layout editor")
         else:
             tooltip.ToolTip(self.toppane.appbar.layout_button, text="Start 'Magic' layout editor")
-            
-    def config_path(self, path):
+    
+    @classmethod
+    def config_path(cls, path):
         #returns the config directory that 'path' contains between .config and .ef-config
         if (os.path.exists(path + '/.config')):
             return '/.config'
@@ -1084,8 +1183,9 @@
     #------------------------------------------------------------------------
     # Check if a name is blacklisted for being a project folder
     #------------------------------------------------------------------------
-
-    def blacklisted(self, dirname):
+    
+    @classmethod
+    def blacklisted(cls, dirname):
         # Blacklist:  Do not show files of these names:
         blacklist = [importdir, 'ip', 'upload', 'export', 'lost+found', 'subcells']
         if dirname in blacklist:
@@ -1190,17 +1290,19 @@
      
         projectlist = []
         
+        def add_projects(projectpath):
+            # Recursively add subprojects to projectlist
+            projectlist.append(projectpath)
+            #check for subprojects and add them
+            if os.path.isdir(projectpath + '/subcells'):
+                for subproj in os.listdir(projectpath + '/subcells'):
+                    if os.path.isdir(projectpath + '/subcells/' + subproj):
+                        add_projects(projectpath + '/subcells/' + subproj)
+        
         for item in os.listdir(self.projectdir):
             if os.path.isdir(self.projectdir + '/' + item):
                 projectpath = self.projectdir + '/' + item
-                projectlist.append(projectpath)
-                
-                #check for subprojects and add them
-                if os.path.isdir(projectpath + '/subcells'):
-                    for subproj in os.listdir(projectpath + '/subcells'):
-                        if os.path.isdir(projectpath + '/subcells/' + subproj):
-                            projectlist.append(projectpath + '/subcells/' + subproj)
-            
+                add_projects(projectpath)
          
                         
         # 'import' and others in the blacklist are not projects!
@@ -1476,9 +1578,11 @@
 
         return pdkdir
 #------------------------------------------------------------------------
-    # Get PDK directory for projects to be imported (without a techdir)
+    # Get PDK directory for projects without a techdir (most likely the project is being imported)
     #------------------------------------------------------------------------
-    def get_import_pdk(self, projectpath):
+    @classmethod
+    def get_import_pdk(cls, projectpath):
+        print(projectpath)
         yamlname = projectpath + '/info.yaml'
        
         with open(yamlname, 'r') as f:
@@ -1491,7 +1595,7 @@
        
         for pdkdir_lr in glob.glob('/usr/share/pdk/*/libs.tech/'):
             pdkdir = os.path.split( os.path.split( pdkdir_lr )[0])[0]
-            foundry, foundry_name, node, desc, status = OpenGalaxyManager.pdkdir2fnd( pdkdir )
+            foundry, foundry_name, node, desc, status = ProjectManager.pdkdir2fnd( pdkdir )
             if not foundry or not node:
                 continue
             if (foundry == project_foundry or foundry_name == project_foundry) and node == project_process:
@@ -2143,14 +2247,13 @@
     # Create info.yaml file (automatically done in create_project.py in case it's executed from the command line)
     #------------------------------------------------------------------------
    
-    def create_yaml(self, ipname, pname, description="(Add project description here)"):
+    def create_yaml(self, ipname, pdk_dir, description="(Add project description here)"):
         # ipname: Project Name
-        # pname: PDK directory
         data = {}
         project={}
         project['description'] = description
         try:
-            project['foundry'], foundry_name, project['process'], pdk_desc, pdk_stat = self.pdkdir2fnd( pname )
+            project['foundry'], foundry_name, project['process'], pdk_desc, pdk_stat = self.pdkdir2fnd( pdk_dir )
         except:
             # Cannot parse PDK name, so foundry and node will remain undefined
             pass
@@ -2887,6 +2990,10 @@
         confirm = ConfirmDialog(self, warning).result
         if not confirm == 'okay':
             return
+        else:
+            self.clean(ppath)
+            
+    def clean(self, ppath):
         if os.path.isdir(ppath + '/simulation'):
             simpath = 'simulation'
         elif os.path.isdir(ppath + '/ngspice'):
@@ -3390,7 +3497,7 @@
                     self.projNameBadrex2.match(name))
 
 #----------------------------------------------------------------------
-    # Import/link a project or subproject to the project manager
+    # Import a project or subproject to the project manager
     #----------------------------------------------------------------------
     
     def importproject(self, value):
@@ -3399,8 +3506,9 @@
         badrex2 = re.compile(".*[/ \t\n\\\><\*\?].*")
         print(warning)
         
-        # Find out whether the user wants to import a subproject or project
+        # Find out whether the user wants to import a subproject or project based on what they selected in the treeview
         parent_pdk = ''
+        parent_path = ''
         try:
             with open(os.path.expanduser(currdesign), 'r') as f:
                 pdirCur = f.read().rstrip()
@@ -3424,7 +3532,7 @@
         
         while True:
             try:
-                newname, projectpath = ImportDialog(self, warning, seed='').result
+                newname, project_pdkdir, projectpath, importoption = ImportDialog(self, warning, seed='', parent_pdk = parent_pdk, parent_path = parent_path, project_dir = self.projectdir).result
             except TypeError:
                 # TypeError occurs when "Cancel" is pressed, just handle exception.
                 return None
@@ -3435,53 +3543,51 @@
                 newproject = self.projectdir + '/' + newname
             else:
                 newproject = parent_path + '/subcells/' + newname
-            
-            if self.blacklisted(newname):
-                warning = newname + ' is not allowed for a project name.'
-            elif badrex1.match(newname):
-                warning = 'project name may not start with "."'
-            elif badrex2.match(newname):
-                warning = 'project name contains illegal characters or whitespace.'
-            elif os.path.exists(newproject):
-                warning = newname + ' is already a project name.'
-            elif not os.path.exists(projectpath + '/info.yaml'):
-                warning = projectpath + 'does not contain an info.yaml file.'
-            else:
-                techdir_exists = False
-                if os.path.exists(projectpath + '/.config/techdir'):
-                    project_pdkdir = os.path.realpath(projectpath + '/.config/techdir')
-                    techdir_exists = True
-                else:
-                    project_pdkdir, foundry, node = self.get_import_pdk(projectpath)
-                
-                if project_pdkdir == '':
-                    warning = 'Could not find PDK directory for ' + projectpath
-                elif parent_pdk!='' and project_pdkdir!=parent_pdkdir:
-                    warning = 'Parent PDK is different from PDK for ' + projectpath
-                else:
-                    break
-        if not techdir_exists:
+            break
+        
+        def make_techdirs(projectpath, project_pdkdir):
+            # Recursively create techdirs in project and subproject folders
             if not (os.path.exists(projectpath + '/.config') or os.path.exists(projectpath + '/.ef-config')):
                 os.makedirs(projectpath + '/.config')
-            os.symlink(project_pdkdir, projectpath + self.config_path(projectpath) + '/techdir')
+            if not os.path.exists(projectpath + self.config_path(projectpath) + '/techdir'):
+                os.symlink(project_pdkdir, projectpath + self.config_path(projectpath) + '/techdir')
+            if os.path.isdir(projectpath + '/subcells'):
+                for subproject in os.listdir(projectpath + '/subcells'):
+                    subproject_path = projectpath + '/subcells/' + subproject
+                    make_techdirs(subproject_path, project_pdkdir)
+                    
+        make_techdirs(projectpath, project_pdkdir)
         
-        # Make techdirs if necessary in the subprojects
-        for subproject in os.listdir(projectpath + '/subcells'):
-            subproject_path = projectpath + '/subcells/' + subproject
-            if not (os.path.exists(subproject_path + '/.config') or os.path.exists(subproject_path + '/.ef-config')):
-                os.makedirs(subproject_path + '/.config')
-                os.symlink(project_pdkdir, subproject_path + '/.config/techdir')
-            elif not os.path.exists(subproject_path + self.config_path(subproject_path) + '/techdir'):
-                os.symlink(project_pdkdir, subproject_path + self.config_path(subproject_path) + '/techdir')
-            
-        #Make symbolic link to imported project
-        if parent_pdk=='':
-            os.symlink(projectpath, self.projectdir + '/' + newname)
+        # Make symbolic link/copy projects
+        if parent_path=='':
+            # Create a regular project
+            if importoption == "link":
+                os.symlink(projectpath, self.projectdir + '/' + newname)
+            else:
+                print("Importing project...this may take a while if the project is large.")
+                shutil.copytree(projectpath, self.projectdir + '/' + newname, symlinks = True)
+            if not os.path.exists(projectpath + '/info.yaml'):
+                yData = self.create_yaml(newname, project_pdkdir)                             
+                with open(projectpath + '/info.yaml', 'w') as ofile:
+                    print('---',file=ofile)
+                    yaml.dump(yData, ofile)
         else:
+            #Create a subproject
             if not os.path.exists(parent_path + '/subcells'):
                 os.makedirs(parent_path + '/subcells')
-            os.symlink(projectpath, parent_path + '/subcells/' + newname)
-            self.update_project_views()
+            if importoption == "copy":
+                print("Importing subproject...this may take a while if the project is large.")
+                shutil.copytree(projectpath, parent_path + '/subcells/' + newname, symlinks = True)
+                if parent_pdkdir != project_pdkdir:
+                    self.clean(parent_path + '/subcells/' + newname)
+            else:
+                os.symlink(projectpath, parent_path + '/subcells/' + newname)
+            if not os.path.exists(parent_path + '/subcells/' + newname + '/info.yaml'):
+                yData = self.create_yaml(newname, project_pdkdir)                             
+                with open(parent_path + '/subcells/' + newname + '/info.yaml', 'w') as ofile:
+                    print('---',file=ofile)
+                    yaml.dump(yData, ofile)
+            self.update_project_views() 
         #----------------------------------------------------------------------
     # "Import As" a dir in import/ as a project. based on renameproject().
     # addWarn is used to augment confirm-dialogue if redirected here via erroneous ImportInto
@@ -4341,7 +4447,7 @@
 
 # main app. fyi: there's a 2nd/earlier __main__ section for splashscreen
 if __name__ == '__main__':
-    OpenGalaxyManager(root)
+    ProjectManager(root)
     if deferLoad:
         # Without this, mainloop may find&run very short clock-delayed events BEFORE main form display.
         # With it 1st project-load can be scheduled using after-time=0 (needn't tune a delay like 100ms).
diff --git a/common/treeviewchoice.py b/common/treeviewchoice.py
index 6b28a47..b709eba 100755
--- a/common/treeviewchoice.py
+++ b/common/treeviewchoice.py
@@ -162,6 +162,8 @@
             # optionally: Mark directories with trailing slash
             if self.markDir and os.path.isdir(item):
                 origname += "/"
+            if os.path.islink(item):
+                origname += " (link)"
             
             if ('subcells' not in item):
                 mode = 'even' if mode == 'odd' else 'odd'
@@ -225,20 +227,27 @@
     # though, or else tuples will have to be generated differently.
 
     def populate2(self, title, itemlist=[], valuelist=[]):
-        # Populate another column
+        # Populate the pdk column
         self.treeView.heading(1, text = title)
         self.treeView.column(1, anchor='center')
         children=list(self.getlist()) 
         
         # Add id's of subprojects
         i=1
-        for c in list(self.getlist()):
-            grandchildren=self.treeView.get_children(item=c)
+        def add_ids(grandchildren):
+            # Recursively add id's of all descendants to the list
+            nonlocal i
             for g in grandchildren:
                 children.insert(i,g)
                 i+=1
+                descendants=self.treeView.get_children(item=g)
+                add_ids(descendants)
             i+=1
         
+        for c in list(self.getlist()):
+            grandchildren=self.treeView.get_children(item=c)
+            add_ids(grandchildren)
+        
         n = 0
         for item in valuelist:
             child = children[n]