# Univeral Tool Template v20.1
tpl_ver = 20.10
tpl_date = 200604
print("tpl_ver: {0}-{1}".format(tpl_ver, tpl_date))
# by ying - https://github.com/shiningdesign/universal_tool_template.py

import importlib
import sys
# ---- hostMode ----
hostMode = ''
hostModeList = [
    ['maya', {'mui':'maya.OpenMayaUI', 'cmds':'maya.cmds'} ],
    ['nuke', {'nuke':'nuke', 'nukescripts':'nukescripts'} ],
    ['fusion', {'fs':'fusionscript'} ],
    ['houdini', {'hou':'hou'} ],
    ['blender', {'bpy':'bpy'} ],
    ['npp', {'Npp':'Npp'} ],
]
for name, libs in hostModeList:
    try:
        for x in libs.keys():
            globals()[x] = importlib.import_module(libs[x])
        hostMode = name
        break
    except ImportError:
        pass
if hostMode == '':
    hostMode = 'desktop'
print('Host: {0}'.format(hostMode))

# ---- qtMode ----
qtMode = 0 # 0: PySide; 1 : PyQt, 2: PySide2, 3: PyQt5
qtModeList = ('PySide', 'PyQt4', 'PySide2', 'PyQt5')
try:
    from PySide import QtGui, QtCore
    import PySide.QtGui as QtWidgets
    qtMode = 0
    if hostMode == "maya":
        import shiboken
except ImportError:
    try:
        from PySide2 import QtCore, QtGui, QtWidgets
        qtMode = 2
        if hostMode == "maya":
            import shiboken2 as shiboken
    except ImportError:
        try:
            from PyQt4 import QtGui,QtCore
            import PyQt4.QtGui as QtWidgets
            import sip
            qtMode = 1
        except ImportError:
            from PyQt5 import QtGui,QtCore,QtWidgets
            import sip
            qtMode = 3
print('Qt: {0}'.format(qtModeList[qtMode]))

# ---- pyMode ----
# python 2,3 support unicode function
try:
    UNICODE_EXISTS = bool(type(unicode))
except NameError:
    # lambda s: str(s) # this works for function but not for class check
    unicode = str
if sys.version_info[:3][0]>=3:
    reload = importlib.reload # add reload

pyMode = '.'.join([ str(n) for n in sys.version_info[:3] ])
print("Python: {0}".format(pyMode))

# ---- osMode ----
osMode = 'other'
if sys.platform in ['win32','win64']:
    osMode = 'win'
elif sys.platform == 'darwin':
    osMode = 'mac'
elif sys.platform == 'linux2':
    osMode = 'linux'
print("OS: {0}".format(osMode))

# ---- template module list ----
import os # for path and language code
from functools import partial # for partial function creation
import json # for ascii data output
import re # for name pattern
import subprocess # for cmd call

#=======================================
#  UniversalToolUI template class
#=======================================

class UniversalToolUI(QtWidgets.QMainWindow): 
    def __init__(self, parent=None):
        QtWidgets.QMainWindow.__init__(self, parent)

        # class property
        self.tpl_ver = tpl_ver
        self.tpl_date = tpl_date
        self.version = '0.1'
        self.date = '2017.01.01'
        self.log = 'no version log in user class'
        self.help = 'no help guide in user class'
        
        # class info
        self.name = self.__class__.__name__
        self.fileType='.{0}_EXT'.format(self.name)
        
        # class path and icon
        self.location = ''
        if getattr(sys, 'frozen', False):
            self.location = sys.executable # frozen case - cx_freeze
        else:
            self.location = os.path.realpath(sys.modules[self.__class__.__module__].__file__)
        self.iconPath = os.path.join(os.path.dirname(self.location),'icons',self.name+'.png')
        self.iconPix = QtGui.QPixmap(self.iconPath)
        self.icon = QtGui.QIcon(self.iconPath)
        
        # class data
        self.hotkey = {}
        self.uiList={} # for ui obj storage
        self.memoData = {} # key based variable data storage
        self.memoData['font_size_default'] = QtGui.QFont().pointSize()
        self.memoData['font_size'] = self.memoData['font_size_default']
        self.memoData['last_export'] = ''
        self.memoData['last_import'] = ''
        self.memoData['last_browse'] = ''
            
        # core function variable
        self.qui_core_dict = {
            'vbox': 'QVBoxLayout','hbox':'QHBoxLayout','grid':'QGridLayout', 'form':'QFormLayout',
            'split': 'QSplitter', 'grp':'QGroupBox', 'tab':'QTabWidget',
            'btn':'QPushButton', 'btnMsg':'QPushButton', 'label':'QLabel', 'input':'QLineEdit', 'check':'QCheckBox', 'choice':'QComboBox', 'spin':'QSpinBox',
            'txt': 'QTextEdit',
            'list': 'QListWidget', 'tree': 'QTreeWidget', 'table': 'QTableWidget',
            'space': 'QSpacerItem',
            'menu' : 'QMenu', 'menubar' : 'QMenuBar',
        }
        self.qui_user_dict = {}
    
    def setupMenu(self):
        if 'help_menu' in self.uiList.keys():
            self.uiList['helpGuide_msg'] = self.help
            self.uiList['helpLog_msg'] = self.log
            item_list = [
                ('helpHostMode_atnNone', 'Host Mode - {}'.format(hostMode) ),
                ('helpPyMode_atnNone','Python Mode - {}'.format(pyMode) ),
                ('helpQtMode_atnNone','Qt Mode - {}'.format(qtModeList[qtMode]) ),
                ('helpTemplate_atnNone','Universal Tool Teamplate - {0}.{1}'.format(tpl_ver, tpl_date) ),
                ('_','_'),
                ('helpGuide_atnMsg','Usage Guide'),
                ('helpLog_atnMsg','About v{0} - {1}'.format(self.version, self.date) ),
            ]
            menu_str = '|'.join(['{0};{1}'.format(*x) for x in item_list])
            self.qui_menu(menu_str, 'help_menu')
            # tip info
            self.uiList['helpTemplate_atnNone'].setStatusTip('based on Univeral Tool Template v{0} by Shining Ying - https://github.com/shiningdesign/universal{1}tool{1}template.py'.format(tpl_ver,'_'))
    
    def setupWin(self):
        self.setWindowTitle(self.name + " - v" + self.version + " - host: " + hostMode)
        self.setWindowIcon(self.icon)
        
    def setupUI(self, layout='grid'):
        main_widget = QtWidgets.QWidget()
        self.setCentralWidget(main_widget)
        self.qui('main_layout;{0}'.format(layout))
        main_widget.setLayout(self.uiList['main_layout'])

    def Establish_Connections(self):
        for ui_name in self.uiList.keys():
            prefix = ui_name.rsplit('_', 1)[0]
            if ui_name.endswith('_btn'):
                self.uiList[ui_name].clicked.connect(getattr(self, prefix+"_action", partial(self.default_action,ui_name)))
            elif ui_name.endswith('_atn'):
                self.uiList[ui_name].triggered.connect(getattr(self, prefix+"_action", partial(self.default_action,ui_name)))
            elif ui_name.endswith('_btnMsg'):
                self.uiList[ui_name].clicked.connect(getattr(self, prefix+"_message", partial(self.default_message,ui_name)))
            elif ui_name.endswith('_atnMsg'):
                self.uiList[ui_name].triggered.connect(getattr(self, prefix+"_message", partial(self.default_message,ui_name)))
                
    #=======================================
    # ui response functions
    #=======================================
    def ____ui_response_functions____():
        pass
    def default_action(self, ui_name, *argv):
        print("No action defined for this UI element: "+ui_name)
    def default_message(self, ui_name):
        prefix = ui_name.rsplit('_', 1)[0]
        msgName = prefix+"_msg"
        msg_txt = msgName + " is not defined in uiList."
        if msgName in self.uiList:
            msg_txt = self.uiList[msgName]
        self.quickMsg(msg_txt)
    def default_menu_call(self, ui_name, point):
        if ui_name in self.uiList.keys() and ui_name+'_menu' in self.uiList.keys():
            self.uiList[ui_name+'_menu'].exec_(self.uiList[ui_name].mapToGlobal(point))
    def toggleTop_action(self):
        self.setWindowFlags(self.windowFlags() ^ QtCore.Qt.WindowStaysOnTopHint)
        self.show()
    def hotkey_action(self):
        txt_list = []
        for each_key in sorted(self.hotkey.keys()):
            txt_list.append(each_key+' : '+unicode(self.hotkey[each_key].key().toString()))
        self.quickMsg('\n'.join(txt_list))
        
    #=======================================
    # ui feedback functions
    #=======================================
    def ____ui_feedback_functions____():
        pass
    def quickInfo(self, info, force=0):
        if hasattr( self.window(), "quickInfo") and force == 0:
            self.window().statusBar().showMessage(info)
        else:
            self.statusBar().showMessage(info)
    def quickMsg(self, msg, block=1, ask=0):
        tmpMsg = QtWidgets.QMessageBox(self) # for simple msg that no need for translation
        tmpMsg.setWindowTitle("Info")
        lineCnt = len(msg.split('\n'))
        if lineCnt > 25:
            scroll = QtWidgets.QScrollArea()
            scroll.setWidgetResizable(1)
            content = QtWidgets.QWidget()
            scroll.setWidget(content)
            layout = QtWidgets.QVBoxLayout(content)
            tmpLabel = QtWidgets.QLabel(msg)
            tmpLabel.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
            layout.addWidget(tmpLabel)
            tmpMsg.layout().addWidget(scroll, 0, 0, 1, tmpMsg.layout().columnCount())
            tmpMsg.setStyleSheet("QScrollArea{min-width:600 px; min-height: 400px}")
        else:
            tmpMsg.setText(msg)
        if block == 0:
            tmpMsg.setWindowModality( QtCore.Qt.NonModal )
        if ask==0:
            tmpMsg.addButton("OK",QtWidgets.QMessageBox.YesRole)
        else:
            tmpMsg.setStandardButtons(QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
        if block:
            value = tmpMsg.exec_()
            if value == QtWidgets.QMessageBox.Ok:
                return 1
            else:
                return 0
        else:
            tmpMsg.show()
            return 0
    def quickMsgAsk(self, msg, mode=0, choice=[]):
        # getItem, getInteger, getDouble, getText
        modeOpt = (QtWidgets.QLineEdit.Normal, QtWidgets.QLineEdit.NoEcho, QtWidgets.QLineEdit.Password, QtWidgets.QLineEdit.PasswordEchoOnEdit)
        # option: QtWidgets.QInputDialog.UseListViewForComboBoxItems
        if len(choice)==0:
            txt, ok = QtWidgets.QInputDialog.getText(self, "Input", msg, modeOpt[mode])
            return (unicode(txt), ok)
        else:
            txt, ok = QtWidgets.QInputDialog.getItem(self, "Input", msg, choice, 0, 0)
            return (unicode(txt), ok)
    def quickModKeyAsk(self):
        modifiers = QtWidgets.QApplication.queryKeyboardModifiers()
        clickMode = 0 # basic mode
        if modifiers == QtCore.Qt.ControlModifier:
            clickMode = 1 # ctrl
        elif modifiers == QtCore.Qt.ShiftModifier:
            clickMode = 2 # shift
        elif modifiers == QtCore.Qt.AltModifier:
            clickMode = 3 # alt
        elif modifiers == QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier | QtCore.Qt.AltModifier:
            clickMode = 4 # ctrl+shift+alt
        elif modifiers == QtCore.Qt.ControlModifier | QtCore.Qt.AltModifier:
            clickMode = 5 # ctrl+alt
        elif modifiers == QtCore.Qt.ControlModifier | QtCore.Qt.ShiftModifier:
            clickMode = 6 # ctrl+shift
        elif modifiers == QtCore.Qt.AltModifier | QtCore.Qt.ShiftModifier:
            clickMode = 7 # alt+shift
        return clickMode
    def quickFileAsk(self, type, ext=None, dir=None):
        if ext == None:
            ext = "RAW data (*.json);;RAW binary data (*.dat);;Format Txt (*{0});;AllFiles (*.*)".format(self.fileType)
        elif isinstance(ext, (str,unicode)):
            if ';;' not in ext:
                if ext == '':
                    ext = 'AllFiles (*.*)'
                else:
                    ext = self.extFormat(ext) + ';;AllFiles (*.*)'
        elif isinstance(ext, (tuple,list)):
            if len(ext) > 0 and isinstance(ext[0], (tuple,list)):
                tmp_list = [self.extFormat(x) for x in ext]
                tmp_list.append('AllFiles (*.*)')
                ext = ';;'.join(tmp_list)
            else:
                ext = ';;'.join([self.extFormat(x) for x in ext].append('AllFiles(*.*)')) 
        elif isinstance(ext, dict):
            tmp_list = [self.extFormat(x) for x in ext.items()]
            tmp_list.append('AllFiles (*.*)')
            ext = ';;'.join(tmp_list)
        else:
            ext = "AllFiles (*.*)"
        file = ''
        if type == 'export':
            if dir == None:
                dir = self.memoData['last_export']
            file = QtWidgets.QFileDialog.getSaveFileName(self, "Save File",dir,ext)
        elif type == 'import':
            if dir == None:
                dir = self.memoData['last_import']
            file = QtWidgets.QFileDialog.getOpenFileName(self, "Open File",dir,ext)
        if isinstance(file, (list, tuple)):
            file = file[0] # for deal with pyside case
        else:
            file = unicode(file) # for deal with pyqt case
        # save last dir in memoData
        if file != '':
            if type == 'export':
                self.memoData['last_export'] = os.path.dirname(file) #QFileInfo().path()
            elif type == 'import':
                self.memoData['last_import'] = os.path.dirname(file)
        return file
    def extFormat(self, ext):
        if isinstance(ext, (tuple,list)):
            ext = '{0} (*.{1})'.format(ext[1],ext[0])
        else:
            if ext.startswith('.'):
                ext = ext[1:]
            ext = '{0} (*.{0})'.format(ext)
        return ext
    def quickFolderAsk(self,dir=None):
        if dir == None:
            dir = self.memoData['last_browse']
            if self.parent is not None and hasattr(self.parent, 'memoData'):
                dir = self.parent.memoData['last_browse']
        folder = unicode(QtWidgets.QFileDialog.getExistingDirectory(self, "Select Directory",dir))
        if folder != '':
            self.memoData['last_browse'] = folder
        return folder
        
    #=======================================
    # file data functions
    #=======================================
    def ____file_data_functions____():
        pass
    def readDataFile(self,file,binary=0):
        if binary != 0:
            print('Lite no longer support binary format')
        with open(file) as f:
            data = json.load(f)
        return data
    def writeDataFile(self,data,file,binary=0):
        if binary != 0:
            print('Lite no longer support binary format')
        with open(file, 'w') as f:
            json.dump(data, f)
    def readTextFile(self, file):
        with open(file) as f:
            txt = f.read()
        return txt
    def writeTextFile(self, txt, file, b=0):
        b = '' if b==0 else 'b'
        with open(file, 'w'+b) as f:
            f.write(txt)
    def dict_merge(self, default_dict, extra_dict, addKey=0):
        # dictionary merge, with optional adding extra data from extra_dict
        new_dict = {}
        for key in default_dict.keys():
            if not isinstance( default_dict[key], dict ):
                # value case
                if key in extra_dict.keys():
                    is_same_text_type = isinstance(extra_dict[key], (str,unicode)) and isinstance(default_dict[key], (str,unicode))
                    is_same_non_text_type = type(extra_dict[key]) is type(default_dict[key])
                    if is_same_text_type or is_same_non_text_type:
                        print('use config file value for key: '+key)
                        new_dict[key] = extra_dict[key]
                    else:
                        new_dict[key] = default_dict[key]
                else:
                    new_dict[key] = default_dict[key]
            else:
                # dictionary case
                if key in extra_dict.keys() and isinstance( extra_dict[key], dict ):
                    new_dict[key] = self.dict_merge( default_dict[key], extra_dict[key], addKey )
                else:
                    new_dict[key] = default_dict[key]
        # optional, add additional keys
        if addKey == 1:
            for key in [ x for x in extra_dict.keys() if x not in default_dict.keys() ]:
                new_dict[key] = extra_dict[key]
        return new_dict
    #=======================================
    # ui text functions
    #=======================================
    def ____ui_text_functions____():
        pass
    def fontNormal_action(self, uiClass_list=[]):
        if len(uiClass_list) == 0:
            uiClass_list = 'QLabel,QPushButton'.split(',')
        self.memoData['font_size'] = self.memoData['font_size_default']
        self.setStyleSheet( "{0} { font-size: {1}pt;}".format(','.join(uiClass_list), self.memoData['font_size']) )
    def fontUp_action(self, uiClass_list=[]):
        if len(uiClass_list) == 0:
            uiClass_list = 'QLabel,QPushButton'.split(',')
        self.memoData['font_size'] += 2
        self.setStyleSheet( "{0} { font-size: {1}pt;}".format(','.join(uiClass_list), self.memoData['font_size']) )
    def fontDown_action(self, uiClass_list=[]):
        if len(uiClass_list) == 0:
            uiClass_list = 'QLabel,QPushButton'.split(',')
        if self.memoData['font_size'] >= self.memoData['font_size_default']:
            self.memoData['font_size'] -= 2
            self.setStyleSheet( "{0} { font-size: {1}pt;}".format(','.join(uiClass_list), self.memoData['font_size']) )
    def loadLang(self, build_menu=1):
        # store default language
        self.memoData['lang']={}
        self.memoData['lang']['default']={}
        for ui_name in self.uiList.keys():
            ui_element = self.uiList[ui_name]
            if isinstance(ui_element, (QtWidgets.QLabel, QtWidgets.QPushButton, QtWidgets.QAction, QtWidgets.QCheckBox) ):
                # uiType: QLabel, QPushButton, QAction(menuItem), QCheckBox
                self.memoData['lang']['default'][ui_name] = unicode(ui_element.text())
            elif isinstance(ui_element, (QtWidgets.QGroupBox, QtWidgets.QMenu) ):
                # uiType: QMenu, QGroupBox
                self.memoData['lang']['default'][ui_name] = unicode(ui_element.title())
            elif isinstance(ui_element, QtWidgets.QTabWidget):
                # uiType: QTabWidget
                tabCnt = ui_element.count()
                tabNameList = []
                for i in range(tabCnt):
                    tabNameList.append(unicode(ui_element.tabText(i)))
                self.memoData['lang']['default'][ui_name]=';'.join(tabNameList)
            elif isinstance(ui_element, QtWidgets.QComboBox):
                # uiType: QComboBox
                itemCnt = ui_element.count()
                itemNameList = []
                for i in range(itemCnt):
                    itemNameList.append(unicode(ui_element.itemText(i)))
                self.memoData['lang']['default'][ui_name]=';'.join(itemNameList)
            elif isinstance(ui_element, QtWidgets.QTreeWidget):
                # uiType: QTreeWidget
                labelCnt = ui_element.headerItem().columnCount()
                labelList = []
                for i in range(labelCnt):
                    labelList.append(unicode(ui_element.headerItem().text(i)))
                self.memoData['lang']['default'][ui_name]=';'.join(labelList)
            elif isinstance(ui_element, QtWidgets.QTableWidget):
                # uiType: QTableWidget
                colCnt = ui_element.columnCount()
                headerList = []
                for i in range(colCnt):
                    if ui_element.horizontalHeaderItem(i):
                        headerList.append( unicode(ui_element.horizontalHeaderItem(i).text()) )
                    else:
                        headerList.append('')
                self.memoData['lang']['default'][ui_name]=';'.join(headerList)
            elif isinstance(ui_element, (str, unicode) ):
                # uiType: string for msg
                self.memoData['lang']['default'][ui_name] = self.uiList[ui_name]
        
        # language menu
        lang_menu = 'language_menu'
        if build_menu == 1:
            self.qui_menubar('language_menu;&Language')
            self.qui_menu('langDefault_atnLang;Default | _', lang_menu)
            self.uiList['langDefault_atnLang'].triggered.connect(partial(self.setLang,'default'))
            
        # scan for language file
        lang_path = os.path.dirname(self.location)
        baseName = os.path.splitext( os.path.basename(self.location) )[0]
        for file in self.getPathChild(lang_path, pattern=baseName+'_lang_[a-zA-Z]+.json', isfile=1):
            langName = re.findall(baseName+'_lang_(.+)\.json', file)
            if len(langName) == 1:
                langName = langName[0].upper()
                self.memoData['lang'][ langName ] = self.readDataFile( os.path.join(lang_path, file) )
                if build_menu == 1:
                    self.qui_menu('{0}_atnLang;{0}'.format(langName), lang_menu)
                    self.uiList[langName+'_atnLang'].triggered.connect(partial(self.setLang,langName))
        # if no language file detected, add export default language option
        if build_menu == 1:
            if isinstance(self, QtWidgets.QMainWindow) and len(self.memoData['lang']) == 1:
                self.qui_menu('langExport_atnLang;Export Default Language', lang_menu)
                self.uiList['langExport_atnLang'].triggered.connect(self.exportLang)
    
    def setLang(self, langName):
        lang_data = self.memoData['lang'][langName]
        for ui_name in lang_data.keys():
            if ui_name in self.uiList.keys() and lang_data[ui_name] != '':
                ui_element = self.uiList[ui_name]
                # '' means no translation availdanle in that data file
                if isinstance(ui_element, (QtWidgets.QLabel, QtWidgets.QPushButton, QtWidgets.QAction, QtWidgets.QCheckBox) ):
                    # uiType: QLabel, QPushButton, QAction(menuItem), QCheckBox
                    ui_element.setText(lang_data[ui_name])
                elif isinstance(ui_element, (QtWidgets.QGroupBox, QtWidgets.QMenu) ):
                    # uiType: QMenu, QGroupBox
                    ui_element.setTitle(lang_data[ui_name])
                elif isinstance(ui_element, QtWidgets.QTabWidget):
                    # uiType: QTabWidget
                    tabCnt = ui_element.count()
                    tabNameList = lang_data[ui_name].split(';')
                    if len(tabNameList) == tabCnt:
                        for i in range(tabCnt):
                            if tabNameList[i] != '':
                                ui_element.setTabText(i,tabNameList[i])
                elif isinstance(ui_element, QtWidgets.QComboBox):
                    # uiType: QComboBox
                    itemCnt = ui_element.count()
                    itemNameList = lang_data[ui_name].split(';')
                    ui_element.clear()
                    ui_element.addItems(itemNameList)
                elif isinstance(ui_element, QtWidgets.QTreeWidget):
                    # uiType: QTreeWidget
                    labelCnt = ui_element.headerItem().columnCount()
                    labelList = lang_data[ui_name].split(';')
                    ui_element.setHeaderLabels(labelList)
                elif isinstance(ui_element, QtWidgets.QTableWidget):
                    # uiType: QTableWidget
                    colCnt = ui_element.columnCount()
                    headerList = lang_data[ui_name].split(';')
                    cur_table.setHorizontalHeaderLabels( headerList )
                elif isinstance(ui_element, (str, unicode) ):
                    # uiType: string for msg
                    self.uiList[ui_name] = lang_data[ui_name]
    def exportLang(self):
        file = self.quickFileAsk('export', ext='json')
        if file != '':
            self.writeDataFile( self.memoData['lang']['default'], file )
            self.quickMsg("Languge File created: '"+file)
            
    #=======================================
    # os functions
    #=======================================
    def ____os_functions____():
        pass
    def openFolder(self, folderPath):
        if os.path.isfile(folderPath):
            folderPath = os.path.dirname(folderPath)
        if os.path.isdir(folderPath):
            cmd_list = None
            if sys.platform == 'darwin':
                cmd_list = ['open', '--', folderPath]
            elif sys.platform == 'linux2':
                cmd_list = ['xdg-open', folderPath]
            elif sys.platform in ['win32','win64']:
                cmd_list = ['explorer', folderPath.replace('/','\\')]
            if cmd_list != None:
                try:
                    subprocess.check_call(cmd_list)
                except subprocess.CalledProcessError:
                    pass # handle errors in the called executable
                except OSError:
                    pass # executable not found
    def openFile(self, filePath):
        if sys.platform in ['win32','win64']:
            os.startfile(filePath)
        elif sys.platform == 'darwin':
            os.open(filePath)
        elif sys.platform == 'linux2':
            os.xdg-open(filePath)
    def newFolder(self, parentPath, name=None):
        if os.path.isfile(parentPath):
            parentPath = os.path.dirname(parentPath)
        created = 0
        if name == None:
            name, ok = self.quickMsgAsk('Enter the folder name:')
            if not ok or name=='':
                return
        create_path = os.path.join(parentPath, name)
        if os.path.isdir(create_path):
            self.quickMsg('Already Exists')
        else:
            try: 
                os.makedirs(create_path)
                created = 1
            except OSError:
                self.quickMsg('Error on creation user data folder')
        return created
    def getPathChild(self, scanPath, pattern='', isfile=0):
        resultList =[]
        scanPath = unicode(scanPath)
        if not os.path.isdir(scanPath):
            return resultList
        if isfile == 0:
            resultList = [x for x in os.listdir(scanPath) if os.path.isdir(os.path.join(scanPath,x))]
        elif isfile == 1:
            resultList = [x for x in os.listdir(scanPath) if os.path.isfile(os.path.join(scanPath,x))]
        else:
            resultList = os.listdir(scanPath)
        if pattern != '':
            cur_pattern = re.compile(pattern)
            resultList = [x for x in resultList if cur_pattern.match(x)]
        resultList.sort()
        return resultList
    
    #=======================================
    #  qui functions
    #=======================================
    def ____qui_functions____():
        pass
    
    def setAsUI(self):
        # turn win to widget
        self.setWindowFlags(QtCore.Qt.Widget)
        self.statusBar().hide()
        self.uiList['main_layout'].setContentsMargins(0, 0, 0, 0)
    def qui_key(self, key_name, key_combo, func):
        self.hotkey[key_name] = QtWidgets.QShortcut(QtGui.QKeySequence(key_combo), self)
        self.hotkey[key_name].activated.connect( func )
    def qui_menubar(self, menu_list_str):
        if not isinstance(self, QtWidgets.QMainWindow):
            print("Warning: Only QMainWindow can have menu bar.")
            return
        menubar = self.menuBar()
        create_opt_list = [ x.strip() for x in menu_list_str.split('|') ]
        for each_creation in create_opt_list:
            ui_info = [ x.strip() for x in each_creation.split(';') ]
            menu_name = ui_info[0]
            menu_title = ''
            if len(ui_info) > 1:
                menu_title = ui_info[1]
            if menu_name not in self.uiList.keys():
                self.uiList[menu_name] = QtWidgets.QMenu(menu_title)
            menubar.addMenu(self.uiList[menu_name])
    def qui_menu(self, action_list_str, menu_str):
        # qui menu creation
        # syntax: self.qui_menu('right_menu_createFolder_atn;Create Folder,Ctrl+D | right_menu_openFolder_atn;Open Folder', 'right_menu')
        if menu_str not in self.uiList.keys():
            self.uiList[menu_str] = QtWidgets.QMenu()
        create_opt_list = [ x.strip() for x in action_list_str.split('|') ]
        for each_creation in create_opt_list:
            ui_info = [ x.strip() for x in each_creation.split(';') ]
            atn_name = ui_info[0]
            atn_title = ''
            atn_hotkey = ''
            if len(ui_info) > 1:
                options = ui_info[1].split(',')
                atn_title = '' if len(options) < 1 else options[0]
                atn_hotkey = '' if len(options) < 2 else options[1]
            if atn_name != '':
                if atn_name == '_':
                    self.uiList[menu_str].addSeparator()
                else:
                    if atn_name not in self.uiList.keys():
                        self.uiList[atn_name] = QtWidgets.QAction(atn_title, self)
                        if atn_hotkey != '':
                            self.uiList[atn_name].setShortcut(QtGui.QKeySequence(atn_hotkey))
                    self.uiList[menu_str].addAction(self.uiList[atn_name])
                    
    def qui_class(self,uiName,uiInfo=[]):
        if not isinstance(uiInfo, (list, tuple)):
            uiInfo = [uiInfo]
            
        uiClass = uiName.rsplit('_',1)[-1]
        if uiClass == 'layout' and len(uiInfo)>0:
            uiClass = uiInfo[0]
        if uiClass in self.qui_user_dict:
            uiClass = self.qui_user_dict[uiClass] # first, try user dict
        elif uiClass in self.qui_core_dict:
            uiClass = self.qui_core_dict[uiClass] # then, try default core dict
        # check
        if hasattr(QtWidgets, uiClass) or uiClass in sys.modules:
            return [uiClass,1]
        else:
            return [uiClass,0]

    def qui(self, ui_list_string, parent_ui_string='', insert_opt=''):
        # ui format: 
        # 'process_label;Process Path: | process_file_input | process_file_btn;Process'
        # 'extra_check;Add Extra'
        # 'option_choice;(TypeA,TypeB) | file_tree;(Name,Path) | option_space;(5,5,5,3)'
        # parent format:
        # 'main_layout;vbox', 'main_hbox', 'main_form'
        # 'main_grid' <h/v>
        # 'main_split;v', 
        # 'main_grp;hbox;Process', 
        # 'main_tab;v' <(Media,Edit,Publish)>
        ui_string_list = [ x.strip() for x in ui_list_string.split('|') if x.strip()!='']
        ui_data_list = []
        for ui_string in ui_string_list:
            creation_result = self.qui_data(ui_string)
            if creation_result is not None:
                ui_data_list.append(creation_result)
        # - ui parent
        if parent_ui_string == '':
            return ui_data_list
        else:
            parent_ui_data = self.qui_data(parent_ui_string)
            if parent_ui_data is not None:
                self.qui_insert(ui_data_list, parent_ui_data, insert_opt)
                return parent_ui_data['name']
            else:
                return
        
    def qui_data(self, ui_string):
        # ui_string to ui_data, if not created, create obj in ui_data
        # reference
        class_error_msg = "WARNING: ({0}) is not defined in self.qui_user_dict and it is not a Qt widget class or User class; Item {1} Ignored."
        # process
        info_list = ui_string.split(';')
        uiName = info_list[0]
        uiLabel = ''
        uiInfo = []
        if '@' in uiName:
            uiName,uiLabel = uiName.split('@',1)
        if len(info_list) > 1:
            uiInfo = info_list[1:] # .split(',')
            
        if uiName in self.uiList.keys():
            # case 1: already created
            return {'name':uiName, 'obj':self.uiList[uiName], 'label':uiLabel}
        else:
            # case 2: create obj
            uiClass, isValid = self.qui_class(uiName, uiInfo)
            if not isValid:
                print(class_error_msg.format(uiClass, uiName))
                return None
            else:
                if len(uiInfo)==0 and uiClass in ('QPushButton','QLabel'):
                    uiInfo.append(uiName) # give empty button and label a place holder name
                return self.qui_obj( {'name': uiName, 'class': uiClass, 'label':uiLabel, 'info': uiInfo} )
                
    def qui_obj(self, ui_data):
        # ui_data to obj generation
        # reference value
        policyList = ( 
            QtWidgets.QSizePolicy.Fixed, 
            QtWidgets.QSizePolicy.Minimum, 
            QtWidgets.QSizePolicy.Maximum, 
            QtWidgets.QSizePolicy.Preferred, 
            QtWidgets.QSizePolicy.Expanding, 
            QtWidgets.QSizePolicy.MinimumExpanding, 
            QtWidgets.QSizePolicy.Ignored,
        )
        
        # case 1: already has obj created, just return
        if 'obj' in ui_data.keys():
            return ui_data
        
        # case 2: create obj
        uiName = ui_data['name']
        uiClass = ui_data['class']
        uiInfo = []
        if 'info' in ui_data.keys():
            uiInfo = ui_data['info']
        # -- 3rd part widget, create like UI_Class.UI_Class()
        if not hasattr(QtWidgets, uiClass):
            self.uiList[uiName] = getattr(sys.modules[uiClass], uiClass)(*uiInfo)
            ui_data['obj'] = self.uiList[uiName]
            return ui_data
            
        # -- QtWidgets
        if uiClass in ('QVBoxLayout', 'QHBoxLayout', 'QFormLayout', 'QGridLayout'):
            # --- Qt Layout creation preset func
            if uiClass == "QFormLayout":
                self.uiList[uiName] = QtWidgets.QFormLayout()
                self.uiList[uiName].setLabelAlignment(QtCore.Qt.AlignLeft)
                self.uiList[uiName].setFieldGrowthPolicy(QtWidgets.QFormLayout.AllNonFixedFieldsGrow)    
                ui_data['obj'] = self.uiList[uiName]
            elif uiClass == "QGridLayout":
                self.uiList[uiName] = QtWidgets.QGridLayout()
                ui_data['obj'] = self.uiList[uiName]
            elif uiClass == "QHBoxLayout":
                self.uiList[uiName] = QtWidgets.QHBoxLayout()
                self.uiList[uiName].setAlignment(QtCore.Qt.AlignTop)
                ui_data['obj'] = self.uiList[uiName]
            else:
                self.uiList[uiName] = QtWidgets.QVBoxLayout()
                self.uiList[uiName].setAlignment(QtCore.Qt.AlignTop)
                ui_data['obj'] = self.uiList[uiName]
        elif uiClass in ('QSplitter', 'QTabWidget', 'QGroupBox'):
            # --- Qt container creation
            if uiClass == 'QSplitter':
                split_type = QtCore.Qt.Horizontal
                if 'v' in uiInfo:
                    split_type = QtCore.Qt.Vertical
                self.uiList[uiName]=QtWidgets.QSplitter(split_type)
                ui_data['obj'] = self.uiList[uiName]
            elif uiClass == 'QTabWidget':
                self.uiList[uiName]=QtWidgets.QTabWidget()
                self.uiList[uiName].setStyleSheet("QTabWidget::tab-bar{alignment:center;}QTabBar::tab { min-width: 100px; }")
                if 'v' in uiInfo:
                    self.uiList[uiName].setTabPosition(QtWidgets.QTabWidget.West)
                ui_data['obj'] = self.uiList[uiName]
            elif uiClass == 'QGroupBox':
                grp_layout_class = 'QVBoxLayout'
                if len(uiInfo)>0:
                    new_grp_layout_class, isValid = self.qui_class(uiName+"_layout", uiInfo[0])
                    if isValid:
                        grp_layout_class = new_grp_layout_class
                grp_title = uiName
                if len(uiInfo)== 2:
                    grp_title = uiInfo[-1]
                grp_layout_obj = self.qui_obj({'name':uiName+"_layout", 'class': grp_layout_class })['obj']
                self.uiList[uiName] = QtWidgets.QGroupBox(grp_title)
                self.uiList[uiName].setLayout(grp_layout_obj)
                ui_data['obj'] = self.uiList[uiName]
        elif uiClass == 'QComboBox':
            self.uiList[uiName] = QtWidgets.QComboBox()
            if len(uiInfo)>0:
                item_list = uiInfo[0].replace('(','').replace(')','').split(',')
                self.uiList[uiName].addItems(item_list)
            ui_data['obj'] = self.uiList[uiName]
        elif uiClass == 'QTreeWidget':
            self.uiList[uiName] = QtWidgets.QTreeWidget()
            if len(uiInfo)>0:
                label_list = uiInfo[0].replace('(','').replace(')','').split(',')
                self.uiList[uiName].setHeaderLabels(label_list)
            ui_data['obj'] = self.uiList[uiName]
        elif uiClass == 'QSpacerItem':
            # 0 = fixed; 1 > min; 2 < max; 3 = prefered; 4 = <expanding>; 5 = expanding> Aggresive; 6=4 ignored size input
            # factors in fighting for space: horizontalStretch
            # extra space: setContentsMargins and setSpacing
            # ref: http://www.cnblogs.com/alleyonline/p/4903337.html
            val = [5,5,5,3]
            for i in range(len(uiInfo)):
                val[i] = int(uiInfo[i])
            self.uiList[uiName] = QtWidgets.QSpacerItem(val[0],val[1], policyList[val[2]], policyList[val[3]] )
            ui_data['obj'] = self.uiList[uiName]
        else:
            if len(uiInfo) == 0:
                self.uiList[uiName] = getattr(QtWidgets, uiClass)()
                ui_data['obj'] = self.uiList[uiName]
            else:
                self.uiList[uiName] = getattr(QtWidgets, uiClass)(*uiInfo)
                ui_data['obj'] = self.uiList[uiName]
        
        return ui_data
     
    def qui_insert(self, ui_data_list, parent_data, insert_opt=''):
        # get parentLayout inside parentObject
        parentObject = parent_data['obj']
        if isinstance(parentObject, QtWidgets.QGroupBox):
            parentObject = parentObject.layout()
        
        if isinstance(parentObject, QtWidgets.QBoxLayout):
            # layout
            for ui_data in ui_data_list:
                ui = ui_data['obj']
                if isinstance(ui, QtWidgets.QWidget):
                    parentObject.addWidget(ui)
                elif isinstance(ui, QtWidgets.QSpacerItem):
                    parentObject.addItem(ui)
                elif isinstance(ui, QtWidgets.QLayout):
                    parentObject.addLayout(ui)
        elif isinstance(parentObject, QtWidgets.QGridLayout):
            # grid: one row/colume operation only
            insertRow = parentObject.rowCount()
            insertCol = parentObject.columnCount()
            for i in range(len(ui_data_list)):
                each_ui = ui_data_list[i]['obj']
                x = insertRow if insert_opt=="h" else i
                y = i if insert_opt=="h" else insertCol
                if isinstance(each_ui, QtWidgets.QWidget):
                    parentObject.addWidget(each_ui,x,y)
                elif isinstance(each_ui, QtWidgets.QSpacerItem):
                    parentObject.addItem(each_ui,x,y)
                elif isinstance(each_ui, QtWidgets.QLayout):
                    parentObject.addLayout(each_ui,x,y)
        elif isinstance(parentObject, QtWidgets.QFormLayout):
            for ui_data in ui_data_list:
                ui = ui_data['obj']
                name = ui_data['name']
                label = '' if 'label' not in ui_data.keys() else ui_data['label']
                if isinstance(ui, QtWidgets.QWidget) or isinstance(ui, QtWidgets.QLayout):
                    if label != '':
                        self.uiList[name+'_label'] = QtWidgets.QLabel(label)
                        parentObject.addRow(self.uiList[name+'_label'], ui)
                    else:
                        parentObject.addRow(ui)
        elif isinstance(parentObject, QtWidgets.QSplitter):
            # split
            for ui_data in ui_data_list:
                each_ui = ui_data['obj']
                if isinstance(each_ui, QtWidgets.QWidget):
                    parentObject.addWidget(each_ui)
                else:
                    tmp_holder = QtWidgets.QWidget()
                    tmp_holder.setLayout(each_ui)
                    parentObject.addWidget(tmp_holder)
        elif isinstance(parentObject, QtWidgets.QTabWidget):
            # tab
            tab_names = insert_opt.replace('(','').replace(')','').split(',')
            for i in range( len(ui_data_list) ):
                each_tab = ui_data_list[i]['obj']
                each_name = 'tab_'+str(i)
                if i < len(tab_names):
                    if tab_names[i] != '':
                        each_name = tab_names[i]
                if isinstance(each_tab, QtWidgets.QWidget):
                    parentObject.addTab(each_tab, each_name)
                else:
                    tmp_holder = QtWidgets.QWidget()
                    tmp_holder.setLayout(each_tab)
                    parentObject.addTab(tmp_holder, each_name)
                    
    def qui_policy(self, ui_list, w, h):
        # reference value
        policyList = ( 
            QtWidgets.QSizePolicy.Fixed, 
            QtWidgets.QSizePolicy.Minimum, 
            QtWidgets.QSizePolicy.Maximum, 
            QtWidgets.QSizePolicy.Preferred, 
            QtWidgets.QSizePolicy.Expanding, 
            QtWidgets.QSizePolicy.MinimumExpanding, 
            QtWidgets.QSizePolicy.Ignored,
        )
        # 0 = fixed; 1 > min; 2 < max; 3 = prefered; 4 = <expanding>; 5 = expanding> Aggresive; 6=4 ignored size input
        if not isinstance(ui_list, (list, tuple)):
            ui_list = [ui_list]
        for each_ui in ui_list:
            if isinstance(each_ui, str):
                each_ui = self.uiList[each_ui]
            each_ui.setSizePolicy(policyList[w],policyList[h])
            
#############################################
# User Class creation
#############################################
version = '0.1'
date = '2019.07.05'
log = '''
#------------------------------
v0.1: (2019.07.05)
  * notes here
#------------------------------
'''
help = '''
wip
'''
# --------------------
#  user module list
# --------------------

class UserClassUI(UniversalToolUI):
    def __init__(self, parent=None, mode=0):
        UniversalToolUI.__init__(self, parent)
        
        # class variables
        self.version= version
        self.date = date
        self.log = log
        self.help = help
        
        # mode: example for receive extra user input as parameter
        self.mode = 0
        if mode in [0,1]:
            self.mode = mode # mode validator
        # Custom user variable
        #------------------------------
        # initial data
        #------------------------------
        self.memoData['data']=[]
        self.memoData['settingUI']=[]
        self.qui_user_dict = {} # e.g: 'edit': 'LNTextEdit',
        
        if isinstance(self, QtWidgets.QMainWindow):
            self.setupMenu()
        self.setupWin()
        self.setupUI()
        self.Establish_Connections()
        self.loadLang()
        self.loadData()
        
    #------------------------------
    # overwrite functions
    #------------------------------
    def setupMenu(self):
        self.qui_menubar('file_menu;&File | setting_menu;&Setting | help_menu;&Help')
        
        info_list = ['export', 'import','user']
        info_item_list = ['{0}Config_atn;{1} Config (&{2}),Ctrl+{2}'.format(info,info.title(),info.title()[0]) for info in info_list]+['_']
        self.qui_menu('|'.join(info_item_list), 'setting_menu')
        # toggle on top
        self.qui_menu('toggleTop_atn;Toggle Always-On-Top', 'setting_menu')
        # default help menu
        super(self.__class__,self).setupMenu()
    
    def setupWin(self):
        super(self.__class__,self).setupWin()
        # self.setGeometry(500, 300, 250, 110) # self.resize(250,250)
        if hostMode == "desktop":
            QtWidgets.QApplication.setStyle(QtWidgets.QStyleFactory.create('Cleanlooks'))
        self.setStyleSheet("QLineEdit:disabled{background-color: gray;}")
        
    def setupUI(self):
        super(self.__class__,self).setupUI('grid')
        #------------------------------
        # user ui creation part
        #------------------------------
        self.qui('box_btn;Box | sphere_btn;Sphere | ring_btn;Ring', 'my_layout;grid', 'h')
        self.qui('box2_btn;Box2 | sphere2_btn;Sphere2 | ring2_btn;Ring2', 'my_layout', 'h')
        
        self.qui('cat_btn;Cat | dog_btn;Dog | pig_btn;Pig', 'pet_layout;grid', 'v')
        self.qui('cat2_btn;Cat2 | dog2_btn;Dog2 | pig2_btn;Pig2', 'pet_layout', 'v')
        
        self.qui('name_input@Name:;John | email_input@Email:;test@test.com', 'entry_form')
        
        self.qui('user2_btn;User2 | info2_btn;Info2', 'my_grp;vbox;Personal Data')
        
        self.qui('source_txt | process_btn;Process and Update', 'upper_vbox')
        self.qui('upper_vbox | result_txt', 'input_split;v')
        self.qui('filePath_input | fileLoad_btn;Load | fileExport_btn;Export', 'fileBtn_layout;hbox')
        self.qui('test_space;5;5;5;3 | testSpace_btn;Test Space', 'testSpace_layout;hbox')
        self.qui('my_layout | my_table | input_split | entry_form | fileBtn_layout | pet_layout | my_grp | testSpace_layout', 'main_layout')
        
        cur_table = self.uiList['my_table']
        cur_table.setRowCount(0)
        cur_table.setColumnCount(1)
        cur_table.insertColumn(cur_table.columnCount())
        cur_item = QtWidgets.QTableWidgetItem('ok') #QtWidgets.QPushButton('Cool') #
        cur_table.insertRow(0)
        cur_table.setItem(0,1, cur_item) #setCellWidget(0,0,cur_item)
        cur_table.setHorizontalHeaderLabels(('a','b'))
        '''
        self.qui('source_txt | process_btn;Process and Update', 'upper_vbox')
        self.qui('upper_vbox | result_txt', 'input_split;v')
        self.qui('filePath_input | fileLoad_btn;Load | fileExport_btn;Export', 'fileBtn_layout;hbox')
        self.qui('input_split | fileBtn_layout', 'main_layout')
        '''
        self.memoData['settingUI']=[]
        #------------- end ui creation --------------------
        keep_margin_layout = ['main_layout']
        keep_margin_layout_obj = []
        # add tab layouts
        for each in self.uiList.values():
            if isinstance(each, QtWidgets.QTabWidget):
                for i in range(each.count()):
                    keep_margin_layout_obj.append( each.widget(i).layout() )
        for name, each in self.uiList.items():
            if isinstance(each, QtWidgets.QLayout) and name not in keep_margin_layout and not name.endswith('_grp_layout') and each not in keep_margin_layout_obj:
                each.setContentsMargins(0, 0, 0, 0)
        self.quickInfo('Ready')
        # self.statusBar().hide()
        
    def Establish_Connections(self):
        super(self.__class__,self).Establish_Connections()
        # custom ui response
        # shortcut connection
        self.hotkey = {}
        # self.hotkey['my_key'] = QtWidgets.QShortcut(QtGui.QKeySequence( "Ctrl+1" ), self)
        # self.hotkey['my_key'].activated.connect(self.my_key_func)
        
    def loadData(self):
        print("Load data")
        # load config
        config = {}
        config['root_name'] = 'root_default_name'
        # overload config file if exists next to it
        # then, save merged config into self.memoData['config']
        prefix, ext = os.path.splitext(self.location)
        config_file = prefix+'_config.json'
        if os.path.isfile(config_file):
            external_config = self.readDataFile(config_file)
            print('info: External config file found.')
            if isinstance( external_config, dict ):
                self.memoData['config'] = self.dict_merge(config, external_config, addKey=1)
                print('info: External config merged.')
            else:
                self.memoData['config'] = config
                print('info: External config is not a dict and ignored.')
        else:
            self.memoData['config'] = config
        
        # load user setting
        user_setting = {}
        if self.mode == 0:
            # for standalone mode only
            user_dirPath = os.path.join(os.path.expanduser('~'), 'Tool_Config', self.__class__.__name__)
            user_setting_filePath = os.path.join(user_dirPath, 'setting.json')
            if os.path.isfile(user_setting_filePath):
                user_setting = self.readDataFile(user_setting_filePath)
                if 'sizeInfo' in user_setting:
                    self.setGeometry(*user_setting['sizeInfo'])
        # custome setting loading here
        preset = {}
        for ui in self.memoData['settingUI']:
            if ui in user_setting:
                preset[ui]=user_setting[ui]
        #self.updateUI(preset)
        
    def closeEvent(self, event):
        if self.mode == 0:
            # for standalone mode only
            user_dirPath = os.path.join(os.path.expanduser('~'), 'Tool_Config', self.__class__.__name__)
            if not os.path.isdir(user_dirPath):
                try: 
                    os.makedirs(user_dirPath)
                except OSError:
                    print('Error on creation user data folder')
            if not os.path.isdir(user_dirPath):
                print('Fail to create user dir.')
                return
            # save setting
            user_setting = {}
            geoInfo = self.geometry()
            user_setting['sizeInfo'] = [geoInfo.x(), geoInfo.y(), geoInfo.width(), geoInfo.height()]
            # custome setting saving here
            for ui in self.memoData['settingUI']:
                if ui.endswith('_choice'):
                    user_setting[ui] = unicode(self.uiList[ui].currentText())
                elif ui.endswith('_check'):
                    user_setting[ui] = self.uiList[ui].isChecked()
                elif ui.endswith('_input'):
                    user_setting[ui] = unicode(self.uiList[ui].text())
                elif ui.endswith('_tab'):
                    user_setting[ui] = self.uiList[ui].currentIndex()
            user_setting_filePath = os.path.join(user_dirPath, 'setting.json')
            self.writeDataFile(user_setting, user_setting_filePath)
        
    # - example button functions
    def updateUI(self, preset):
        for ui_name in preset:
            if ui_name.endswith('_choice'):
                if preset[ui_name] != '':
                    the_idx = self.uiList[ui_name].findText(preset[ui_name])
                    if the_idx != -1:
                        self.uiList[ui_name].setCurrentIndex(the_idx)
            elif ui_name.endswith('_check'):
                self.uiList[ui_name].setChecked(preset[ui_name])
            elif ui_name.endswith('_input'):
                if preset[ui_name] != '':
                    self.uiList[ui_name].setText(preset[ui_name])
            elif ui_name.endswith('_tab'):
                self.uiList[ui_name].setCurrentIndex(preset[ui_name])
    def process_action(self): # (optional)
        config = self.memoData['config']
        print("Process ....")
        source_txt = unicode(self.uiList['source_txt'].toPlainText())
        # 2: update memory
        self.memoData['data'] = [row.strip() for row in source_txt.split('\n')]
        print("Update Result")
        txt=config['root_name']+'\n'+'\n'.join([('>>: '+row) for row in self.memoData['data']])
        self.uiList['result_txt'].setText(txt)
    
    # - example file io function
    def exportConfig_action(self):
        file= self.quickFileAsk('export', {'json':'JSON data file', 'xdat':'Pickle binary file'})
        if file == "":
            return
        # export process
        ui_data = self.memoData['config']
        # file process
        if file.endswith('.xdat'):
            self.writeDataFile(ui_data, file, binary=1)
        else:
            self.writeDataFile(ui_data, file)
        self.quickInfo("File: '"+file+"' creation finished.")
    def importConfig_action(self):
        file= self.quickFileAsk('import',{'json':'JSON data file', 'xdat':'Pickle binary file'})
        if file == "":
            return
        # import process
        ui_data = ""
        if file.endswith('.xdat'):
            ui_data = self.readDataFile(file, binary=1)
        else:
            ui_data = self.readDataFile(file)
        self.memoData['config'] = ui_data
        self.quickInfo("File: '"+file+"' loading finished.")
    def userConfig_action(self):
        user_dirPath = os.path.join(os.path.expanduser('~'), 'Tool_Config', self.__class__.__name__)
        self.openFolder(user_dirPath)
        
#=======================================
#  window instance creation
#=======================================

import ctypes # for windows instance detection
single_UserClassUI = None
app_UserClassUI = None
def main(mode=0):
    # get parent window in Maya
    parentWin = None
    if hostMode == "maya":
        if qtMode in (0,2): # pyside
            parentWin = shiboken.wrapInstance(long(mui.MQtUtil.mainWindow()), QtWidgets.QWidget)
        elif qtMode in (1,3): # PyQt
            parentWin = sip.wrapinstance(long(mui.MQtUtil.mainWindow()), QtCore.QObject)
    # create app object for certain host
    global app_UserClassUI
    if hostMode in ('desktop', 'blender', 'npp', 'fusion'):
        # single instance app mode on windows
        if osMode == 'win':
            # check if already open for single desktop instance
            from ctypes import wintypes
            order_list = []
            result_list = []
            top = ctypes.windll.user32.GetTopWindow(None)
            if top: 
                length = ctypes.windll.user32.GetWindowTextLengthW(top)
                buff = ctypes.create_unicode_buffer(length + 1)
                ctypes.windll.user32.GetWindowTextW(top, buff, length + 1)
                class_name = ctypes.create_string_buffer(200)
                ctypes.windll.user32.GetClassNameA(top, ctypes.byref(class_name), 200)
                result_list.append( [buff.value, class_name.value, top ])
                order_list.append(top)
                while True:
                    next = ctypes.windll.user32.GetWindow(order_list[-1], 2) # win32con.GW_HWNDNEXT
                    if not next:
                        break
                    length = ctypes.windll.user32.GetWindowTextLengthW(next)
                    buff = ctypes.create_unicode_buffer(length + 1)
                    ctypes.windll.user32.GetWindowTextW(next, buff, length + 1)
                    class_name = ctypes.create_string_buffer(200)
                    ctypes.windll.user32.GetClassNameA(next, ctypes.byref(class_name), 200)
                    result_list.append( [buff.value, class_name.value, next] )
                    order_list.append(next)
            # result_list: [(title, class, hwnd int)]
            winTitle = 'UserClassUI' # os.path.basename(os.path.dirname(__file__))
            is_opened = 0
            for each in result_list:
                if re.match(winTitle+' - v[0-9.]* - host: desktop',each[0]) and each[1] == 'QWidget':
                    is_opened += 1
                    if is_opened == 1:
                        ctypes.windll.user32.SetForegroundWindow(each[2])
                        sys.exit(0) # 0: success, 1-127: bad error
                        return
        if hostMode in ('npp','fusion'):
            app_UserClassUI = QtWidgets.QApplication([])
        elif hostMode in ('houdini'):
            pass
        else:
            app_UserClassUI = QtWidgets.QApplication(sys.argv)
    
    #--------------------------
    # ui instance
    #--------------------------
    # Keep only one copy of windows ui in Maya
    global single_UserClassUI
    if single_UserClassUI is None:
        if hostMode == 'maya':
            single_UserClassUI = UserClassUI(parentWin, mode)
        elif hostMode == 'nuke':
            single_UserClassUI = UserClassUI(QtWidgets.QApplication.activeWindow(), mode)
        elif hostMode == 'houdini':
            hou.session.mainWindow = hou.qt.mainWindow()
            single_UserClassUI = UserClassUI(hou.session.mainWindow, mode)
        else:
            single_UserClassUI = UserClassUI()
    single_UserClassUI.show()
    ui = single_UserClassUI
    if hostMode != 'desktop':
        ui.activateWindow()
    
    # loop app object for certain host
    if hostMode in ('desktop'):
        sys.exit(app_UserClassUI.exec_())
    elif hostMode in ('npp','fusion'):
        app_UserClassUI.exec_()
    return ui

if __name__ == "__main__":
    main()