Esempio n. 1
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:
                button.setChecked(v in value)
        else:
            for v, button in self._buttons:
                button.setChecked(v == value)
Esempio n. 2
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)
Esempio n. 3
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 DefaultSelectParameterWidget(SelectParameterWidget):

    """Widget class for Default Select Parameter."""

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

        :param parameter: A DefaultSelectParameter object.
        :type parameter: DefaultSelectParameter
        """
        super(DefaultSelectParameterWidget, self).__init__(parameter, parent)

        self.default_layout = QHBoxLayout()
        self.radio_button_layout = QHBoxLayout()
        self.radio_button_widget = QWidget()

        self.default_label = QLabel(tr('Default'))

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

        # Define string enabler for radio button
        self.radio_button_enabler = self.input.itemData(0, Qt.UserRole)

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

            radio_button = QRadioButton(label)
            self.radio_button_layout.addWidget(radio_button)
            self.default_input_button_group.addButton(radio_button, i)
            if self._parameter.default_value == \
                    self._parameter.default_values[i]:
                radio_button.setChecked(True)

        # Create double spin box for custom value
        self.custom_value = QDoubleSpinBox()
        if self._parameter.default_values[-1]:
            self.custom_value.setValue(self._parameter.default_values[-1])
        has_min = False
        if self._parameter.minimum is not None:
            has_min = True
            self.custom_value.setMinimum(self._parameter.minimum)
        has_max = False
        if self._parameter.maximum is not None:
            has_max = True
            self.custom_value.setMaximum(self._parameter.maximum)
        if has_min and has_max:
            step = (self._parameter.maximum - self._parameter.minimum) / 100.0
            self.custom_value.setSingleStep(step)
        self.radio_button_layout.addWidget(self.custom_value)

        self.toggle_custom_value()

        # Reset the layout
        self.input_layout.setParent(None)
        self.help_layout.setParent(None)

        self.label.setParent(None)
        self.inner_input_layout.setParent(None)

        self.input_layout = QGridLayout()
        self.input_layout.setSpacing(0)

        self.input_layout.addWidget(self.label, 0, 0)
        self.input_layout.addLayout(self.inner_input_layout, 0, 1)
        self.input_layout.addWidget(self.default_label, 1, 0)
        self.input_layout.addLayout(self.radio_button_layout, 1, 1)

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

        # check every added combobox, it could have been toggled by
        # the existing keyword
        self.toggle_input()

        # Connect
        # noinspection PyUnresolvedReferences
        self.input.currentIndexChanged.connect(self.toggle_input)
        self.default_input_button_group.buttonClicked.connect(
            self.toggle_custom_value)

    def raise_invalid_type_exception(self):
        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 DefaultSelectParameter from the current state of widget.
        """
        current_index = self.input.currentIndex()
        selected_value = self.input.itemData(current_index, Qt.UserRole)
        if hasattr(selected_value, 'toPyObject'):
            selected_value = selected_value.toPyObject()

        try:
            self._parameter.value = selected_value
        except ValueError:
            err = self.raise_invalid_type_exception()
            raise err

        radio_button_checked_id = self.default_input_button_group.checkedId()
        # No radio button checked, then default value = None
        if radio_button_checked_id == -1:
            self._parameter.default = None
        # The last radio button (custom) is checked, get the value from the
        # line edit
        elif (radio_button_checked_id
              == len(self._parameter.default_values) - 1):
            self._parameter.default_values[radio_button_checked_id] \
                = self.custom_value.value()
            self._parameter.default = self.custom_value.value()
        else:
            self._parameter.default = self._parameter.default_values[
                radio_button_checked_id]

        return self._parameter

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

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

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

        self.toggle_custom_value()

    def toggle_custom_value(self):
        radio_button_checked_id = self.default_input_button_group.checkedId()
        if (radio_button_checked_id
                == len(self._parameter.default_values) - 1):
            self.custom_value.setDisabled(False)
        else:
            self.custom_value.setDisabled(True)

    def toggle_input(self):
        """Change behaviour of radio button based on input."""
        current_index = self.input.currentIndex()
        # If current input is not a radio button enabler, disable radio button.
        if self.input.itemData(current_index, Qt.UserRole) != (
                self.radio_button_enabler):
            self.disable_radio_button()
        # Otherwise, enable radio button.
        else:
            self.enable_radio_button()

    def set_selected_radio_button(self):
        """Set selected radio button to 'Do not report'."""
        dont_use_button = self.default_input_button_group.button(
            len(self._parameter.default_values) - 2)
        dont_use_button.setChecked(True)

    def disable_radio_button(self):
        """Disable radio button group and custom value input area."""
        checked = self.default_input_button_group.checkedButton()
        if checked:
            self.default_input_button_group.setExclusive(False)
            checked.setChecked(False)
            self.default_input_button_group.setExclusive(True)
        for button in self.default_input_button_group.buttons():
            button.setDisabled(True)
        self.custom_value.setDisabled(True)

    def enable_radio_button(self):
        """Enable radio button and custom value input area then set selected
        radio button to 'Do not report'.
        """
        for button in self.default_input_button_group.buttons():
            button.setEnabled(True)
        self.set_selected_radio_button()
        self.custom_value.setEnabled(True)
Esempio n. 5
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
Esempio n. 6
0
class DateTimePickerWidget(datepicker_widget, QWidget):
    ok = pyqtSignal()
    cancel = pyqtSignal()
    """
    A custom date picker with a time and date picker
    """

    def __init__(self, parent=None, mode="DateTime"):
        super(DateTimePickerWidget, self).__init__(parent)

        self.setupUi(self)
        self.mode = mode
        self.group = QButtonGroup()
        self.group.setExclusive(True)
        self.group.addButton(self.ambutton)
        self.group.addButton(self.pmbutton)

        self.ambutton.toggled.connect(self.isDirty)
        self.pmbutton.toggled.connect(self.isDirty)
        self.datepicker.selectionChanged.connect(self.isDirty)
        self.hourpicker.itemSelectionChanged.connect(self.isDirty)
        self.minutepicker.itemSelectionChanged.connect(self.isDirty)

        self.setasnowbutton.pressed.connect(self.setAsNow)
        self.setWindowFlags(Qt.Dialog | Qt.CustomizeWindowHint)

        self.okButton.pressed.connect(self.ok.emit)
        self.closebutton.pressed.connect(self.cancel.emit)

    def setMinValue(self, mindate):
        self.datepicker.setMinimumDate(mindate)

    def setmode(self, mode):
        if mode == "Date":
            self.timesection.hide()
        elif mode == "Time":
            self.datepicker.hide()

    def isDirty(self, *args):
        date = self.getSelectedDate()
        time = self.getSelectedTime()

        datetime = QDateTime(date, time)

        if self.mode == "Date":
            value = datetime.toString("ddd d MMM yyyy")
        elif self.mode == "Time":
            value = datetime.toString("hh:mm ap")
        else:
            value = datetime.toString("ddd d MMM yyyy 'at' hh:mm ap")

        self.label.setText("Set as: {}".format(value))

    def setDateTime(self, datetime):
        """
        Set the picker to datatime

        datetime - The QDateTime with the value to set.
        """
        self.setTime(datetime.time())
        self.setDate(datetime.date())

    def setAsNow(self):
        """
        Set the current date and time on the picker as now.
        """
        now = QDateTime.currentDateTime()
        self.setDateTime(now)

    def setTime(self, time):
        """
        Set just the time part of the picker
        """
        hour = time.hour()
        if hour > 12:
            hour = hour - 12
        if hour == 0:
            hour = hour + 12

        minute = time.minute()
        minute = int(round(minute / 5.0) * 5.0)
        amap = time.toString("AP")
        utils.log("Hour %s Minute %s" % (hour, minute))
        try:
            houritems = self.hourpicker.findItems(str(hour), Qt.MatchFixedString)
            self.hourpicker.setCurrentItem(houritems[0])
        except IndexError:
            utils.log("Can't find hour")

        try:
            minuteitems = self.minutepicker.findItems(str(minute), Qt.MatchFixedString)
            self.minutepicker.setCurrentItem(minuteitems[0])
        except IndexError:
            utils.log("Can't find minute")

        if amap == "PM":
            self.pmbutton.toggle()

    def setDate(self, date):
        """
        Set just the date part of the picker
        """
        self.datepicker.setSelectedDate(date)

    def getSelectedTime(self):
        """
        Returns the currently selected data and time
        """
        try:
            hour = self.hourpicker.currentItem().text()
        except AttributeError:
            hour = ""

        try:
            minute = self.minutepicker.currentItem().text()
        except AttributeError:
            minute = ""

        zone = self.ambutton.isChecked() and "AM" or "PM"
        return QTime.fromString("%s%s%s" % (hour.zfill(2), minute.zfill(2), zone), "hhmAP")

    def getSelectedDate(self):
        """
        Returns just the date part of the picker
        """
        return self.datepicker.selectedDate()

    @property
    def value(self):
        datetime = QDateTime()
        datetime.setDate(self.getSelectedDate())
        datetime.setTime(self.getSelectedTime())
        return datetime

    @value.setter
    def value(self, value):
        if value is None:
            self.setAsNow()
            return

        if isinstance(value, str):
            value = QDateTime.fromString(value, Qt.ISODate)

        self.setDate(value.date())
        self.setTime(value.time())
Esempio n. 7
0
class OptionWidget(EditorWidget):
    widgettype = 'Option Row'

    def __init__(self, *args, **kwargs):
        super(OptionWidget, self).__init__(*args, **kwargs)
        self._bindvalue = None
        self.group = QButtonGroup()
        self.group.buttonClicked.connect(self.emitvaluechanged)

    def createWidget(self, parent=None):
        widget = QWidget(parent)
        return widget

    def _buildfromlist(self, listconfig, multiselect):
        def chunks(l, n):
            """ Yield successive n-sized chunks from l.
            """
            for i in range(0, len(l), n):
                yield l[i:i + n]

        items = listconfig['items']
        wrap = self.config.get('wrap', 0)
        showcolor = self.config.get('always_color', False)
        if wrap > 0:
            rows = list(chunks(items, wrap))
        else:
            rows = [items]

        for rowcount, row in enumerate(rows):
            for column, item in enumerate(row):
                parts = item.split(';')
                data = parts[0]
                try:
                    desc = parts[1]
                except IndexError:
                    desc = data

                button = QPushButton()
                button.setCheckable(multiselect)
                self.group.setExclusive(not multiselect)

                icon = QIcon()
                try:
                    path = parts[2]
                    if path.startswith("#"):
                        # Colour the button with the hex value.
                        # If show color is enabled we always show the color regardless of selection.
                        if not showcolor:
                            style = """
                                QPushButton::checked {{
                                    border: 3px solid rgb(137, 175, 255);
                                    background-color: {colour};
                                }}""".format(colour=path)
                        else:
                            style = """
                                QPushButton::checked {{
                                    border: 3px solid rgb(137, 175, 255);
                                }}
                                QPushButton {{
                                    background-color: {colour};
                                }}""".format(colour=path)
                        button.setStyleSheet(style)
                    elif path.endswith("_icon"):
                        icon = QIcon(":/icons/{}".format(path))
                    else:
                        icon = QIcon(path)
                except:
                    icon = QIcon()

                button.setCheckable(True)
                button.setText(desc)
                button.setProperty("value", data)
                button.setIcon(icon)
                button.setIconSize(QSize(24, 24))
                if isinstance(self.widget.layout(), QBoxLayout):
                    self.widget.layout().addWidget(button)
                else:
                    self.widget.layout().addWidget(button, rowcount, column)
                self.group.addButton(button)

    def initWidget(self, widget, config):
        if not widget.layout():
            widget.setLayout(QGridLayout())
            widget.layout().setContentsMargins(0, 0, 0, 0)

    def updatefromconfig(self):
        super(OptionWidget, self).updatefromconfig()

        for button in self.group.buttons():
            self.group.removeButton(button)
            self.widget.layout().removeWidget(button)
            button.deleteLater()
            button.setParent(None)

        listconfig = self.config['list']
        multiselect = self.config.get('multi', False)
        self._buildfromlist(listconfig, multiselect)

        super(OptionWidget, self).endupdatefromconfig()

    def validate(self, *args):
        button = self.group.checkedButton()
        if button:
            return True

        return False

    @property
    def buttons(self):
        return self.group.buttons()

    @property
    def nullvalues(self):
        return ['NULL']

    @property
    def multioption(self):
        return self.config.get('multi', False)

    def setvalue(self, value):
        def set_button(setvalue):
            for button in self.group.buttons():
                buttonvalue = button.property("value")
                if (setvalue is None and buttonvalue in self.nullvalues) or buttonvalue == str(setvalue):
                    button.setChecked(True)
                    self.emitvaluechanged()
                    return

        if value in self.nullvalues:
            value = None

        if self.multioption and value:
            values = value.split(';')
        else:
            values = [value]

        for value in values:
            set_button(value)

    def value(self):
        def _returnvalue():
            if self.multioption:
                _values = []
                checked = [button for button in self.group.buttons() if button.isChecked()]
                for button in checked:
                    value = button.property("value")
                    _values.append(value)
                if not _values:
                    return None
                return ";".join(_values)
            else:
                checked = self.group.checkedButton()
                if not checked:
                    return None

                value = checked.property("value")
                return value

        returnvalue = _returnvalue()

        if returnvalue in self.nullvalues:
            returnvalue = None

        return returnvalue

    def reset(self):
        for button in self.group.buttons():
            if button.isChecked():
                button.setChecked(False)
                self.emitvaluechanged()
        return

    def togglebutton(self, value):
        for button in self.group.buttons():
            if button.property("value") == value:
                button.setChecked(not button.isChecked())
                self.emitvaluechanged()
                return
        return