PyQt5: one callback works, the other doesn't - why?

Tejaswini UL's icon

My research has included:

I am building a linux 'launcher' program which currently has two callbacks. One simply launches the clicked app, the other creates a new launcher. The first one works fine - the second one has been extremely tricky. I've done a lot to solve this.

  1. Running PyCharm debug on script and watch values for self, etc. to learn more

  2. Moving the NewLauncher function to within the InitUI method.

  3. Endlessly changing "self", "centralWidget" and other object references.

  4. Using functools partial.

The error I'm getting is "AttributeError: 'QWidget' object has no attribute 'newLauncher'"

Here's the code: (my apologies if it's too long - I was recently advised not to edit too much out).

import sys, os
import subprocess
from functools import partial

from PyQt5.QtWidgets import QFileDialog, QToolButton, QHBoxLayout, QGridLayout, QSizePolicy, QSpacerItem, QWidget, QPushButton, QFormLayout, QLineEdit, QAction, QApplication, QDesktopWidget, QMainWindow, QTabWidget, QVBoxLayout

from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QSize

from ruamel.yaml import YAML


yaml = YAML()
file_object = open("/home/tsc/PycharmProjects/launcher/Matrix.yaml", "r")
code = file_object.read()
matrix = yaml.load(code)
file_object.close()


class App(QMainWindow):
    def __init__(self):
        super(App, self).__init__()

        self.initUI()

    def launch(self, filepath):
        subprocess.run(filepath)


    def newLauncher(self):
        num_butts = len(matrix)
        btn_str = 'btn' + str(num_butts)

        file_object = open("/home/tsc/PycharmProjects/launcher/Matrix.yaml", "a")
        btn_str = 'btn' + str(num_butts + 1)
        file_object.write("\n" + btn_str + ":\n")

        self.setStyleSheet('padding: 3px; background: white');
        fname, _ = QFileDialog.getOpenFileName(self, "select an executable or document to launch:", "",
                                               "all files (*.*)")

        path = fname
        fname = os.path.basename(fname)

        file_object.write("  " + "name: " + str(fname) + "\n" + "  " + "path: " + str(path) + "\n")

        self.setStyleSheet('padding: 3px; background: white');
        icon, _ = QFileDialog.getOpenFileName(self, "select an image file for the icon:", "",
                                              "all files (*.*)")

        file_object.write("  " + "icon: " + str(icon) + "\n")
        file_object.close()


    def initUI(self):
        super(App, self).__init__()

        centralWidget = QWidget()
        tabWidget = QTabWidget()

        lay = QVBoxLayout(centralWidget)

        for i in range(3):
            page = QWidget()
            pagelay = QGridLayout(page)
            bmatrix = {}

            for btn in matrix:
                name = matrix[btn]['name']
                filepath = matrix[btn]['path']
                icon = matrix[btn]['icon']
                bmatrix[btn] = QToolButton(page)
                bmatrix[btn].setIcon(QIcon(icon))
                bmatrix[btn].setIconSize(QSize(64, 64))
                bmatrix[btn].resize(100, 100)
                bmatrix[btn].clicked.connect(lambda checked, arg=filepath: self.launch(arg))

                pagelay.addWidget(bmatrix[btn])

            tabWidget.addTab(page, 'tab{}'.format(i))

        mainMenu = self.menuBar()
        fileMenu = mainMenu.addMenu('File')
        mainMenu.addMenu(fileMenu)
        newAction = QAction('&New', centralWidget)

        #1 newAction.triggered.connect(lambda checked, arg=matrix: centralWidget.newLauncher(arg)) - shows window.
        #2 newAction.triggered.connect(partial(self.NewLauncher, self)) - shows nothing, App has no NewLauncher

        fileMenu.addAction(newAction)
        editMenu = mainMenu.addMenu('Edit')

        lay.addWidget(mainMenu)
        lay.addWidget(tabWidget)

        centralWidget.setGeometry(100, 100, 1080, 630)
        centralWidget.setWindowTitle('LaunchMaster')
        qtRectangle = centralWidget.frameGeometry()
        centerPoint = QDesktopWidget().availableGeometry().center()
        qtRectangle.moveCenter(centerPoint)
        centralWidget.move(qtRectangle.topLeft())

        centralWidget.show()


if __name__ == '__main__':

    app = QApplication(sys.argv)
    ex = App()
    sys.exit(app.exec_())

And here's the yaml config file. You'll need to customize the paths, etc. if you want to test it. The interface has a menuBar and a tabWidget containing pages that themself contain the launcher buttons.

Matrix.yaml: substitute spaces for the underscores (indent is 2 chars.). I'm unsure of this markup syntax just yet, sorry for the hassle.

btn1:  
  name: firefox  
  path: firefox-esr  
  icon: /home/tsc/PycharmProjects/launcher/icons/firefox.jpeg  

btn2:  
  name: thunderbird  
  path: /home/tsc/thunderbird/thunderbird  
  icon: /home/tsc/PycharmProjects/launcher/icons/thunderbird.jpeg