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()