Removed IP and Import windows from project manager screen. Added an import button that brings up a file explorer for users to import subprojects or projects with a symbolic link. Small treeview UI changes.
diff --git a/common/project_manager.py b/common/project_manager.py index 50c09a2..e8c0aa4 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, node, desc, status) = OpenGalaxyManager.pdkdir2fnd( pdkdir ) + (foundry, foundry_name, node, desc, status) = OpenGalaxyManager.pdkdir2fnd( pdkdir ) if not foundry or not node: continue key = foundry + '/' + node @@ -578,6 +578,46 @@ return 'okay' #------------------------------------------------------ +# Dialog to import a project into the project manager +#------------------------------------------------------ + +class ImportDialog(tksimpledialog.Dialog): + def body(self, master, warning, seed): + 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.nentry.grid(row = 1, column = 1, sticky = 'ewns') + self.projectpath = "" + 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.grid(row = 3, column = 1) + + + return self.nentry + + 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",) + + if os.path.isdir(str(selected_dir)): + self.projectpath = selected_dir + self.pathlabel.configure(text=self.projectpath) + # Change label contents + if (self.nentry.get() == ''): + self.nentry.insert(0, os.path.split(self.projectpath)[1]) + + def apply(self): + return self.nentry.get(), self.projectpath + +#------------------------------------------------------ # Project Manager class #------------------------------------------------------ @@ -746,20 +786,21 @@ self.projectselect = TreeViewChoice(self.toppane, fontsize=fontsize, deferLoad=deferLoad, selectVal=pdirCur, natSort=True) self.projectselect.populate("Available Projects:", projectlist, [["New", True, self.createproject], + ["Import", True, self.importproject], ["Flow", False, self.synthesize], ["Copy", False, self.copyproject], ["Rename", False, self.renameproject], - ["Delete", False, self.deleteproject]], + ["Delete", False, self.deleteproject],], height=height, columns=[0, 1]) self.projectselect.grid(row = 3, sticky = 'news') self.projectselect.bindselect(self.setcurrent) tooltip.ToolTip(self.projectselect.get_button(0), text="Create a new project/subproject") - #TODO: Try to have tooltip display what type of flow if possible - tooltip.ToolTip(self.projectselect.get_button(1), text="Start design flow") - tooltip.ToolTip(self.projectselect.get_button(2), text="Make a copy of an entire project") - tooltip.ToolTip(self.projectselect.get_button(3), text="Rename a project folder") - tooltip.ToolTip(self.projectselect.get_button(4), text="Delete an entire project") + tooltip.ToolTip(self.projectselect.get_button(1), text="Import a project/subproject") + tooltip.ToolTip(self.projectselect.get_button(2), text="Start design flow") + tooltip.ToolTip(self.projectselect.get_button(3), text="Make a copy of an entire project") + tooltip.ToolTip(self.projectselect.get_button(4), text="Rename a project folder") + tooltip.ToolTip(self.projectselect.get_button(5), text="Delete an entire project") pdklist = self.get_pdk_list(projectlist) self.projectselect.populate2("PDK", projectlist, pdklist) @@ -842,6 +883,7 @@ ttk.Separator(self.toppane, orient='horizontal').grid(row = 6, sticky = 'news') #--------------------------------------------- # List of IP libraries: + ''' self.toppane.library_frame = ttk.Frame(self.toppane) self.toppane.library_frame.grid(row = 7, sticky = 'news') @@ -869,9 +911,11 @@ self.ipselect.populate2("date", itemlist, datelist) if allPaneOpen: self.library_open() + #--------------------------------------------- ttk.Separator(self.toppane, orient='horizontal').grid(row = 9, sticky = 'news') + #--------------------------------------------- # List of imports: self.toppane.import_frame = ttk.Frame(self.toppane) @@ -908,7 +952,7 @@ tooltip.ToolTip(self.importselect.get_button(2), text="Remove the import file(s)") if allPaneOpen: self.import_open() - + ''' #--------------------------------------------- # ttk.Separator(self, orient='horizontal').grid(column = 0, row = 8, columnspan=4, sticky='ew') #--------------------------------------------- @@ -1035,7 +1079,7 @@ return '/.config' elif (os.path.exists(path + '/.ef-config')): return '/.ef-config' - raise FileNotFoundError('Neither '+path+'/.config nor '+path+'/.ef-config exists.') + raise Exception('Neither '+path+'/.config nor '+path+'/.ef-config exists.') #------------------------------------------------------------------------ # Check if a name is blacklisted for being a project folder @@ -1303,6 +1347,7 @@ @classmethod def pdkdir2fnd(cls, pdkdir, def_foundry='', def_node='', def_description=''): foundry = '' + foundry_name = '' node = '' description = '' status = 'active' @@ -1322,13 +1367,15 @@ nodeinfo = json.load(ifile) if 'foundry' in nodeinfo: foundry = nodeinfo['foundry'] + if 'foundry-name' in nodeinfo: + foundry_name = nodeinfo['foundry-name'] if 'node' in nodeinfo: node = nodeinfo['node'] if 'description' in nodeinfo: description = nodeinfo['description'] if 'status' in nodeinfo: status = nodeinfo['status'] - return foundry, node, description, status + return foundry, foundry_name, node, description, status infofile = pdkdir + '/.ef-config/nodeinfo.json' if os.path.exists(infofile): @@ -1336,15 +1383,16 @@ nodeinfo = json.load(ifile) if 'foundry' in nodeinfo: foundry = nodeinfo['foundry'] + if 'foundry-name' in nodeinfo: + foundry_name = nodeinfo['foundry-name'] if 'node' in nodeinfo: node = nodeinfo['node'] if 'description' in nodeinfo: description = nodeinfo['description'] if 'status' in nodeinfo: status = nodeinfo['status'] - - - return foundry, node, description, status + return foundry, foundry_name, node, description, status + raise Exception('malformed pdkPath: can\'t determine foundry or node') #------------------------------------------------------------------------ # Get a list of the electric-libraries (DELIB only) in a given project. @@ -1393,7 +1441,7 @@ pdkdir = os.path.realpath(project + self.config_path(project)+'/techdir') if path: return pdkdir - foundry, node, desc, status = self.pdkdir2fnd( pdkdir ) + foundry, foundry_name, node, desc, status = self.pdkdir2fnd( pdkdir ) return foundry + ' : ' + node ''' if os.path.isdir(project + '/.ef-config'): @@ -1427,8 +1475,30 @@ return pdkdir - +#------------------------------------------------------------------------ + # Get PDK directory for projects to be imported (without a techdir) #------------------------------------------------------------------------ + def get_import_pdk(self, projectpath): + yamlname = projectpath + '/info.yaml' + + with open(yamlname, 'r') as f: + datatop = yaml.safe_load(f) + project_data = datatop['project'] + project_foundry = project_data['foundry'] + project_process = project_data['process'] + + project_pdkdir = '' + + 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 ) + if not foundry or not node: + continue + if (foundry == project_foundry or foundry_name == project_foundry) and node == project_process: + project_pdkdir = pdkdir + break + + return project_pdkdir, foundry, node #------------------------------------------------------------------------ # Get the list of PDKs that are attached to each project #------------------------------------------------------------------------ def get_pdk_list(self, projectlist): @@ -2080,7 +2150,7 @@ project={} project['description'] = description try: - project['foundry'], project['process'], pdk_desc, pdk_stat = self.pdkdir2fnd( pname ) + project['foundry'], foundry_name, project['process'], pdk_desc, pdk_stat = self.pdkdir2fnd( pname ) except: # Cannot parse PDK name, so foundry and node will remain undefined pass @@ -2742,6 +2812,7 @@ pdklist = self.get_pdk_list(projectlist) self.projectselect.populate2("PDK", projectlist, pdklist) + ''' old_imports = self.number_of_imports importlist = self.get_import_list() self.importselect.repopulate(importlist) @@ -2749,6 +2820,7 @@ datelist = self.get_date_list(valuelist) itemlist = self.importselect.getlist() self.importselect.populate2("date", itemlist, datelist) + # To do: Check if itemlist in imports changed, and open if a new import # has arrived. @@ -2762,7 +2834,7 @@ datelist = self.get_date_list(valuelist) itemlist = self.ipselect.getlist() self.ipselect.populate2("date", itemlist, datelist) - + ''' def update_alert(self): # Project manager has been updated. Generate an alert window and # provide option to restart the project manager. @@ -2790,7 +2862,11 @@ confirm = ProtectedConfirmDialog(self, warning).result if not confirm == 'okay': return - shutil.rmtree(value['values'][0]) + if os.path.islink(path): + os.unlink(path) + self.update_project_views() + else: + shutil.rmtree(value['values'][0]) if ('subcells' in path): self.update_project_views() @@ -2883,14 +2959,14 @@ # subproject is selected parent_path = os.path.split(os.path.split(pdirCur)[0])[0] pdkdir = self.get_pdk_dir(parent_path, path=True) - (foundry, node, desc, status) = self.pdkdir2fnd( pdkdir ) + (foundry, foundry_name, node, desc, status) = self.pdkdir2fnd( pdkdir ) parent_pdk = foundry + '/' + node warning = 'Create new subproject in '+ parent_path + ':' elif (pdirCur[0] == '.'): # the project's 'subproject' of itself is selected parent_path = pdirCur[1:] pdkdir = self.get_pdk_dir(parent_path, path=True) - (foundry, node, desc, status) = self.pdkdir2fnd( pdkdir ) + (foundry, foundry_name, node, desc, status) = self.pdkdir2fnd( pdkdir ) parent_pdk = foundry + '/' + node warning = 'Create new subproject in '+ parent_path + ':' @@ -2912,8 +2988,6 @@ if parent_pdk == '': newproject = self.projectdir + '/' + newname else: - if not os.path.isdir(parent_path + '/subcells'): - os.makedirs(parent_path + '/subcells') newproject = parent_path + '/subcells/' + newname if self.blacklisted(newname): @@ -2927,6 +3001,9 @@ else: break + if parent_pdk !='' and not os.path.isdir(parent_path + '/subcells'): + os.makedirs(parent_path + '/subcells') + try: subprocess.Popen([config.apps_path + '/create_project.py', newproject, newpdk]).wait() @@ -3209,7 +3286,7 @@ found = False if os.path.isfile(yamlname): - # Pull the ipname into local store (may want to do this with the + # Pull the project_name into local store (may want to do this with the # datasheet as well) with open(yamlname, 'r') as f: datatop = yaml.safe_load(f) @@ -3312,7 +3389,100 @@ self.projNameBadrex1.match(name) or self.projNameBadrex2.match(name)) +#---------------------------------------------------------------------- + # Import/link a project or subproject to the project manager #---------------------------------------------------------------------- + + def importproject(self, value): + warning = "Import project:" + badrex1 = re.compile("^\.") + badrex2 = re.compile(".*[/ \t\n\\\><\*\?].*") + print(warning) + + # Find out whether the user wants to import a subproject or project + parent_pdk = '' + try: + with open(os.path.expanduser(currdesign), 'r') as f: + pdirCur = f.read().rstrip() + if ('subcells' in pdirCur): + # subproject is selected + parent_path = os.path.split(os.path.split(pdirCur)[0])[0] + parent_pdkdir = self.get_pdk_dir(parent_path, path=True) + (foundry, foundry_name, node, desc, status) = self.pdkdir2fnd( parent_pdkdir ) + parent_pdk = foundry + '/' + node + warning = 'Import a subproject to '+ parent_path + ':' + elif (pdirCur[0] == '.'): + # the project's 'subproject' of itself is selected + parent_path = pdirCur[1:] + parent_pdkdir = self.get_pdk_dir(parent_path, path=True) + (foundry, foundry_name, node, desc, status) = self.pdkdir2fnd( parent_pdkdir ) + parent_pdk = foundry + '/' + node + warning = 'Import a subproject to '+ parent_path + ':' + + except: + pass + + while True: + try: + newname, projectpath = ImportDialog(self, warning, seed='').result + except TypeError: + # TypeError occurs when "Cancel" is pressed, just handle exception. + return None + if not newname: + return None # Canceled, no action. + + if parent_pdk == '': + 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: + 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') + + # 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) + else: + if not os.path.exists(parent_path + '/subcells'): + os.makedirs(parent_path + '/subcells') + os.symlink(projectpath, parent_path + '/subcells/' + newname) + 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 #---------------------------------------------------------------------- @@ -3459,6 +3629,7 @@ def synthesize(self): value = self.projectselect.selected() + print(value) if value: design = value['values'][0] # designname = value['text']
diff --git a/common/treeviewchoice.py b/common/treeviewchoice.py index c6f4ca4..6b28a47 100755 --- a/common/treeviewchoice.py +++ b/common/treeviewchoice.py
@@ -95,8 +95,9 @@ pass if self.selectcallback: self.selectcallback(value) - self.lastselected = selection - self.lasttag = oldtag + if (selection!=self.lastselected): + self.lastselected = selection + self.lasttag = oldtag #Populate the project view def repopulate(self, itemlist=[], versioning=False): @@ -154,7 +155,6 @@ while self.treeView.exists(name): n += 1 name = origname + '(' + str(n) + ')' - mode = 'even' if mode == 'odd' else 'odd' # Note: iid value with spaces in it is a bad idea. if ' ' in name: name = name.replace(' ', '_') @@ -162,8 +162,12 @@ # optionally: Mark directories with trailing slash if self.markDir and os.path.isdir(item): origname += "/" - - self.treeView.insert('', 'end', text=origname, iid=item, value=item, tag=mode) + + if ('subcells' not in item): + mode = 'even' if mode == 'odd' else 'odd' + self.treeView.insert('', 'end', text=origname, iid=item, value=item, tag=mode) + else: + self.treeView.insert('', 'end', text=origname, iid=item, value=item, tag='odd') if 'subcells' in os.path.split(item)[0]: # If a project is a subproject, move it under its parent @@ -174,17 +178,21 @@ else: # If its not a subproject, create a "subproject" of itself # iid shouldn't be repeated since it starts with '.' - self.treeView.insert('', 'end', text=origname, iid='.'+item, value=item, tag=mode) + self.treeView.insert('', 'end', text=origname, iid='.'+item, value=item, tag='odd') self.treeView.move('.'+item,item,0) m=1 - + + if self.initSelected and self.treeView.exists(self.initSelected): if 'subcells' in self.initSelected: - parent_path = os.path.split(os.path.split(self.initSelected)[0])[0] + parent_path = os.path.split(os.path.split(self.initSelected)[0])[0] self.setselect(parent_path) + elif self.initSelected[0]=='.': + self.setselect(self.initSelected[1:]) else: self.setselect(self.initSelected) self.initSelected = None + for button in self.func_buttons: button[0].pack_forget()