def addCodeFile(self, dct, pth=None, encoding=None): if pth is None: pth = self._srcFile if encoding is None: encoding = self._encoding # Get the associated code file, if any codePth = "%s-code.py" % os.path.splitext(pth)[0] if os.path.exists(codePth): try: codeContent = codecs.open(codePth, "r", encoding).read() codeDict = desUtil.parseCodeFile(codeContent) dct["importStatements"] = codeDict.pop("importStatements", "") desUtil.addCodeToClassDict(dct, codeDict) except Exception as e: print("Failed to parse code file:", e)
def addCodeFile(self, dct, pth=None, encoding=None): if pth is None: pth = self._srcFile if encoding is None: encoding = self._encoding # Get the associated code file, if any codePth = "%s-code.py" % os.path.splitext(pth)[0] if os.path.exists(codePth): try: codeContent = codecs.open(codePth, "r", encoding).read() codeDict = desUtil.parseCodeFile(codeContent) dct["importStatements"] = codeDict.pop("importStatements", "") desUtil.addCodeToClassDict(dct, codeDict) except StandardError, e: print "Failed to parse code file:", e
def _createDesignerCode(self, cd): """Given a dict of code, create the Python script containing that code.""" ret = """# -*- coding: utf-8 -*- ### Dabo Class Designer code. You many freely edit the code, ### but do not change the comments containing: ### 'Dabo Code ID: XXXX', ### as these are needed to link the code to the objects.\n\n""" codeHeaderTemplate = desUtil.getCodeObjectSeperator() + "%s" body = [] for codeKey, mthds in cd.items(): # Add the import statements first, if any try: code = mthds.pop("importStatements").strip() while not code.endswith("\n\n"): code += "\n" except KeyError: code = "" if code: body.insert(0, code) code = "" # Sort the methods alphabetically mthNames = mthds.keys() mthNames.sort() for mthd in mthNames: code += mthds[mthd].strip() while not code.endswith("\n\n\n"): code += "\n" hdr = codeHeaderTemplate % codeKey body.append("%s\n%s" % (hdr, code)) return ret + "\n".join(body)
def xmltodict(xml, attsToSkip=[], addCodeFile=False, encoding=None): """Given an xml string or file, return a Python dictionary.""" parser = Xml2Obj(encoding=encoding) parser.attsToSkip = attsToSkip if eol in xml and "<?xml" in xml: isPath = False else: isPath = os.path.exists(xml) errmsg = "" if eol not in xml and isPath: # argument was a file xmlContent = codecs.open(xml, "r", encoding).read() if isinstance(xmlContent, str): xmlContent = xmlContent.encode(encoding) try: ret = parser.Parse(xmlContent) except expat.ExpatError as e: errmsg = _("The XML in '%(xml)s' is not well-formed and cannot be parsed: %(e)s") % locals() else: # argument must have been raw xml: try: ret = parser.Parse(xml) except expat.ExpatError as e: errmsg = _("An invalid XML string was encountered: %s") % e if errmsg: raise dabo.dException.XmlException(errmsg) if addCodeFile and isPath: # Get the associated code file, if any codePth = "%s-code.py" % os.path.splitext(xml)[0] if os.path.exists(codePth): try: codeContent = codecs.open(codePth, "r", encoding).read() codeDict = desUtil.parseCodeFile(codeContent) ret["importStatements"] = codeDict.pop("importStatements", "") desUtil.addCodeToClassDict(ret, codeDict) except Exception as e: print("Failed to parse code file:", e) return ret
errmsg = _("The XML in '%(xml)s' is not well-formed and cannot be parsed: %(e)s") % locals() else: # argument must have been raw xml: try: ret = parser.Parse(xml) except expat.ExpatError, e: errmsg = _("An invalid XML string was encountered: %s") % e if errmsg: raise dabo.dException.XmlException(errmsg) if addCodeFile and isPath: # Get the associated code file, if any codePth = "%s-code.py" % os.path.splitext(xml)[0] if os.path.exists(codePth): try: codeContent = codecs.open(codePth, "r", encoding).read() codeDict = desUtil.parseCodeFile(codeContent) ret["importStatements"] = codeDict.pop("importStatements", "") desUtil.addCodeToClassDict(ret, codeDict) except StandardError, e: print "Failed to parse code file:", e return ret def escQuote(val, noEscape=False, noQuote=False): """ Add surrounding quotes to the string, and escape any illegal XML characters. """ val = ustr(val) if noQuote: qt = ''
) % locals() else: # argument must have been raw xml: try: ret = parser.Parse(xml) except expat.ExpatError, e: errmsg = _("An invalid XML string was encountered: %s") % e if errmsg: raise dabo.dException.XmlException(errmsg) if addCodeFile and isPath: # Get the associated code file, if any codePth = "%s-code.py" % os.path.splitext(xml)[0] if os.path.exists(codePth): try: codeContent = codecs.open(codePth, "r", encoding).read() codeDict = desUtil.parseCodeFile(codeContent) ret["importStatements"] = codeDict.pop("importStatements", "") desUtil.addCodeToClassDict(ret, codeDict) except StandardError, e: print "Failed to parse code file:", e return ret def escQuote(val, noEscape=False, noQuote=False): """ Add surrounding quotes to the string, and escape any illegal XML characters. """ val = ustr(val) if noQuote: qt = ''
def createChildCode(self, childList, specChildList=[], force1x=False): """Takes a list of child object dicts, and adds their code to the generated class text. """ if not isinstance(childList, (list, tuple)): childList = [childList] if not isinstance(specChildList, (list, tuple)): specChildList = [[specChildList]] elif (len(specChildList) == 0) or not isinstance(specChildList[0], (list, tuple)): specChildList = [specChildList] for child in childList: nm = child.get("name") try: modpath, shortClsName = nm.rsplit(".", 1) except ValueError: # Default to the dabo.ui module modpath = "dabo.ui" shortClsName = nm atts = child.get("attributes", {}) clsID = atts.get("classID", "") specChild = {} specKids = [] specCode = {} for spc in specChildList: specChildMatch = [specChild for specChild in spc if specChild.get("attributes", {}).get("classID", None) == clsID] if specChildMatch: specChild = specChildMatch[0] atts.update(specChild.get("attributes", {})) specKids.append(specChild.get("children", [])) specCode.update(specChild.get("code", {})) cleanAtts = self.cleanAttributes(atts) szInfo = self._extractKey(atts, "sizerInfo", {}) if force1x: szInfo["Expand"] = True szInfo["Proportion"] = 1 rcPos = self._extractKey(atts, "rowColPos") rowColString = "" if rcPos: rowColString = ", row=%s, col=%s" % eval(rcPos) kids = child.get("children", []) code = child.get("code", {}) custProps = child.get("properties", {}) code.update(specCode) isCustom = False isInherited = False # Do we need to pop the containership/sizer stacks? needPop = True clsname = self._extractKey(atts, "designerClass", "") isSizer = (clsname in ("LayoutSizer", "LayoutGridSizer", "LayoutBorderSizer")) or (nm in ("dSizer", "dBorderSizer", "dGridSizer")) isTree = (nm == "dTreeView") # This will get set to True if we process a splitter control isSplitter = False splitterString = "" try: # Classes will have a single numeric classID (e.g.: 123456789). # Components inside those classes will have the outer ID hypenated # with their own ID (e.g.: 123456789-987654321). isInherited = (len(atts["classID"].split("-")) == 1) except KeyError: isInherited = False if isInherited: if not os.path.exists(clsname): clsname = dabo.lib.utils.locateRelativeTo(self._srcFile, clsname) chldList = [[child]] + specChildList[:] nm = self.createInheritedClass(clsname, chldList) code = {} kids = [] isCustom = True isInherited = True else: if code or custProps: nm = self.createInnerClass(nm, atts, code, custProps) isCustom = True if isSizer: isGridSizer = clsname == "LayoutGridSizer" if isGridSizer: propString = "" propsToSend = [] for att, val in list(atts.items()): if att in ("HGap", "MaxRows", "MaxCols", "VGap"): propsToSend.append("%s=%s" % (att, val)) elif att == "MaxDimension": propsToSend.append("%s='%s'" % (att, val)) if propsToSend: propString = ", ".join(propsToSend) isBorderSizer = clsname == "LayoutBorderSizer" ornt = "" prnt = "" if isGridSizer: szType = "G" else: if "Orientation" not in atts: # Default to Horizontal atts["Orientation"] = "H" szType = atts["Orientation"] for unneeded in ("SlotCount", "classID"): atts.pop(unneeded, None) propString = ", ".join(["%s='%s'" % (k,v) for k,v in list(atts.items())]) if isBorderSizer: prnt = "currParent, " if self.CreateDesignerControls: superName = clsname else: superName = "dabo.ui.%s" % nm self.classText += LINESEP + self._szText % locals() self._sizerTypeStack.append(szType) elif clsname == "LayoutSpacerPanel": if self.CreateDesignerControls: spcObjDef = "currSizer.append(LayoutSpacerPanel(currParent, Spacing=%(spc)s))" else: spcObjDef = "currSizer.appendSpacer(%(spc)s)" # Insert a spacer spc = atts.get("Spacing", "10") spcObjDef = spcObjDef % locals() self.classText += LINESEP + self._spcText % locals() elif clsname == "LayoutPanel": if isinstance(szInfo, str): szInfo = eval(szInfo) defSizerInfo = {"Expand": True, "Proportion": 1} defSizerInfo.update(szInfo) szInfo = defSizerInfo if self.CreateDesignerControls: superName = "getControlClass(%s.%s)" % (modpath, "dPanel") else: superName = "%s.%s" % (modpath, "dPanel") attPropString = ", attProperties=%s" % cleanAtts self.classText += LINESEP + self._createControlText % locals() else: # This isn't a sizer; it's a control attPropString = "" moduleString = "" try: typ = self._sizerTypeStack[-1] except IndexError: typ = "H" szDefaults = desUtil.getDefaultSizerProps(nm, typ) if isinstance(szInfo, str): szInfo = eval(szInfo) szDefaults.update(szInfo) szInfo = szDefaults isSplitter = ("SashPosition" in atts) isSlidePanel = ("PanelCount" in atts) if isSplitter: pos = self._extractKey(cleanAtts, "SashPosition") ornt = self._extractKey(cleanAtts, "Orientation") splt = self._extractKey(cleanAtts, "Split") cleanAtts["Split"] = "False" cleanAtts["ShowPanelSplitMenu"] = "False" splitterString = self._spltText % locals() elif isSlidePanel: # We don't want panels auto-created by the PanelCount prop pnlCnt = self._extractKey(cleanAtts, "PanelCount") if isCustom: superName = "self.getCustControlClass('%s')" % nm else: if self.CreateDesignerControls: superName = "getControlClass(%s.%s)" % (modpath, shortClsName) else: superName = "%s.%s" % (modpath, shortClsName) attPropString = ", attProperties=%s" % cleanAtts self.classText += LINESEP + self._createControlText % locals() # If this item has child objects, push the appropriate objects # on their stacks, and add the push statements to the code. # We'll pop them back off at the end. if kids: if isSizer: # We need to set the current sizer to this one, and push any # existing sizer onto the stack. self.classText += LINESEP + self._kidSzText elif isSplitter: # Create the two panels as custom classes, and add them to the # splitter as those classes splitName = self.uniqename("splt") self.classText += LINESEP + (""" %s = obj""" % splitName) kid = kids[0] kidCleanAtts = self.cleanAttributes(kid.get("attributes", {})) nm = kid.get("name") code = kid.get("code", {}) grandkids1 = kid.get("children") p1nm = self.createInnerClass(nm, kidCleanAtts, code, custProps) self.classText += (LINESEP + """ %(splitName)s.createPanes(self.getCustControlClass('%(p1nm)s'), pane=1)""" % locals()) kid = kids[1] kidCleanAtts = self.cleanAttributes(kid.get("attributes", {})) nm = kid.get("name") code = kid.get("code", {}) grandkids2 = kid.get("children") p2nm = self.createInnerClass(nm, kidCleanAtts, code, custProps) self.classText += (LINESEP + """ %(splitName)s.createPanes(self.getCustControlClass('%(p2nm)s'), pane=2)""" % locals()) hasGK = grandkids1 or grandkids2 if hasGK: self.classText += LINESEP + self._childPushText # Clear the 'kids' value kids = [] # We'll do our own stack popping here. needPop = False # Now create the panel kids, if any. if grandkids1: self.classText += LINESEP + (self._gk1Text % locals()) # Call the create method recursively. When execution # returns to this level, all the children for this object will # have been added. self.createChildCode(grandkids1, specKids) if grandkids2: self.classText += LINESEP + (self._gk2Text % locals()) # Call the create method recursively. When execution # returns to this level, all the children for this object will # have been added. self.createChildCode(grandkids2, specKids) if hasGK: self.classText += LINESEP + self._gkPopText elif isTree: self.classText += LINESEP + self._treeNodeText self.classText += LINESEP + (self._treeRootText % kids[0]) needPop = False kids = [] else: # We need to handle Grids and PageFrames separately, # since these 'children' are not random objects, but specific # classes. if (("ColumnCount" in atts) or ("PageCount" in atts) or ("PanelCount" in atts)): # Grid, pageframe or slide panel self.classText += LINESEP + self._complexCtlText isGrid = ("ColumnCount" in atts) isPageFrame = ("PageCount" in atts) if isPageFrame or isSlidePanel: # We need to set up a unique name for the control so # that all of the pages/panels can reference their # parent. Since these child containers can contain # lots of other stuff, the default 'obj' reference # will be trampled by the time the second child is # created. if isPageFrame: prntName = self.uniqename("pgf") elif isSlidePanel: prntName = self.uniqename("sldpn") self.classText += LINESEP + self._complexPrntRef % locals() for kid in kids: kidCleanAtts = self.cleanAttributes(kid.get("attributes", {})) if isGrid: self.classText += LINESEP + self._grdColText % locals() elif isPageFrame or isSlidePanel: # Paged control or Slide Panel control nm = kid.get("name") code = kid.get("code", {}) subKids = kid.get("children") attPropString = "" moduleString = "" if code: nm = self.createInnerClass(nm, kidCleanAtts, code, {}) nm = "self.getCustControlClass('%s')" % nm else: moduleString = "dabo.ui." attPropString = ", attProperties=%s" % kidCleanAtts kidCleanAtts = {} if isPageFrame: baseText = self._pgfPageText elif isSlidePanel: baseText = self._slidePanelText self.classText += LINESEP + baseText % locals() if subKids: self.createChildCode(subKids) self.classText += LINESEP + self._complexKidsText # We've already processed the child objects for these # grid/page controls, so clear the kids list. kids = [] else: # We're adding things to a control. We have to clear # the current sizer, since the most likely child will # be the sizer that governs the contained controls. # Tell the class that we are dealing with a new parent object self.classText += LINESEP + self._childPushText if kids: # Call the create method recursively. When execution # returns to this level, all the children for this object will # have been added. self.createChildCode(kids, specKids) # Pop as needed off of the stacks. if needPop: if isSizer: self.classText += LINESEP + self._szPopText self._sizerTypeStack.pop() else: self.classText += LINESEP + self._ctlPopText return
def createChildCode(self, childList, specChildList=[], force1x=False): """Takes a list of child object dicts, and adds their code to the generated class text. """ if not isinstance(childList, (list, tuple)): childList = [childList] if not isinstance(specChildList, (list, tuple)): specChildList = [[specChildList]] elif (len(specChildList) == 0) or not isinstance(specChildList[0], (list, tuple)): specChildList = [specChildList] for child in childList: nm = child.get("name") try: modpath, shortClsName = nm.rsplit(".", 1) except ValueError: # Default to the dabo.ui module modpath = "dabo.ui" shortClsName = nm atts = child.get("attributes", {}) clsID = atts.get("classID", "") specChild = {} specKids = [] specCode = {} for spc in specChildList: specChildMatch = [specChild for specChild in spc if specChild.get("attributes", {}).get("classID", None) == clsID] if specChildMatch: specChild = specChildMatch[0] atts.update(specChild.get("attributes", {})) specKids.append(specChild.get("children", [])) specCode.update(specChild.get("code", {})) cleanAtts = self.cleanAttributes(atts) szInfo = self._extractKey(atts, "sizerInfo", {}) if force1x: szInfo["Expand"] = True szInfo["Proportion"] = 1 rcPos = self._extractKey(atts, "rowColPos") rowColString = "" if rcPos: rowColString = ", row=%s, col=%s" % eval(rcPos) kids = child.get("children", []) code = child.get("code", {}) custProps = child.get("properties", {}) code.update(specCode) isCustom = False isInherited = False # Do we need to pop the containership/sizer stacks? needPop = True clsname = self._extractKey(atts, "designerClass", "") isSizer = (clsname in ("LayoutSizer", "LayoutGridSizer", "LayoutBorderSizer")) or (nm in ("dSizer", "dBorderSizer", "dGridSizer")) isTree = (nm == "dTreeView") # This will get set to True if we process a splitter control isSplitter = False splitterString = "" try: # Classes will have a single numeric classID (e.g.: 123456789). # Components inside those classes will have the outer ID hypenated # with their own ID (e.g.: 123456789-987654321). isInherited = (len(atts["classID"].split("-")) == 1) except KeyError: isInherited = False if isInherited: if not os.path.exists(clsname): clsname = dabo.lib.utils.locateRelativeTo(self._srcFile, clsname) chldList = [[child]] + specChildList[:] nm = self.createInheritedClass(clsname, chldList) code = {} kids = [] isCustom = True isInherited = True else: if code or custProps: nm = self.createInnerClass(nm, atts, code, custProps) isCustom = True if isSizer: isGridSizer = clsname == "LayoutGridSizer" if isGridSizer: propString = "" propsToSend = [] for att, val in atts.items(): if att in ("HGap", "MaxRows", "MaxCols", "VGap"): propsToSend.append("%s=%s" % (att, val)) elif att == "MaxDimension": propsToSend.append("%s='%s'" % (att, val)) if propsToSend: propString = ", ".join(propsToSend) isBorderSizer = clsname == "LayoutBorderSizer" ornt = "" prnt = "" if isGridSizer: szType = "G" else: if "Orientation" not in atts: # Default to Horizontal atts["Orientation"] = "H" szType = atts["Orientation"] for unneeded in ("SlotCount", "classID"): atts.pop(unneeded, None) propString = ", ".join(["%s='%s'" % (k,v) for k,v in atts.items()]) if isBorderSizer: prnt = "currParent, " if self.CreateDesignerControls: superName = clsname else: superName = "dabo.ui.%s" % nm self.classText += LINESEP + self._szText % locals() self._sizerTypeStack.append(szType) elif clsname == "LayoutSpacerPanel": if self.CreateDesignerControls: spcObjDef = "currSizer.append(LayoutSpacerPanel(currParent, Spacing=%(spc)s))" else: spcObjDef = "currSizer.appendSpacer(%(spc)s)" # Insert a spacer spc = atts.get("Spacing", "10") spcObjDef = spcObjDef % locals() self.classText += LINESEP + self._spcText % locals() elif clsname == "LayoutPanel": if isinstance(szInfo, basestring): szInfo = eval(szInfo) defSizerInfo = {"Expand": True, "Proportion": 1} defSizerInfo.update(szInfo) szInfo = defSizerInfo if self.CreateDesignerControls: superName = "getControlClass(%s.%s)" % (modpath, "dPanel") else: superName = "%s.%s" % (modpath, "dPanel") attPropString = ", attProperties=%s" % cleanAtts self.classText += LINESEP + self._createControlText % locals() else: # This isn't a sizer; it's a control attPropString = "" moduleString = "" try: typ = self._sizerTypeStack[-1] except IndexError: typ = "H" szDefaults = desUtil.getDefaultSizerProps(nm, typ) if isinstance(szInfo, basestring): szInfo = eval(szInfo) szDefaults.update(szInfo) szInfo = szDefaults isSplitter = ("SashPosition" in atts) isSlidePanel = ("PanelCount" in atts) if isSplitter: pos = self._extractKey(cleanAtts, "SashPosition") ornt = self._extractKey(cleanAtts, "Orientation") splt = self._extractKey(cleanAtts, "Split") cleanAtts["Split"] = "False" cleanAtts["ShowPanelSplitMenu"] = "False" splitterString = self._spltText % locals() elif isSlidePanel: # We don't want panels auto-created by the PanelCount prop pnlCnt = self._extractKey(cleanAtts, "PanelCount") if isCustom: superName = "self.getCustControlClass('%s')" % nm else: if self.CreateDesignerControls: superName = "getControlClass(%s.%s)" % (modpath, shortClsName) else: superName = "%s.%s" % (modpath, shortClsName) attPropString = ", attProperties=%s" % cleanAtts self.classText += LINESEP + self._createControlText % locals() # If this item has child objects, push the appropriate objects # on their stacks, and add the push statements to the code. # We'll pop them back off at the end. if kids: if isSizer: # We need to set the current sizer to this one, and push any # existing sizer onto the stack. self.classText += LINESEP + self._kidSzText elif isSplitter: # Create the two panels as custom classes, and add them to the # splitter as those classes splitName = self.uniqename("splt") self.classText += LINESEP + (""" %s = obj""" % splitName) kid = kids[0] kidCleanAtts = self.cleanAttributes(kid.get("attributes", {})) nm = kid.get("name") code = kid.get("code", {}) grandkids1 = kid.get("children") p1nm = self.createInnerClass(nm, kidCleanAtts, code, custProps) self.classText += (LINESEP + """ %(splitName)s.createPanes(self.getCustControlClass('%(p1nm)s'), pane=1)""" % locals()) kid = kids[1] kidCleanAtts = self.cleanAttributes(kid.get("attributes", {})) nm = kid.get("name") code = kid.get("code", {}) grandkids2 = kid.get("children") p2nm = self.createInnerClass(nm, kidCleanAtts, code, custProps) self.classText += (LINESEP + """ %(splitName)s.createPanes(self.getCustControlClass('%(p2nm)s'), pane=2)""" % locals()) hasGK = grandkids1 or grandkids2 if hasGK: self.classText += LINESEP + self._childPushText # Clear the 'kids' value kids = [] # We'll do our own stack popping here. needPop = False # Now create the panel kids, if any. if grandkids1: self.classText += LINESEP + (self._gk1Text % locals()) # Call the create method recursively. When execution # returns to this level, all the children for this object will # have been added. self.createChildCode(grandkids1, specKids) if grandkids2: self.classText += LINESEP + (self._gk2Text % locals()) # Call the create method recursively. When execution # returns to this level, all the children for this object will # have been added. self.createChildCode(grandkids2, specKids) if hasGK: self.classText += LINESEP + self._gkPopText elif isTree: self.classText += LINESEP + self._treeNodeText self.classText += LINESEP + (self._treeRootText % kids[0]) needPop = False kids = [] else: # We need to handle Grids and PageFrames separately, # since these 'children' are not random objects, but specific # classes. if (("ColumnCount" in atts) or ("PageCount" in atts) or ("PanelCount" in atts)): # Grid, pageframe or slide panel self.classText += LINESEP + self._complexCtlText isGrid = ("ColumnCount" in atts) isPageFrame = ("PageCount" in atts) if isPageFrame or isSlidePanel: # We need to set up a unique name for the control so # that all of the pages/panels can reference their # parent. Since these child containers can contain # lots of other stuff, the default 'obj' reference # will be trampled by the time the second child is # created. if isPageFrame: prntName = self.uniqename("pgf") elif isSlidePanel: prntName = self.uniqename("sldpn") self.classText += LINESEP + self._complexPrntRef % locals() for kid in kids: kidCleanAtts = self.cleanAttributes(kid.get("attributes", {})) if isGrid: self.classText += LINESEP + self._grdColText % locals() elif isPageFrame or isSlidePanel: # Paged control or Slide Panel control nm = kid.get("name") code = kid.get("code", {}) subKids = kid.get("children") attPropString = "" moduleString = "" if code: nm = self.createInnerClass(nm, kidCleanAtts, code, {}) nm = "self.getCustControlClass('%s')" % nm else: moduleString = "dabo.ui." attPropString = ", attProperties=%s" % kidCleanAtts kidCleanAtts = {} if isPageFrame: baseText = self._pgfPageText elif isSlidePanel: baseText = self._slidePanelText self.classText += LINESEP + baseText % locals() if subKids: self.createChildCode(subKids) self.classText += LINESEP + self._complexKidsText # We've already processed the child objects for these # grid/page controls, so clear the kids list. kids = [] else: # We're adding things to a control. We have to clear # the current sizer, since the most likely child will # be the sizer that governs the contained controls. # Tell the class that we are dealing with a new parent object self.classText += LINESEP + self._childPushText if kids: # Call the create method recursively. When execution # returns to this level, all the children for this object will # have been added. self.createChildCode(kids, specKids) # Pop as needed off of the stacks. if needPop: if isSizer: self.classText += LINESEP + self._szPopText self._sizerTypeStack.pop() else: self.classText += LINESEP + self._ctlPopText return