示例#1
0
class MainWindow(object):
    '''
    Contains the implementation for building and displaying the 
    application's main window.
    '''

    def __init__(self, model, alg):
        '''
        Constructs the GUI and initializes internal parameters.
        '''
        self._window = QMainWindow()
        self._window.setWindowTitle("Reverse A*")
        self._worldWidget = WorldWidget(model, alg)
        self._model = model
        self._alg = alg
        self._spdSetting = 0
        self._timer = QTimer()
        #Every time the timer times out, invoke the _onStep method.
        self._timer.timeout.connect(self._onStep)        
        self._buildGUI()
        self._window.show()

    def _buildGUI(self):
        '''
        Construct the GUI widgets and layouts.
        '''
        centerWidget = QWidget()
        self._window.setCentralWidget(centerWidget)
        
        worldLayout = QHBoxLayout()
        worldLayout.addWidget(self._worldWidget)
        grpBx = QGroupBox("2D World")
        grpBx.setLayout(worldLayout)
        grpBx.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        
        ctrlPan = self._buildControlPanel()
        layout = QHBoxLayout()
        layout.addWidget(ctrlPan)
        layout.addWidget(grpBx)
        layout.setAlignment(ctrlPan, Qt.AlignLeft | Qt.AlignTop)
        centerWidget.setLayout(layout)

        
    def _buildControlPanel(self):
        '''
        Create all buttons, labels, etc for the application control elements
        '''
        layout = QVBoxLayout()
        layout.addWidget(self._buildSetupPanel())
        layout.addWidget(self._buildSpeedPanel())
        layout.addWidget(self._buildResultsPanel())
        layout.addWidget(self._buildRenderingOptions())
        layout.setAlignment(Qt.AlignLeft | Qt.AlignTop)
        
        ctrlWidget = QWidget(self._window)
        ctrlWidget.setLayout(layout)
        ctrlWidget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        return ctrlWidget        
    
    def _buildSetupPanel(self):
        '''
        Creates the sub-panel containing control widgets for re-initializing 
        the world on demand.
        '''
        self._percentLbl = QLabel("%")
        self._setupBtn = QPushButton("Setup", self._window)
        self._setupBtn.clicked.connect(self._onSetup)
        
        self._percentObstacleSldr = QSlider(Qt.Horizontal, self._window)
        self._percentObstacleSldr.setTickPosition(QSlider.TickPosition.TicksBelow)
        self._percentObstacleSldr.setTickInterval(10)
        self._percentObstacleSldr.setMinimum(0)
        self._percentObstacleSldr.setMaximum(100)
        self._percentObstacleSldr.valueChanged.connect(self._onPercentSlideChange)
        self._percentObstacleSldr.setValue(33)
        
        layout = QGridLayout()
        layout.addWidget(self._setupBtn, 0, 0, 1, 2)
        layout.addWidget(QLabel("Percent Occupied:"), 1, 0)
        layout.addWidget(self._percentLbl, 1, 1)
        layout.addWidget(self._percentObstacleSldr, 2, 0, 1, 2)
        
        grpBx = QGroupBox("Setup Controls")
        grpBx.setLayout(layout)
        grpBx.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        return grpBx        
        
    def _buildSpeedPanel(self):
        '''
        Creates the sub-panel containing control widgets for controlling the 
        speed of execution of the algorithm.
        '''
        self._runBtn = QPushButton("Run", self._window)
        self._stepBtn = QPushButton("Step Once", self._window)        
        self._runBtn.clicked.connect(self._onRun)
        self._stepBtn.clicked.connect(self._onStep)        
        
        slowRadio = QRadioButton('Slow', self._window)
        medRadio = QRadioButton('Medium', self._window)
        fastRadio = QRadioButton('Fast', self._window)
        notVisRadio = QRadioButton('Not visible', self._window)
        slowRadio.setChecked(True)        
        
        self._speedGroup = QButtonGroup(self._window)
        self._speedGroup.addButton(slowRadio, 0)
        self._speedGroup.addButton(medRadio, 1)
        self._speedGroup.addButton(fastRadio, 2)
        self._speedGroup.addButton(notVisRadio, 3)
        self._speedGroup.buttonClicked.connect(self._onSpeedChange)
          
        layout = QVBoxLayout()
        layout.addWidget(self._runBtn)
        layout.addWidget(self._stepBtn)
        layout.addWidget(slowRadio)
        layout.addWidget(medRadio)
        layout.addWidget(fastRadio)
        layout.addWidget(notVisRadio)
        
        grpBx = QGroupBox("Run Controls")
        grpBx.setLayout(layout)
        grpBx.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        return grpBx
    
    def _buildResultsPanel(self):
        '''
        Creates the sub-panel containing displays widgets for informing the 
        user on the results of running the algorithm.
        '''        
        self._doneLbl = QLabel("No", self._window)
        self._solvableLbl = QLabel("Yes", self._window)
        
        #_doneLbl is highlighted green upon successful algorithm completion
        pal = self._doneLbl.palette()
        pal.setColor(QPalette.Window, Qt.green)
        self._doneLbl.setPalette(pal)

        #_solvableLbl is highlighted red if the world model isn't solvable
        pal = self._solvableLbl.palette()
        pal.setColor(QPalette.Window, Qt.red)
        self._solvableLbl.setPalette(pal)          
        
        layout = QGridLayout()
        layout.addWidget(QLabel("Path Found:"), 0, 0)
        layout.addWidget(self._doneLbl, 0, 1)
        layout.addWidget(QLabel("Is Solvable:"), 1, 0)
        layout.addWidget(self._solvableLbl, 1, 1)
        
        grpBx = QGroupBox("Results")
        grpBx.setLayout(layout)
        grpBx.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        return grpBx
    
    def _buildRenderingOptions(self):
        '''
        Creates the sub-panel containing control widgets for setting options
        in how the world is rendered on the GUI.
        '''        
        self._openChk = QCheckBox("Active Cells")
        self._visitedChk = QCheckBox("Visited Cells")
        self._pathChk = QCheckBox("Draw Path")
        self._costChk = QCheckBox("Draw Estimated Costs")
        
        pal = self._openChk.palette()
        pal.setColor(QPalette.WindowText, Qt.green)
        self._openChk.setPalette(pal)
        
        pal = self._visitedChk.palette()
        pal.setColor(QPalette.WindowText, Qt.cyan)
        self._visitedChk.setPalette(pal)
        
        pal = self._pathChk.palette()
        pal.setColor(QPalette.WindowText, Qt.red)
        self._pathChk.setPalette(pal)
        
        self._visitedChk.setChecked(True)
        self._pathChk.setChecked(True)
        self._costChk.setChecked(True)
        
        self._openChk.stateChanged.connect(self._renderingOptionChanged)
        self._visitedChk.stateChanged.connect(self._renderingOptionChanged)
        self._pathChk.stateChanged.connect(self._renderingOptionChanged)
        self._costChk.stateChanged.connect(self._renderingOptionChanged)
        
        layout = QVBoxLayout()
        layout.addWidget(self._openChk)
        layout.addWidget(self._visitedChk)
        layout.addWidget(self._pathChk)
        layout.addWidget(self._costChk)
        
        grpBx = QGroupBox("Rendering Options")
        grpBx.setLayout(layout)
        grpBx.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        return grpBx        
    
    @Slot()
    def _renderingOptionChanged(self, value):
        '''
        When any rendering option is changed this method is invoked.  It polls
        the GUI for the selected setting values and passes them to the 2D
        world widget.
        '''
        self._worldWidget.setDrawActiveCells(self._openChk.isChecked())
        self._worldWidget.setDrawVisitedCells(self._visitedChk.isChecked())
        self._worldWidget.setDrawPath(self._pathChk.isChecked())
        self._worldWidget.setDrawCosts(self._costChk.isChecked())
        self._worldWidget.repaint()
    
    @Slot()
    def _onPercentSlideChange(self, value):
        '''
        Invoked every time the percent slider is changed.  Displays the percent
        value on the GUI.
        '''
        
        #Add extra padding to the front of the string to help prevent
        #gui layout resizing
        if value < 10:
            self._percentLbl.setText("  " + str(value) + "%")
        elif value < 100:
            self._percentLbl.setText(" " + str(value) + "%")
        else:
            self._percentLbl.setText(str(value) + "%")
    
    @Slot()
    def _onSpeedChange(self, value):
        '''
        Invoked every time one of the speed setting radio buttons are selected.
        Resets the algorithm iterating callback timer if it's currently running.
        '''
        self._spdSetting = self._speedGroup.checkedId()
        if self._timer.isActive():
            self._resetTimer()            
         
    @Slot()
    def _onSetup(self):
        '''
        Invoked when the setup button is pushed.  Re-initializes the world model
        and the algorithm.
        '''
        self._timer.stop()
        self._runBtn.setText('Run')
        self._model.reset(self._percentObstacleSldr.value() / 100.0)
        self._alg.reset()
        self._doneLbl.setText("No")
        self._solvableLbl.setText("Yes")
        self._doneLbl.setAutoFillBackground(False)
        self._solvableLbl.setAutoFillBackground(False)
        self._worldWidget.repaint()
    
    @Slot()
    def _onRun(self):
        '''
        Invoked when the run button is pushed.  Toggles the algorithm iterating
        timer on and off.
        '''
        if self._timer.isActive():
            self._timer.stop()
            self._runBtn.setText("Run")
        else:
            self._resetTimer()
            self._runBtn.setText("Stop")
    
    @Slot()
    def _onStep(self):
        '''
        Invoked on every 'step once' call and on every timer timeout.  Iterates
        one step of the algorithm and then checks for termination conditions
        such as the algorithm being done or solvable.
        '''
        self._alg.step()
        self._worldWidget.repaint()
        
        if self._alg.isDone() or not self._alg.isSolvable():
            self._timer.stop()
            self._runBtn.setText('Run')
        
        self._checkTerminalConditions()
            
    def _checkTerminalConditions(self):
        '''
        Sets the 'results' labels based on the algorithm results.
        '''
        if self._alg.isDone():
            self._doneLbl.setText("Yes")
            self._doneLbl.setAutoFillBackground(True)

        if not self._alg.isSolvable():
            self._solvableLbl.setAutoFillBackground(True)
            self._solvableLbl.setText("No")

    def _resetTimer(self):
        '''
        When the algorithm run speed is modified by the user this resets the
        algorithm timer.
        '''
        if self._spdSetting == 3:
            while not self._alg.isDone() and self._alg.isSolvable():
                self._alg.step()
                
            self._worldWidget.repaint()
            self._timer.stop()
            self._runBtn.setText("Run")
            
            self._checkTerminalConditions()            
        else:
            timeOut = 1
            if self._spdSetting == 0:
                timeOut = 500
            elif self._spdSetting == 1:
                timeOut = 250
            elif self._spdSetting == 2:
                timeOut = 1            
            self._timer.start(timeOut)