0

I have tried a direct cut and paste of the code found here: Best way to display logs in pyqt?

and it works. I have been trying to link the logging handler to a QPlainTextEdit widget named "log" in the ui file made with qtcreator. I have tried a bunch of things at random, but I need to get advice from someone who actually knows what they are doing.

It would be convenient if I could have some reusable code I can drop in my projects and then use the QT designer to make the GUI and when I need a log (which will be almost always) to be able to have something I could post log messages to. I would mostly put print commands in the log so the user knows where the project is. Some of my projects take days (power system studies). It would also be convenient to be able to write to the window from workers in a pool. I am not an advanced Python coder, but have been able to use Python to multiply my productivity at work. I have looked at some other code on stackoverflow but just cannot seem to get it to work.

Here is some code I have been experimenting with:

import sys
from PyQt5 import uic
from PyQt5 import QtWidgets
import logging

class QTextEditLogger(logging.Handler):
    def __init__(self, parent):
        super().__init__()
        self.widget = QtWidgets.QPlainTextEdit(parent)
        self.widget.setReadOnly(True)

    def emit(self, record):
        msg = self.format(record)
        self.widget.appendPlainText(msg)


class MyDialog(QtWidgets.QDialog, QtWidgets.QPlainTextEdit):
    #def __init__(self, parent=None):
    #    super().__init__(parent)
   
    def __init__(self):
        super().__init__()
        uic.loadUi(r"C:\MyProject\Python\logging_display.ui", self)
        # Contains a plain text box named "log" and a button named "Clicker".
        
        self.show()
        self.raise_()
        
        # how do I connect the widget named "log" to
        # the logging handler defined by the 
        # "QTextEditLogger" class above?
        # or define some other logging handler to
        # send log messages to the "log" widget?

        # Connect "Clicker" button
        self.Clicker.clicked.connect(self.test)

    def test(self):
        self.log.appendPlainText('test')  # <-- would like to be able to use a logging handler

app = QtWidgets.QApplication(sys.argv)
dlg = MyDialog()
sys.exit(app.exec_())

here is the ui file text:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Window</class>
 <widget class="QDialog" name="Window">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1013</width>
    <height>623</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Learn Logging</string>
  </property>
  <property name="accessibleName">
   <string>Learning Logging</string>
  </property>
  <property name="sizeGripEnabled">
   <bool>true</bool>
  </property>
  <property name="modal">
   <bool>false</bool>
  </property>
  <widget class="QPlainTextEdit" name="log">
   <property name="geometry">
    <rect>
     <x>10</x>
     <y>10</y>
     <width>981</width>
     <height>561</height>
    </rect>
   </property>
  </widget>
  <widget class="QPushButton" name="Clicker">
   <property name="geometry">
    <rect>
     <x>360</x>
     <y>580</y>
     <width>261</width>
     <height>27</height>
    </rect>
   </property>
   <property name="text">
    <string>Button</string>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections/>
</ui>

Thank you!

1 Answers1

0

I did figure this out. I would say that learning the object oriented techniques needed for pyQt is not so easy. I wanted to post my code in the hopes that it will help others starting out with pyQt. It is heavily commented and covers:

  • Creating a logger that reports to a QT text window defined by QT
  • Designer Qt signals for communicating with the main loop and worker thread
  • transferring variables when starting a worker thread
  • altering logging levels using the GUI
  • having the worker thread update a progress bar
  • Running under Spyder without causing the kernal to die all the time

This answer is maybe longer than generally accepted, but having a template like this would have saved me an immense amount of frustration and internet searching, so I will present it anyway. It is more or less my application template going forward. There may still be bugs in the code, and I am new at this, so any suggestions or improvements would be welcome.

The code copies and modifies much help from the stackoverflow community. I am very grateful for all your help.

Template GUI code:

#  Template GUI application using
#  pyqt5 and Qtdesigner
#  by "BikeClubVest"
#  3 June 2022

import sys
import logging
from time import sleep

from PyQt5  import (
    QtCore, # Core routines
    QtGui, # Not sure
    uic, # contains translator for .ui files
    QtWidgets  # contains all widget descriptors
)
from PyQt5.QtCore import (
        Qt, 
        QObject, 
        QThread, 
        pyqtSignal
)

# shortcut declarations to make programming simpler
Signal = QtCore.pyqtSignal
Slot = QtCore.pyqtSlot

from PyQt5.QtWidgets import (
    QApplication,
    QLabel,
    QMainWindow,
    QPushButton,
    QVBoxLayout,
    QWidget,
    QPlainTextEdit,
)

# This points to the creator XML file
#qtCreatorFile = r'C:\(your path here)\logging_display2.ui'
# use ".\(filename)" to point to the same directory as the Python code
qtCreatorFile = r'.\logging_display4.ui'

# This activates a translator that converts the .ui file
# into a Class with name "Ui_My_App_Window" that contains
# all the pyQt5 descriptors for the designed GUI.  You don't
# get to see the code here, but it gets loaded into memory
Ui_My_App_Window, QtBaseClass = uic.loadUiType(qtCreatorFile)


# Calls the basic config routine to initialize default config settings.
# Enter desired values for the default logger here.
# If this is not the first call to basicConfig,
# nothing will be changed.
logging.basicConfig() 

# identifies the current logger
logger = logging.getLogger(__name__)
# Sets the logging level for the default logger
# Can be: DEBUG, INFO, WARNING, ERROR, or CRITICAL, by default
#logger.setLevel(logging.INFO)
logger.setLevel(logging.DEBUG)

#
# Signals need to be contained in a QObject or subclass in order to be correctly
# initialized.
#
class Signaller(QtCore.QObject):
    # creates a signal named "log event" with the type as shown
    log_event = Signal(str, logging.LogRecord)
#
# Output to a Qt GUI is only supposed to happen on the main thread. So, this
# handler is designed to take a slot function which is set up to run in the main
# thread. In this example, the function takes a string argument which is a
# formatted log message, and the log record which generated it. The formatted
# string is just a convenience - you could format a string for output any way
# you like in the slot function itself.
#
# You specify the slot function to do whatever GUI updates you want. The handler
# doesn't know or care about specific UI elements.
#   
    
class QtHandler(QObject, logging.Handler):
    def __init__(self, slotfunc, *args, **kwargs):
        super(QtHandler, self).__init__(*args, **kwargs)
        # Get available signals from "Signaller" class
        self.signaller = Signaller()
        # Connect the specific signal named "log_event"
        # to a function designated by the caller
        self.signaller.log_event.connect(slotfunc)

    # When a new log event happens, send the event
    # out via the named signal, in this case "log_event"
    def emit(self, record):
        s = self.format(record)
        self.signaller.log_event.emit(s, record)

#
# This example uses QThreads, which means that the threads at the Python level
# are named something like "Dummy-1". The function below gets the Qt name of the
# current thread.
#
def ctname():
    return QtCore.QThread.currentThread().objectName()

    # Create a worker class.  In general, this is where your
    # desired custom code will go for execution outside the main
    # event handling loop.

class Worker(QObject):
    
    # The initialization function is where the data from the event 
    # loop (My_Application) gets passed to the worker function.
    # The worker can reference the data as self.data_passed
    def __init__(self, data_passed = [], parent=None):
        QThread.__init__(self, parent)
        self.data_passed = data_passed
    
   
    # with the proper initialization, these signals
    # Could be added to the Signaller class, above
    finished = Signal()
    progress = Signal(int)
    progress_report = Signal(list)
        
    # This is the definition of the task  the worker will 
    # perform.  You can have more than one task for work.
    # Other workers will be defs within this class
    # with different names performing different tasks.
    def worker_task_name(self):
        try:
            extra = {'qThreadName': QtCore.QThread.currentThread().objectName() }
            logger.log(logging.INFO, 'Started work', extra=extra)
            
            '''
            worker code goes here!
            '''
            x=2
            # send a message to the logger
            logger.log(logging.DEBUG, 'test 0: ' + self.data_passed[0], extra=extra)
            # emit data on the "progress" signal
            self.progress.emit(0)
            # emit data on the "progress_report" signals=
            self.progress_report.emit([20, self.data_passed[0]])
            sleep(x)
            logger.log(logging.INFO, 'test 1: ' + self.data_passed[1], extra=extra)
            self.progress.emit(1)
            self.progress_report.emit([40, self.data_passed[1]])
            sleep(x)
            logger.log(logging.WARNING, 'test 2: ' + self.data_passed[2], extra=extra)
            self.progress.emit(2)
            self.progress_report.emit([60, self.data_passed[2]])
            sleep(x)
            logger.log(logging.ERROR, f'test 3: ' + self.data_passed[3], extra=extra)
            self.progress.emit(3)
            self.progress_report.emit([80, self.data_passed[3]])
            sleep(x)
            logger.log(logging.CRITICAL, f'test 4: ' + self.data_passed[4], extra=extra)
            self.progress.emit(4)
            self.progress_report.emit([99, self.data_passed[4]])
            sleep(x)
            
            '''
            end worker code
            '''
        # the "try-except" structure catches run-time errors and performs
        # an action, in this case reporting the error to the log window
        except Exception as e:
            logger.log(logging.ERROR, 'Exception = ' + str(e), extra = extra)
        # signal main thread that the code is done
        self.progress.emit(0)
        self.progress_report.emit([0, 'Ready'])
        self.finished.emit()


# This is the main application loop
# it references the widget "QMainWindow"
# and the GUI code implemented by
# the uic.loadUiType call, above.
class My_Application(QMainWindow, Ui_My_App_Window):
    # Colors are used in the "update status"
    # routine, below.
    COLORS = {
        logging.DEBUG: 'black',
        logging.INFO: 'blue',
        logging.WARNING: 'brown',
        logging.ERROR: 'red',
        logging.CRITICAL: 'purple',
    }
    LOG_LEVELS = {
        'DEBUG': logging.DEBUG,
        'INFO': logging.INFO,
        'WARNING': logging.WARNING,
        'ERROR': logging.ERROR,
        'CRITICAL': logging.CRITICAL,
    }
    
    # some data for passing to the worker function
    passed_data = ['This','is','all','passed','data!']
        
    def __init__(self):
        # Run the QMainWindow initialization routine
        QMainWindow.__init__(self)
        # initialize the Ui
        # the name should be "Ui_(object_name_of_top-level_object_in_ui_file)
        # This makes the setupUi routine, below, callable
        Ui_My_App_Window.__init__(self)
        # this runs the setupUi code inside the Ui class that was loaded with
        # the call to uic.loadUiType, above.  No Ui variables will be
        # accessible until the setupUi routine is run.
        self.setupUi(self)
        
        # Widgets for the GUI were created with "Qt Designer"
        # so there is no need to set up the GUI using code.
        # The widgets do need to be connected to the event loop:
        self.log_button.clicked.connect(self.manual_update)
        self.work_button.clicked.connect(self.start_worker)
        self.exit_button.clicked.connect(self.run_exit)
        self.logging_level_select.currentTextChanged.connect(self.change_logging_level)
        
        #routine to set up the logger
        self.setup_logger()
        self.handler.setLevel('INFO')
        
        # a status indicator for the loop
        self.statuss = 0
        
        
    # This routine is called once by the "My_Application"
    # class  init routine to set up the log handler
    def setup_logger(self):        
        
        # Set up the logging handler, in this case "QtHandler"
        # defined above.  Remember to use qThreadName rather 
        # than threadName in the format string.
        
        # This line connects the handler to "QtHandler" and
        # points the handler to the slot function ("update_status")
        # for the emit signal.  This sends the
        # signal to the routine "update_status", defined below.
        self.handler = QtHandler(self.update_status)
        
        # fs is a string containing format information
        # some options are:
        # %(pathname)s Full pathname of the source file where the logging call was issued(if available).
        # %(filename)s Filename portion of pathname.
        # %(module)s Module (name portion of filename).
        # %(funcName)s Name of function containing the logging call.
        # %(lineno)d Source line number where the logging call was issued (if available).
        # %(asctime)s time of log event
        #Trying different formatters, below        
        #fs = '%(asctime)s %(qThreadName)-12s %(levelname)-8s %(message)s'
        fs = '(line %(lineno)d) %(qThreadName)-12s %(levelname)-8s %(message)s'
        
        # formatter calls the logging formatter with the format
        # string and takes a return reference for the desired 
        # format.
        formatter = logging.Formatter(fs)
        
        # sets the formatter for "QtHandler"
        self.handler.setFormatter(formatter)
        
        # Adds QtHandler to the current logger identified above
        # at the very beginning of the program
        logger.addHandler(self.handler)
    
    # When data is emitted in the "progress" signal, this routine
    # will be called, and the value of the "progress" signal will
    # be passed into "n" and the "worker_label" text will be updated
    def reportProgress(self, n):
        self.statuss = n
        self.worker_label.setText(f"Long-Running Step: {n}")
    
    
    def change_logging_level(self, level):
        extra = {'qThreadName': ctname() }
        #level = self.logging_level_select.currentText
        log_level = self.LOG_LEVELS.get(level, logging.INFO)
        message = f'change_logging_level to {level}, {log_level},  handler = {self.handler}'
        message = message.replace("<", "_").replace(">", "_")
        logger.log(logging.DEBUG, message, extra = extra)
        self.handler.setLevel(level)
        message = f'now log level = ' + str(self.handler)
        message = message.replace("<", "_").replace(">", "_")
        logger.log(log_level, message, extra = extra)
    
    # receives data when a log event happens and posts the data
    # to the log window
    def update_status(self, status, record):
        color = self.COLORS.get(record.levelno, 'black')
        s = '<pre><font color="%s">%s</font></pre>' % (color, status)
        self.log_text.appendHtml(s)
    
    def manual_update(self):
        # This routine happens when the "manual_update" button is clicked
        # This function uses the formatted message passed in, but also uses
        # information from the record to format the message in an appropriate
        # color according to its severity (level).
        extra = {'qThreadName': ctname() }
        
        #if self.thread.isRunning():
        if self.statuss == 0:
            level, message = logging.WARNING, 'Thread Stopped ' + str(self.statuss)
        else:
            try:
                if self.thread.isRunning() == True:
                    level, message = logging.INFO, 'Thread Running ' + str(self.statuss)
                else:
                    level, message = logging.WARNING, 'Thread Stopped ' + str(self.statuss)
            except Exception as e:
                level, message = logging.ERROR, 'Thread Killed Before Completion: ' + str(e)
                self.statuss = 0
        logger.log(level, message, extra=extra)
    
    # this routine is executed when a signal is emitted by the worker thread
    # a change in the declared signal "progress_rreport" wlll trigger this function.
    def report_progress_bar(self, progress_report):
        extra = {'qThreadName': ctname() }
        logger.log(logging.DEBUG, 'Starting report_progress_bar ', extra = extra)
        try:
            logger.log(logging.DEBUG, f'progress_report = {progress_report}', extra = extra)
            self.load_progress_bar.setValue(progress_report[0])
            self.case_currently_loading.setText(progress_report[1])
        except Exception as e:
            logger.log(logging.ERROR, 'Exception = ' + str(e), extra = extra)
        logger.log(logging.DEBUG, 'report_progress_bar finished', extra = extra)

    # this starts up the worker thread.  First the thread is created, then the 
    # specific worker is constructed by calling the "Worker" class, and passing the
    # required data (in this case, "self.data_passed".  then the worker is moved to the
    # thread, then the specific function is started, then signals are connected,
    # then the thread is started, then other stuff is adjusted.
    def start_worker(self):
        try:
            # Create a QThread object
            self.thread = QThread()
            # Give the thread a name
            self.thread.setObjectName('WorkerThread')
            # Create a worker object by calling the "Worker" class defined above
            self.worker = Worker(self.passed_data)
            # Move worker to the thread just created
            self.worker.moveToThread(self.thread)
            
            # Connect signals and slots:
            # This connects the thread start troutine to the
            # name of the routine defined under the "Worker" class, above
            self.thread.started.connect(self.worker.worker_task_name)
                        
            # this connects the "finished' signal to three routines
            # to end the thread when the worker is finished.
            self.worker.finished.connect(self.thread.quit)
            self.worker.finished.connect(self.worker.deleteLater)
            self.thread.finished.connect(self.thread.deleteLater)
            
            # this connects the "progress" signal defined in
            # in the "Worker" class
            self.worker.progress.connect(self.reportProgress)
            
            # this executes the "progress_report" signal to the "" function
            # for updating the progress bar in the display
            self.worker.progress_report.connect(self.report_progress_bar)
            
            #  Start the thread
            self.thread.start()
            
            # Final resets
            
            # Disables the "worker_start" button when the
            # thread is launched.  Leaving the button enabled
            # will mean that the task can be re-launched multiple
            # times which may or may not be a problem.
            self.work_button.setEnabled(False)
            
            # re-enables the "Work Start" button disabled above when
            # when the thread has finished
            self.thread.finished.connect(lambda: self.work_button.setEnabled(True))
            # sets the stepLabel text when the thread has finished
            self.thread.finished.connect(lambda: self.stepLabel.setText("Long-Running Step: 0"))
        except Exception as e:
            logger.log(logging.ERROR, 'Exception = ' + str(e), extra = {'qThreadName': ctname() })        
        
    # Quits the event loop and closes the window when the Exit
    # button is pressed.
    def run_exit(self):
        self.close()
        
    def force_quit(self):
        pass
        # For use when the window is closed
        #if self.worker_thread.isRunning():
            #self.kill_thread()

######################################################################
# The code below  resolves the need to kill the kernal after closing

def main():
    # sets the name of the current thread in QThread
    QtCore.QThread.currentThread().setObjectName('MainThread')
    
    # If the application is not already runnng, launch it,
    # otherwise enter the exitsting instance.
    if not QApplication.instance():
        app = QApplication(sys.argv)
    else:
        print('already running')
        app = QApplication.instance()
        
    # launch ghd main window
    main = My_Application()
    # show he main window
    main.show()
    # put the main window at the top of the desktop
    main.raise_()
    
    # for execution from Spyder, disable the next
    # line or Spyder will throw an error
    #sys.exit(app.exec_())

    # returning main to the global coller will avoid
    # problems with Spyder kernel terminating when
    # the application is launched under Spyder
    return main

if __name__ == '__main__':      
    
    # creating a dummy variable ("m") to accept the 
    # object returned from "main" will prevent problems
    # with the kernal dying while running under Spyder.
    m = main()

Here is the .ui file I created in qtdesigner named "logging_display4.ui". For those who are new, the .ui files are just xml files that you can open and view in a text editor. To use this file, just copy it into notepad or your favorite text editor and save it as "logging_display4.ui". It can then be opened by qt designer.

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>My_Main_Window</class>
 <widget class="QMainWindow" name="My_Main_Window">
  <property name="enabled">
   <bool>true</bool>
  </property>
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>1037</width>
    <height>844</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Logging Example</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <widget class="QPushButton" name="work_button">
    <property name="geometry">
     <rect>
      <x>30</x>
      <y>20</y>
      <width>261</width>
      <height>81</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <family>Arial</family>
      <pointsize>14</pointsize>
     </font>
    </property>
    <property name="text">
     <string>Start Worker</string>
    </property>
   </widget>
   <widget class="QPushButton" name="log_button">
    <property name="geometry">
     <rect>
      <x>320</x>
      <y>20</y>
      <width>261</width>
      <height>81</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <family>Arial</family>
      <pointsize>14</pointsize>
     </font>
    </property>
    <property name="text">
     <string>Manual Log</string>
    </property>
   </widget>
   <widget class="QPushButton" name="exit_button">
    <property name="geometry">
     <rect>
      <x>740</x>
      <y>20</y>
      <width>261</width>
      <height>81</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <family>Arial</family>
      <pointsize>14</pointsize>
     </font>
    </property>
    <property name="text">
     <string>Exit</string>
    </property>
   </widget>
   <widget class="QLabel" name="worker_label">
    <property name="geometry">
     <rect>
      <x>340</x>
      <y>110</y>
      <width>661</width>
      <height>51</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <family>Arial</family>
      <pointsize>12</pointsize>
     </font>
    </property>
    <property name="accessibleName">
     <string>Worker Status</string>
    </property>
    <property name="layoutDirection">
     <enum>Qt::LeftToRight</enum>
    </property>
    <property name="autoFillBackground">
     <bool>false</bool>
    </property>
    <property name="styleSheet">
     <string notr="true"/>
    </property>
    <property name="frameShape">
     <enum>QFrame::Panel</enum>
    </property>
    <property name="frameShadow">
     <enum>QFrame::Raised</enum>
    </property>
    <property name="lineWidth">
     <number>3</number>
    </property>
    <property name="text">
     <string>Worker Running -- Step: 0</string>
    </property>
    <property name="alignment">
     <set>Qt::AlignCenter</set>
    </property>
   </widget>
   <widget class="QPlainTextEdit" name="log_text">
    <property name="geometry">
     <rect>
      <x>30</x>
      <y>200</y>
      <width>971</width>
      <height>561</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <family>8514oem</family>
     </font>
    </property>
    <property name="plainText">
     <string/>
    </property>
   </widget>
   <widget class="QLabel" name="worker_label_2">
    <property name="geometry">
     <rect>
      <x>30</x>
      <y>110</y>
      <width>301</width>
      <height>51</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <family>Arial</family>
      <pointsize>12</pointsize>
     </font>
    </property>
    <property name="accessibleName">
     <string>Worker Status</string>
    </property>
    <property name="layoutDirection">
     <enum>Qt::LeftToRight</enum>
    </property>
    <property name="autoFillBackground">
     <bool>false</bool>
    </property>
    <property name="styleSheet">
     <string notr="true"/>
    </property>
    <property name="frameShape">
     <enum>QFrame::Panel</enum>
    </property>
    <property name="frameShadow">
     <enum>QFrame::Raised</enum>
    </property>
    <property name="lineWidth">
     <number>3</number>
    </property>
    <property name="text">
     <string>Set Log Level:</string>
    </property>
    <property name="alignment">
     <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
    </property>
   </widget>
   <widget class="QComboBox" name="logging_level_select">
    <property name="geometry">
     <rect>
      <x>200</x>
      <y>120</y>
      <width>111</width>
      <height>31</height>
     </rect>
    </property>
    <property name="font">
     <font>
      <pointsize>10</pointsize>
     </font>
    </property>
    <property name="currentIndex">
     <number>3</number>
    </property>
    <item>
     <property name="text">
      <string>CRITICAL</string>
     </property>
    </item>
    <item>
     <property name="text">
      <string>ERROR</string>
     </property>
    </item>
    <item>
     <property name="text">
      <string>WARNING</string>
     </property>
    </item>
    <item>
     <property name="text">
      <string>INFO</string>
     </property>
    </item>
    <item>
     <property name="text">
      <string>DEBUG</string>
     </property>
    </item>
   </widget>
   <widget class="QLabel" name="case_currently_loading">
    <property name="geometry">
     <rect>
      <x>30</x>
      <y>170</y>
      <width>301</width>
      <height>21</height>
     </rect>
    </property>
    <property name="sizePolicy">
     <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
      <horstretch>0</horstretch>
      <verstretch>0</verstretch>
     </sizepolicy>
    </property>
    <property name="font">
     <font>
      <family>Arial</family>
      <pointsize>8</pointsize>
     </font>
    </property>
    <property name="accessibleName">
     <string>Worker Status</string>
    </property>
    <property name="layoutDirection">
     <enum>Qt::LeftToRight</enum>
    </property>
    <property name="autoFillBackground">
     <bool>false</bool>
    </property>
    <property name="styleSheet">
     <string notr="true"/>
    </property>
    <property name="frameShape">
     <enum>QFrame::StyledPanel</enum>
    </property>
    <property name="frameShadow">
     <enum>QFrame::Plain</enum>
    </property>
    <property name="lineWidth">
     <number>0</number>
    </property>
    <property name="text">
     <string>Ready</string>
    </property>
    <property name="alignment">
     <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
    </property>
   </widget>
   <widget class="QProgressBar" name="load_progress_bar">
    <property name="geometry">
     <rect>
      <x>340</x>
      <y>170</y>
      <width>661</width>
      <height>23</height>
     </rect>
    </property>
    <property name="value">
     <number>0</number>
    </property>
    <property name="textVisible">
     <bool>false</bool>
    </property>
    <property name="orientation">
     <enum>Qt::Horizontal</enum>
    </property>
    <property name="invertedAppearance">
     <bool>false</bool>
    </property>
   </widget>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>1037</width>
     <height>31</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections/>
</ui>