Пример #1
0
    def add_statusbar(self):
        """Create status bar, then add to the main window.

        The status bar shows the coordinates on the frame where the cursor is located and
        its pixel value. The pixel value has RGB if the format of is color (RGB), does grayscale
        value if grayscale.
        """
        self.statbar_list = []
        if self.colorspace == "rgb":
            self.stat_css = {
                "postion": "color: white",
                "R": "color: white;",
                "G": "color: white;",
                "B": "color: white;",
                "alpha": "color: white;",
            }
        else:
            self.stat_css = {
                "postion": "color: black;",
                "gray": "color: black"
            }

        for s in self.stat_css.values():
            stat = QStatusBar(self)
            stat.setStyleSheet(s)
            self.statbar_list.append(stat)

        first = True
        for stat in self.statbar_list:
            if first:
                self.setStatusBar(stat)
                self.statbar_list[0].reformat()
                first = False
            else:
                self.statbar_list[0].addPermanentWidget(stat)
Пример #2
0
class AddDataConnectionWidget(QWidget):
    """A widget that queries user's preferences for a new item.

    Attributes:
        toolbox (ToolboxUI): toolbox widget
        x (int): X coordinate of new item
        y (int): Y coordinate of new item
    """
    def __init__(self, toolbox, x, y):
        """Initialize class."""
        super().__init__(
            parent=toolbox,
            f=Qt.Window)  # Setting the parent inherits the stylesheet
        self._toolbox = toolbox
        self._x = x
        self._y = y
        self._project = self._toolbox.project()
        #  Set up the user interface from Designer.
        self.ui = ui.add_data_connection.Ui_Form()
        self.ui.setupUi(self)
        # Add status bar to form
        self.statusbar = QStatusBar(self)
        self.statusbar.setFixedHeight(20)
        self.statusbar.setSizeGripEnabled(False)
        self.statusbar.setStyleSheet(STATUSBAR_SS)
        self.ui.horizontalLayout_statusbar_placeholder.addWidget(
            self.statusbar)
        # Class attributes
        self.name = ''
        self.description = ''
        self.connect_signals()
        self.ui.lineEdit_name.setFocus()
        # Ensure this window gets garbage-collected when closed
        self.setAttribute(Qt.WA_DeleteOnClose)

    def connect_signals(self):
        """Connect signals to slots."""
        self.ui.lineEdit_name.textChanged.connect(
            self.name_changed)  # Name -> folder name connection
        self.ui.pushButton_ok.clicked.connect(self.ok_clicked)
        self.ui.pushButton_cancel.clicked.connect(self.close)

    @Slot(name='name_changed')
    def name_changed(self):
        """Update label to show upcoming folder name."""
        name = self.ui.lineEdit_name.text()
        default = "Folder:"
        if name == '':
            self.ui.label_folder.setText(default)
        else:
            folder_name = name.lower().replace(' ', '_')
            msg = default + " " + folder_name
            self.ui.label_folder.setText(msg)

    @Slot(name='ok_clicked')
    def ok_clicked(self):
        """Check that given item name is valid and add it to project."""
        self.name = self.ui.lineEdit_name.text()
        self.description = self.ui.lineEdit_description.text()
        if not self.name:  # No name given
            self.statusbar.showMessage("Name missing", 3000)
            return
        # Check for invalid characters for a folder name
        if any((True for x in self.name if x in INVALID_CHARS)):
            self.statusbar.showMessage("Name not valid for a folder name",
                                       3000)
            return
        # Check that name is not reserved
        if self._toolbox.project_item_model.find_item(self.name):
            msg = "Item '{0}' already exists".format(self.name)
            self.statusbar.showMessage(msg, 3000)
            return
        # Check that short name (folder) is not reserved
        short_name = self.name.lower().replace(' ', '_')
        if self._toolbox.project_item_model.short_name_reserved(short_name):
            msg = "Item using folder '{0}' already exists".format(short_name)
            self.statusbar.showMessage(msg, 3000)
            return
        # Create new Item
        self.call_add_item()
        self.close()

    def call_add_item(self):
        """Creates new Item according to user's selections."""
        self._project.add_data_connection(self.name,
                                          self.description,
                                          list(),
                                          self._x,
                                          self._y,
                                          set_selected=True)

    def keyPressEvent(self, e):
        """Close Setup form when escape key is pressed.

        Args:
            e (QKeyEvent): Received key press event.
        """
        if e.key() == Qt.Key_Escape:
            self.close()
        elif e.key() == Qt.Key_Enter or e.key() == Qt.Key_Return:
            self.ok_clicked()

    def closeEvent(self, event=None):
        """Handle close window.

        Args:
            event (QEvent): Closing event if 'X' is clicked.
        """
        if event:
            event.accept()
            scene = self._toolbox.ui.graphicsView.scene()
            item_shadow = scene.item_shadow
            if item_shadow:
                scene.removeItem(item_shadow)
                scene.item_shadow = None
Пример #3
0
class MainWindow(QMainWindow):
    changePage = Signal(bool)

    def __init__(self):
        super().__init__()
        self.setWindowTitle('Petro-Explorer v1.0')
        win_icon = QIcon('icons/Logo.ico')
        self.setWindowIcon(win_icon)
        self.setStyleSheet('background-color: #363f49;')

        self.header = Header()
        self.navigation = Navigation()
        self.p_top_bar = PetrophysicsTopBar()
        self.s_top_bar = StatisticsTopBar()
        self.p_options_1 = PetrophysicsOptions(_mode='KCarman')
        self.p_options_2 = PetrophysicsOptions(_mode='TCoates')
        self.p_options_3 = PetrophysicsOptions(_mode='Winland')
        self.p_options_4 = PetrophysicsOptions(_mode='RQIFZI')
        self.p_options_5 = PetrophysicsOptions(_mode='Lucia')
        self.p_options_6 = PetrophysicsOptions(_mode='DParsons')
        self.s_options_1 = StatisticsOptions(_mode='Regression')
        self.s_options_2 = StatisticsOptions(_mode='Statistics')
        self.s_options_3 = StatisticsOptions(_mode='Histogram')
        self.s_options_4 = StatisticsOptions(_mode='Boxplot')
        self.sp_controls = SpreadsheetControls()
        self.plot_controls = PlotControls()
        self.plot_viewer = PlotViewer()
        self.spreadsheet = Spreadsheet()
        self.status_bar = QStatusBar()

        self.scroll_area_1 = QScrollArea()
        self.scroll_area_1.setWidget(self.spreadsheet)
        self.scroll_area_1.setWidgetResizable(True)

        self.scroll_area_2 = QScrollArea()
        self.scroll_area_2.setWidget(self.plot_viewer)
        self.scroll_area_2.setWidgetResizable(True)

        self.bar_widget = QWidget()
        self.bar_widget.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum)

        self.central_widget = QWidget()
        self.options_widget = QWidget()

        self.docking_options = QDockWidget()
        self.docking_options.setWidget(self.options_widget)
        self.docking_options.setTitleBarWidget(
            DockWidgetRibbon(' Opções de cálculo'))

        self.docking_options2 = QDockWidget()
        self.docking_options2.setWidget(self.sp_controls)
        self.docking_options2.setTitleBarWidget(
            DockWidgetRibbon(' Controles de visualização'))

        self.setCentralWidget(self.central_widget)
        self.setStatusBar(self.status_bar)
        self.addToolBar(self.navigation)
        self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea,
                           self.docking_options)
        self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea,
                           self.docking_options2)
        self.connections()
        self.buildLayout()

        self.centralWidget().setStyleSheet('background-color: #2e3843')
        self.spreadsheet.setStyleSheet('background-color: white')
        self.status_bar.setStyleSheet('color: white')

    def buildLayout(self):
        stacked_layout_1 = QStackedLayout()
        # stacked_layout_1.addWidget(QWidget())
        stacked_layout_1.addWidget(self.p_top_bar)
        stacked_layout_1.addWidget(self.s_top_bar)

        stacked_layout_2 = QStackedLayout()
        # stacked_layout_2.addWidget(QWidget())
        stacked_layout_2.addWidget(self.p_options_1)
        stacked_layout_2.addWidget(self.p_options_2)
        stacked_layout_2.addWidget(self.p_options_3)
        stacked_layout_2.addWidget(self.p_options_4)
        stacked_layout_2.addWidget(self.p_options_5)
        stacked_layout_2.addWidget(self.p_options_6)
        stacked_layout_2.addWidget(self.s_options_1)
        stacked_layout_2.addWidget(self.s_options_2)
        stacked_layout_2.addWidget(self.s_options_3)
        stacked_layout_2.addWidget(self.s_options_4)

        self.stacked_layout_3 = QStackedLayout()
        self.stacked_layout_3.addWidget(self.scroll_area_1)
        self.stacked_layout_3.addWidget(self.scroll_area_2)

        central_widget_layout = QVBoxLayout()
        central_widget_layout.addWidget(self.bar_widget)
        central_widget_layout.addLayout(self.stacked_layout_3)

        self.central_widget.setLayout(central_widget_layout)
        self.bar_widget.setLayout(stacked_layout_1)
        self.options_widget.setLayout(stacked_layout_2)

    def connections(self):
        self.navigation.PetroAnalysis.connect(
            lambda: self.bar_widget.layout().setCurrentIndex(0))
        self.navigation.StatsAnalysis.connect(
            lambda: self.bar_widget.layout().setCurrentIndex(1))
        self.navigation.Save.connect(lambda: self.saveDialog())
        self.navigation.Import.connect(lambda: self.importDialog())
        self.navigation.About.connect(lambda: self.aboutDialog())
        self.navigation.Help.connect(lambda: self.helpDialog())
        self.navigation.header.HomePage.connect(
            lambda: self.changePage.emit(True))
        self.navigation.ViewSheet.connect(lambda: self.displaySheet())
        self.navigation.ViewPlot.connect(lambda: self.displayPltE())
        self.navigation.New.connect(lambda: self.spreadsheet.addBlankSheet())

        self.p_top_bar.KCarman.connect(
            lambda: self.options_widget.layout().setCurrentIndex(0))
        self.p_top_bar.TCoates.connect(
            lambda: self.options_widget.layout().setCurrentIndex(1))
        self.p_top_bar.Winland.connect(
            lambda: self.options_widget.layout().setCurrentIndex(2))
        self.p_top_bar.DParsons.connect(
            lambda: self.options_widget.layout().setCurrentIndex(5))
        self.p_top_bar.Lucia.connect(
            lambda: self.options_widget.layout().setCurrentIndex(4))
        self.p_top_bar.RF.connect(
            lambda: self.options_widget.layout().setCurrentIndex(3))

        self.s_top_bar.Regr.connect(
            lambda: self.options_widget.layout().setCurrentIndex(6))
        self.s_top_bar.StatDesc.connect(
            lambda: self.options_widget.layout().setCurrentIndex(7))
        self.s_top_bar.Boxplt.connect(
            lambda: self.options_widget.layout().setCurrentIndex(9))
        self.s_top_bar.Hist.connect(
            lambda: self.options_widget.layout().setCurrentIndex(8))

        self.s_options_1.run_button.clicked.connect(self.startRegr)
        self.s_options_2.run_button.clicked.connect(self.startStat)
        self.s_options_3.run_button.clicked.connect(self.startHist)
        self.s_options_4.run_button.clicked.connect(self.startBxpl)

        self.p_options_1.run.clicked.connect(self.startKCoz)
        self.p_options_2.run.clicked.connect(self.startTCoa)
        self.p_options_3.run.clicked.connect(self.startWinl)
        self.p_options_4.run.clicked.connect(self.startFZIR)
        self.p_options_5.run.clicked.connect(self.startLuci)
        self.p_options_6.run.clicked.connect(self.startDyks)

        self.sp_controls.AddColumn.connect(
            lambda: self.spreadsheet.addColumn())
        self.sp_controls.AddRow.connect(lambda: self.spreadsheet.addRow())
        self.sp_controls.DeleteRow.connect(
            lambda: self.spreadsheet.deleteRow())
        self.sp_controls.DeleteColumn.connect(
            lambda: self.spreadsheet.deleteColumn())

        self.plot_controls.run_button.clicked.connect(lambda: self.startPltE())
        self.plot_controls.erasePlot.connect(
            lambda: self.plot_viewer.erasePlot())

        self.plot_viewer.feedPlotControls.connect(
            self.plot_controls.fillFromJson)

    def startPltE(self):
        old_json = self.plot_viewer.json_data
        title = self.plot_controls.modify_title.text()
        xtype = self.plot_controls.modify_x_axis_type.currentText()
        ytype = self.plot_controls.modify_y_axis_type.currentText()
        xlabel = self.plot_controls.modify_x_axis_label.text()
        ylabel = self.plot_controls.modify_y_axis_label.text()

        if len(self.plot_controls.modify_lower_x_range.text()) > 0:
            lxrange = float(self.plot_controls.modify_lower_x_range.text())
        else:
            lxrange = None
        if len(self.plot_controls.modify_upper_x_range.text()) > 0:
            uxrange = float(self.plot_controls.modify_upper_x_range.text())
        else:
            uxrange = None
        if len(self.plot_controls.modify_lower_y_range.text()) > 0:
            lyrange = float(self.plot_controls.modify_lower_y_range.text())
        else:
            lyrange = None
        if len(self.plot_controls.modify_upper_y_range.text()) > 0:
            uyrange = float(self.plot_controls.modify_upper_y_range.text())
        else:
            uyrange = None

        if len(self.plot_controls.add_x_trace.text()) > 0 and len(
                self.plot_controls.add_y_trace.text()):
            trace_type = self.plot_controls.type_of_trace.currentText()
            x_trace = self.spreadsheet.model.input_data[:,
                                                        self.spreadsheet.model.
                                                        header_info.index(
                                                            self.plot_controls.
                                                            add_x_trace.text(
                                                            ))]
            y_trace = self.spreadsheet.model.input_data[:,
                                                        self.spreadsheet.model.
                                                        header_info.index(
                                                            self.plot_controls.
                                                            add_y_trace.text(
                                                            ))]
            trace_name = self.plot_controls.trace_name.text()
        else:
            trace_type = None
            x_trace = None
            y_trace = None
            trace_name = None

        io_operations_handler = HandlerThread()
        io_operations_handler.messageSent.connect(self.status_bar.showMessage)
        io_operations_handler.daemon = True
        io_operations_handler.hasFinished.connect(
            lambda: self.loadPltE(io_operations_handler.results))
        io_operations_handler.loadParameters(f=pceManifold,
                                             _args=[
                                                 old_json, title, xtype, ytype,
                                                 xlabel, ylabel, lxrange,
                                                 uxrange, lyrange, uyrange,
                                                 trace_type, x_trace, y_trace,
                                                 trace_name
                                             ])
        io_operations_handler.start()

    def loadPltE(self, results, typ='Regressão'):
        self.plot_viewer.loadPlot(results[0], results[1], _type=typ)
        self.displayPltE()

    def displayPltE(self):
        self.stacked_layout_3.setCurrentIndex(1)
        self.docking_options2.setWidget(self.plot_controls)
        #self.removeDockWidget(self.docking_options3)
        #self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.docking_options2)

    def loadSheet(self, df):
        self.spreadsheet.changeModel(data=df, header=list(df.keys()))
        self.displaySheet()

    def displaySheet(self):
        self.stacked_layout_3.setCurrentIndex(0)
        self.docking_options2.setWidget(self.sp_controls)
        self.status_bar.clearMessage()
        #self.removeDockWidget(self.docking_options2)
        #self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea, self.docking_options3)

    def importDialog(self):
        func = None
        file_settings = QFileDialog().getOpenFileName(
            self,
            'Importar Arquivo',
            filter=
            "Todos os arquivos (*);; Arquivo de Texto (*.txt);; Arquivo CSV (*.csv);; "
            "Planilha Excel (*.xlsx)")
        if ".txt" in file_settings[0]:
            func = Data.readTXT
        if ".csv" in file_settings[0]:
            func = Data.readCSV
        if ".xlsx" in file_settings[0]:
            func = Data.readExcel

        self.status_bar.showMessage('Importando arquivo. Aguarde.')
        io_operations_handler = HandlerThread()
        io_operations_handler.messageSent.connect(self.status_bar.showMessage)
        io_operations_handler.daemon = True
        io_operations_handler.hasFinished.connect(
            lambda: self.loadSheet(io_operations_handler.results))
        io_operations_handler.loadParameters(f=func,
                                             _args=[
                                                 file_settings[0],
                                             ])
        io_operations_handler.start()

    def saveDialog(self):
        func = None
        file_settings = QFileDialog().getSaveFileName(
            self,
            "Salvar Arquivo",
            filter="Arquivo de Texto (*.txt);; Arquivo CSV (*.csv);; "
            "Planilha Excel (*.xlsx)")
        if ".txt" in file_settings[0]:
            func = Data.toTXT
        if ".csv" in file_settings[0]:
            func = Data.toCSV
        if ".xlsx" in file_settings[0]:
            func = Data.toExcel

        io_operations_handler = HandlerThread()
        io_operations_handler.messageSent.connect(self.status_bar.showMessage)
        io_operations_handler.daemon = True
        io_operations_handler.loadParameters(
            f=func, _args=[self.spreadsheet.retrieveModel(), file_settings[0]])
        io_operations_handler.start()

    def aboutDialog(self):
        icon = QIcon(r'icons/sobre.png')
        text = "O Petro-Explorer é um software desenvolvido no Laboratório de Exploração e Produção de Petróleo (" \
               "LENEP) da UENF para análises petrofísicas e estatísticas de dados de rocha. "
        msg = QMessageBox()
        msg.setWindowTitle('Sobre Petro-Explorer')
        msg.setWindowIcon(icon)
        msg.setText(text)
        msg.exec_()

    def helpDialog(self):
        icon = QIcon(r'icons/ajuda.png')
        text = r"Para utilizar o Petro-Explorer de maneira correta siga os seguintes passos:" \
               "\n1 - Clique no botão Novo na barra de navegação, isto irá limpar quaisquer dados de antigas análises.\n" \
               "2 - Para começar a sua análise é preciso importar os dados, assim, clique no botão Importar para escolher o seu arquivo. Atente para o fato que somente três tipos de arquivo são suportados (i.e. *.txt, *.csv, *.xlsx). Depois da sua escolha, os dados devem aparecer na planilha do software.\n" \
               "3 - Se você desejar realizar uma análise petrofísica, clique no botão de Análise Petrofísica na barra de navegação. Caso queira realizar uma análise estatística, clique no botão de Análise Estatística na barra de navegação.\n" \
               "4 - Selecione na barra superior à planilha, a análise que desejas realizar sobre seus dados.\n" \
               "5 - No canto direito da janela, selecione os parâmetros de sua análise assim como o destino de seus resultados. Clique no botão 'Começar Análise' para continuar.\n" \
               "6 - Analise seus resultados na planilha, e, quando disponível, no gráfico também.\n" \
               "7 - Quando terminar, clique no botão Salvar para salvar os resultados de suas análises no disco.\n" \
               "8 - Caso queira realizar outra análise, clique no botão \"Novo\" e comece novamente.\n"
        msg = QMessageBox()
        msg.setWindowTitle('Ajuda')
        msg.setWindowIcon(icon)
        msg.setText(text)
        msg.exec_()

    def startRegr(self):
        xdata = self.spreadsheet.model.input_data[:,
                                                  self.spreadsheet.model.
                                                  header_info.
                                                  index(self.s_options_1.
                                                        payload['x_column'])]
        ydata = self.spreadsheet.model.input_data[:,
                                                  self.spreadsheet.model.
                                                  header_info.
                                                  index(self.s_options_1.
                                                        payload['y_column'])]
        _type = self.s_options_1.payload['calculate']
        dgr = self.s_options_1.degree.value()

        io_operations_handler = HandlerThread()
        io_operations_handler.messageSent.connect(self.status_bar.showMessage)
        io_operations_handler.daemon = True
        io_operations_handler.hasFinished.connect(
            lambda: self.displayRegr(self.s_options_1.payload['output_column'],
                                     io_operations_handler.results))
        io_operations_handler.loadParameters(f=regressionManifold,
                                             _args=[xdata, ydata, dgr, _type])
        io_operations_handler.start()

    def startStat(self):
        xdata = self.spreadsheet.model.input_data[:,
                                                  self.spreadsheet.model.
                                                  header_info.
                                                  index(self.s_options_2.
                                                        payload['x_column'])]
        mthd = self.s_options_2.payload['calculate']
        qtl = self.s_options_2.quartile.value()
        pctl = self.s_options_2.percentile.value()

        io_operations_handler = HandlerThread()
        io_operations_handler.messageSent.connect(self.status_bar.showMessage)
        io_operations_handler.daemon = True
        io_operations_handler.hasFinished.connect(
            lambda: self.displayStat(self.s_options_2.payload['output_column'],
                                     io_operations_handler.results))
        io_operations_handler.loadParameters(f=statisticsManifold,
                                             _args=[xdata, mthd, qtl, pctl])
        io_operations_handler.start()

    def startHist(self):
        xdata = self.spreadsheet.model.input_data[:,
                                                  self.spreadsheet.model.
                                                  header_info.
                                                  index(self.s_options_3.
                                                        payload['x_column'])]
        nbins = self.s_options_3.payload['y_column']

        io_operations_handler = HandlerThread()
        io_operations_handler.messageSent.connect(self.status_bar.showMessage)
        io_operations_handler.daemon = True
        io_operations_handler.hasFinished.connect(
            lambda: self.displayHist(io_operations_handler.results))
        io_operations_handler.loadParameters(f=histogramManifold,
                                             _args=[xdata, nbins])
        io_operations_handler.start()

    def startBxpl(self):
        indexes = []
        columns = self.s_options_4.payload['column_range']
        for col in columns:
            indexes.append(self.spreadsheet.model.header_info.index(col))
        xdata = self.spreadsheet.model.input_data[:, indexes]
        box_orientation = self.s_options_4.payload['y_column']

        io_operations_handler = HandlerThread()
        io_operations_handler.messageSent.connect(self.status_bar.showMessage)
        io_operations_handler.daemon = True
        io_operations_handler.hasFinished.connect(
            lambda: self.displayBxpl(io_operations_handler.results))
        io_operations_handler.loadParameters(f=boxplotManifold,
                                             _args=[xdata, box_orientation])
        io_operations_handler.start()

    def startKCoz(self):
        #you need to ascertain if x corresponds to Permeability, y to Porosity and z to Swir/Svgr

        k = None
        phi = None
        svgr = None

        prp = self.p_options_1.payload['calculate']

        if prp == 'Permeabilidade (mD)':
            phi = self.spreadsheet.model.input_data[:,
                                                    self.spreadsheet.model.
                                                    header_info.
                                                    index(self.p_options_1.
                                                          payload['x_column'])]
            svgr = self.spreadsheet.model.input_data[:,
                                                     self.spreadsheet.model.
                                                     header_info.index(
                                                         self.p_options_1.
                                                         payload['y_column'])]
        elif prp == 'SVgr (cm-1)':
            k = self.spreadsheet.model.input_data[:,
                                                  self.spreadsheet.model.
                                                  header_info.
                                                  index(self.p_options_1.
                                                        payload['x_column'])]
            phi = self.spreadsheet.model.input_data[:,
                                                    self.spreadsheet.model.
                                                    header_info.
                                                    index(self.p_options_1.
                                                          payload['y_column'])]
        else:
            k = self.spreadsheet.model.input_data[:,
                                                  self.spreadsheet.model.
                                                  header_info.
                                                  index(self.p_options_1.
                                                        payload['x_column'])]
            phi = self.spreadsheet.model.input_data[:,
                                                    self.spreadsheet.model.
                                                    header_info.
                                                    index(self.p_options_1.
                                                          payload['y_column'])]

        io_operations_handler = HandlerThread()
        io_operations_handler.messageSent.connect(self.status_bar.showMessage)
        io_operations_handler.daemon = True
        io_operations_handler.hasFinished.connect(
            lambda: self.displayKCoz(io_operations_handler.results))
        io_operations_handler.loadParameters(f=kCarmanManifold,
                                             _args=[k, phi, svgr, prp])
        io_operations_handler.start()

    def startTCoa(self):
        #you need to ascertain if x corresponds to Permeability, y to Porosity and z to Swir/Svgr
        prp = self.p_options_2.payload['calculate']

        if prp == "Swir (%)":
            xdata = self.spreadsheet.model.input_data[:,
                                                      self.spreadsheet.model.
                                                      header_info.index(
                                                          self.p_options_2.
                                                          payload['x_column'])]
            ydata = self.spreadsheet.model.input_data[:,
                                                      self.spreadsheet.model.
                                                      header_info.index(
                                                          self.p_options_2.
                                                          payload['y_column'])]
            zdata = None
        else:
            xdata = None
            ydata = self.spreadsheet.model.input_data[:,
                                                      self.spreadsheet.model.
                                                      header_info.index(
                                                          self.p_options_2.
                                                          payload['y_column'])]
            zdata = self.spreadsheet.model.input_data[:,
                                                      self.spreadsheet.model.
                                                      header_info.index(
                                                          self.p_options_2.
                                                          payload['x_column'])]

        io_operations_handler = HandlerThread()
        io_operations_handler.messageSent.connect(self.status_bar.showMessage)
        io_operations_handler.daemon = True
        io_operations_handler.hasFinished.connect(
            lambda: self.displayTCoa(io_operations_handler.results))
        io_operations_handler.loadParameters(f=tCoatesManifold,
                                             _args=[xdata, ydata, zdata, prp])
        io_operations_handler.start()

    def startWinl(self):
        #you need to ascertain if x corresponds to Permeability, y to Porosity and z to Swir/Svgr
        if self.p_options_3.payload['x_column'] != '':
            xdata = self.spreadsheet.model.input_data[:,
                                                      self.spreadsheet.model.
                                                      header_info.index(
                                                          self.p_options_3.
                                                          payload['x_column'])]
        else:
            xdata = []
        if self.p_options_3.payload['y_column'] != '':
            ydata = self.spreadsheet.model.input_data[:,
                                                      self.spreadsheet.model.
                                                      header_info.index(
                                                          self.p_options_3.
                                                          payload['y_column'])]
        else:
            ydata = []

        io_operations_handler = HandlerThread()
        io_operations_handler.messageSent.connect(self.status_bar.showMessage)
        io_operations_handler.daemon = True
        io_operations_handler.hasFinished.connect(
            lambda: self.displayWinl(io_operations_handler.results))
        io_operations_handler.loadParameters(f=winlandManifold,
                                             _args=[xdata, ydata])
        io_operations_handler.start()

    def startFZIR(self):
        # you need to ascertain if x corresponds to Permeability, y to Porosity and z to Swir/Svgr
        if self.p_options_4.payload['x_column'] != '':
            xdata = self.spreadsheet.model.input_data[:,
                                                      self.spreadsheet.model.
                                                      header_info.index(
                                                          self.p_options_4.
                                                          payload['x_column'])]
        else:
            xdata = None
        if self.p_options_4.payload['y_column'] != '':
            ydata = self.spreadsheet.model.input_data[:,
                                                      self.spreadsheet.model.
                                                      header_info.index(
                                                          self.p_options_4.
                                                          payload['y_column'])]
        else:
            ydata = None
        if self.p_options_4.payload['z_column'] != '':
            zdata = self.spreadsheet.model.input_data[:,
                                                      self.spreadsheet.model.
                                                      header_info.index(
                                                          self.p_options_4.
                                                          payload['z_column'])]
        else:
            zdata = None

        prp = self.p_options_4.payload['calculate']
        un = self.p_options_4.payload['column_range']

        io_operations_handler = HandlerThread()
        io_operations_handler.messageSent.connect(self.status_bar.showMessage)
        io_operations_handler.daemon = True
        io_operations_handler.hasFinished.connect(
            lambda: self.displayFZIR(io_operations_handler.results))
        io_operations_handler.loadParameters(
            f=fzManifold, _args=[xdata, ydata, zdata, un, prp])
        io_operations_handler.start()

    def startLuci(self):
        #you need to ascertain if x corresponds to Permeability, y to Porosity and z to Swir/Svgr
        if self.p_options_5.payload['x_column'] != '':
            xdata = self.spreadsheet.model.input_data[:,
                                                      self.spreadsheet.model.
                                                      header_info.index(
                                                          self.p_options_5.
                                                          payload['x_column'])]
        else:
            xdata = []
        if self.p_options_5.payload['y_column'] != '':
            ydata = self.spreadsheet.model.input_data[:,
                                                      self.spreadsheet.model.
                                                      header_info.index(
                                                          self.p_options_5.
                                                          payload['y_column'])]
        else:
            ydata = []

        io_operations_handler = HandlerThread()
        io_operations_handler.messageSent.connect(self.status_bar.showMessage)
        io_operations_handler.daemon = True
        io_operations_handler.hasFinished.connect(
            lambda: self.displayLuci(io_operations_handler.results))
        io_operations_handler.loadParameters(f=luciaManifold,
                                             _args=[xdata, ydata])
        io_operations_handler.start()

    def startDyks(self):
        #you need to ascertain if x corresponds to Permeability, y to Porosity and z to Swir/Svgr
        if self.p_options_6.payload['x_column'] != '':
            xdata = self.spreadsheet.model.input_data[:,
                                                      self.spreadsheet.model.
                                                      header_info.index(
                                                          self.p_options_6.
                                                          payload['x_column'])]
        else:
            xdata = []
        prp = self.p_options_6.payload['calculate']

        io_operations_handler = HandlerThread()
        io_operations_handler.messageSent.connect(self.status_bar.showMessage)
        io_operations_handler.daemon = True
        io_operations_handler.hasFinished.connect(
            lambda: self.displayDyks(io_operations_handler.results))
        io_operations_handler.loadParameters(f=dParsonsManifold,
                                             _args=[xdata, prp])
        io_operations_handler.start()

    def displayKCoz(self, content):
        content = list(content)
        content.insert(0, 'Resultados - Kozeny-Carman')
        self.spreadsheet.addColumn(content)

    def displayWinl(self, content):
        self.loadPltE(content[:2])
        content[2].insert(0, 'R35 - Winland')
        content[3].insert(0, 'Ports - Winland')
        self.spreadsheet.addColumn(content[2])
        self.spreadsheet.addColumn(content[3])

    def displayTCoa(self, content):
        content.insert(0, 'Resultados - Timur Coates')
        self.spreadsheet.addColumn(content)

    def displayDyks(self, content):
        content.insert(0, 'Resultados - Dykstra-Parsons')
        self.spreadsheet.addColumn(content)
        self.p_options_6.results.setText(
            'Atenção! Resultado pode ser diferente do coeficiente calculado através do gráfico de probabilidade. \n'
            + str(content[0]) + ': ' + str(content[1]))

    def displayLuci(self, results):
        self.loadPltE(results[:2])
        results[2].insert(0, 'Resultados - RFN/Lucia')
        self.spreadsheet.addColumn(results[2])

    def displayFZIR(self, results):
        results[0].insert(0, 'Resultados - RQI (μm)')
        self.spreadsheet.addColumn(results[0])
        results[1].insert(0, 'Resultados - PhiZ')
        self.spreadsheet.addColumn(results[1])
        self.loadPltE(results[2:])

    def displayRegr(self, display_name, results):
        self.s_options_1.results.setText(str(results[0]))
        self.loadPltE(results[-2:])

    def displayStat(self, display_name, results):
        self.s_options_2.results.setText(str(results))

    def displayHist(self, results):
        self.loadPltE(results, 'Histograma')

    def displayBxpl(self, results):
        self.loadPltE(results, 'Boxplot')
class ToolSpecificationWidget(QWidget):
    def __init__(self, toolbox, tool_specification=None):
        """A widget to query user's preferences for a new tool specification.

        Args:
            toolbox (ToolboxUI): QMainWindow instance
            tool_specification (ToolSpecification): If given, the form is pre-filled with this specification
        """
        from ..ui.tool_specification_form import Ui_Form

        super().__init__(parent=toolbox, f=Qt.Window)  # Inherit stylesheet from ToolboxUI
        # Setup UI from Qt Designer file
        self.ui = Ui_Form()
        self.ui.setupUi(self)
        # Class attributes
        self._toolbox = toolbox
        self._project = self._toolbox.project()
        # init models
        self.sourcefiles_model = QStandardItemModel()
        self.inputfiles_model = QStandardItemModel()
        self.inputfiles_opt_model = QStandardItemModel()
        self.outputfiles_model = QStandardItemModel()
        # Add status bar to form
        self.statusbar = QStatusBar(self)
        self.statusbar.setFixedHeight(20)
        self.statusbar.setSizeGripEnabled(False)
        self.statusbar.setStyleSheet(STATUSBAR_SS)
        self.ui.horizontalLayout_statusbar_placeholder.addWidget(self.statusbar)
        # init ui
        self.ui.treeView_sourcefiles.setModel(self.sourcefiles_model)
        self.ui.treeView_inputfiles.setModel(self.inputfiles_model)
        self.ui.treeView_inputfiles_opt.setModel(self.inputfiles_opt_model)
        self.ui.treeView_outputfiles.setModel(self.outputfiles_model)
        self.ui.treeView_sourcefiles.setStyleSheet(TREEVIEW_HEADER_SS)
        self.ui.treeView_inputfiles.setStyleSheet(TREEVIEW_HEADER_SS)
        self.ui.treeView_inputfiles_opt.setStyleSheet(TREEVIEW_HEADER_SS)
        self.ui.treeView_outputfiles.setStyleSheet(TREEVIEW_HEADER_SS)
        self.ui.comboBox_tooltype.addItem("Select type...")
        self.ui.comboBox_tooltype.addItems(TOOL_TYPES)
        # if a specification is given, fill the form with data from it
        if tool_specification:
            self.ui.lineEdit_name.setText(tool_specification.name)
            check_state = Qt.Checked if tool_specification.execute_in_work else Qt.Unchecked
            self.ui.checkBox_execute_in_work.setCheckState(check_state)
            self.ui.textEdit_description.setPlainText(tool_specification.description)
            self.ui.lineEdit_args.setText(" ".join(tool_specification.cmdline_args))
            tool_types = [x.lower() for x in TOOL_TYPES]
            index = tool_types.index(tool_specification.tooltype) + 1
            self.ui.comboBox_tooltype.setCurrentIndex(index)
        # Init lists
        self.main_program_file = ""
        self.sourcefiles = list(tool_specification.includes) if tool_specification else list()
        self.inputfiles = list(tool_specification.inputfiles) if tool_specification else list()
        self.inputfiles_opt = list(tool_specification.inputfiles_opt) if tool_specification else list()
        self.outputfiles = list(tool_specification.outputfiles) if tool_specification else list()
        self.def_file_path = tool_specification.def_file_path if tool_specification else None
        self.program_path = tool_specification.path if tool_specification else None
        self.definition = dict()
        # Get first item from sourcefiles list as the main program file
        try:
            self.main_program_file = self.sourcefiles.pop(0)
            self.ui.lineEdit_main_program.setText(os.path.join(self.program_path, self.main_program_file))
        except IndexError:
            pass  # sourcefiles list is empty
        # Populate lists (this will also create headers)
        self.populate_sourcefile_list(self.sourcefiles)
        self.populate_inputfiles_list(self.inputfiles)
        self.populate_inputfiles_opt_list(self.inputfiles_opt)
        self.populate_outputfiles_list(self.outputfiles)
        self.ui.lineEdit_name.setFocus()
        self.ui.label_mainpath.setText(self.program_path)
        # Add includes popup menu
        self.add_source_files_popup_menu = AddIncludesPopupMenu(self)
        self.ui.toolButton_add_source_files.setMenu(self.add_source_files_popup_menu)
        self.ui.toolButton_add_source_files.setStyleSheet('QToolButton::menu-indicator { image: none; }')
        # Add create new or add existing main program popup menu
        self.add_main_prgm_popup_menu = CreateMainProgramPopupMenu(self)
        self.ui.toolButton_add_main_program.setMenu(self.add_main_prgm_popup_menu)
        self.ui.toolButton_add_source_files.setStyleSheet('QToolButton::menu-indicator { image: none; }')
        self.ui.toolButton_add_cmdline_tag.setMenu(self._make_add_cmdline_tag_menu())
        self.connect_signals()

    def connect_signals(self):
        """Connect signals to slots."""
        self.ui.toolButton_add_source_files.clicked.connect(self.show_add_source_files_dialog)
        self.ui.toolButton_add_source_dirs.clicked.connect(self.show_add_source_dirs_dialog)
        self.ui.lineEdit_main_program.file_dropped.connect(self.set_main_program_path)
        self.ui.treeView_sourcefiles.files_dropped.connect(self.add_dropped_includes)
        self.ui.treeView_sourcefiles.doubleClicked.connect(self.open_includes_file)
        self.ui.toolButton_minus_source_files.clicked.connect(self.remove_source_files)
        self.ui.toolButton_plus_inputfiles.clicked.connect(self.add_inputfiles)
        self.ui.toolButton_minus_inputfiles.clicked.connect(self.remove_inputfiles)
        self.ui.toolButton_plus_inputfiles_opt.clicked.connect(self.add_inputfiles_opt)
        self.ui.toolButton_minus_inputfiles_opt.clicked.connect(self.remove_inputfiles_opt)
        self.ui.toolButton_plus_outputfiles.clicked.connect(self.add_outputfiles)
        self.ui.toolButton_minus_outputfiles.clicked.connect(self.remove_outputfiles)
        self.ui.pushButton_ok.clicked.connect(self.handle_ok_clicked)
        self.ui.pushButton_cancel.clicked.connect(self.close)
        # Enable removing items from QTreeViews by pressing the Delete key
        self.ui.treeView_sourcefiles.del_key_pressed.connect(self.remove_source_files_with_del)
        self.ui.treeView_inputfiles.del_key_pressed.connect(self.remove_inputfiles_with_del)
        self.ui.treeView_inputfiles_opt.del_key_pressed.connect(self.remove_inputfiles_opt_with_del)
        self.ui.treeView_outputfiles.del_key_pressed.connect(self.remove_outputfiles_with_del)

    def populate_sourcefile_list(self, items):
        """List source files in QTreeView.
        If items is None or empty list, model is cleared.
        """
        self.sourcefiles_model.clear()
        self.sourcefiles_model.setHorizontalHeaderItem(0, QStandardItem("Additional source files"))  # Add header
        if items is not None:
            for item in items:
                qitem = QStandardItem(item)
                qitem.setFlags(~Qt.ItemIsEditable)
                qitem.setData(QFileIconProvider().icon(QFileInfo(item)), Qt.DecorationRole)
                self.sourcefiles_model.appendRow(qitem)

    def populate_inputfiles_list(self, items):
        """List input files in QTreeView.
        If items is None or empty list, model is cleared.
        """
        self.inputfiles_model.clear()
        self.inputfiles_model.setHorizontalHeaderItem(0, QStandardItem("Input files"))  # Add header
        if items is not None:
            for item in items:
                qitem = QStandardItem(item)
                qitem.setData(QFileIconProvider().icon(QFileInfo(item)), Qt.DecorationRole)
                self.inputfiles_model.appendRow(qitem)

    def populate_inputfiles_opt_list(self, items):
        """List optional input files in QTreeView.
        If items is None or empty list, model is cleared.
        """
        self.inputfiles_opt_model.clear()
        self.inputfiles_opt_model.setHorizontalHeaderItem(0, QStandardItem("Optional input files"))  # Add header
        if items is not None:
            for item in items:
                qitem = QStandardItem(item)
                qitem.setData(QFileIconProvider().icon(QFileInfo(item)), Qt.DecorationRole)
                self.inputfiles_opt_model.appendRow(qitem)

    def populate_outputfiles_list(self, items):
        """List output files in QTreeView.
        If items is None or empty list, model is cleared.
        """
        self.outputfiles_model.clear()
        self.outputfiles_model.setHorizontalHeaderItem(0, QStandardItem("Output files"))  # Add header
        if items is not None:
            for item in items:
                qitem = QStandardItem(item)
                qitem.setData(QFileIconProvider().icon(QFileInfo(item)), Qt.DecorationRole)
                self.outputfiles_model.appendRow(qitem)

    @Slot(bool, name="browse_main_program")
    def browse_main_program(self, checked=False):
        """Open file browser where user can select the path of the main program file."""
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QFileDialog.getOpenFileName(self, "Add existing main program file", APPLICATION_PATH, "*.*")
        file_path = answer[0]
        if not file_path:  # Cancel button clicked
            return
        self.set_main_program_path(file_path)

    @Slot("QString", name="set_main_program_path")
    def set_main_program_path(self, file_path):
        """Set main program file and folder path."""
        folder_path = os.path.split(file_path)[0]
        self.program_path = os.path.abspath(folder_path)
        # Update UI
        self.ui.lineEdit_main_program.setText(file_path)
        self.ui.label_mainpath.setText(self.program_path)

    @Slot()
    def new_main_program_file(self):
        """Creates a new blank main program file. Let's user decide the file name and path.
         Alternative version using only one getSaveFileName dialog.
         """
        # noinspection PyCallByClass
        answer = QFileDialog.getSaveFileName(self, "Create new main program", APPLICATION_PATH)
        file_path = answer[0]
        if not file_path:  # Cancel button clicked
            return
        # Remove file if it exists. getSaveFileName has asked confirmation for us.
        try:
            os.remove(file_path)
        except OSError:
            pass
        try:
            with open(file_path, "w"):
                pass
        except OSError:
            msg = "Please check directory permissions."
            # noinspection PyTypeChecker, PyArgumentList, PyCallByClass
            QMessageBox.information(self, "Creating file failed", msg)
            return
        main_dir = os.path.dirname(file_path)
        self.program_path = os.path.abspath(main_dir)
        # Update UI
        self.ui.lineEdit_main_program.setText(file_path)
        self.ui.label_mainpath.setText(self.program_path)

    @Slot(name="new_source_file")
    def new_source_file(self):
        """Let user create a new source file for this tool specification."""
        path = self.program_path if self.program_path else APPLICATION_PATH
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        dir_path = QFileDialog.getSaveFileName(self, "Create source file", path, "*.*")
        file_path = dir_path[0]
        if file_path == '':  # Cancel button clicked
            return
        # create file. NOTE: getSaveFileName does the 'check for existence' for us
        open(file_path, 'w').close()
        self.add_single_include(file_path)

    @Slot(bool, name="show_add_source_files_dialog")
    def show_add_source_files_dialog(self, checked=False):
        """Let user select source files for this tool specification."""
        path = self.program_path if self.program_path else APPLICATION_PATH
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QFileDialog.getOpenFileNames(self, "Add source file", path, "*.*")
        file_paths = answer[0]
        if not file_paths:  # Cancel button clicked
            return
        for path in file_paths:
            if not self.add_single_include(path):
                continue

    @Slot(bool, name="show_add_source_dirs_dialog")
    def show_add_source_dirs_dialog(self, checked=False):
        """Let user select a source directory for this tool specification.
        All files and sub-directories will be added to the source files.
        """
        path = self.program_path if self.program_path else APPLICATION_PATH
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QFileDialog.getExistingDirectory(self, "Select a directory to add to source files", path)
        file_paths = list()
        for root, _, files in os.walk(answer):
            for file in files:
                file_paths.append(os.path.abspath(os.path.join(root, file)))
        for path in file_paths:
            if not self.add_single_include(path):
                continue

    @Slot("QVariant", name="add_dropped_includes")
    def add_dropped_includes(self, file_paths):
        """Adds dropped file paths to Source files list."""
        for path in file_paths:
            if not self.add_single_include(path):
                continue

    def add_single_include(self, path):
        """Add file path to Source files list."""
        dirname, file_pattern = os.path.split(path)
        # logging.debug("program path:{0}".format(self.program_path))
        # logging.debug("{0}, {1}".format(dirname, file_pattern))
        if not self.program_path:
            self.program_path = dirname
            self.ui.label_mainpath.setText(self.program_path)
            path_to_add = file_pattern
        else:
            # check if path is a descendant of main dir.
            common_prefix = os.path.commonprefix([os.path.abspath(self.program_path), os.path.abspath(path)])
            # logging.debug("common_prefix:{0}".format(common_prefix))
            if common_prefix != self.program_path:
                self.statusbar.showMessage(
                    "Source file {0}'s location is invalid " "(should be in main directory)".format(file_pattern), 5000
                )
                return False
            path_to_add = os.path.relpath(path, self.program_path)
        if self.sourcefiles_model.findItems(path_to_add):
            self.statusbar.showMessage("Source file {0} already included".format(path_to_add), 5000)
            return False
        qitem = QStandardItem(path_to_add)
        qitem.setFlags(~Qt.ItemIsEditable)
        qitem.setData(QFileIconProvider().icon(QFileInfo(path_to_add)), Qt.DecorationRole)
        self.sourcefiles_model.appendRow(qitem)
        return True

    @busy_effect
    @Slot("QModelIndex", name="open_includes_file")
    def open_includes_file(self, index):
        """Open source file in default program."""
        if not index:
            return
        if not index.isValid():
            self._toolbox.msg_error.emit("Selected index not valid")
            return
        includes_file = self.sourcefiles_model.itemFromIndex(index).text()
        _, ext = os.path.splitext(includes_file)
        if ext in [".bat", ".exe"]:
            self._toolbox.msg_warning.emit(
                "Sorry, opening files with extension <b>{0}</b> not implemented. "
                "Please open the file manually.".format(ext)
            )
            return
        url = "file:///" + os.path.join(self.program_path, includes_file)
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        res = QDesktopServices.openUrl(QUrl(url, QUrl.TolerantMode))
        if not res:
            self._toolbox.msg_error.emit("Failed to open file: <b>{0}</b>".format(includes_file))

    @Slot(name="remove_source_files_with_del")
    def remove_source_files_with_del(self):
        """Support for deleting items with the Delete key."""
        self.remove_source_files()

    @Slot(bool, name="remove_source_files")
    def remove_source_files(self, checked=False):
        """Remove selected source files from include list.
        Do not remove anything if there are no items selected.
        """
        indexes = self.ui.treeView_sourcefiles.selectedIndexes()
        if not indexes:  # Nothing selected
            self.statusbar.showMessage("Please select the source files to remove", 3000)
        else:
            rows = [ind.row() for ind in indexes]
            rows.sort(reverse=True)
            for row in rows:
                self.sourcefiles_model.removeRow(row)
            if self.sourcefiles_model.rowCount() == 0:
                if self.ui.lineEdit_main_program.text().strip() == "":
                    self.program_path = None
                    self.ui.label_mainpath.clear()
            self.statusbar.showMessage("Selected source files removed", 3000)

    @Slot(bool, name="add_inputfiles")
    def add_inputfiles(self, checked=False):
        """Let user select input files for this tool specification."""
        msg = (
            "Add an input file or a directory required by your program. Wildcards "
            "<b>are not</b> supported.<br/><br/>"
            "Examples:<br/>"
            "<b>data.csv</b> -> File is copied to the same work directory as the main program.<br/>"
            "<b>input/data.csv</b> -> Creates subdirectory /input to work directory and "
            "copies file data.csv there.<br/>"
            "<b>output/</b> -> Creates an empty directory into the work directory.<br/><br/>"
        )
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QInputDialog.getText(self, "Add input item", msg, flags=Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
        file_name = answer[0]
        if not file_name:  # Cancel button clicked
            return
        qitem = QStandardItem(file_name)
        qitem.setData(QFileIconProvider().icon(QFileInfo(file_name)), Qt.DecorationRole)
        self.inputfiles_model.appendRow(qitem)

    @Slot(name="remove_inputfiles_with_del")
    def remove_inputfiles_with_del(self):
        """Support for deleting items with the Delete key."""
        self.remove_inputfiles()

    @Slot(bool, name="remove_inputfiles")
    def remove_inputfiles(self, checked=False):
        """Remove selected input files from list.
        Do not remove anything if there are no items selected.
        """
        indexes = self.ui.treeView_inputfiles.selectedIndexes()
        if not indexes:  # Nothing selected
            self.statusbar.showMessage("Please select the input files to remove", 3000)
        else:
            rows = [ind.row() for ind in indexes]
            rows.sort(reverse=True)
            for row in rows:
                self.inputfiles_model.removeRow(row)
            self.statusbar.showMessage("Selected input files removed", 3000)

    @Slot(bool, name="add_inputfiles_opt")
    def add_inputfiles_opt(self, checked=False):
        """Let user select optional input files for this tool specification."""
        msg = (
            "Add optional input files that may be utilized by your program. <br/>"
            "Wildcards are supported.<br/><br/>"
            "Examples:<br/>"
            "<b>data.csv</b> -> If found, file is copied to the same work directory as the main program.<br/>"
            "<b>*.csv</b> -> All found CSV files are copied to the same work directory as the main program.<br/>"
            "<b>input/data_?.dat</b> -> All found files matching the pattern 'data_?.dat' will be copied to <br/>"
            "input/ subdirectory under the same work directory as the main program.<br/><br/>"
        )
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QInputDialog.getText(
            self, "Add optional input item", msg, flags=Qt.WindowTitleHint | Qt.WindowCloseButtonHint
        )
        file_name = answer[0]
        if not file_name:  # Cancel button clicked
            return
        qitem = QStandardItem(file_name)
        qitem.setData(QFileIconProvider().icon(QFileInfo(file_name)), Qt.DecorationRole)
        self.inputfiles_opt_model.appendRow(qitem)

    @Slot(name="remove_inputfiles_opt_with_del")
    def remove_inputfiles_opt_with_del(self):
        """Support for deleting items with the Delete key."""
        self.remove_inputfiles_opt()

    @Slot(bool, name="remove_inputfiles_opt")
    def remove_inputfiles_opt(self, checked=False):
        """Remove selected optional input files from list.
        Do not remove anything if there are no items selected.
        """
        indexes = self.ui.treeView_inputfiles_opt.selectedIndexes()
        if not indexes:  # Nothing selected
            self.statusbar.showMessage("Please select the optional input files to remove", 3000)
        else:
            rows = [ind.row() for ind in indexes]
            rows.sort(reverse=True)
            for row in rows:
                self.inputfiles_opt_model.removeRow(row)
            self.statusbar.showMessage("Selected optional input files removed", 3000)

    @Slot(bool, name="add_outputfiles")
    def add_outputfiles(self, checked=False):
        """Let user select output files for this tool specification."""
        msg = (
            "Add output files that will be archived into the Tool results directory after the <br/>"
            "Tool specification has finished execution. Wildcards are supported.<br/><br/>"
            "Examples:<br/>"
            "<b>results.csv</b> -> File is copied from work directory into results.<br/> "
            "<b>*.csv</b> -> All CSV files will copied into results.<br/> "
            "<b>output/*.gdx</b> -> All GDX files from the work subdirectory /output will be copied into <br/>"
            "results /output subdirectory.<br/><br/>"
        )
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QInputDialog.getText(self, "Add output item", msg, flags=Qt.WindowTitleHint | Qt.WindowCloseButtonHint)
        file_name = answer[0]
        if not file_name:  # Cancel button clicked
            return
        qitem = QStandardItem(file_name)
        qitem.setData(QFileIconProvider().icon(QFileInfo(file_name)), Qt.DecorationRole)
        self.outputfiles_model.appendRow(qitem)

    @Slot(name="remove_outputfiles_with_del")
    def remove_outputfiles_with_del(self):
        """Support for deleting items with the Delete key."""
        self.remove_outputfiles()

    @Slot(bool, name="remove_outputfiles")
    def remove_outputfiles(self, checked=False):
        """Remove selected output files from list.
        Do not remove anything if there are no items selected.
        """
        indexes = self.ui.treeView_outputfiles.selectedIndexes()
        if not indexes:  # Nothing selected
            self.statusbar.showMessage("Please select the output files to remove", 3000)
        else:
            rows = [ind.row() for ind in indexes]
            rows.sort(reverse=True)
            for row in rows:
                self.outputfiles_model.removeRow(row)
            self.statusbar.showMessage("Selected output files removed", 3000)

    @Slot()
    def handle_ok_clicked(self):
        """Checks that everything is valid, creates Tool spec definition dictionary and adds Tool spec to project."""
        # Check that tool type is selected
        if self.ui.comboBox_tooltype.currentIndex() == 0:
            self.statusbar.showMessage("Tool type not selected", 3000)
            return
        self.definition["name"] = self.ui.lineEdit_name.text()
        self.definition["description"] = self.ui.textEdit_description.toPlainText()
        self.definition["tooltype"] = self.ui.comboBox_tooltype.currentText().lower()
        flags = Qt.MatchContains
        # Check that path of main program file is valid before saving it
        main_program = self.ui.lineEdit_main_program.text().strip()
        if not os.path.isfile(main_program):
            self.statusbar.showMessage("Main program file is not valid", 6000)
            return
        # Fix for issue #241
        folder_path, file_path = os.path.split(main_program)
        self.program_path = os.path.abspath(folder_path)
        self.ui.label_mainpath.setText(self.program_path)
        self.definition["execute_in_work"] = self.ui.checkBox_execute_in_work.isChecked()
        self.definition["includes"] = [file_path]
        self.definition["includes"] += [i.text() for i in self.sourcefiles_model.findItems("", flags)]
        self.definition["inputfiles"] = [i.text() for i in self.inputfiles_model.findItems("", flags)]
        self.definition["inputfiles_opt"] = [i.text() for i in self.inputfiles_opt_model.findItems("", flags)]
        self.definition["outputfiles"] = [i.text() for i in self.outputfiles_model.findItems("", flags)]
        # Strip whitespace from args before saving it to JSON
        self.definition["cmdline_args"] = ToolSpecification.split_cmdline_args(self.ui.lineEdit_args.text())
        for k in REQUIRED_KEYS:
            if not self.definition[k]:
                self.statusbar.showMessage("{} missing".format(k), 3000)
                return
        # Create new Tool specification
        short_name = self.definition["name"].lower().replace(" ", "_")
        self.def_file_path = os.path.join(self.program_path, short_name + ".json")
        if self.call_add_tool_specification():
            self.close()

    def call_add_tool_specification(self):
        """Adds or updates Tool specification according to user's selections.
        If the name is the same as an existing tool specification, it is updated and
        auto-saved to the definition file. (User is editing an existing
        tool specification.) If the name is not in the tool specification model, creates
        a new tool specification and offer to save the definition file. (User is
        creating a new tool specification from scratch or spawning from an existing one).
        """
        # Load tool specification
        path = self.program_path
        tool = self._project.load_tool_specification_from_dict(self.definition, path)
        if not tool:
            self.statusbar.showMessage("Adding Tool specification failed", 3000)
            return False
        # Check if a tool specification with this name already exists
        row = self._toolbox.tool_specification_model.tool_specification_row(tool.name)
        if row >= 0:  # NOTE: Row 0 at this moment has 'No tool', but in the future it may change. Better be ready.
            old_tool = self._toolbox.tool_specification_model.tool_specification(row)
            def_file = old_tool.get_def_path()
            tool.set_def_path(def_file)
            if tool.__dict__ == old_tool.__dict__:  # Nothing changed. We're done here.
                return True
            # logging.debug("Updating definition for tool specification '{}'".format(tool.name))
            self._toolbox.update_tool_specification(row, tool)
        else:
            # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
            answer = QFileDialog.getSaveFileName(
                self, "Save Tool specification file", self.def_file_path, "JSON (*.json)"
            )
            if answer[0] == "":  # Cancel button clicked
                return False
            def_file = os.path.abspath(answer[0])
            tool.set_def_path(def_file)
            self._toolbox.add_tool_specification(tool)
        # Save path of main program file relative to definition file in case they differ
        def_path = os.path.dirname(def_file)
        if def_path != self.program_path:
            self.definition["includes_main_path"] = os.path.relpath(self.program_path, def_path)
        # Save file descriptor
        with open(def_file, "w") as fp:
            try:
                json.dump(self.definition, fp, indent=4)
            except ValueError:
                self.statusbar.showMessage("Error saving file", 3000)
                self._toolbox.msg_error.emit("Saving Tool specification file failed. Path:{0}".format(def_file))
                return False
        return True

    def keyPressEvent(self, e):
        """Close Setup form when escape key is pressed.

        Args:
            e (QKeyEvent): Received key press event.
        """
        if e.key() == Qt.Key_Escape:
            self.close()

    def closeEvent(self, event=None):
        """Handle close window.

        Args:
            event (QEvent): Closing event if 'X' is clicked.
        """
        if event:
            event.accept()

    def _make_add_cmdline_tag_menu(self):
        """Constructs a popup menu for the '@@' button."""
        menu = QMenu(self.ui.toolButton_add_cmdline_tag)
        action = menu.addAction(str(CmdlineTag.URL_INPUTS))
        action.triggered.connect(self._add_cmdline_tag_url_inputs)
        action.setToolTip("Insert a tag that is replaced by all input database URLs.")
        action = menu.addAction(str(CmdlineTag.URL_OUTPUTS))
        action.triggered.connect(self._add_cmdline_tag_url_outputs)
        action.setToolTip("Insert a tag that is replaced be all output database URLs.")
        action = menu.addAction(str(CmdlineTag.URL))
        action.triggered.connect(self._add_cmdline_tag_data_store_url)
        action.setToolTip("Insert a tag that is replaced by the URL provided by Data Store '<data-store-name>'.")
        action = menu.addAction(str(CmdlineTag.OPTIONAL_INPUTS))
        action.triggered.connect(self._add_cmdline_tag_optional_inputs)
        action.setToolTip("Insert a tag that is replaced by a list of optional input files.")
        return menu

    def _insert_spaces_around_tag_in_args_edit(self, tag_length, restore_cursor_to_tag_end=False):
        """
        Inserts spaces before/after @@ around cursor position/selection

        Expects cursor to be at the end of the tag.
        """
        args_edit = self.ui.lineEdit_args
        text = args_edit.text()
        cursor_position = args_edit.cursorPosition()
        if cursor_position == len(text) or (cursor_position < len(text) - 1 and not text[cursor_position].isspace()):
            args_edit.insert(" ")
            appended_spaces = 1
            text = args_edit.text()
        else:
            appended_spaces = 0
        tag_start = cursor_position - tag_length
        if tag_start > 1 and text[tag_start - 2 : tag_start] == CMDLINE_TAG_EDGE:
            args_edit.setCursorPosition(tag_start)
            args_edit.insert(" ")
            prepended_spaces = 1
        else:
            prepended_spaces = 0
        if restore_cursor_to_tag_end:
            args_edit.setCursorPosition(cursor_position + prepended_spaces)
        else:
            args_edit.setCursorPosition(cursor_position + appended_spaces + prepended_spaces)

    @Slot("QAction")
    def _add_cmdline_tag_url_inputs(self, _):
        """Inserts @@url_inputs@@ tag to command line arguments."""
        tag = CmdlineTag.URL_INPUTS
        self.ui.lineEdit_args.insert(tag)
        self._insert_spaces_around_tag_in_args_edit(len(tag))

    @Slot("QAction")
    def _add_cmdline_tag_url_outputs(self, _):
        """Inserts @@url_outputs@@ tag to command line arguments."""
        tag = CmdlineTag.URL_OUTPUTS
        self.ui.lineEdit_args.insert(tag)
        self._insert_spaces_around_tag_in_args_edit(len(tag))

    @Slot("QAction")
    def _add_cmdline_tag_data_store_url(self, _):
        """Inserts @@url:<data-store-name>@@ tag to command line arguments and selects '<data-store-name>'."""
        args_edit = self.ui.lineEdit_args
        tag = CmdlineTag.URL
        args_edit.insert(tag)
        self._insert_spaces_around_tag_in_args_edit(len(tag), restore_cursor_to_tag_end=True)
        cursor_position = args_edit.cursorPosition()
        args_edit.setSelection(cursor_position - len(CMDLINE_TAG_EDGE + "<data-store_name>"), len("<data-store_name>"))

    @Slot("QAction")
    def _add_cmdline_tag_optional_inputs(self, _):
        """Inserts @@optional_inputs@@ tag to command line arguments."""
        tag = CmdlineTag.OPTIONAL_INPUTS
        self.ui.lineEdit_args.insert(tag)
        self._insert_spaces_around_tag_in_args_edit(len(tag))
class NewProjectForm(QWidget):
    """Class for a new project widget.

    Attributes:
        toolbox (ToolboxUI): Parent widget.
        configs (ConfigurationParser): Configurations object
    """
    def __init__(self, toolbox, configs):
        """Initialize class."""
        super().__init__(parent=toolbox,
                         f=Qt.Window)  # Inherits stylesheet from parent
        self._toolbox = toolbox
        self._configs = configs
        # Set up the user interface from Designer.
        self.ui = ui.project_form.Ui_Form()
        self.ui.setupUi(self)
        # Add status bar to form
        self.statusbar = QStatusBar(self)
        self.statusbar.setFixedHeight(20)
        self.statusbar.setSizeGripEnabled(False)
        self.statusbar.setStyleSheet(STATUSBAR_SS)
        self.ui.horizontalLayout_statusbar_placeholder.addWidget(
            self.statusbar)
        # Class attributes
        self.name = ''  # Project name
        self.description = ''  # Project description
        self.connect_signals()
        self.ui.pushButton_ok.setDefault(True)
        self.ui.lineEdit_project_name.setFocus()
        # Ensure this window gets garbage-collected when closed
        self.setAttribute(Qt.WA_DeleteOnClose)

    def connect_signals(self):
        """Connect signals to slots."""
        self.ui.lineEdit_project_name.textChanged.connect(self.name_changed)
        self.ui.pushButton_ok.clicked.connect(self.ok_clicked)
        self.ui.pushButton_cancel.clicked.connect(self.close)

    @Slot(name='name_changed')
    def name_changed(self):
        """Update label to show a preview of the project directory name."""
        project_name = self.ui.lineEdit_project_name.text()
        default = "Project folder:"
        if project_name == '':
            self.ui.label_folder.setText(default)
        else:
            folder_name = project_name.lower().replace(' ', '_')
            msg = default + " " + folder_name
            self.ui.label_folder.setText(msg)

    @Slot(name='ok_clicked')
    def ok_clicked(self):
        """Check that project name is valid and create project."""
        self.name = self.ui.lineEdit_project_name.text()
        self.description = self.ui.textEdit_description.toPlainText()
        if self.name == '':
            self.statusbar.showMessage("No project name given", 5000)
            return
        # Check for invalid characters for a folder name
        invalid_chars = ["<", ">", ":", "\"", "/", "\\", "|", "?", "*", "."]
        # "." is actually valid in a folder name but
        # this is to prevent creating folders like "...."
        if any((True for x in self.name if x in invalid_chars)):
            self.statusbar.showMessage(
                "Project name contains invalid character(s) for a folder name",
                5000)
            return
        # Check if project with same name already exists
        short_name = self.name.lower().replace(' ', '_')
        project_folder = os.path.join(project_dir(self._configs), short_name)
        if os.path.isdir(project_folder):
            self.statusbar.showMessage("Project already exists", 5000)
            return
        # Create new project
        self.call_create_project()
        self.close()

    def call_create_project(self):
        """Call ToolboxUI method create_project()."""
        self._toolbox.create_project(self.name, self.description)

    def keyPressEvent(self, e):
        """Close project form when escape key is pressed.

        Args:
            e (QKeyEvent): Received key press event.
        """
        if e.key() == Qt.Key_Escape:
            self.close()
        elif e.key() == Qt.Key_Enter or e.key() == Qt.Key_Return:
            self.ok_clicked()

    def closeEvent(self, event=None):
        """Handle close window.

        Args:
            event (QEvent): Closing event if 'X' is clicked.
        """
        if event:
            event.accept()
Пример #6
0
class ViewMainFrame(QMainWindow):
    def __init__(self, sig_quit: Signal, sig_config_mode_changed: Signal,
                 sig_export_csv: Signal):
        """
        Main application's frame

        :param sig_quit: signal to trigger when the application closes
        :param sig_config_mode_changed: signal to trigger when the configuration mode changes
        :param sig_export_csv: signal to emit with the filepath to perform the export to CSV
        """
        QMainWindow.__init__(self)

        self.setWindowTitle(
            f"{tr('app_title')} | {AssetManager.getInstance().config('main', 'version')}"
        )
        self.setContextMenuPolicy(Qt.PreventContextMenu)

        self.bdd_version: str = ""  # For the about box

        self.reboot_requested = False

        # Widgets
        self.status_bar = QStatusBar()
        self.status_bar.setStyleSheet(
            "QStatusBar {background: lightgrey; color: black;}")
        self.maintoolbar = ViewMainToolBar()
        self.sidewidget = SideDockWidget()
        self.central_widget = CentralWidget(self.status_bar.showMessage,
                                            self.__active_tab_changed)

        self.sidewidget.dockLocationChanged.connect(
            self.on_side_widget_docked_state_changed)

        self.__config_mode = False  # Config mode flag, should be initialized to False in all widgets
        self.__init_callbacks()

        # Layout
        self.setCentralWidget(self.central_widget)
        self.addDockWidget(Qt.LeftDockWidgetArea, self.sidewidget)
        self.addToolBar(Qt.RightToolBarArea, self.maintoolbar)
        self.setStatusBar(self.status_bar)

        # Signals
        self.sig_quit = sig_quit
        self.sig_config_mode_changed = sig_config_mode_changed
        self.sig_export_csv = sig_export_csv

        self.setStyleSheet(
            "QMainWindow {" +
            f"background-color: {AssetManager.getInstance().config('colors', 'main_bg')};"
            + "}")

    def restart(self):
        return QtCore.QCoreApplication.exit(EXIT_CODE_REBOOT)

    def set_bdd_version(self, bdd_version: str) -> None:
        """
        Sets the current BDD version. Used in the About box
        """
        self.bdd_version = bdd_version

    def __init_callbacks(self):
        """
        Dispatches the callbacks from the toolbar to the handling widgets. Also init the signal triggered to enable
        or disable some buttons.
        """
        self.maintoolbar.on_btn_perspective_clicked = self.central_widget.on_perspective_changed
        self.maintoolbar.on_btn_shuffle_clicked = self.central_widget.do_shuffle
        self.maintoolbar.on_config_mode = self.on_config_mode
        self.maintoolbar.on_export_csv = self.on_export_csv
        self.maintoolbar.on_edit_config = self.on_edit_config
        self.maintoolbar.open_about_box = lambda: AboutFrame(self.bdd_version
                                                             ).exec_()

        self.central_widget.sig_enable_animation_btns = self.maintoolbar.sig_enable_animation_btns

        self.sidewidget.attributes(
        ).attributes_selection_changed = self.maintoolbar.enable_one_attributes_buttons

    def __active_tab_changed(self, is_view_classroom: bool) -> None:
        """
        Triggered when the active tab changed.
        Updates main toolbar widgets

        :param is_view_classroom: View classroom or view table attributes
        """
        self.maintoolbar.set_widgets(is_view_classroom)

        # Manually update buttons enable state given the number of selected attributes
        self.maintoolbar.enable_one_attributes_buttons(
            self.sidewidget.attributes().get_selected_rows_count() == 1)

        self.sidewidget.sidepanel.tabBar().setTabEnabled(
            0, is_view_classroom)  # Courses
        self.sidewidget.sidepanel.tabBar().setTabEnabled(
            1, is_view_classroom)  # Students

    def on_config_mode(self, is_in_config_mode: bool) -> None:
        """
        Switches the current mode. If in config mode, almost all features are made available, whereas in 'secured'
        mode, some features will be inaccessible to prevent the user from doing critic modification actions.
        """
        self.__config_mode = is_in_config_mode

        self.maintoolbar.lock_buttons(self.__config_mode)
        self.central_widget.classroom_tab.v_canvas.config_mode(
            self.__config_mode)
        self.sidewidget.courses().courses_toolbar.setVisible(
            self.__config_mode)
        self.sidewidget.attributes().attributes_toolbar.setVisible(
            self.__config_mode)
        self.sidewidget.students().students_toolbar.switch_config_mode(
            self.__config_mode)

        self.sig_config_mode_changed.emit()

    def get_config(self) -> bool:
        """
        Getter for the config mode

        :return: True if the user is in Configuration mode
        """
        return self.__config_mode

    def on_side_widget_docked_state_changed(self) -> None:
        """
        Triggered when the side dockable widget has been docked or undocked
        """
        if self.sidewidget.isFloating():
            self.adjustSize()

    def on_export_csv(self) -> None:
        """
        Displays a save dialog to select a file path for the export csv of the attributes table.
        Then signals this path to the controller
        """
        file_path = QFileDialog.getSaveFileName(self,
                                                tr("export_dialog_title"),
                                                "untitled", "(*.csv)")[0]
        if file_path:
            self.sig_export_csv.emit(file_path)

    def on_edit_config(self) -> None:
        """
        Displays the edition dialog for application settings
        """
        dlg = SettingsEditionDialog()

        if dlg.exec_():
            if dlg.restore_default():
                AssetManager.getInstance().restore_default_settings()
            else:
                AssetManager.getInstance().save_config(dlg.new_config())

            self.status_bar.showMessage(tr("acknowledge_changes"), 3000)
            self.repaint()

            if dlg.need_restart():
                restart_confirm = VConfirmDialog(self, "need_restart")
                restart_confirm.ok_btn.setText(tr("restart_now"))
                restart_confirm.ok_btn.setFixedSize(QSize(105, 33))
                restart_confirm.cancel_btn.setText(tr("restart_later"))
                restart_confirm.cancel_btn.setFixedSize(QSize(105, 33))

                if restart_confirm.exec_():
                    self.reboot_requested = True
                    self.close()
                    self.restart()

    def closeEvent(self, event):
        """
        Triggered on a close operation. Signals to the controller the event
        """
        if self.reboot_requested:
            self.reboot_requested = False
            self.sig_quit.emit(EXIT_CODE_REBOOT)
        else:
            self.sig_quit.emit(0)
        event.accept()
Пример #7
0
class main(QWidget):
    def __init__(self, app: QApplication):
        super().__init__()

        logging.basicConfig(format='%(asctime)s %(levelname)-8s %(message)s',
                            level=logging.DEBUG,
                            datefmt='%Y-%m-%d %H:%M:%S')
        logging.info("Application started")

        # Generic setup stuff
        self.app = app
        # Load Dark mode because it's 2021
        styleStream = QFile(
            os.path.join(os.path.dirname(__file__),
                         'resources/qdarkstyle/style.qss'))
        styleStream.open(QIODevice.ReadOnly)
        self.ensurePolished()
        self.app.setStyleSheet(QTextStream(styleStream).readAll())
        self.ui = mainwindow.Ui_main()
        self.ui.setupUi(self)

        # Add status bar manually because Qt Creator doesn't seem to support it
        self.statusBar = QStatusBar(self)
        styleString = ("font: Waree; font-size: 32px;")
        self.statusBar.setStyleSheet(styleString)
        self.ui.verticalLayout_2.addWidget(self.statusBar)
        self.statusBar.showMessage("Hello World!", 2000)

        # Static signal connections
        self.ui.button_qt.clicked.connect(self.app.aboutQt)

        # Make list of always visible buttons to pass to everything
        self.buttons = [
            self.ui.button_rhs_0, self.ui.button_rhs_1, self.ui.button_rhs_2,
            self.ui.button_rhs_3, self.ui.button_rhs_4, self.ui.button_rhs_5
        ]

        # Warning Handler
        self.warnings = WarningHandler(self.ui.tab_widget,
                                       self.ui.table_warnings, self.buttons,
                                       self.statusBar)

        # MQTT Stuff
        with MqttHandler(MQTT_ADDRESS, MQTT_PORT,
                         self.warnings) as self.mqtt, NetworkHandler(
                             self.ui.tabWidget_network, self.mqtt,
                             self.warnings, self.buttons) as self.network:
            self.ui.label_mqttAddr.setText(MQTT_ADDRESS)
            self.ui.label_mqttPort.setText(str(MQTT_PORT))

            # Info / Status Handler
            self.info = InfoHandler(self.ui, self.warnings)

            # Add callback for when tab is changed
            self.ui.tab_widget.currentChanged.connect(self.tabChangeDespatcher)

            # VFO Declaration
            self.vfoA = Vfo("A", self.mqtt, self.network, self.warnings,
                            self.ui.button_A_rx, self.ui.button_A_tx,
                            self.ui.label_A_fc, self.ui.label_A_bw,
                            self.ui.button_A_mode, self.ui.button_A_freq)
            self.vfoB = Vfo("B", self.mqtt, self.network, self.warnings,
                            self.ui.button_B_rx, self.ui.button_B_tx,
                            self.ui.label_B_fc, self.ui.label_B_bw,
                            self.ui.button_B_mode, self.ui.button_B_freq)

            # Doesn't count as change on startup so poke to ensure tab gets updated
            self.tabChangeDespatcher(self.ui.tab_widget.currentIndex())

            self.showFullScreen()
            self.app.exec_()

    def keyPressEvent(self, event):
        if event.key() != 16777248:  # Shift key
            logging.debug(f"Key {event.key()} ({event.text()}) pressed")

            if (event.text() == 'a'):
                self.vfoA.increment()
            elif (event.text() == 'A'):
                self.vfoA.decrement()
            elif (event.text() == 'G'):
                self.app.exit()  # DEBUG

    def tabChangeDespatcher(self, tabId):
        logging.debug(f"Changing tab to {tabId}")
        for x in self.buttons:
            x.setText("")
            x.setEnabled(False)
            x.setCheckable(False)
            try:
                x.clicked.disconnect()
            except RuntimeError:
                pass
            try:
                x.toggled.disconnect()
            except RuntimeError:
                pass

        if (tabId == TAB_HOME):
            pass  # @TODO
        elif (tabId == TAB_SETTINGS):
            pass  # @TODO
        elif (tabId == TAB_INFO):
            self.info.tab_enabled()
        elif (tabId == TAB_WARNINGS):
            self.warnings.tab_enabled()
        elif (tabId == TAB_NETWORK):
            self.network.tab_enabled()
        else:
            raise NotImplementedError
Пример #8
0
class AddProjectItemWidget(QWidget):
    """A widget to query user's preferences for a new item.

    Attributes:
        toolbox (ToolboxUI): Parent widget
        x (int): X coordinate of new item
        y (int): Y coordinate of new item
    """
    def __init__(self, toolbox, x, y, class_, spec=""):
        """Initialize class."""
        from ..ui.add_project_item import Ui_Form  # pylint: disable=import-outside-toplevel

        super().__init__(parent=toolbox,
                         f=Qt.Window)  # Setting parent inherits stylesheet
        self._toolbox = toolbox
        self._x = x
        self._y = y
        self._project = self._toolbox.project()
        #  Set up the user interface from Designer.
        self.ui = Ui_Form()
        self.ui.setupUi(self)
        # Add status bar to form
        self.statusbar = QStatusBar(self)
        self.statusbar.setFixedHeight(20)
        self.statusbar.setSizeGripEnabled(False)
        self.statusbar.setStyleSheet(STATUSBAR_SS)
        self.ui.horizontalLayout_statusbar_placeholder.addWidget(
            self.statusbar)
        # Init
        if toolbox.supports_specifications(class_.item_type()):
            self.ui.comboBox_specification.setModel(
                toolbox.filtered_spec_factory_models[class_.item_type()])
            if spec:
                self.ui.comboBox_specification.setCurrentText(spec)
                prefix = spec
            else:
                prefix = class_.item_type()
                self.ui.comboBox_specification.setCurrentIndex(-1)
        else:
            prefix = class_.item_type()
            self.ui.comboBox_specification.setEnabled(False)
        self.name = toolbox.propose_item_name(prefix)
        self.ui.lineEdit_name.setText(self.name)
        self.ui.lineEdit_name.selectAll()
        self.description = ""
        self.connect_signals()
        self.ui.lineEdit_name.setFocus()
        # Ensure this window gets garbage-collected when closed
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.setWindowTitle(f"Add {class_.item_type()}")

    def connect_signals(self):
        """Connect signals to slots."""
        self.ui.lineEdit_name.textChanged.connect(
            self.handle_name_changed)  # Name -> folder name connection
        self.ui.pushButton_ok.clicked.connect(self.handle_ok_clicked)
        self.ui.pushButton_cancel.clicked.connect(self.close)

    @Slot()
    def handle_name_changed(self):
        """Update label to show upcoming folder name."""
        name = self.ui.lineEdit_name.text()
        default = "Folder:"
        if name == "":
            self.ui.label_folder.setText(default)
        else:
            folder_name = name.lower().replace(" ", "_")
            msg = default + " " + folder_name
            self.ui.label_folder.setText(msg)

    @Slot()
    def handle_ok_clicked(self):
        """Check that given item name is valid and add it to project."""
        self.name = self.ui.lineEdit_name.text()
        self.description = self.ui.lineEdit_description.text()
        if not self.name:  # No name given
            self.statusbar.showMessage("Name missing", 3000)
            return
        # Check for invalid characters for a folder name
        if any((True for x in self.name if x in INVALID_CHARS)):
            self.statusbar.showMessage("Name not valid for a folder name",
                                       3000)
            return
        # Check that name is not reserved
        if self._toolbox.project_item_model.find_item(self.name):
            msg = "Item '{0}' already exists".format(self.name)
            self.statusbar.showMessage(msg, 3000)
            return
        # Check that short name (folder) is not reserved
        short_name = self.name.lower().replace(" ", "_")
        if self._toolbox.project_item_model.short_name_reserved(short_name):
            msg = "Item using folder '{0}' already exists".format(short_name)
            self.statusbar.showMessage(msg, 3000)
            return
        # Create new Item
        self.call_add_item()
        self.close()

    def call_add_item(self):
        """Creates new Item according to user's selections.

        Must be reimplemented by subclasses.
        """
        raise NotImplementedError()

    def keyPressEvent(self, e):
        """Close Setup form when escape key is pressed.

        Args:
            e (QKeyEvent): Received key press event.
        """
        if e.key() == Qt.Key_Escape:
            self.close()
        elif e.key() == Qt.Key_Enter or e.key() == Qt.Key_Return:
            self.handle_ok_clicked()

    def closeEvent(self, event=None):
        """Handle close window.

        Args:
            event (QEvent): Closing event if 'X' is clicked.
        """
        if event:
            event.accept()
            scene = self._toolbox.ui.graphicsView.scene()
            item_shadow = scene.item_shadow
            if item_shadow:
                scene.removeItem(item_shadow)
                scene.item_shadow = None
Пример #9
0
class AddRelationshipsDialog(AddItemsDialog):
    """A dialog to query user's preferences for new relationships.

    Attributes:
        parent (TreeViewForm): data store widget
        relationship_class_id (int): default relationship class id
        object_id (int): default object id
        object_class_id (int): default object class id
    """
    def __init__(self,
                 parent,
                 relationship_class_id=None,
                 object_id=None,
                 object_class_id=None,
                 force_default=False):
        super().__init__(parent, force_default=force_default)
        self.remove_row_icon = QIcon(":/icons/minus_relationship_icon.png")
        self.relationship_class_list = \
            [x for x in self._parent.db_map.wide_relationship_class_list(object_class_id=object_class_id)]
        self.relationship_class = None
        self.relationship_class_id = relationship_class_id
        self.object_id = object_id
        self.object_class_id = object_class_id
        self.default_object_column = None
        self.default_object_name = None
        self.set_default_object_name()
        self.setup_ui(ui.add_relationships.Ui_Dialog())
        self.ui.tableView.setItemDelegate(AddRelationshipsDelegate(parent))
        self.init_relationship_class(force_default)
        # Add status bar to form
        self.statusbar = QStatusBar(self)
        self.statusbar.setFixedHeight(20)
        self.statusbar.setSizeGripEnabled(False)
        self.statusbar.setStyleSheet(STATUSBAR_SS)
        self.ui.horizontalLayout_statusbar_placeholder.addWidget(
            self.statusbar)
        self.connect_signals()
        self.reset_model()

    def init_relationship_class(self, force_default):
        """Populate combobox and initialize relationship class if any."""
        relationship_class_name_list = [
            x.name for x in self.relationship_class_list
        ]
        if not force_default:
            self.ui.comboBox_relationship_class.addItems(
                relationship_class_name_list)
            self.ui.comboBox_relationship_class.setCurrentIndex(-1)
        self.relationship_class = self._parent.db_map.\
            single_wide_relationship_class(id=self.relationship_class_id).one_or_none()
        if not self.relationship_class:
            # Default not found
            return
        try:
            if not force_default:
                combo_index = relationship_class_name_list.index(
                    self.relationship_class.name)
                self.ui.comboBox_relationship_class.setCurrentIndex(
                    combo_index)
                return
            self.ui.comboBox_relationship_class.addItem(
                self.relationship_class.name)
        except ValueError:
            pass

    def connect_signals(self):
        """Connect signals to slots."""
        self.ui.comboBox_relationship_class.currentIndexChanged.connect(
            self.call_reset_model)
        super().connect_signals()

    @Slot("int", name='call_reset_model')
    def call_reset_model(self, index):
        """Called when relationship class's combobox's index changes.
        Update relationship_class attribute accordingly and reset model."""
        self.relationship_class = self.relationship_class_list[index]
        self.reset_model()

    def reset_model(self):
        """Setup model according to current relationship class selected in combobox
        (or given as input).
        """
        if not self.relationship_class:
            return
        object_class_name_list = self.relationship_class.object_class_name_list.split(
            ',')
        header = [
            *[x + " name" for x in object_class_name_list], 'relationship name'
        ]
        self.model.set_horizontal_header_labels(header)
        self.reset_default_object_column()
        if self.default_object_name and self.default_object_column is not None:
            defaults = {
                header[self.default_object_column]: self.default_object_name
            }
            self.model.set_default_row(**defaults)
        self.model.clear()
        self.ui.tableView.resizeColumnsToContents()

    def set_default_object_name(self):
        if not self.object_id:
            return
        object_ = self._parent.db_map.single_object(
            id=self.object_id).one_or_none()
        if not object_:
            return
        self.default_object_name = object_.name

    def reset_default_object_column(self):
        if not self.default_object_name:
            return
        if not self.relationship_class or not self.object_class_id:
            return
        try:
            object_class_id_list = self.relationship_class.object_class_id_list
            self.default_object_column = [
                int(x) for x in object_class_id_list.split(',')
            ].index(self.object_class_id)
        except ValueError:
            pass

    @busy_effect
    def accept(self):
        """Collect info from dialog and try to add items."""
        wide_kwargs_list = list()
        name_column = self.model.horizontal_header_labels().index(
            "relationship name")
        for i in range(self.model.rowCount() -
                       1):  # last row will always be empty
            row_data = self.model.row_data(i)[:-1]
            relationship_name = row_data[name_column]
            if not relationship_name:
                self._parent.msg_error.emit(
                    "Relationship name missing at row {}".format(i + 1))
                return
            object_id_list = list()
            for column in range(name_column):  # Leave 'name' column outside
                object_name = row_data[column]
                if not object_name:
                    self._parent.msg_error.emit(
                        "Object name missing at row {}".format(i + 1))
                    return
                object_ = self._parent.db_map.single_object(
                    name=object_name).one_or_none()
                if not object_:
                    self._parent.msg_error.emit(
                        "Couldn't find object '{}' at row {}".format(
                            object_name, i + 1))
                    return
                object_id_list.append(object_.id)
            if len(object_id_list) < 2:
                self._parent.msg_error.emit(
                    "Not enough dimensions at row {} (at least two are needed)"
                    .format(i + 1))
                return
            wide_kwargs = {
                'name': relationship_name,
                'object_id_list': object_id_list,
                'class_id': self.relationship_class.id
            }
            wide_kwargs_list.append(wide_kwargs)
        if not wide_kwargs_list:
            self._parent.msg_error.emit("Nothing to add")
            return
        try:
            wide_relationships = self._parent.db_map.add_wide_relationships(
                *wide_kwargs_list)
            self._parent.add_relationships(wide_relationships)
            super().accept()
        except SpineIntegrityError as e:
            self._parent.msg_error.emit(e.msg)
        except SpineDBAPIError as e:
            self._parent.msg_error.emit(e.msg)
class AddToolWidget(QWidget):
    """A widget that queries user's preferences for a new item.

    Attributes:
        toolbox (ToolboxUI): Parent widget
        x (int): X coordinate of new item
        y (int): Y coordinate of new item
    """
    def __init__(self, toolbox, x, y):
        """Initialize class."""
        from ..ui.add_tool import Ui_Form

        super().__init__(parent=toolbox,
                         f=Qt.Window)  # Setting parent inherits stylesheet
        self._toolbox = toolbox
        self._x = x
        self._y = y
        self._project = self._toolbox.project()
        #  Set up the user interface from Designer.
        self.ui = Ui_Form()
        self.ui.setupUi(self)
        # Add status bar to form
        self.statusbar = QStatusBar(self)
        self.statusbar.setFixedHeight(20)
        self.statusbar.setSizeGripEnabled(False)
        self.statusbar.setStyleSheet(STATUSBAR_SS)
        self.ui.horizontalLayout_statusbar_placeholder.addWidget(
            self.statusbar)
        # Class attributes
        self.name = toolbox.propose_item_name(Tool.default_name_prefix())
        self.ui.lineEdit_name.setText(self.name)
        self.ui.lineEdit_name.selectAll()
        self.description = ''
        # Init
        self.ui.comboBox_tool.setModel(self._toolbox.tool_specification_model)
        self.ui.lineEdit_name.setFocus()
        self.connect_signals()
        # Ensure this window gets garbage-collected when closed
        self.setAttribute(Qt.WA_DeleteOnClose)

    def connect_signals(self):
        """Connect signals to slots."""
        self.ui.lineEdit_name.textChanged.connect(
            self.name_changed)  # Name -> folder name connection
        self.ui.pushButton_ok.clicked.connect(self.ok_clicked)
        self.ui.pushButton_cancel.clicked.connect(self.close)
        self.ui.comboBox_tool.currentIndexChanged.connect(self.update_args)

    @Slot(int, name='update_args')
    def update_args(self, row):
        """Show Tool specification command line arguments in text input.

        Args:
            row (int): Selected row number
        """
        if row == 0:
            # No Tool selected
            self.ui.lineEdit_tool_args.setText("")
            return
        selected_tool = self._toolbox.tool_specification_model.tool_specification(
            row)
        args = selected_tool.cmdline_args
        if not args:
            # Tool cmdline_args is None if the line does not exist in Tool definition file
            args = ''
        self.ui.lineEdit_tool_args.setText("{0}".format(args))
        return

    @Slot(name='name_changed')
    def name_changed(self):
        """Update label to show upcoming folder name."""
        name = self.ui.lineEdit_name.text()
        default = "Folder:"
        if name == '':
            self.ui.label_folder.setText(default)
        else:
            folder_name = name.lower().replace(' ', '_')
            msg = default + " " + folder_name
            self.ui.label_folder.setText(msg)

    @Slot(name='ok_clicked')
    def ok_clicked(self):
        """Check that given item name is valid and add it to project."""
        self.name = self.ui.lineEdit_name.text()
        self.description = self.ui.lineEdit_description.text()
        if not self.name:  # No name given
            self.statusbar.showMessage("Name missing", 3000)
            return
        # Check for invalid characters for a folder name
        if any((True for x in self.name if x in INVALID_CHARS)):
            self.statusbar.showMessage("Name not valid for a folder name",
                                       3000)
            return
        # Check that name is not reserved
        if self._toolbox.project_item_model.find_item(self.name):
            msg = "Item '{0}' already exists".format(self.name)
            self.statusbar.showMessage(msg, 3000)
            return
        # Check that short name (folder) is not reserved
        short_name = self.name.lower().replace(' ', '_')
        if self._toolbox.project_item_model.short_name_reserved(short_name):
            msg = "Item using folder '{0}' already exists".format(short_name)
            self.statusbar.showMessage(msg, 3000)
            return
        # Create new Item
        self.call_add_item()
        self.close()

    def call_add_item(self):
        """Creates new Item according to user's selections."""
        tool = self.ui.comboBox_tool.currentText()
        item = dict(name=self.name,
                    description=self.description,
                    x=self._x,
                    y=self._y,
                    tool=tool,
                    execute_in_work=True)
        self._project.add_project_items("Tools", item, set_selected=True)

    def keyPressEvent(self, e):
        """Close Setup form when escape key is pressed.

        Args:
            e (QKeyEvent): Received key press event.
        """
        if e.key() == Qt.Key_Escape:
            self.close()
        elif e.key() == Qt.Key_Enter or e.key() == Qt.Key_Return:
            self.ok_clicked()

    def closeEvent(self, event=None):
        """Handle close window.

        Args:
            event (QEvent): Closing event if 'X' is clicked.
        """
        if event:
            event.accept()
            scene = self._toolbox.ui.graphicsView.scene()
            item_shadow = scene.item_shadow
            if item_shadow:
                scene.removeItem(item_shadow)
                scene.item_shadow = None
Пример #11
0
class WTreeEdit(QWidget):
    """TreeEdit widget is to show and edit all of the pyleecan objects data."""

    # Signals
    dataChanged = Signal()

    def __init__(self, obj, *args, **kwargs):
        QWidget.__init__(self, *args, **kwargs)

        self.class_dict = ClassInfo().get_dict()
        self.treeDict = None  # helper to track changes
        self.obj = obj  # the object
        self.is_save_needed = False

        self.model = TreeEditModel(obj)

        self.setupUi()

        # === Signals ===
        self.selectionModel.selectionChanged.connect(self.onSelectionChanged)
        self.treeView.collapsed.connect(self.onItemCollapse)
        self.treeView.expanded.connect(self.onItemExpand)
        self.treeView.customContextMenuRequested.connect(self.openContextMenu)
        self.model.dataChanged.connect(self.onDataChanged)
        self.dataChanged.connect(self.setSaveNeeded)

        # === Finalize ===
        # set 'root' the selected item and resize columns
        self.treeView.setCurrentIndex(self.treeView.model().index(0, 0))
        self.treeView.resizeColumnToContents(0)

    def setupUi(self):
        """Setup the UI"""
        # === Widgets ===
        # TreeView
        self.treeView = QTreeView()
        # self.treeView.rootNode = model.invisibleRootItem()
        self.treeView.setModel(self.model)
        self.treeView.setAlternatingRowColors(False)

        # self.treeView.setColumnWidth(0, 150)
        self.treeView.setMinimumWidth(100)

        self.treeView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.selectionModel = self.treeView.selectionModel()

        self.statusBar = QStatusBar()
        self.statusBar.setSizeGripEnabled(False)
        self.statusBar.setSizePolicy(QSizePolicy.Expanding,
                                     QSizePolicy.Maximum)
        self.statusBar.setStyleSheet(
            "QStatusBar {border: 1px solid rgb(200, 200, 200)}")
        self.saveLabel = QLabel("unsaved")
        self.saveLabel.setVisible(False)
        self.statusBar.addPermanentWidget(self.saveLabel)

        # Splitters
        self.leftSplitter = QSplitter()
        self.leftSplitter.setStretchFactor(0, 0)
        self.leftSplitter.setStretchFactor(1, 1)

        # === Layout ===
        # Horizontal Div.
        self.hLayout = QVBoxLayout()
        self.hLayout.setContentsMargins(0, 0, 0, 0)
        self.hLayout.setSpacing(0)

        # add widgets to layout
        self.hLayout.addWidget(self.leftSplitter)
        self.hLayout.addWidget(self.statusBar)

        # add widgets
        self.leftSplitter.addWidget(self.treeView)

        self.setLayout(self.hLayout)

    def update(self, obj):
        """Check if object has changed and update tree in case."""
        if not obj is self.obj:
            self.obj = obj
            self.model = TreeEditModel(obj)
            self.treeView.setModel(self.model)
            self.model.dataChanged.connect(self.onDataChanged)
            self.selectionModel = self.treeView.selectionModel()
            self.selectionModel.selectionChanged.connect(
                self.onSelectionChanged)
            self.treeView.setCurrentIndex(self.treeView.model().index(0, 0))
            self.setSaveNeeded(True)

    def setSaveNeeded(self, state=True):
        self.is_save_needed = state
        self.saveLabel.setVisible(state)

    def openContextMenu(self, point):
        """Generate and open context the menu at the given point position."""
        index = self.treeView.indexAt(point)
        pos = QtGui.QCursor.pos()

        if not index.isValid():
            return

        # get the data
        item = self.model.item(index)
        obj_info = self.model.get_obj_info(item)

        # init the menu
        menu = TreeEditContextMenu(obj_dict=obj_info, parent=self)
        menu.exec_(pos)

        self.onSelectionChanged(self.selectionModel.selection())

    def onItemCollapse(self, index):
        """Slot for item collapsed"""
        # dynamic resize
        for ii in range(3):
            self.treeView.resizeColumnToContents(ii)

    def onItemExpand(self, index):
        """Slot for item expand"""
        # dynamic resize
        for ii in range(3):
            self.treeView.resizeColumnToContents(ii)

    def onDataChanged(self, first=None, last=None):
        """Slot for changed data"""
        self.dataChanged.emit()
        self.onSelectionChanged(self.selectionModel.selection())

    def onSelectionChanged(self, itemSelection):
        """Slot for changed item selection"""
        # get the index
        if itemSelection.indexes():
            index = itemSelection.indexes()[0]
        else:
            index = self.treeView.model().index(0, 0)
            self.treeView.setCurrentIndex(index)
            return

        # get the data
        item = self.model.item(index)
        obj = item.object()
        typ = type(obj).__name__
        obj_info = self.model.get_obj_info(item)
        ref_typ = obj_info["ref_typ"] if obj_info else None

        # set statusbar information on class typ
        msg = f"{typ} (Ref: {ref_typ})" if ref_typ else f"{typ}"
        self.statusBar.showMessage(msg)

        # --- choose the respective widget by class type ---
        # numpy array -> table editor
        if typ == "ndarray":
            widget = WTableData(obj, editable=True)
            widget.dataChanged.connect(self.dataChanged.emit)

        elif typ == "MeshSolution":
            widget = WMeshSolution(obj)  # only a view (not editable)

        # list (no pyleecan type, non empty) -> table editor
        # TODO add another widget for lists of non 'primitive' types (e.g. DataND)
        elif isinstance(obj, list) and not self.isListType(ref_typ) and obj:
            widget = WTableData(obj, editable=True)
            widget.dataChanged.connect(self.dataChanged.emit)

        # generic editor
        else:
            # widget = SimpleInputWidget().generate(obj)
            widget = WTableParameterEdit(obj)
            widget.dataChanged.connect(self.dataChanged.emit)

        # show the widget
        if self.leftSplitter.widget(1) is None:
            self.leftSplitter.addWidget(widget)
        else:
            self.leftSplitter.replaceWidget(1, widget)
            widget.setParent(
                self.leftSplitter)  # workaround for PySide2 replace bug
            widget.show()
        pass

    def isListType(self, typ):
        if not typ:
            return False
        return typ[0] == "[" and typ[-1] == "]" and typ[1:-1] in self.class_dict

    def isDictType(self, typ):
        if not typ:
            return False
        return typ[0] == "{" and typ[-1] == "}" and typ[1:-1] in self.class_dict
Пример #12
0
class SettingsWidget(QWidget):
    """ A widget to change user's preferred settings.

    Attributes:
        toolbox (ToolboxUI): Parent widget.
        configs (ConfigurationParser): Configuration object
    """
    def __init__(self, toolbox, configs):
        """ Initialize class. """
        # FIXME: setting the parent to toolbox causes the checkboxes in the
        # groupBox_general to not layout correctly, this might be caused elsewhere?
        super().__init__(
            parent=None)  # Do not set parent. Uses own stylesheet.
        self._toolbox = toolbox  # QWidget parent
        self._configs = configs
        self._project = self._toolbox.project()
        self.orig_work_dir = ""  # Work dir when this widget was opened
        # Set up the ui from Qt Designer files
        self.ui = ui.settings.Ui_SettingsForm()
        self.ui.setupUi(self)
        self.ui.toolButton_browse_gams.setIcon(self.style().standardIcon(
            QStyle.SP_DialogOpenButton))
        self.ui.toolButton_browse_julia.setIcon(self.style().standardIcon(
            QStyle.SP_DialogOpenButton))
        self.ui.toolButton_browse_work.setIcon(self.style().standardIcon(
            QStyle.SP_DialogOpenButton))
        self.setWindowFlags(Qt.Window | Qt.CustomizeWindowHint)
        # Ensure this window gets garbage-collected when closed
        self.setAttribute(Qt.WA_DeleteOnClose)
        self.statusbar = QStatusBar(self)
        self.statusbar.setFixedHeight(20)
        self.statusbar.setSizeGripEnabled(False)
        self.statusbar.setStyleSheet(STATUSBAR_SS)
        self.ui.horizontalLayout_statusbar_placeholder.addWidget(
            self.statusbar)
        self.setStyleSheet(SETTINGS_SS)
        self.ui.pushButton_ok.setDefault(True)
        self._mousePressPos = None
        self._mouseReleasePos = None
        self._mouseMovePos = None
        self.connect_signals()
        self.read_settings()
        self.read_project_settings()

    def connect_signals(self):
        """ Connect PyQt signals. """
        self.ui.pushButton_ok.clicked.connect(self.ok_clicked)
        self.ui.pushButton_cancel.clicked.connect(self.close)
        self.ui.toolButton_browse_gams.clicked.connect(self.browse_gams_path)
        self.ui.toolButton_browse_julia.clicked.connect(self.browse_julia_path)
        self.ui.toolButton_browse_work.clicked.connect(self.browse_work_path)

    @Slot(bool, name="browse_gams_path")
    def browse_gams_path(self, checked=False):
        """Open file browser where user can select the directory of
        GAMS that the user wants to use."""
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QFileDialog.getExistingDirectory(self,
                                                  'Select GAMS Directory',
                                                  os.path.abspath('C:\\'))
        if answer == '':  # Cancel button clicked
            return
        selected_path = os.path.abspath(answer)
        gams_path = os.path.join(selected_path, GAMS_EXECUTABLE)
        gamside_path = os.path.join(selected_path, GAMSIDE_EXECUTABLE)
        if not os.path.isfile(gams_path) and not os.path.isfile(gamside_path):
            self.statusbar.showMessage(
                "gams.exe and gamside.exe not found in selected directory",
                10000)
            self.ui.lineEdit_gams_path.setText("")
            return
        else:
            self.statusbar.showMessage(
                "Selected directory is valid GAMS directory", 10000)
            self.ui.lineEdit_gams_path.setText(selected_path)
        return

    @Slot(bool, name="browse_julia_path")
    def browse_julia_path(self, checked=False):
        """Open file browser where user can select the path to wanted Julia version."""
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QFileDialog.getExistingDirectory(self,
                                                  'Select Julia Directory',
                                                  os.path.abspath('C:\\'))
        if answer == '':  # Cancel button clicked
            return
        selected_path = os.path.abspath(answer)
        julia_path = os.path.join(selected_path, JULIA_EXECUTABLE)
        if not os.path.isfile(julia_path):
            self.statusbar.showMessage(
                "julia.exe not found in selected directory", 10000)
            self.ui.lineEdit_julia_path.setText("")
            return
        else:
            self.statusbar.showMessage(
                "Selected directory is valid Julia directory", 10000)
            self.ui.lineEdit_julia_path.setText(selected_path)
        return

    @Slot(bool, name="browse_work_path")
    def browse_work_path(self, checked=False):
        """Open file browser where user can select the path to wanted work directory."""
        # noinspection PyCallByClass, PyTypeChecker, PyArgumentList
        answer = QFileDialog.getExistingDirectory(self,
                                                  'Select work directory',
                                                  os.path.abspath('C:\\'))
        if answer == '':  # Cancel button clicked
            return
        selected_path = os.path.abspath(answer)
        self.ui.lineEdit_work_dir.setText(selected_path)

    def read_settings(self):
        """Read current settings from config object and update UI to show them."""
        open_previous_project = self._configs.getboolean(
            "settings", "open_previous_project")
        show_exit_prompt = self._configs.getboolean("settings",
                                                    "show_exit_prompt")
        save_at_exit = self._configs.get("settings",
                                         "save_at_exit")  # Tri-state checkBox
        commit_at_exit = self._configs.get(
            "settings", "commit_at_exit")  # Tri-state checkBox
        use_smooth_zoom = self._configs.getboolean("settings",
                                                   "use_smooth_zoom")
        proj_dir = self._configs.get("settings", "project_directory")
        datetime = self._configs.getboolean("settings", "datetime")
        gams_path = self._configs.get("settings", "gams_path")
        use_repl = self._configs.getboolean("settings", "use_repl")
        julia_path = self._configs.get("settings", "julia_path")
        delete_data = self._configs.getboolean("settings", "delete_data")
        if open_previous_project:
            self.ui.checkBox_open_previous_project.setCheckState(Qt.Checked)
        if show_exit_prompt:
            self.ui.checkBox_exit_prompt.setCheckState(Qt.Checked)
        if save_at_exit == "0":  # Not needed but makes the code more readable.
            self.ui.checkBox_save_at_exit.setCheckState(Qt.Unchecked)
        elif save_at_exit == "1":
            self.ui.checkBox_save_at_exit.setCheckState(Qt.PartiallyChecked)
        elif save_at_exit == "2":
            self.ui.checkBox_save_at_exit.setCheckState(Qt.Checked)
        else:  # default
            self.ui.checkBox_save_at_exit.setCheckState(Qt.PartiallyChecked)
        if commit_at_exit == "0":  # Not needed but makes the code more readable.
            self.ui.checkBox_commit_at_exit.setCheckState(Qt.Unchecked)
        elif commit_at_exit == "1":
            self.ui.checkBox_commit_at_exit.setCheckState(Qt.PartiallyChecked)
        elif commit_at_exit == "2":
            self.ui.checkBox_commit_at_exit.setCheckState(Qt.Checked)
        else:  # default
            self.ui.checkBox_commit_at_exit.setCheckState(Qt.PartiallyChecked)
        if use_smooth_zoom:
            self.ui.checkBox_use_smooth_zoom.setCheckState(Qt.Checked)
        if datetime:
            self.ui.checkBox_datetime.setCheckState(Qt.Checked)
        if delete_data:
            self.ui.checkBox_delete_data.setCheckState(Qt.Checked)
        if not proj_dir:
            proj_dir = DEFAULT_PROJECT_DIR
        self.ui.lineEdit_project_dir.setText(proj_dir)
        self.ui.lineEdit_gams_path.setText(gams_path)
        if use_repl:
            self.ui.checkBox_use_repl.setCheckState(Qt.Checked)
        self.ui.lineEdit_julia_path.setText(julia_path)

    def read_project_settings(self):
        """Read project settings from config object and update settings widgets accordingly."""
        work_dir = DEFAULT_WORK_DIR
        if self._project:
            self.ui.lineEdit_project_name.setText(self._project.name)
            self.ui.textEdit_project_description.setText(
                self._project.description)
            work_dir = self._project.work_dir
        self.ui.lineEdit_work_dir.setText(work_dir)
        self.orig_work_dir = work_dir

    @Slot(name='ok_clicked')
    def ok_clicked(self):
        """Get selections and save them to conf file."""
        a = int(self.ui.checkBox_open_previous_project.checkState())
        b = int(self.ui.checkBox_exit_prompt.checkState())
        f = str(int(self.ui.checkBox_save_at_exit.checkState()))
        g = str(int(self.ui.checkBox_commit_at_exit.checkState()))
        h = int(self.ui.checkBox_use_smooth_zoom.checkState())
        d = int(self.ui.checkBox_datetime.checkState())
        delete_data = int(self.ui.checkBox_delete_data.checkState())
        # Check that GAMS directory is valid. Set it empty if not.
        gams_path = self.ui.lineEdit_gams_path.text()
        if not gams_path == "":  # Skip this if using GAMS in system path
            gams_exe_path = os.path.join(gams_path, GAMS_EXECUTABLE)
            gamside_exe_path = os.path.join(gams_path, GAMSIDE_EXECUTABLE)
            if not os.path.isfile(gams_exe_path) and not os.path.isfile(
                    gamside_exe_path):
                self.statusbar.showMessage(
                    "GAMS executables not found in selected directory", 10000)
                return
        e = int(self.ui.checkBox_use_repl.checkState())
        # Check that Julia directory is valid. Set it empty if not.
        julia_path = self.ui.lineEdit_julia_path.text()
        if not julia_path == "":  # Skip this if using Julia in system path
            julia_exe_path = os.path.join(julia_path, JULIA_EXECUTABLE)
            if not os.path.isfile(julia_exe_path):
                self.statusbar.showMessage(
                    "Julia executable not found in selected directory", 10000)
                return
        # Write to config object
        self._configs.setboolean("settings", "open_previous_project", a)
        self._configs.setboolean("settings", "show_exit_prompt", b)
        self._configs.set("settings", "save_at_exit", f)
        self._configs.set("settings", "commit_at_exit", g)
        self._configs.setboolean("settings", "use_smooth_zoom", h)
        self._configs.setboolean("settings", "datetime", d)
        self._configs.setboolean("settings", "delete_data", delete_data)
        self._configs.set("settings", "gams_path", gams_path)
        self._configs.setboolean("settings", "use_repl", e)
        self._configs.set("settings", "julia_path", julia_path)
        # Update project settings
        self.update_project_settings()
        self._configs.save()
        self.close()

    def update_project_settings(self):
        """Update project settings when Ok has been clicked."""
        if not self._project:
            return
        save = False
        if not self.ui.lineEdit_work_dir.text():
            work_dir = DEFAULT_WORK_DIR
        else:
            work_dir = self.ui.lineEdit_work_dir.text()
        # Check if work directory was changed
        if not self.orig_work_dir == work_dir:
            if not self._project.change_work_dir(work_dir):
                self._toolbox.msg_error.emit(
                    "Could not change project work directory. Creating new dir:{0} failed "
                    .format(work_dir))
            else:
                save = True
        if not self._project.description == self.ui.textEdit_project_description.toPlainText(
        ):
            # Set new project description
            self._project.set_description(
                self.ui.textEdit_project_description.toPlainText())
            save = True
        if save:
            self._toolbox.msg.emit(
                "Project settings changed. Saving project...")
            self._toolbox.save_project()

    def keyPressEvent(self, e):
        """Close settings form when escape key is pressed.

        Args:
            e (QKeyEvent): Received key press event.
        """
        if e.key() == Qt.Key_Escape:
            self.close()

    def closeEvent(self, event=None):
        """Handle close window.

        Args:
            event (QEvent): Closing event if 'X' is clicked.
        """
        if event:
            event.accept()

    def mousePressEvent(self, e):
        """Save mouse position at the start of dragging.

        Args:
            e (QMouseEvent): Mouse event
        """
        self._mousePressPos = e.globalPos()
        self._mouseMovePos = e.globalPos()
        super().mousePressEvent(e)

    def mouseReleaseEvent(self, e):
        """Save mouse position at the end of dragging.

        Args:
            e (QMouseEvent): Mouse event
        """
        if self._mousePressPos is not None:
            self._mouseReleasePos = e.globalPos()
            moved = self._mouseReleasePos - self._mousePressPos
            if moved.manhattanLength() > 3:
                e.ignore()
                return

    def mouseMoveEvent(self, e):
        """Moves the window when mouse button is pressed and mouse cursor is moved.

        Args:
            e (QMouseEvent): Mouse event
        """
        # logging.debug("MouseMoveEvent at pos:%s" % e.pos())
        # logging.debug("MouseMoveEvent globalpos:%s" % e.globalPos())
        currentpos = self.pos()
        globalpos = e.globalPos()
        if not self._mouseMovePos:
            e.ignore()
            return
        diff = globalpos - self._mouseMovePos
        newpos = currentpos + diff
        self.move(newpos)
        self._mouseMovePos = globalpos
Пример #13
0
class myWindow(QMainWindow):
    def __init__(self, parent=None):
        super(myWindow, self).__init__(parent)
        clicked = QtCore.Signal()
        self.primed = 0
        self.pointer = None
        self.histlist = []
        self.HIST_SIZE = 10

        self.tb = self.addToolBar("File")
        self.tb.setMovable(False)
        self.tb.setToolButtonStyle(QtCore.Qt.ToolButtonTextUnderIcon)
        self.tb.actionTriggered[QAction].connect(self.toolbtnpressed)

        self.previous = QAction(QIcon("./imgs/56.png"), "Previous", self)
        self.tb.addAction(self.previous)
        self.next = QAction(QIcon("./imgs/57.png"), "Next", self)
        self.tb.addAction(self.next)
        self.options = QAction(QIcon("./imgs/46.png"), "Options", self)
        self.tb.addAction(self.options)
        self.about = QAction(QIcon("./imgs/48.png"), "About", self)
        self.about.triggered.connect(self._show_about)
        self.tb.addAction(self.about)

        if (self.pointer is None):
            self.previous.setEnabled(False)
            self.next.setEnabled(False)

        wdg = QWidget(self)
        wdg.setMinimumSize(460, 460)

        glayout = QGridLayout()
        self.setMinimumSize(600, 600)

        self.le = ClickableLineEdit(wdg)
        self.le.setStyleSheet('color:maroon;font-size: 14px;')
        self.le.setObjectName("searchword")
        self.le.setText("Enter word to Search...!")

        self.le.clicked.connect(self.le.clear)

        self.wordlist = []
        self.loadwords()
        completer = QCompleter(self.wordlist, self.le)
        completer.setCompletionMode(completer.PopupCompletion)
        self.le.setCompleter(completer)

        self.pb = QPushButton(wdg)
        self.pb.setObjectName("go")
        self.pb.setText("தேடு")
        self.pb.setStyleSheet(
            'color: #0077CC; font-weight:bold; font-size: 14px;')

        self.te = QTextEdit(wdg)
        self.te.setLineWrapMode(QTextEdit.NoWrap)

        self.sbar = QStatusBar()
        self.sbar.setStyleSheet('color:maroon;')
        self.sbar.showMessage('Ready...!')

        glayout.addWidget(QLabel("Find ", self), 1, 1)
        glayout.addWidget(self.le, 1, 2)
        glayout.addWidget(self.pb, 1, 3)
        glayout.addWidget(self.te, 2, 1, 1, 3)
        glayout.addWidget(self.sbar, 3, 1, 1, 3)

        wdg.setLayout(glayout)

        self.setWindowTitle("Power Tamil Dictionary")
        app_icon = QtGui.QIcon()
        app_icon.addFile('./imgs/PT-Icon.png', QtCore.QSize(16, 16))
        self.setWindowIcon(app_icon)

        self.connect(self.pb, SIGNAL("clicked()"), self.button_click)
        self.le.returnPressed.connect(self.pb.click)

        self.hotkey = {}
        self.hotkey['my_key'] = QShortcut(QtGui.QKeySequence("Ctrl+E"), self)
        self.hotkey['my_key'].activated.connect(self.animate_click)

        self.te.selectionChanged.connect(self.handleSelectionChanged)

        self.setCentralWidget(wdg)

    @Slot()
    def button_click(self):
        # sword is a QString object
        dbh = DBHelper()
        sword = self.le.text().strip()
        print(sword)
        if (sword.strip() == ""):
            self.sbar.showMessage("Please type a word to Search!")
        elif (dbh.checkwordpresent(sword) == 0):
            self.sbar.showMessage("Cannot find :: " + sword)
        else:
            self.addtohistory(sword)
            if (self.primed == 0):
                self.primed = 1
            self.enablePN()

            meanings = dbh.getwordMeaning(sword)
            hhelpr = HTMLHelper()
            HTML_word = hhelpr.getHTMLword(sword)
            hhelpr.setmeaningslist(meanings)
            HTML_meanings = hhelpr.getHTMLmeanings(hhelpr.meaninglist)

            print("bcPointer :: " + str(self.pointer))

            self.te.setText(HTML_word + HTML_meanings)
            self.sbar.showMessage("Found :: " + sword)

    @Slot()
    def animate_click(self, selword):
        print("CALLED.....!!!!!!!!!")
        dbh = DBHelper()
        sword = selword
        print(sword)
        presentflag = dbh.checkwordpresent(sword)

        if (presentflag == 0):
            self.sbar.showMessage("Cannot find :: " + sword)
        else:
            self.addtohistory(sword)
            if (self.primed == 0):
                self.primed = 1
                self.pointer = 0
            self.enablePN()

            meanings = dbh.getwordMeaning(sword)
            hhelpr = HTMLHelper()
            HTML_word = hhelpr.getHTMLword(sword)

            hhelpr.setmeaningslist(meanings)
            HTML_meanings = hhelpr.getHTMLmeanings(hhelpr.meaninglist)

            self.le.setText(sword)
            self.te.setText(HTML_word + HTML_meanings)
            self.sbar.showMessage("Found :: " + sword)

    def histnavigate(self, selword):

        dbh = DBHelper()
        sword = selword.title()
        wmdict = dbh.getwordMeaning(sword)
        hhelpr = HTMLHelper()
        HTML_T = ""
        meanings = dbh.getwordMeaning(sword)
        hhelpr = HTMLHelper()
        HTML_word = hhelpr.getHTMLword(sword)
        hhelpr.setmeaningslist(meanings)
        HTML_meanings = hhelpr.getHTMLmeanings(hhelpr.meaninglist)
        HTML_T = HTML_word + HTML_meanings

        self.te.setText(HTML_T)
        self.sbar.showMessage("Found :: " + sword)

    def addtohistory(self, hword):
        if (self.pointer is None):
            if (not hword in self.histlist):
                self.histlist.append(hword)
                self.pointer = 0
        elif (len(self.histlist) == self.HIST_SIZE):
            self.histlist.pop(0)
            if (not hword in self.histlist):
                self.histlist.append(hword)
                self.pointer = self.HIST_SIZE - 1
        else:
            if (not hword in self.histlist):
                self.histlist.append(hword)
                self.pointer = self.pointer + 1
        self.enablePN()
        print(self.histlist)

    def toolbtnpressed(self, action):
        if (action.text() == "Previous"):
            print("PREV :: P::" + str(self.pointer) + " LS::" +
                  str(len(self.histlist)))
            # edge case P0 L1
            if (self.pointer == 0 & len(self.histlist) == 1):
                self.pointer = self.pointer - 1
                self.le.setText(self.histlist[self.pointer])
                self.histnavigate(self.histlist[self.pointer])
                #print("previousR:: " + str(self.pointer))
            elif (self.pointer == 0):
                self.pointer = self.pointer
                self.le.setText(self.histlist[self.pointer])
                self.histnavigate(self.histlist[self.pointer])
                #print("previousL :: " + str(self.pointer))
                #print(self.histlist[self.pointer])
            # majority case
            elif (self.pointer <= len(self.histlist) - 1):
                self.pointer = self.pointer - 1
                self.le.setText(self.histlist[self.pointer])
                self.histnavigate(self.histlist[self.pointer])
                #print("previous:: " + str(self.pointer))

        elif (action.text() == "Next"):
            print("NEXT :: P::" + str(self.pointer) + " LS::" +
                  str(len(self.histlist)))
            # edge case
            if (self.pointer == len(self.histlist) - 1):
                self.pointer = self.pointer
                self.le.setText(self.histlist[self.pointer])
                self.histnavigate(self.histlist[self.pointer])
                #print("nextR:: " + str(self.pointer))
            # edge case
            elif (self.pointer == 0):
                self.pointer = self.pointer + 1
                self.le.setText(self.histlist[self.pointer])
                self.histnavigate(self.histlist[self.pointer])
                #print("nextL:: " + str(self.pointer))
            # majority case
            elif (self.pointer < len(self.histlist) - 1):
                self.pointer = self.pointer + 1
                self.le.setText(self.histlist[self.pointer])
                self.histnavigate(self.histlist[self.pointer])
                #print("next:: " + str(self.pointer))
        self.enablePN()

    def enablePN(self):
        # pointer starts from 0 while len counts from 1
        if (self.pointer is None):
            pass
        elif (self.pointer == len(self.histlist) - 1):
            self.previous.setEnabled(True)
            self.next.setEnabled(False)
        elif (self.pointer == 0):
            self.previous.setEnabled(False)
            self.next.setEnabled(True)
        elif (self.pointer < len(self.histlist) - 1):
            self.previous.setEnabled(True)
            self.next.setEnabled(True)

    def handleSelectionChanged(self):
        cursor = self.te.textCursor()
        textSelected = cursor.selectedText()
        self.le.setText(textSelected)
        if (textSelected != ""):
            self.animate_click(textSelected)

    def _show_about(self):
        QMessageBox.about(
            self, 'About', '<p><b>PowerTamil Dictionary V1.0</b></p>'
            '<p>Copyright © 2020 Rajkumar Palani</p>'
            '<p>Power Tamil dictionary is free to use and distribute. </p>'
            '<p><a href="http://www.rajkumarpalani.com/software">www.rajkumarpalani.com/software</a></p>'
        )

    def loadwords(self):
        with open("AllTamilWords.txt", "r", encoding="utf8") as f:
            self.wordlist = f.read().split()