PYQT5:基于QsciScintilla的代码编辑器分析8--子菜单及对话框

这一篇介绍子菜单“example”、“recentProject”、“Chip”、“Port”;对话框“complier”、“download setting”、“Find”的实现思路和源代码。

1.example子菜单

在这里插入图片描述
在第1章说到:example子菜单在实现时,要求将文件夹《example》放在与软件相同的目录下。example里面有子文件夹,例如图中的《01BasicCode》《02display》等。《01BasicCode》下面又有子文件夹,例如图中的《01target001》,后者里面就有一个同名的.cbp的文件,也就是《01target001.cbp》。如果工程文件名和所在目录不同名,将会检测不到工程文件,不能添加到example菜单中。

        #“fileMenu” 增加exampleMenu,再增加子菜单,example文件夹下面的每个子文件夹就是一个子菜单
        #子菜单再增加Action,每个子子文件夹对应一个Action(就是工程名)
        exampleMenu = QMenu('example', self)
        fileMenu.addMenu(exampleMenu)
        #0. exampleMenu 上的动作项触发信号链接到槽函数 OpenRecentProject
        exampleMenu.triggered[QAction].connect(self.OpenRecentProject)
        if os.path.exists(self.Code51_dir+'/example'):
            #1. 遍历examp 文件夹下面的子文件夹
            for dir in os.listdir(self.Code51_dir+'/example'):  
                if os.path.isfile(dir):     #1.2 为文件 则忽略
                    continue
                #2. 子文件夹也生成一个子子菜单
                exampleSubMenu=QMenu(dir, self)
                exampleMenu.addMenu(exampleSubMenu) 
                fulldir = self.Code51_dir+'/example'+'/'+dir
                #3. 遍历子文件夹里面的子子文件夹,子子文件夹就是工程所在
                for  _dir in os.listdir(fulldir):
                    if os.path.isfile(_dir):
                        continue
                    #        example       dir      _dir              _dir.cbp
                    #3.1 举例 example/01BasicCode/01target001/01target001.cbp
                    sub_fulldir=fulldir+'/'+_dir+'/'+_dir+'.cbp'
                    #4. 将工程名创建为一个动作项Action。
                    if os.path.exists(sub_fulldir):
                        fileAction = self.createAction(sub_fulldir, None, None, None, 
                                                            'Open an example project', checkable=False)
                        exampleSubMenu.addAction(fileAction)    

然后在点击Action时,通过Action.text()获得工程的完整路径,就可以打开工程:

    def OpenRecentProject(self, qaction):  
        #1. 已打开了工程,先关闭
        if len(self.fullfile) !=0:
            self.ProjectClose()
        #2. 获取工程完整路径
        self.fullfile = qaction.text()
        if os.path.exists(self.fullfile)==None:
            self.SetTipString("工程文件名不存在。打开失败。")
        #3. 打开工程
        self.OpenProjectByName()

OpenProjectByName()的源代码分析请参考第4章《xml格式的工程文件与树状列表部件》。

2.recentProject子菜单

在这里插入图片描述
本子菜单位于菜单Project的最后一项,记录了最近打开的10个工程路径。在打开软件时,从保存的初始化文件《Setting.ini》读入最近的10个工程路径。关闭软件时,当前的工程路径将保存回《Setting.ini》,且排在第1位。子菜单的动作项也是链接到上面的函数OpenRecentProject().

        #1. 创建RecentProject 子菜单,依附在 Project 菜单的最后一项
        self.RecentProject = QMenu('recent Project', self)
        projectMenu.addMenu(self.RecentProject)        
        fileGroup = QActionGroup(self)
        #2.从《setting.ini》读入10个工程路径
        settings=QSettings("setting.ini",QSettings.IniFormat)
        settings.setIniCodec(QTextCodec.codecForName("utf-8"))
        filesize=  settings.beginReadArray("recentProject")
        #3. 每个 工程路径 就是一个动作项 Action
        for i in range(filesize):
            settings.setArrayIndex(i)
            fileAction = self.createAction(settings.value("File_Name"), None, None, None, 
                                                            'Open a project', checkable=True)
            self.RecentProject.addAction(fileAction)
            fileGroup.addAction(fileAction)
        settings.endArray()
        #4. 动作项触发信号链接到槽函数 OpenRecentProject
        self.RecentProject.triggered[QAction].connect(self.OpenRecentProject)

与examp子菜单类似,点击Action时,通过Action.text()获得工程的完整路径,就可以打开工程。

3.Chip子菜单

芯片选择子菜单
Chip子菜单用于选择目标芯片,实际就是告诉软件用什么下载协议。这里只提供3类芯片(4种具体型号):
1】STC12C5A08S2
2】STC15F2K60S2
3】STC8F2K32S2
4】STC8A8K64S4A12(与STC8F2K32S2相同协议)
因为手头只有这几种,其它的没有测试。详情在第9章讲解。打开本编辑器时,会从《setting.ini》读取上一次的设定,选择芯片时会触发对应的槽函数,将改动保存到《setting.ini》。

        #创建setting——menu 
        #       -----Chip-select    1]12C5A ;   2]15F2K;     3]8F2K
        #       -----Port-select     
        SettingMenu = self.menuBar().addMenu("&Setting")
        self.ChipMenu = QMenu('Chip', self)
        self.ChipMenu.setIcon(QIcon(":/{0}.png".format("chip")))
        SettingMenu.addMenu(self.ChipMenu)
        Chip1 = self.createAction(PROTOCOL_12C5A, self.ChangChip, None, None, 
                                            'STC12C5A08S2',checkable=True )  
        Chip2 = self.createAction(PROTOCOL_15F2K, self.ChangChip, None, None, 
                                            'STC15F2K60S2', checkable=True)  
        Chip3 = self.createAction(PROTOCOL_8F2K, self.ChangChip, None, None, 
                                            'STC8F2K32S2', checkable=True)          
        PortGroup = QActionGroup(self)  #QActionGroup 里面的项是互斥的,只能有一个被选中
        PortGroup.addAction(Chip1)
        PortGroup.addAction(Chip2)
        PortGroup.addAction(Chip3)
        self.addActions(self.ChipMenu, (Chip1, Chip2, Chip3))
        # 根据 保存的设置信息,将对应的芯片项打钩
        actions = self.ChipMenu.actions()
        self.Chipstr =  settings.value("Download/Chip" )
        for i in range(len(actions)):
            if actions[i].text() == self.Chipstr:
                actions[i].setChecked(True)
                break     
    def ChangChip(self):    #选择芯片就是选择下载协议 
        actions = self.ChipMenu.actions()
        for i in range(len(actions)):
            if actions[i].isChecked():
                self.Chipstr = actions[i].text()
                settings=QSettings("setting.ini",QSettings.IniFormat)
                settings.setIniCodec(QTextCodec.codecForName("utf-8"))
                settings.setValue("Download/Chip",self.Chipstr )
                break     

4.串口Port子菜单

串口子菜单
在打开本编辑器时,初始化过程读取上一次的保存信息来决定哪个串口打钩。如果串口有变动,点击“Detect Port”刷新串口列表。

        #生成 串口子菜单
        self.PortMenu = QMenu('Port', self)
        self.PortMenu.setIcon(QIcon(":/{0}.png".format("com32")))
        DetectAct = self.createAction("&Detect Port", self.DetectPort, None, None, 
                                                            'Creat a project') 
        SettingMenu.addMenu(self.PortMenu)
        self.addActions(self.PortMenu,(DetectAct, None) )  
        #检测有限串口
        self.DetectPort() 
        # 根据 保存的设置信息,将对应的串口打钩
        actions = self.PortMenu.actions()
        self.Portstr =  settings.value("Download/Port" )
        for i in range(2, len(actions)):
            if actions[i].text() == self.Portstr:
                actions[i].setChecked(True)
                break     
    def DetectPort(self):          
        #1]检测所有存在的串口,将信息存储在字典中
        PortGroup = QActionGroup(self)
        port_list = list(serial.tools.list_ports.comports())
        actions = self.PortMenu.actions()
        #2]self.PortMenu.保留前2项,其它清空
        for i in range(2, len(actions)):
            self.PortMenu.removeAction(actions[i])           
        #3]将port_list 添加到菜单中
        for port in port_list:
            portAction = self.createAction(port[0], self.ChangPort, None, None, 
                                                            '选择下载口', checkable=True)
            self.PortMenu.addAction(portAction)
            PortGroup.addAction(portAction) 
    def ChangPort(self): 
        actions = self.PortMenu.actions()  
        for i in range(2, len(actions)):
            if actions[i].isChecked():
                self.Portstr = actions[i].text()
                settings=QSettings("setting.ini",QSettings.IniFormat)
                settings.setIniCodec(QTextCodec.codecForName("utf-8"))
                settings.setValue("Download/Port", self.Portstr)

5.下载方式选择对话框

在这里插入图片描述
下载hex文件的方式:1】冷启动;2】发送[0x5a,0x3a,0x6c];3】自定义数据,输入时用空格隔开,2个十六进制数字对齐。
波特率是指目标芯片运行时的下载口的波特率。

        #在设置菜单3中增加一个弹出对话框,用于选择下载方式:1]断电;2]默认指令;3]自定义指令
        SettingAction = self.createAction("Download way下载方式", self.OpenSettingDlg, None, None, 
                                                            '选择下载方式', checkable=False)
        self.downloadWay = settings.value("Download/way" )
        self.DIYString=settings.value("Download/baudrateIndex")
        if self.DIYString is not None:    #借用DIYString  
            self.baudIndex=int(self.DIYString)

        self.DIYString = settings.value("Download/DIY_Data" )
        #在设置菜单4中增加【编译设置】对话框,用于设置:1]编译路径;2]外部ram大小 --xdata 从工程文件中读取
        complierSettingAction = self.createAction("complier编译设置", self.OpenComplierSettingDlg, None, None, 
                                                            '设置编译器路径和外部ram大小', checkable=False)
        self.addActions(SettingMenu,(SettingAction, complierSettingAction))
        self.isComplierPathDefault=settings.value("Complier/Path/Default" )
        if self.isComplierPathDefault != None and self.isComplierPathDefault == '1':
            self.compilePath = self.Code51_dir
        else:
            self.compilePath =settings.value("Complier/Path/Input" )
            
        self.isXdataDefault=settings.value("Complier/Xdata/Default" )

这里的代码包含了下一节的编译设置。调用对话框的函数OpenSettingDlg()d 源码:

    def OpenSettingDlg(self):
        SettingDlg = Setting()
        #1. 初始化对话框参数
        if self.downloadWay == '1' :
            SettingDlg.radioButton_1.setChecked(True)
        elif self.downloadWay == '2' :
            SettingDlg.radioButton_2.setChecked(True)
        else :
            SettingDlg.radioButton_3.setChecked(True)
        SettingDlg.lineEdit.setText(self.DIYString)
        SettingDlg.comboBox.setCurrentIndex(self.baudIndex)
        #2.显示对话框    
        ret = SettingDlg.exec_()
        #3. 对话框按下 OK 时,保存设置改动
        if ret:
            settings=QSettings("setting.ini",QSettings.IniFormat)
            if SettingDlg.radioButton_1.isChecked():
                self.downloadWay = '1'
                settings.setValue("Download/way" , self.downloadWay)
            elif SettingDlg.radioButton_2.isChecked():
                self.downloadWay = '2'
                settings.setValue("Download/way" , self.downloadWay)
            else :
                self.downloadWay = '3'
                settings.setValue("Download/way" , self.downloadWay)
            self.DIYString=SettingDlg.lineEdit.text()
            self.baudIndex=SettingDlg.comboBox.currentIndex()
            settings.setValue("Download/DIY_Data" ,self.DIYString)
            settings.setValue("Download/baudrateIndex", self.baudIndex)

6.编译器设置对话框

在这里插入图片描述
编译器路径选择后保存在软件目录下的《setting.ini》文件中;外部ram则保存于工程文件《xxx.cbp》中。

    def OpenComplierSettingDlg(self):
        settings=QSettings("setting.ini",QSettings.IniFormat)
        SettingDlg = complierSetting(self)
        #1.初始化设置,xdata的参数不是通用的,每个工程都可以不同,要保存在工程文件中。
        if self.isComplierPathDefault == '1':
            SettingDlg.rdb_pathDefault.setChecked(True)
        else:
            SettingDlg.rdb_pathSelect.setChecked(True)            
        SettingDlg.lineEdit_path.setText(settings.value("Complier/Path/Input" ))
        
        if self.isXdataDefault == '1':
            SettingDlg.rdb_xdataDefault.setChecked(True)
            SettingDlg.lineEdit_xdata.setEnabled(False)
        else:
            SettingDlg.radioButton.setChecked(True)
        SettingDlg.lineEdit_xdata.setText(self.XdataLen)
        #2.打开对话框    
        ret = SettingDlg.exec_()
        if not ret :
            return
        #3.保存设置
        if SettingDlg.rdb_pathDefault.isChecked():
            self.isComplierPathDefault = '1'
        else:
            self.isComplierPathDefault = '0'            
        settings.setValue("Complier/Path/Default",self.isComplierPathDefault)
        self.compilePath=SettingDlg.lineEdit_path.text()
        settings.setValue("Complier/Path/Input",self.compilePath)
        if SettingDlg.rdb_xdataDefault.isChecked():
            self.isXdataDefault = '1'
            SettingDlg.lineEdit_xdata.setText('8192')
        else:
            self.isXdataDefault = '0'
        self.XdataLen = SettingDlg.lineEdit_xdata.text()
        settings.setValue("Complier/Xdata/Default",self.isXdataDefault)
        #settings.setValue("Complier/Xdata/Input",SettingDlg.lineEdit_xdata.text())
        #写人工程文件
        self.mycbp.SetOptionOfXram(self.XdataLen)

7.查找对话框

通过菜单Edit–>Find 或者 快捷键 Ctrl + F打开查找对话框。如果打开查找窗口时已有选择文字, 所选文字将自动传入;当然也可以复制粘贴,手动输入。查找后的结果显示在“built output”窗口。双击窗口将读取鼠标所在行的文字对应的文件名和行数,进行跳转。实现跳转的代码看回第7章《编译器命令与信息输出视图》第2节信息输出视图的源代码。
在这里插入图片描述
主窗口MainWindow对应菜单动作的槽函数源代码:

    def editFind(self):
        #1. 获取Mdi的焦点窗口
        textEdit = self.mdi.activeSubWindow()
        if textEdit is None :
            return     
        #2. 获取窗口里面的 SciTextEdit   
        textEdit=textEdit.widget()
        if textEdit is None :
            return
        #3. 初始化 查找窗口,传入SciTextEdit 中选中的文字 
        FindDlg = FindText(self)
        FindDlg.lineEdit_destText.setText(textEdit.selectedText())
        #4. 显示查找窗口
        FindDlg.exec_()

查找对话框FindText类的源代码:

class FindText(QDialog, Ui_Dialog):
    
    def __init__(self, parent=None):
        super(FindText, self).__init__(parent)
        self.setupUi(self)
        #1.主窗口作为父窗口 
        self.parent = parent  
        #2. 点击查找按钮的信号的槽函数
        self.ButtonFind.clicked.connect(self.FineTextInFiles)
        self.rdb_cbpDir.setChecked(True)
        
    def FineTextInFiles(self):
        self.parent.text_browser.clear()
        if os.path.isdir(self.parent.ProjectDir) == False:
            self.parent.text_browser.append("工程目录出错。")
            return
        
        text_file_list = []
        #1. 仅仅查找当前的文件
        if  self.rdb_currentFile.isChecked():
            text_file_list.append(self.parent.mdi.activeSubWindow().widget().filename)
        #2. 扫描整个工程目录的c、h文件,并存放到 text_file_list
        else:   
            walk_tree = os.walk(self.parent.ProjectDir)
            #"walk" through the directory tree and save the readable files to a list
            for root, subFolders, files in walk_tree:
                for file in files:
                    full_with_path = os.path.join(root, file)
                    if os.path.exists(full_with_path) != None:
                        endChar = full_with_path[-1]
                        if 'h' ==  endChar or 'H'  ==  endChar   or 'c' ==  endChar or 'C' ==  endChar:
                            full_with_path = full_with_path.replace("\\",  "/")
                            text_file_list.append(full_with_path)
        #3. 开始查找
        destCount=0
        for file in text_file_list:          
            fin = open(file, "r",  encoding='utf-8', errors="ignore")
            input_file = fin.read() 
            input_str = input_file.splitlines(False)  
            for i in range(len(input_str) ) :
                if self.lineEdit_destText.text() in input_str[i]:
                    destCount += 1
                    self.parent.text_browser.append("%s:%d:%s"%(file, i+1, input_str[i]))
        #4. 在信息输出窗口中显示结果
        self.parent.text_browser.append("在 %d 个文件中找到 %d 处。"%(len(text_file_list), destCount))
        #5. 发送一个 QCloseEvent,窗口自杀
        e = QCloseEvent()
        QApplication.postEvent(self, e)

查找结果

posted @ 2023-03-28 21:01  汉塘阿德  阅读(120)  评论(0编辑  收藏  举报  来源