Example #1
0
    def __init__(self, options, multiple, columns=2, parent=None):
        super(CheckboxesPanel, self).__init__(parent)

        self._options = []
        for i, option in enumerate(options):
            if isinstance(option, str):
                self._options.append((i, option))
            else:
                self.options.append(option)
        self._multiple = multiple
        self._buttons = []
        rows = len(options) / columns

        self._buttonGroup = QButtonGroup()
        self._buttonGroup.setExclusive(not multiple)
        layout = QGridLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setMargin(0)
        for i, (v, t) in enumerate(self._options):
            if multiple:
                button = QCheckBox(t)
            else:
                button = QRadioButton(t)
            self._buttons.append((v, button))
            self._buttonGroup.addButton(button, i)
            layout.addWidget(button, i % rows, i / rows)
        layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum),
                       0, columns)
        self.setLayout(layout)

        if multiple:
            self.setContextMenuPolicy(Qt.CustomContextMenu)
            self.customContextMenuRequested.connect(self.showPopupMenu)
    def __init__(self, iface, parent=None):
        self.iface = iface
        self.plugin_dir = os.path.dirname(__file__)

        # Some constants
        self.SDELLIPSE = self.tr('SD Ellipse')
        self.CANCEL = self.tr('Cancel')
        self.HELP = self.tr('Help')
        self.CLOSE = self.tr('Close')
        self.OK = self.tr('OK')

        """Constructor."""
        super(SDEllipseDialog, self).__init__(parent)
        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)

        self.method_group = QButtonGroup()
        self.method_group.addButton(self.yuill_rb)
        self.method_group.addButton(self.crimestat_rb)
        self.method_group.buttonClicked[QAbstractButton].connect(
                                                   self.methodChanged)
        self.yuill_rb.setChecked(True)
        self.method = 1
        okButton = self.button_box.button(QDialogButtonBox.Ok)
        okButton.setText(self.OK)
        cancelButton = self.button_box.button(QDialogButtonBox.Cancel)
        cancelButton.setText(self.CANCEL)
        cancelButton.setEnabled(False)
        # helpButton = self.button_box.button(QDialogButtonBox.Help)
        helpButton = self.helpButton
        helpButton.setText(self.HELP)
        closeButton = self.button_box.button(QDialogButtonBox.Close)
        closeButton.setText(self.CLOSE)

        # Connect signals
        okButton.clicked.connect(self.startWorker)
        cancelButton.clicked.connect(self.killWorker)
        helpButton.clicked.connect(self.giveHelp)
        closeButton.clicked.connect(self.reject)
        self.cumulative = False
        inpIndexCh = self.InputLayer.currentIndexChanged['QString']
        inpIndexCh.connect(self.layerchanged)
        # QObject.disconnect(self.button_box, SIGNAL("rejected()"),
        #                    self.reject)
        self.button_box.rejected.disconnect(self.reject)

        # Set instance variables
        self.worker = None
        self.inputlayerid = None
        self.layerlistchanging = False
        self.selectedFeatures_cb.setChecked(True)
        self.useWeights_cb.setChecked(False)
        self.result = None
Example #3
0
    def __init__(self, parent=None, iface=None):
        """Constructor for import dialog.

        .. versionadded: 3.3

        :param parent: Optional widget to use as parent.
        :type parent: QWidget

        :param iface: An instance of QgisInterface.
        :type iface: QgisInterface
        """
        QDialog.__init__(self, parent)
        self.parent = parent
        self.setupUi(self)

        title = self.tr('PetaBencana Downloader')
        self.setWindowTitle(title)
        icon = resources_path('img', 'icons', 'add-petabencana-layer.svg')
        self.setWindowIcon(QtGui.QIcon(icon))

        self.iface = iface

        self.source = None

        self.radio_button_group = QButtonGroup()
        self.radio_button_group.addButton(self.radio_button_production)
        self.radio_button_group.addButton(self.radio_button_development)

        self.radio_button_group.setExclusive(True)
        self.radio_button_production.setChecked(True)
        self.populate_combo_box()

        developer_mode = setting('developer_mode', False, bool)
        if not developer_mode:
            self.radio_button_widget.hide()
            self.source_label.hide()
            self.output_group.adjustSize()

        # signals
        self.radio_button_production.clicked.connect(self.populate_combo_box)
        self.radio_button_development.clicked.connect(self.populate_combo_box)

        # Set up things for context help
        self.help_button = self.button_box.button(
            QtWidgets.QDialogButtonBox.Help)
        # Allow toggling the help button
        self.help_button.setCheckable(True)
        self.help_button.toggled.connect(self.help_toggled)
        self.main_stacked_widget.setCurrentIndex(1)

        # set up the validator for the file name prefix
        expression = QRegExp('^[A-Za-z0-9-_]*$')
        validator = QtGui.QRegExpValidator(expression, self.filename_prefix)
        self.filename_prefix.setValidator(validator)
        self.time_stamp = None
        self.restore_state()
    def __init__(self, parameter, parent=None):
        """Constructor.

        :param parameter: A DefaultValueParameter object.
        :type parameter: DefaultValueParameter

        """
        super(DefaultValueParameterWidget, self).__init__(parameter, parent)

        self.radio_button_layout = QHBoxLayout()

        # Create radio button group
        self.input_button_group = QButtonGroup()

        for i in range(len(self._parameter.labels)):
            if '%s' in self._parameter.labels[i]:
                label = (
                    self._parameter.labels[i] %
                    self._parameter.options[i])
            else:
                label = self._parameter.labels[i]

            radio_button = QRadioButton(label)
            self.radio_button_layout.addWidget(radio_button)
            self.input_button_group.addButton(radio_button, i)
            if self._parameter.value == \
                    self._parameter.options[i]:
                radio_button.setChecked(True)

        # Create double spin box for custom value
        self.custom_value = QDoubleSpinBox()
        self.custom_value.setSingleStep(0.1)
        if self._parameter.options[-1]:
            self.custom_value.setValue(self._parameter.options[-1])
        self.radio_button_layout.addWidget(self.custom_value)

        self.toggle_custom_value()

        self.inner_input_layout.addLayout(self.radio_button_layout)

        # Connect
        # noinspection PyUnresolvedReferences
        self.input_button_group.buttonClicked.connect(
            self.toggle_custom_value)
Example #5
0
class CheckboxesPanel(QWidget):

    def __init__(self, options, multiple, columns=2, parent=None):
        super(CheckboxesPanel, self).__init__(parent)

        self._options = []
        for i, option in enumerate(options):
            if isinstance(option, str):
                self._options.append((i, option))
            else:
                self.options.append(option)
        self._multiple = multiple
        self._buttons = []
        rows = len(options) / columns

        self._buttonGroup = QButtonGroup()
        self._buttonGroup.setExclusive(not multiple)
        layout = QGridLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setMargin(0)
        for i, (v, t) in enumerate(self._options):
            if multiple:
                button = QCheckBox(t)
            else:
                button = QRadioButton(t)
            self._buttons.append((v, button))
            self._buttonGroup.addButton(button, i)
            layout.addWidget(button, i % rows, i / rows)
        layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum),
                       0, columns)
        self.setLayout(layout)

        if multiple:
            self.setContextMenuPolicy(Qt.CustomContextMenu)
            self.customContextMenuRequested.connect(self.showPopupMenu)

    def showPopupMenu(self):
        popup_menu = QMenu()
        select_all_action = QAction(self.tr('Select All'), popup_menu)
        select_all_action.triggered.connect(self.selectAll)
        clear_all_action = QAction(self.tr('Clear Selection'), popup_menu)
        clear_all_action.triggered.connect(self.deselectAll)
        popup_menu.addAction(select_all_action)
        popup_menu.addAction(clear_all_action)
        popup_menu.exec_(QCursor.pos())

    def selectAll(self):
        for (v, button) in self._buttons:
            button.setChecked(True)

    def deselectAll(self):
        for (v, button) in self._buttons:
            button.setChecked(False)

    def value(self):
        if self._multiple:
            value = []
            for (v, checkbox) in self._buttons:
                if checkbox.isChecked():
                    value.append(v)
            return value
        else:
            return self._options[self._buttonGroup.checkedId()][0]

    def setValue(self, value):
        if self._multiple:
            for (v, button) in self._buttons:
                if v in value:
                    button.setChecked(True)
        else:
            for v, button in self._buttons:
                if v == value:
                    button.setChecked(True)
class SDEllipseDialog(QDialog, FORM_CLASS):

    def __init__(self, iface, parent=None):
        self.iface = iface
        self.plugin_dir = os.path.dirname(__file__)

        # Some constants
        self.SDELLIPSE = self.tr('SD Ellipse')
        self.CANCEL = self.tr('Cancel')
        self.HELP = self.tr('Help')
        self.CLOSE = self.tr('Close')
        self.OK = self.tr('OK')

        """Constructor."""
        super(SDEllipseDialog, self).__init__(parent)
        # Set up the user interface from Designer.
        # After setupUI you can access any designer object by doing
        # self.<objectname>, and you can use autoconnect slots - see
        # http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
        # #widgets-and-dialogs-with-auto-connect
        self.setupUi(self)

        self.method_group = QButtonGroup()
        self.method_group.addButton(self.yuill_rb)
        self.method_group.addButton(self.crimestat_rb)
        self.method_group.buttonClicked[QAbstractButton].connect(
                                                   self.methodChanged)
        self.yuill_rb.setChecked(True)
        self.method = 1
        okButton = self.button_box.button(QDialogButtonBox.Ok)
        okButton.setText(self.OK)
        cancelButton = self.button_box.button(QDialogButtonBox.Cancel)
        cancelButton.setText(self.CANCEL)
        cancelButton.setEnabled(False)
        # helpButton = self.button_box.button(QDialogButtonBox.Help)
        helpButton = self.helpButton
        helpButton.setText(self.HELP)
        closeButton = self.button_box.button(QDialogButtonBox.Close)
        closeButton.setText(self.CLOSE)

        # Connect signals
        okButton.clicked.connect(self.startWorker)
        cancelButton.clicked.connect(self.killWorker)
        helpButton.clicked.connect(self.giveHelp)
        closeButton.clicked.connect(self.reject)
        self.cumulative = False
        inpIndexCh = self.InputLayer.currentIndexChanged['QString']
        inpIndexCh.connect(self.layerchanged)
        # QObject.disconnect(self.button_box, SIGNAL("rejected()"),
        #                    self.reject)
        self.button_box.rejected.disconnect(self.reject)

        # Set instance variables
        self.worker = None
        self.inputlayerid = None
        self.layerlistchanging = False
        self.selectedFeatures_cb.setChecked(True)
        self.useWeights_cb.setChecked(False)
        self.result = None
    # end of __init__

    def giveHelp(self):
        self.showInfo('Giving help')
        QDesktopServices.openUrl(QUrl.fromLocalFile(
                         self.plugin_dir + "/help/html/index.html"))
        # showPluginHelp(None, "help/html/index")
    # end of giveHelp

    def startWorker(self):
        # self.showInfo('Ready to start worker')
        self.degfreedCorr = self.degfreedcorr_cb.isChecked()
        self.crimestatCorr = self.crimestatcorr_cb.isChecked()
        # Get the input layer
        layerindex = self.InputLayer.currentIndex()
        layerId = self.InputLayer.itemData(layerindex)
        inputlayer = QgsProject.instance().mapLayer(layerId)
        if inputlayer is None:
            self.showError(self.tr('No input layer defined'))
            return
        self.featureCount = 0
        if self.selectedFeatures_cb.isChecked():
            self.featureCount = inputlayer.selectedFeatureCount()
        if self.featureCount == 0:
            self.featureCount = inputlayer.featureCount()
        if self.featureCount < 2:
            self.showError(self.tr('Not enough features'))
            # self.scene.clear()
            return
        if (self.useWeights_cb.isChecked() and
                 self.inputField.count() == 0):
            self.showError(self.tr('Missing numerical field'))
            return
        fieldindex = self.inputField.currentIndex()
        fieldname = self.inputField.itemData(fieldindex)
        # inpfield = inputlayer.fieldNameIndex(fieldindex)
        # minval = inputlayer.minimumValue(inpfield)

        if (not self.useWeights_cb.isChecked()):
            fieldname = None
        self.result = None
        self.SDLayer = inputlayer
        self.method = 1
        if self.yuill_rb.isChecked():
            self.method = 1
        elif self.crimestat_rb.isChecked():
            self.method = 2
        if self.featureCount < 3 and (self.method == 2 or
                                      self.degfreedCorr):
            self.showError(self.tr('Not enough features'))
            return
        # create a new worker instance
        worker = Worker(inputlayer,
                        self.selectedFeatures_cb.isChecked(),
                        fieldname, self.method)
        # start the worker in a new thread
        thread = QThread(self)
        worker.moveToThread(thread)
        worker.finished.connect(self.workerFinished)
        worker.error.connect(self.workerError)
        worker.status.connect(self.workerInfo)
        worker.progress.connect(self.progressBar.setValue)
        # worker.progress.connect(self.aprogressBar.setValue)
        thread.started.connect(worker.run)
        thread.start()
        self.thread = thread
        self.worker = worker
        self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
        self.button_box.button(QDialogButtonBox.Close).setEnabled(False)
        self.button_box.button(QDialogButtonBox.Cancel).setEnabled(True)
        self.InputLayer.setEnabled(False)
        self.inputField.setEnabled(False)
    # end of startWorker

    def workerFinished(self, ok, ret):
        """Handles the output from the worker and cleans up after the
           worker has finished."""
        # clean up the worker and thread
        self.worker.deleteLater()
        self.thread.quit()
        self.thread.wait()
        self.thread.deleteLater()
        # remove widget from the message bar (pop)
        # self.iface.messageBar().popWidget(self.messageBar)
        if ok and ret is not None:
            # self.showInfo("Result: " + str(ret))
            self.result = ret
            # Draw the ellipse
            self.drawEllipse()
        else:
            # notify the user that something went wrong
            if not ok:
                self.showError(self.tr('Aborted') + '!')
            else:
                self.showError(self.tr('Not able to create ellipse') + '!')
        # Update the user interface
        self.progressBar.setValue(0.0)
        self.button_box.button(QDialogButtonBox.Ok).setEnabled(True)
        self.button_box.button(QDialogButtonBox.Close).setEnabled(True)
        self.button_box.button(QDialogButtonBox.Cancel).setEnabled(False)
        self.InputLayer.setEnabled(True)
        if self.method == 1 and self.inputField.count() > 0:
            self.inputField.setEnabled(True)
    # end of workerFinished(self, ok, ret)

    def workerError(self, exception_string):
        """Report an error from the worker."""
        self.showError(self.tr('Worker failed - exception') +
                               ': ' + exception_string)

    def workerInfo(self, message_string):
        """Report an info message from the worker."""
        self.showInfo(self.tr('Worker') + ': ' + message_string)

    def killWorker(self):
        """Kill the worker thread."""
        if self.worker is not None:
            QgsMessageLog.logMessage(self.tr('Killing worker'),
                                     self.SDELLIPSE,
                                     Qgis.Info)
            self.worker.kill()

    # Implement the reject method to have the possibility to avoid
    # exiting the dialog when cancelling
    def reject(self):
        """Reject override."""
        # exit the dialog
        QDialog.reject(self)

    def drawEllipse(self):
        # self.showInfo('Result: ' + str(self.result))
        meanx = self.result[0]
        meany = self.result[1]
        angle1 = self.result[2]
        angle2 = self.result[3]
        SD1 = self.result[4]
        SD2 = self.result[5]
        if self.method == 2:  # CrimeStat
            SD1 = SD1 * (sqrt(2) *
                         sqrt(self.featureCount) /
                         sqrt(self.featureCount - 2))
            SD2 = SD2 * (sqrt(2) *
                         sqrt(self.featureCount) /
                         sqrt(self.featureCount - 2))
        if self.crimestatCorr and self.method != 2:
            SD1 = SD1 * sqrt(2)
            SD2 = SD2 * sqrt(2)
        if self.degfreedCorr and self.method != 2:
            SD1 = SD1 * sqrt(self.featureCount) / sqrt(self.featureCount - 2)
            SD2 = SD2 * sqrt(self.featureCount) / sqrt(self.featureCount - 2)
            # SD1 = SD1 * sqrt(self.featureCount) / sqrt(self.featureCount - 1)
            # SD2 = SD2 * sqrt(self.featureCount) / sqrt(self.featureCount - 1)
        # Find the major and minor axis
        majoraxisangle = angle1
        minoraxisangle = angle2
        majorSD = SD2
        minorSD = SD1
        if SD2 < SD1:
            majoraxisangle = angle2
            minoraxisangle = angle1
            majorSD = SD1
            minorSD = SD2
        # Calculate the "compass" direction angle (clockwise from north)
        direction = 90.0 - majoraxisangle * 180 / pi
        # Calculte the eccentricity
        eccentricity = sqrt(1 - pow(minorSD, 2) / pow(majorSD, 2))
        # Create the memory layer for the ellipse
        sdefields = []
        sdefields.append(QgsField("meanx", QVariant.Double))
        sdefields.append(QgsField("meany", QVariant.Double))
        sdefields.append(QgsField("majoranglerad", QVariant.Double))
        # sdefields.append(QgsField("minoranglerad", QVariant.Double))
        sdefields.append(QgsField("directiondeg", QVariant.Double))
        sdefields.append(QgsField("majorsd", QVariant.Double))
        sdefields.append(QgsField("minorsd", QVariant.Double))
        sdefields.append(QgsField("eccentricity", QVariant.Double))
        layeruri = 'Polygon?'
        layeruri = (layeruri + 'crs=' +
                    str(self.SDLayer.dataProvider().crs().authid()))
        memSDlayer = QgsVectorLayer(layeruri, self.OutputLayerName.text(),
                                    "memory")
        # Set the CRS to the original CRS object
        memSDlayer.setCrs(self.SDLayer.dataProvider().crs())
        memSDlayer.startEditing()  # ?
        for field in sdefields:
            memSDlayer.dataProvider().addAttributes([field])
        sdfeature = QgsFeature()
        theta1 = majoraxisangle
        points = []
        step = pi / 180    # 360 points to draw the ellipse
        t = 0.0
        while t < 2 * pi:
            p1 = QPointF(meanx + majorSD * cos(t) * cos(majoraxisangle) -
                         minorSD * sin(t) * sin(majoraxisangle),
                         meany + majorSD * cos(t) * sin(majoraxisangle) +
                         minorSD * sin(t) * cos(majoraxisangle))
            points.append(QgsPointXY(p1))
            t = t + step
        # Close the polygon
        p1 = QPointF(meanx + majorSD * cos(majoraxisangle),
                     meany + majorSD * sin(majoraxisangle))
        points.append(QgsPointXY(p1))
        sdfeature.setGeometry(QgsGeometry.fromPolygonXY([points]))
        attrs = [meanx, meany, majoraxisangle, direction,
                 majorSD, minorSD, eccentricity]
        sdfeature.setAttributes(attrs)
        memSDlayer.dataProvider().addFeatures([sdfeature])
        memSDlayer.commitChanges()  # ?
        memSDlayer.updateExtents()
        QgsProject.instance().addMapLayers([memSDlayer])
    # end of drawEllipse

    def layerchanged(self, number=0):
        """Do the necessary updates after a layer selection has
           been changed."""
        self.layerselectionactive = True
        layerindex = self.InputLayer.currentIndex()
        layerId = self.InputLayer.itemData(layerindex)
        self.inputlayerid = layerId
        inputlayer = QgsProject.instance().mapLayer(layerId)
        while self.inputField.count() > 0:
            self.inputField.removeItem(0)
        self.useWeights_cb.setEnabled(False)
        self.inputField.setEnabled(False)
        self.layerselectionactive = False
        # Get the numerical fields
        if inputlayer is not None:
            provider = inputlayer.dataProvider()
            attribs = provider.fields()
            if str(type(attribs)) != "<type 'dict'>":
                atr = {}
                for i in range(attribs.count()):
                    atr[i] = attribs.at(i)
                attrdict = atr
            for id, attrib in attrdict.items():
                # Check for numeric attribute
                #if attrib.typeName().upper() in ('REAL', 'INTEGER', 'INT4',
                #                                 'INT8', 'FLOAT4'):
                if attrib.isNumeric():
                    self.inputField.addItem(attrib.name(), attrib.name())
            if (self.inputField.count() > 0):
                if self.method == 1:
                    self.useWeights_cb.setEnabled(True)
                    self.inputField.setEnabled(True)
            else:
                self.useWeights_cb.setChecked(False)
            self.OutputLayerName.setText(
                "SDE_" +
                self.method_group.checkedButton().text().strip('"') +
                "_" + self.InputLayer.currentText())
    # end of layerchanged

    def methodChanged(self, button):
        if self.InputLayer.currentText() is not None:
            self.OutputLayerName.setText("SDE_" + button.text().strip('"') +
                                         "_" + self.InputLayer.currentText())
        # Disable all options
        self.crimestatcorr_cb.setEnabled(False)
        self.crimestatcorr_cb.setChecked(False)
        self.degfreedcorr_cb.setEnabled(False)
        self.degfreedcorr_cb.setChecked(False)
        self.useWeights_cb.setEnabled(False)
        self.useWeights_cb.setChecked(False)
        self.inputField.setEnabled(False)
        if button.text() == '"CrimeStat"':
            self.method = 2
        elif button.text() == "Yuill":
            self.method = 1
            self.crimestatcorr_cb.setEnabled(True)
            self.degfreedcorr_cb.setEnabled(True)
            if self.inputField.count() > 0:
                self.useWeights_cb.setEnabled(True)
                self.inputField.setEnabled(True)
        else:  # Should not be reached yet
            if self.inputField.count() > 0:
                self.useWeights_cb.setEnabled(True)
                self.inputField.setEnabled(True)

    def showError(self, text):
        """Show an error."""
        # self.iface.messageBar().pushMessage(self.tr('Error'), text,
        #                                     level=QgsMessageBar.CRITICAL,
        #                                     duration=3)
        QgsMessageLog.logMessage('Error: ' + text,
                                 self.SDELLIPSE,
                                 Qgis.Critical)

    def showWarning(self, text):
        """Show a warning."""
        # self.iface.messageBar().pushMessage(self.tr('Warning'), text,
        #                                     level=QgsMessageBar.WARNING,
        #                                     duration=2)
        QgsMessageLog.logMessage('Warning: ' + text,
                                 self.SDELLIPSE,
                                 Qgis.Warning)

    def showInfo(self, text):
        """Show info."""
        # self.iface.messageBar().pushMessage(self.tr('Info'), text,
        #                                     level=QgsMessageBar.INFO,
        #                                     duration=2)
        QgsMessageLog.logMessage('Info: ' + text,
                                 self.SDELLIPSE,
                                 Qgis.Info)

    # Implement the accept method to avoid exiting the dialog when
    # starting the work
    def accept(self):
        """Accept override."""
        pass

    # Implement the reject method to have the possibility to avoid
    # exiting the dialog when cancelling
    def reject(self):
        """Reject override."""
        # exit the dialog
        QDialog.reject(self)

    # Translation
    def tr(self, message):
        """Get the translation for a string using Qt translation API.

        :param message: String for translation.
        :type message: str, QString

        :returns: Translated version of message.
        :rtype: QString
        """
        return QCoreApplication.translate('Dialog', message)

    # Overriding
    def resizeEvent(self, event):
        return
        # self.showInfo("resizeEvent")

    # Overriding
    def showEvent(self, event):
        return
Example #7
0
class PetaBencanaDialog(QDialog, FORM_CLASS):

    """Downloader for PetaBencana data.

    .. versionadded: 3.3
    """

    def __init__(self, parent=None, iface=None):
        """Constructor for import dialog.

        .. versionadded: 3.3

        :param parent: Optional widget to use as parent.
        :type parent: QWidget

        :param iface: An instance of QgisInterface.
        :type iface: QgisInterface
        """
        QDialog.__init__(self, parent)
        self.parent = parent
        self.setupUi(self)

        title = self.tr('PetaBencana Downloader')
        self.setWindowTitle(title)
        icon = resources_path('img', 'icons', 'add-petabencana-layer.svg')
        self.setWindowIcon(QtGui.QIcon(icon))

        self.iface = iface

        self.source = None

        self.radio_button_group = QButtonGroup()
        self.radio_button_group.addButton(self.radio_button_production)
        self.radio_button_group.addButton(self.radio_button_development)

        self.radio_button_group.setExclusive(True)
        self.radio_button_production.setChecked(True)
        self.populate_combo_box()

        developer_mode = setting('developer_mode', False, bool)
        if not developer_mode:
            self.radio_button_widget.hide()
            self.source_label.hide()
            self.output_group.adjustSize()

        # signals
        self.radio_button_production.clicked.connect(self.populate_combo_box)
        self.radio_button_development.clicked.connect(self.populate_combo_box)

        # Set up things for context help
        self.help_button = self.button_box.button(
            QtWidgets.QDialogButtonBox.Help)
        # Allow toggling the help button
        self.help_button.setCheckable(True)
        self.help_button.toggled.connect(self.help_toggled)
        self.main_stacked_widget.setCurrentIndex(1)

        # set up the validator for the file name prefix
        expression = QRegExp('^[A-Za-z0-9-_]*$')
        validator = QtGui.QRegExpValidator(expression, self.filename_prefix)
        self.filename_prefix.setValidator(validator)
        self.time_stamp = None
        self.restore_state()

    @pyqtSlot(bool)  # prevents actions being handled twice
    def help_toggled(self, flag):
        """Show or hide the help tab in the stacked widget.

        .. versionadded: 3.3

        :param flag: Flag indicating whether help should be shown or hidden.
        :type flag: bool
        """
        if flag:
            self.help_button.setText(self.tr('Hide Help'))
            self.show_help()
        else:
            self.help_button.setText(self.tr('Show Help'))
            self.hide_help()

    def hide_help(self):
        """Hide the usage info from the user.

        .. versionadded:: 3.3
        """
        self.main_stacked_widget.setCurrentIndex(1)

    def show_help(self):
        """Show usage info to the user.

        .. versionadded: 3.3
        """
        # Read the header and footer html snippets
        self.main_stacked_widget.setCurrentIndex(0)
        header = html_header()
        footer = html_footer()

        string = header

        message = peta_bencana_help()
        string += message.to_html()
        string += footer

        self.help_web_view.setHtml(string)

    def restore_state(self):
        """Read last state of GUI from configuration file.

        .. versionadded: 3.3
        """
        settings = QSettings()
        try:
            last_path = settings.value('directory', type=str)
        except TypeError:
            last_path = ''
        self.output_directory.setText(last_path)

    def save_state(self):
        """Store current state of GUI to configuration file.

        .. versionadded: 3.3
        """
        settings = QSettings()
        settings.setValue('directory', self.output_directory.text())

    @pyqtSlot()  # prevents actions being handled twice
    def on_directory_button_clicked(self):
        """Show a dialog to choose directory.

        .. versionadded: 3.3
        """
        # noinspection PyCallByClass,PyTypeChecker
        self.output_directory.setText(QFileDialog.getExistingDirectory(
            self, self.tr('Select download directory')))

    def accept(self):
        """Do PetaBencana download and display it in QGIS.

        .. versionadded: 3.3
        """

        self.save_state()
        try:
            self.require_directory()
        except CanceledImportDialogError:
            return

        QgsApplication.instance().setOverrideCursor(
            QtGui.QCursor(QtCore.Qt.WaitCursor)
        )

        source = self.define_url()
        # save the file as json first
        name = 'jakarta_flood.json'
        output_directory = self.output_directory.text()
        output_prefix = self.filename_prefix.text()
        overwrite = self.overwrite_flag.isChecked()
        date_stamp_flag = self.include_date_flag.isChecked()
        output_base_file_path = self.get_output_base_path(
            output_directory,
            output_prefix,
            date_stamp_flag,
            name,
            overwrite)

        title = self.tr("Can't access API")

        try:
            self.download(source, output_base_file_path)

            # Open downloaded file as QgsMapLayer
            options = QgsVectorLayer.LayerOptions(False)
            layer = QgsVectorLayer(
                output_base_file_path, 'flood', 'ogr', options)
        except Exception as e:
            disable_busy_cursor()
            QMessageBox.critical(self, title, str(e))
            return

        self.time_stamp = time.strftime('%d-%b-%Y %H:%M:%S')
        # Now save as shp
        name = 'jakarta_flood.shp'
        output_base_file_path = self.get_output_base_path(
            output_directory,
            output_prefix,
            date_stamp_flag,
            name,
            overwrite)
        QgsVectorFileWriter.writeAsVectorFormat(
            layer,
            output_base_file_path,
            'CP1250',
            QgsCoordinateTransform(),
            'ESRI Shapefile')
        # Get rid of the GeoJSON layer and rather use local shp
        del layer

        self.copy_style(output_base_file_path)

        self.copy_keywords(output_base_file_path)
        layer = self.add_flooded_field(output_base_file_path)

        # check if the layer has feature or not
        if layer.featureCount() <= 0:
            city = self.city_combo_box.currentText()
            message = self.tr(
                'There are no floods data available on {city} '
                'at this time.').format(city=city)
            display_warning_message_box(
                self,
                self.tr('No data'),
                message)
            disable_busy_cursor()
        else:
            # add the layer to the map
            project = QgsProject.instance()
            project.addMapLayer(layer)
            disable_busy_cursor()
            self.done(QDialog.Accepted)

    def add_flooded_field(self, shapefile_path):
        """Create the layer from the local shp adding the flooded field.

        .. versionadded:: 3.3

        Use this method to add a calculated field to a shapefile. The shapefile
        should have a field called 'count' containing the number of flood
        reports for the field. The field values will be set to 0 if the count
        field is < 1, otherwise it will be set to 1.

        :param shapefile_path: Path to the shapefile that will have the flooded
            field added.
        :type shapefile_path: basestring

        :return: A vector layer with the flooded field added.
        :rtype: QgsVectorLayer
        """
        layer = QgsVectorLayer(
            shapefile_path, self.tr('Jakarta Floods'), 'ogr')
        # Add a calculated field indicating if a poly is flooded or not
        # from qgis.PyQt.QtCore import QVariant
        layer.startEditing()
        # Add field with integer from 0 to 4 which represents the flood
        # class. Its the same as 'state' field except that is being treated
        # as a string.
        # This is used for cartography
        flood_class_field = QgsField('floodclass', QVariant.Int)
        layer.addAttribute(flood_class_field)
        layer.commitChanges()
        layer.startEditing()
        flood_class_idx = layer.fields().lookupField('floodclass')
        flood_class_expression = QgsExpression('to_int(state)')
        context = QgsExpressionContext()
        context.setFields(layer.fields())
        flood_class_expression.prepare(context)

        # Add field with boolean flag to say if the area is flooded
        # This is used by the impact function
        flooded_field = QgsField('flooded', QVariant.Int)
        layer.dataProvider().addAttributes([flooded_field])
        layer.commitChanges()
        layer.startEditing()
        flooded_idx = layer.fields().lookupField('flooded')
        flood_flag_expression = QgsExpression('state > 0')
        flood_flag_expression.prepare(context)
        for feature in layer.getFeatures():
            context.setFeature(feature)
            feature[flood_class_idx] = flood_class_expression.evaluate(context)
            feature[flooded_idx] = flood_flag_expression.evaluate(context)
            layer.updateFeature(feature)
        layer.commitChanges()
        return layer

    def copy_keywords(self, shapefile_path):
        """Copy keywords from the OSM resource directory to the output path.

        .. versionadded: 3.3

        In addition to copying the template, tokens within the template will
        be replaced with new values for the date token and title token.

        :param shapefile_path: Path to the shapefile that will have the flooded
            field added.
        :type shapefile_path: basestring
        """
        source_xml_path = resources_path('petabencana', 'flood-keywords.xml')
        output_xml_path = shapefile_path.replace('shp', 'xml')
        LOGGER.info('Copying xml to: %s' % output_xml_path)

        title_token = '[TITLE]'
        new_title = self.tr('Jakarta Floods - %s' % self.time_stamp)

        date_token = '[DATE]'
        new_date = self.time_stamp
        with open(source_xml_path) as source_file, \
                open(output_xml_path, 'w') as output_file:
            for line in source_file:
                line = line.replace(date_token, new_date)
                line = line.replace(title_token, new_title)
                output_file.write(line)

    @staticmethod
    def copy_style(shapefile_path):
        """Copy style from the OSM resource directory to the output path.

        .. versionadded: 3.3

        :param shapefile_path: Path to the shapefile that should get the path
            added.
        :type shapefile_path: basestring
        """
        source_qml_path = resources_path('petabencana', 'flood-style.qml')
        output_qml_path = shapefile_path.replace('shp', 'qml')
        LOGGER.info('Copying qml to: %s' % output_qml_path)
        copy(source_qml_path, output_qml_path)

    def get_output_base_path(
            self,
            output_directory,
            output_prefix,
            with_date_stamp,
            feature_type,
            overwrite):
        """Get a full base name path to save the shapefile.

        TODO: This is cut & paste from OSM - refactor to have one method

        :param output_directory: The directory where to put results.
        :type output_directory: str

        :param output_prefix: The prefix to add for the shapefile.
        :type output_prefix: str

        :param with_date_stamp: Whether to add a datestamp in between the
            file prefix and the feature_type for the shapefile name.
        :type output_prefix: str

        :param feature_type: What kind of data will be downloaded. Will be
            used for the shapefile name.
        :type feature_type: str

        :param overwrite: Boolean to know if we can overwrite existing files.
        :type overwrite: bool

        :return: The base path.
        :rtype: str
        """
        if with_date_stamp and self.time_stamp is not None:
            time_stamp = self.time_stamp.replace(' ', '-')
            time_stamp = time_stamp.replace(':', '-')
            time_stamp += '-'
            feature_type = time_stamp + feature_type

        path = os.path.join(
            output_directory, '%s%s' % (output_prefix, feature_type))

        if overwrite:

            # If a shapefile exists, we must remove it (only the .shp)
            shp = '%s.shp' % path
            if os.path.isfile(shp):
                os.remove(shp)

        else:
            separator = '-'
            suffix = self.get_unique_file_path_suffix(
                '%s.shp' % path, separator)

            if suffix:
                path = os.path.join(output_directory, '%s%s%s%s' % (
                    output_prefix, feature_type, separator, suffix))

        return path

    @staticmethod
    def get_unique_file_path_suffix(file_path, separator='-', i=0):
        """Return the minimum number to suffix the file to not overwrite one.
        Example : /tmp/a.txt exists.
            - With file_path='/tmp/b.txt' will return 0.
            - With file_path='/tmp/a.txt' will return 1 (/tmp/a-1.txt)

        TODO: This is cut & paste from OSM - refactor to have one method

        :param file_path: The file to check.
        :type file_path: str

        :param separator: The separator to add before the prefix.
        :type separator: str

        :param i: The minimum prefix to check.
        :type i: int

        :return: The minimum prefix you should add to not overwrite a file.
        :rtype: int
        """
        basename = os.path.splitext(file_path)
        if i != 0:
            file_path_test = os.path.join(
                '%s%s%s%s' % (basename[0], separator, i, basename[1]))
        else:
            file_path_test = file_path

        if os.path.isfile(file_path_test):
            return PetaBencanaDialog.get_unique_file_path_suffix(
                file_path, separator, i + 1)
        else:
            return i

    def require_directory(self):
        """Ensure directory path entered in dialog exist.

        When the path does not exist, this function will ask the user if he
        want to create it or not.

        TODO: This is cut & paste from OSM - refactor to have one method

        :raises: CanceledImportDialogError - when user choose 'No' in
            the question dialog for creating directory.
        """
        path = self.output_directory.text()

        if os.path.exists(path):
            return

        title = self.tr('Directory %s not exist') % path
        question = self.tr(
            'Directory %s not exist. Do you want to create it?') % path
        # noinspection PyCallByClass,PyTypeChecker
        answer = QMessageBox.question(
            self, title, question, QMessageBox.Yes | QMessageBox.No)

        if answer == QMessageBox.Yes:
            if len(path) != 0:
                os.makedirs(path)
            else:
                # noinspection PyCallByClass,PyTypeChecker,PyArgumentList
                display_warning_message_box(
                    self,
                    self.tr('InaSAFE error'),
                    self.tr('Output directory can not be empty.'))
                raise CanceledImportDialogError()
        else:
            raise CanceledImportDialogError()

    def load_shapefile(self, feature_type, base_path):
        """Load downloaded shape file to QGIS Main Window.

        TODO: This is cut & paste from OSM - refactor to have one method

        :param feature_type: What kind of features should be downloaded.
            Currently 'buildings', 'building-points' or 'roads' are supported.
        :type feature_type: str

        :param base_path: The base path of the shape file (without extension).
        :type base_path: str

        :raises: FileMissingError - when buildings.shp not exist
        """

        path = '%s.shp' % base_path

        if not os.path.exists(path):
            message = self.tr(
                '%s does not exist. The server does not have any data for '
                'this extent.' % path)
            raise FileMissingError(message)

        self.iface.addVectorLayer(path, feature_type, 'ogr')

    def reject(self):
        """Redefinition of the method.

        It will call the super method.
        """
        super(PetaBencanaDialog, self).reject()

    def download(self, url, output_path):
        """Download file from API url and write to output path.

        :param url: URL of the API.
        :type url: str

        :param output_path: Path of output file,
        :type output_path: str
        """
        request_failed_message = self.tr(
            "Can't access PetaBencana API: {source}").format(
            source=url)
        downloader = FileDownloader(url, output_path)
        result, message = downloader.download()
        if not result:
            display_warning_message_box(
                self,
                self.tr('Download error'),
                self.tr(request_failed_message + '\n' + message))

        if result == QNetworkReply.OperationCanceledError:
            display_warning_message_box(
                self,
                self.tr('Download error'),
                self.tr(message))

    # The function below might be usefull for future usage.

    # def get_available_area(self):
    #     """Function to automatically get the available area on API.
    #        *still cannot get string data from QByteArray*
    #     """
    #     available_area = []
    #     network_manager = QgsNetworkAccessManager.instance()
    #     api_url = QUrl('https://data.petabencana.id/cities')
    #     api_request = QNetworkRequest(api_url)
    #     api_response = network_manager.get(api_request)
    #     data = api_response.readAll()
    #     json_response = QScriptEngine().evaluate(data)
    #     geometries = json_response.property('output').property('geometries')
    #     iterator = QScriptValueIterator(geometries)
    #     while iterator.hasNext():
    #         iterator.next()
    #         geometry = iterator.value()
    #         geometry_code = (
    #             geometry.property('properties').property('code').toString())
    #         available_area.append(geometry_code)

    def populate_combo_box(self):
        """Populate combobox for selecting city."""
        if self.radio_button_production.isChecked():
            self.source = production_api['url']
            available_data = production_api['available_data']

        else:
            self.source = development_api['url']
            available_data = development_api['available_data']

        self.city_combo_box.clear()
        for index, data in enumerate(available_data):
            self.city_combo_box.addItem(data['name'])
            self.city_combo_box.setItemData(
                index, data['code'], Qt.UserRole)

    def define_url(self):
        """Define API url based on which source is selected.

        :return: Valid url of selected source.
        :rtype: str
        """
        current_index = self.city_combo_box.currentIndex()
        city_code = self.city_combo_box.itemData(current_index, Qt.UserRole)
        source = (self.source).format(city_code=city_code)
        return source
class DefaultValueParameterWidget(GenericParameterWidget):

    """Widget class for Default Value Parameter."""

    def __init__(self, parameter, parent=None):
        """Constructor.

        :param parameter: A DefaultValueParameter object.
        :type parameter: DefaultValueParameter

        """
        super(DefaultValueParameterWidget, self).__init__(parameter, parent)

        self.radio_button_layout = QHBoxLayout()

        # Create radio button group
        self.input_button_group = QButtonGroup()

        for i in range(len(self._parameter.labels)):
            if '%s' in self._parameter.labels[i]:
                label = (
                    self._parameter.labels[i] %
                    self._parameter.options[i])
            else:
                label = self._parameter.labels[i]

            radio_button = QRadioButton(label)
            self.radio_button_layout.addWidget(radio_button)
            self.input_button_group.addButton(radio_button, i)
            if self._parameter.value == \
                    self._parameter.options[i]:
                radio_button.setChecked(True)

        # Create double spin box for custom value
        self.custom_value = QDoubleSpinBox()
        self.custom_value.setSingleStep(0.1)
        if self._parameter.options[-1]:
            self.custom_value.setValue(self._parameter.options[-1])
        self.radio_button_layout.addWidget(self.custom_value)

        self.toggle_custom_value()

        self.inner_input_layout.addLayout(self.radio_button_layout)

        # Connect
        # noinspection PyUnresolvedReferences
        self.input_button_group.buttonClicked.connect(
            self.toggle_custom_value)

    def raise_invalid_type_exception(self):
        """Raise invalid type."""
        message = 'Expecting element type of %s' % (
            self._parameter.element_type.__name__)
        err = ValueError(message)
        return err

    def get_parameter(self):
        """Obtain list parameter object from the current widget state.

        :returns: A DefaultValueParameter from the current state of widget
        :rtype: DefaultValueParameter
        """
        radio_button_checked_id = self.input_button_group.checkedId()
        # No radio button checked, then default value = None
        if radio_button_checked_id == -1:
            self._parameter.value = None
        # The last radio button (custom) is checked, get the value from the
        # line edit
        elif radio_button_checked_id == len(self._parameter.options) - 1:
            self._parameter.options[radio_button_checked_id] = \
                self.custom_value.value()
            self._parameter.value = self.custom_value.value()
        else:
            self._parameter.value = self._parameter.options[
                radio_button_checked_id]

        return self._parameter

    def set_value(self, value):
        """Set value by item's string.

        :param value: The value.
        :type value: str, int

        :returns: True if success, else False.
        :rtype: bool
        """
        # Find index of choice
        try:
            value_index = self._parameter.options.index(value)
            self.input_button_group.button(value_index).setChecked(True)
        except ValueError:
            last_index = len(self._parameter.options) - 1
            self.input_button_group.button(last_index).setChecked(
                True)
            self.custom_value.setValue(value)

        self.toggle_custom_value()

    def toggle_custom_value(self):
        """Enable or disable the custom value line edit."""
        radio_button_checked_id = self.input_button_group.checkedId()
        if (radio_button_checked_id
                == len(self._parameter.options) - 1):
            self.custom_value.setDisabled(False)
        else:
            self.custom_value.setDisabled(True)
    def __init__(self, parameter, parent=None):
        """Constructor.

        :param parameter: A GroupSelectParameter object.
        :type parameter: GroupSelectParameter
        """
        QWidget.__init__(self, parent)
        self._parameter = parameter

        # Store spin box
        self.spin_boxes = {}

        # Create elements
        # Label (name)
        self.label = QLabel(self._parameter.name)

        # Layouts
        self.main_layout = QVBoxLayout()
        self.input_layout = QVBoxLayout()

        # _inner_input_layout must be filled with widget in the child class
        self.inner_input_layout = QVBoxLayout()

        self.radio_button_layout = QGridLayout()

        # Create radio button group
        self.input_button_group = QButtonGroup()

        # List widget
        self.list_widget = QListWidget()
        self.list_widget.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.list_widget.setDragDropMode(QAbstractItemView.DragDrop)
        self.list_widget.setDefaultDropAction(Qt.MoveAction)
        self.list_widget.setEnabled(False)
        self.list_widget.setSizePolicy(
            QSizePolicy.Maximum, QSizePolicy.Expanding)

        for i, key in enumerate(self._parameter.options):
            value = self._parameter.options[key]
            radio_button = QRadioButton(value.get('label'))
            self.radio_button_layout.addWidget(radio_button, i, 0)
            if value.get('type') == SINGLE_DYNAMIC:
                percentage_spin_box = PercentageSpinBox(self)
                self.radio_button_layout.addWidget(percentage_spin_box, i, 1)
                percentage_spin_box.setValue(value.get('value', 0))
                step = percentage_spin_box.singleStep()
                if step > 1:
                    precision = 0
                else:
                    precision = len(str(step).split('.')[1])
                    if precision > 3:
                        precision = 3
                percentage_spin_box.setDecimals(precision)
                self.spin_boxes[key] = percentage_spin_box

                # Enable spin box depends on the selected option
                if self._parameter.selected == key:
                    percentage_spin_box.setEnabled(True)
                else:
                    percentage_spin_box.setEnabled(False)

            elif value.get('type') == STATIC:
                static_value = value.get('value', 0)
                if static_value is not None:
                    self.radio_button_layout.addWidget(
                        QLabel(str(static_value * 100) + ' %'), i, 1)
            elif value.get('type') == MULTIPLE_DYNAMIC:
                if self._parameter.selected == key:
                    self.list_widget.setEnabled(True)
                else:
                    self.list_widget.setEnabled(False)

            self.input_button_group.addButton(radio_button, i)
            if self._parameter.selected == key:
                radio_button.setChecked(True)

        # Help text
        self.help_label = QLabel(self._parameter.help_text)
        self.help_label.setSizePolicy(
            QSizePolicy.Maximum, QSizePolicy.Expanding)
        self.help_label.setWordWrap(True)
        self.help_label.setAlignment(Qt.AlignTop)

        self.inner_input_layout.addLayout(self.radio_button_layout)
        self.inner_input_layout.addWidget(self.list_widget)

        # Put elements into layouts
        self.input_layout.addWidget(self.label)
        self.input_layout.addLayout(self.inner_input_layout)

        self.help_layout = QVBoxLayout()
        self.help_layout.addWidget(self.help_label)

        self.main_layout.addLayout(self.input_layout)
        self.main_layout.addLayout(self.help_layout)

        self.setLayout(self.main_layout)

        self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding)

        # Update list widget
        self.update_list_widget()

        # Connect signal
        # noinspection PyUnresolvedReferences
        self.input_button_group.buttonClicked.connect(
            self.radio_buttons_clicked)
class GroupSelectParameterWidget(GenericParameterWidget):

    """Widget class for Group Select Parameter."""

    def __init__(self, parameter, parent=None):
        """Constructor.

        :param parameter: A GroupSelectParameter object.
        :type parameter: GroupSelectParameter
        """
        QWidget.__init__(self, parent)
        self._parameter = parameter

        # Store spin box
        self.spin_boxes = {}

        # Create elements
        # Label (name)
        self.label = QLabel(self._parameter.name)

        # Layouts
        self.main_layout = QVBoxLayout()
        self.input_layout = QVBoxLayout()

        # _inner_input_layout must be filled with widget in the child class
        self.inner_input_layout = QVBoxLayout()

        self.radio_button_layout = QGridLayout()

        # Create radio button group
        self.input_button_group = QButtonGroup()

        # List widget
        self.list_widget = QListWidget()
        self.list_widget.setSelectionMode(QAbstractItemView.ExtendedSelection)
        self.list_widget.setDragDropMode(QAbstractItemView.DragDrop)
        self.list_widget.setDefaultDropAction(Qt.MoveAction)
        self.list_widget.setEnabled(False)
        self.list_widget.setSizePolicy(
            QSizePolicy.Maximum, QSizePolicy.Expanding)

        for i, key in enumerate(self._parameter.options):
            value = self._parameter.options[key]
            radio_button = QRadioButton(value.get('label'))
            self.radio_button_layout.addWidget(radio_button, i, 0)
            if value.get('type') == SINGLE_DYNAMIC:
                percentage_spin_box = PercentageSpinBox(self)
                self.radio_button_layout.addWidget(percentage_spin_box, i, 1)
                percentage_spin_box.setValue(value.get('value', 0))
                step = percentage_spin_box.singleStep()
                if step > 1:
                    precision = 0
                else:
                    precision = len(str(step).split('.')[1])
                    if precision > 3:
                        precision = 3
                percentage_spin_box.setDecimals(precision)
                self.spin_boxes[key] = percentage_spin_box

                # Enable spin box depends on the selected option
                if self._parameter.selected == key:
                    percentage_spin_box.setEnabled(True)
                else:
                    percentage_spin_box.setEnabled(False)

            elif value.get('type') == STATIC:
                static_value = value.get('value', 0)
                if static_value is not None:
                    self.radio_button_layout.addWidget(
                        QLabel(str(static_value * 100) + ' %'), i, 1)
            elif value.get('type') == MULTIPLE_DYNAMIC:
                if self._parameter.selected == key:
                    self.list_widget.setEnabled(True)
                else:
                    self.list_widget.setEnabled(False)

            self.input_button_group.addButton(radio_button, i)
            if self._parameter.selected == key:
                radio_button.setChecked(True)

        # Help text
        self.help_label = QLabel(self._parameter.help_text)
        self.help_label.setSizePolicy(
            QSizePolicy.Maximum, QSizePolicy.Expanding)
        self.help_label.setWordWrap(True)
        self.help_label.setAlignment(Qt.AlignTop)

        self.inner_input_layout.addLayout(self.radio_button_layout)
        self.inner_input_layout.addWidget(self.list_widget)

        # Put elements into layouts
        self.input_layout.addWidget(self.label)
        self.input_layout.addLayout(self.inner_input_layout)

        self.help_layout = QVBoxLayout()
        self.help_layout.addWidget(self.help_label)

        self.main_layout.addLayout(self.input_layout)
        self.main_layout.addLayout(self.help_layout)

        self.setLayout(self.main_layout)

        self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Expanding)

        # Update list widget
        self.update_list_widget()

        # Connect signal
        # noinspection PyUnresolvedReferences
        self.input_button_group.buttonClicked.connect(
            self.radio_buttons_clicked)

    def get_parameter(self):
        """Obtain list parameter object from the current widget state.

        :returns: A DefaultValueParameter from the current state of widget
        :rtype: DefaultValueParameter
        """
        # Set value for each key
        for key, value in list(self._parameter.options.items()):
            if value.get('type') == STATIC:
                continue
            elif value.get('type') == SINGLE_DYNAMIC:
                new_value = self.spin_boxes.get(key).value()
                self._parameter.set_value_for_key(key, new_value)
            elif value.get('type') == MULTIPLE_DYNAMIC:
                # Need to iterate through all items
                items = []
                for index in range(self.list_widget.count()):
                    items.append(self.list_widget.item(index))
                new_value = [i.text() for i in items]
                self._parameter.set_value_for_key(key, new_value)

        # Get selected radio button
        radio_button_checked_id = self.input_button_group.checkedId()
        # No radio button checked, then default value = None
        if radio_button_checked_id == -1:
            self._parameter.selected = None
        else:
            self._parameter.selected = list(self._parameter.options.keys())[
                radio_button_checked_id]

        return self._parameter

    def update_list_widget(self):
        """Update list widget when radio button is clicked."""
        # Get selected radio button
        radio_button_checked_id = self.input_button_group.checkedId()
        # No radio button checked, then default value = None
        if radio_button_checked_id > -1:
            selected_dict = list(self._parameter.options.values())[
                radio_button_checked_id]
            if selected_dict.get('type') == MULTIPLE_DYNAMIC:
                for field in selected_dict.get('value'):
                    # Update list widget
                    field_item = QListWidgetItem(self.list_widget)
                    field_item.setFlags(
                        Qt.ItemIsEnabled
                        | Qt.ItemIsSelectable
                        | Qt.ItemIsDragEnabled)
                    field_item.setData(Qt.UserRole, field)
                    field_item.setText(field)
                    self.list_widget.addItem(field_item)

    def radio_buttons_clicked(self):
        """Handler when selected radio button changed."""
        # Disable all spin boxes
        for spin_box in list(self.spin_boxes.values()):
            spin_box.setEnabled(False)
        # Disable list widget
        self.list_widget.setEnabled(False)

        # Get selected radio button
        radio_button_checked_id = self.input_button_group.checkedId()

        if radio_button_checked_id > -1:
            selected_value = list(self._parameter.options.values())[
                radio_button_checked_id]
            if selected_value.get('type') == MULTIPLE_DYNAMIC:
                # Enable list widget
                self.list_widget.setEnabled(True)
            elif selected_value.get('type') == SINGLE_DYNAMIC:
                selected_key = list(self._parameter.options.keys())[
                    radio_button_checked_id]
                self.spin_boxes[selected_key].setEnabled(True)

    def select_radio_button(self, key):
        """Helper to select a radio button with key.

        :param key: The key of the radio button.
        :type key: str
        """
        key_index = list(self._parameter.options.keys()).index(key)
        radio_button = self.input_button_group.button(key_index)
        radio_button.click()
Example #11
0
class CheckboxesPanel(QWidget):
    def __init__(self, options, multiple, columns=2, parent=None):
        super(CheckboxesPanel, self).__init__(parent)

        self._options = []
        for i, option in enumerate(options):
            if isinstance(option, str):
                self._options.append((i, option))
            else:
                self.options.append(option)
        self._multiple = multiple
        self._buttons = []
        rows = len(options) / columns

        self._buttonGroup = QButtonGroup()
        self._buttonGroup.setExclusive(not multiple)
        layout = QGridLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setMargin(0)
        for i, (v, t) in enumerate(self._options):
            if multiple:
                button = QCheckBox(t)
            else:
                button = QRadioButton(t)
            self._buttons.append((v, button))
            self._buttonGroup.addButton(button, i)
            layout.addWidget(button, i % rows, i / rows)
        layout.addItem(
            QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Minimum), 0,
            columns)
        self.setLayout(layout)

        if multiple:
            self.setContextMenuPolicy(Qt.CustomContextMenu)
            self.customContextMenuRequested.connect(self.showPopupMenu)

    def showPopupMenu(self):
        popup_menu = QMenu()
        select_all_action = QAction(self.tr('Select All'), popup_menu)
        select_all_action.triggered.connect(self.selectAll)
        clear_all_action = QAction(self.tr('Clear Selection'), popup_menu)
        clear_all_action.triggered.connect(self.deselectAll)
        popup_menu.addAction(select_all_action)
        popup_menu.addAction(clear_all_action)
        popup_menu.exec_(QCursor.pos())

    def selectAll(self):
        for (v, button) in self._buttons:
            button.setChecked(True)

    def deselectAll(self):
        for (v, button) in self._buttons:
            button.setChecked(False)

    def value(self):
        if self._multiple:
            value = []
            for (v, checkbox) in self._buttons:
                if checkbox.isChecked():
                    value.append(v)
            return value
        else:
            return self._options[self._buttonGroup.checkedId()][0]

    def setValue(self, value):
        if self._multiple:
            for (v, button) in self._buttons:
                if v in value:
                    button.setChecked(True)
        else:
            for v, button in self._buttons:
                if v == value:
                    button.setChecked(True)