Beispiel #1
0
class GuiPreferencesEditor(QWidget):
    def __init__(self, theParent):
        QWidget.__init__(self, theParent)

        self.mainConf = nw.CONFIG
        self.theParent = theParent
        self.theTheme = theParent.theTheme

        # The Form
        self.mainForm = QConfigLayout()
        self.mainForm.setHelpTextStyle(self.theTheme.helpText)
        self.setLayout(self.mainForm)

        # Spell Checking
        # ==============
        self.mainForm.addGroupLabel("Spell Checking")

        ## Spell Check Provider and Language
        self.spellLangList = QComboBox(self)
        self.spellToolList = QComboBox(self)
        self.spellToolList.addItem("Internal (difflib)", nwConst.SP_INTERNAL)
        self.spellToolList.addItem("Spell Enchant (pyenchant)",
                                   nwConst.SP_ENCHANT)

        theModel = self.spellToolList.model()
        idEnchant = self.spellToolList.findData(nwConst.SP_ENCHANT)
        theModel.item(idEnchant).setEnabled(self.mainConf.hasEnchant)

        self.spellToolList.currentIndexChanged.connect(self._doUpdateSpellTool)
        toolIdx = self.spellToolList.findData(self.mainConf.spellTool)
        if toolIdx != -1:
            self.spellToolList.setCurrentIndex(toolIdx)
        self._doUpdateSpellTool(0)

        self.mainForm.addRow(
            "Spell check provider", self.spellToolList,
            "Note that the internal spell check tool is quite slow.")
        self.mainForm.addRow(
            "Spell check language", self.spellLangList,
            "Available languages are determined by your system.")

        ## Big Document Size Limit
        self.bigDocLimit = QSpinBox(self)
        self.bigDocLimit.setMinimum(10)
        self.bigDocLimit.setMaximum(10000)
        self.bigDocLimit.setSingleStep(10)
        self.bigDocLimit.setValue(self.mainConf.bigDocLimit)
        self.mainForm.addRow(
            "Big document limit",
            self.bigDocLimit,
            "Full spell checking is disabled above this limit.",
            theUnit="kB")

        # Word Count
        # ==========
        self.mainForm.addGroupLabel("Word Count")

        ## Word Count Timer
        self.wordCountTimer = QDoubleSpinBox(self)
        self.wordCountTimer.setDecimals(1)
        self.wordCountTimer.setMinimum(2.0)
        self.wordCountTimer.setMaximum(600.0)
        self.wordCountTimer.setSingleStep(0.1)
        self.wordCountTimer.setValue(self.mainConf.wordCountTimer)
        self.mainForm.addRow("Word count interval",
                             self.wordCountTimer,
                             "How often the word count is updated.",
                             theUnit="seconds")

        # Writing Guides
        # ==============
        self.mainForm.addGroupLabel("Writing Guides")

        ## Show Tabs and Spaces
        self.showTabsNSpaces = QSwitch()
        self.showTabsNSpaces.setChecked(self.mainConf.showTabsNSpaces)
        self.mainForm.addRow(
            "Show tabs and spaces", self.showTabsNSpaces,
            "Add symbols to indicate tabs and spaces in the editor.")

        ## Show Line Endings
        self.showLineEndings = QSwitch()
        self.showLineEndings.setChecked(self.mainConf.showLineEndings)
        self.mainForm.addRow(
            "Show line endings", self.showLineEndings,
            "Add a symbol to indicate line endings in the editor.")

        # Scroll Behaviour
        # ================
        self.mainForm.addGroupLabel("Scroll Behaviour")

        ## Scroll Past End
        self.scrollPastEnd = QSwitch()
        self.scrollPastEnd.setChecked(self.mainConf.scrollPastEnd)
        self.mainForm.addRow(
            "Scroll past end of the document", self.scrollPastEnd,
            "Also improves trypewriter scrolling for short documents.")

        ## Typewriter Scrolling
        self.autoScroll = QSwitch()
        self.autoScroll.setChecked(self.mainConf.autoScroll)
        self.mainForm.addRow(
            "Typewriter style scrolling when you type", self.autoScroll,
            "Try to keep the cursor at a fixed vertical position.")

        ## Typewriter Position
        self.autoScrollPos = QSpinBox(self)
        self.autoScrollPos.setMinimum(10)
        self.autoScrollPos.setMaximum(90)
        self.autoScrollPos.setSingleStep(1)
        self.autoScrollPos.setValue(int(self.mainConf.autoScrollPos))
        self.mainForm.addRow("Minimum position for Typewriter scrolling",
                             self.autoScrollPos,
                             "Percentage of the editor height from the top.",
                             theUnit="%")

        return

    def saveValues(self):
        """Save the values set for this tab.
        """
        # Spell Checking
        self.mainConf.spellTool = self.spellToolList.currentData()
        self.mainConf.spellLanguage = self.spellLangList.currentData()
        self.mainConf.bigDocLimit = self.bigDocLimit.value()

        # Word Count
        self.mainConf.wordCountTimer = self.wordCountTimer.value()

        # Writing Guides
        self.mainConf.showTabsNSpaces = self.showTabsNSpaces.isChecked()
        self.mainConf.showLineEndings = self.showLineEndings.isChecked()

        # Scroll Behaviour
        self.mainConf.scrollPastEnd = self.scrollPastEnd.isChecked()
        self.mainConf.autoScroll = self.autoScroll.isChecked()
        self.mainConf.autoScrollPos = self.autoScrollPos.value()

        self.mainConf.confChanged = True

        return

    ##
    #  Internal Functions
    ##

    def _doUpdateSpellTool(self, currIdx):
        """Update the list of dictionaries based on spell tool selected.
        """
        spellTool = self.spellToolList.currentData()
        self._updateLanguageList(spellTool)
        return

    def _updateLanguageList(self, spellTool):
        """Updates the list of available spell checking dictionaries
        available for the selected spell check tool. It will try to
        preserve the language choice, if the language exists in the
        updated list.
        """
        if spellTool == nwConst.SP_ENCHANT:
            theDict = NWSpellEnchant()
        else:
            theDict = NWSpellSimple()

        self.spellLangList.clear()
        for spTag, spName in theDict.listDictionaries():
            self.spellLangList.addItem(spName, spTag)

        spellIdx = self.spellLangList.findData(self.mainConf.spellLanguage)
        if spellIdx != -1:
            self.spellLangList.setCurrentIndex(spellIdx)

        return
Beispiel #2
0
class DomainWidget(QWidget):
    tab_active = pyqtSignal()
    go_to_data_tab = pyqtSignal()

    def __init__(self, iface: QgisInterface) -> None:
        super().__init__()
        self.iface = iface

        # Import/Export
        ## Import from 'namelist.wps'
        import_from_namelist_button = QPushButton("Import from namelist")
        import_from_namelist_button.setObjectName('import_from_namelist')

        ## Export to namelist
        export_geogrid_namelist_button = QPushButton("Export to namelist")
        export_geogrid_namelist_button.setObjectName(
            'export_geogrid_namelist_button')

        vbox_import_export = QVBoxLayout()
        vbox_import_export.addWidget(import_from_namelist_button)
        vbox_import_export.addWidget(export_geogrid_namelist_button)

        self.gbox_import_export = QGroupBox("Import/Export")
        self.gbox_import_export.setLayout(vbox_import_export)

        # Group: Map Type
        self.group_box_map_type = QGroupBox("Map Type")
        vbox_map_type = QVBoxLayout()
        hbox_map_type = QHBoxLayout()

        self.projection = QComboBox()
        self.projection.setObjectName('projection')
        projs = {
            'undefined':
            '-',  # do not use a default projection - let the user pick the projection.
            'lat-lon': 'Latitude/Longitude',
            'lambert': 'Lambert'
        }
        for proj_id, proj_label in projs.items():
            self.projection.addItem(proj_label, proj_id)

        hbox_map_type.addWidget(QLabel('GCS/Projection:'))
        # TODO: when the user select the type of GCS/Projection
        # we should automatically change the GCS/Projection for the whole
        # project. This will do an on-the-fly remapping of any of the other CRS
        # which are different from the one supported by our tool/WRF.
        # The Project CRS can be accessed in QGIS under the menu `Project` > `Project Properties` > `CRS.`
        # -> This is only really possible for Lat/Lon as this one is a CRS, where the others are projections
        #    that only become a full CRS with the additional parameters like truelat1.
        # TODO: fields should be cleared when the user changes the CRS/Projection.
        hbox_map_type.addWidget(self.projection)
        vbox_map_type.addLayout(hbox_map_type)

        ## Lambert only: show 'True Latitudes' field
        truelat_grid = QGridLayout()
        self.truelat1 = add_grid_lineedit(truelat_grid,
                                          0,
                                          'True Latitude 1',
                                          LAT_VALIDATOR,
                                          unit='°',
                                          required=True)
        self.truelat2 = add_grid_lineedit(truelat_grid,
                                          1,
                                          'True Latitude 2',
                                          LAT_VALIDATOR,
                                          unit='°',
                                          required=True)
        self.widget_true_lats = QWidget()
        self.widget_true_lats.setLayout(truelat_grid)
        vbox_map_type.addWidget(self.widget_true_lats)

        self.domain_pb_set_projection = QPushButton("Set Map CRS")
        self.domain_pb_set_projection.setObjectName('set_projection_button')
        vbox_map_type.addWidget(self.domain_pb_set_projection)
        self.group_box_map_type.setLayout(vbox_map_type)

        # Group: Horizontal Resolution
        self.group_box_resol = QGroupBox("Horizontal Grid Spacing")
        hbox_resol = QHBoxLayout()
        self.resolution = MyLineEdit(required=True)
        self.resolution.setValidator(RESOLUTION_VALIDATOR)
        self.resolution.textChanged.connect(
            lambda _: update_input_validation_style(self.resolution))
        self.resolution.textChanged.emit(self.resolution.text())
        hbox_resol.addWidget(self.resolution)
        self.resolution_label = QLabel()
        hbox_resol.addWidget(self.resolution_label)

        self.group_box_resol.setLayout(hbox_resol)

        # Group: Automatic Domain Generator
        self.group_box_auto_domain = QGroupBox("Grid Extent Calculator")
        vbox_auto_domain = QVBoxLayout()
        hbox_auto_domain = QHBoxLayout()
        domain_pb_set_canvas_extent = QPushButton("Set to Canvas Extent")
        domain_pb_set_canvas_extent.setObjectName('set_canvas_extent_button')
        domain_pb_set_layer_extent = QPushButton("Set to Active Layer Extent")
        domain_pb_set_layer_extent.setObjectName('set_layer_extent_button')
        vbox_auto_domain.addLayout(hbox_auto_domain)
        vbox_auto_domain.addWidget(domain_pb_set_canvas_extent)
        vbox_auto_domain.addWidget(domain_pb_set_layer_extent)
        self.group_box_auto_domain.setLayout(vbox_auto_domain)

        # Group: Manual Domain Configuration

        ## Subgroup: Centre Point
        grid_center_point = QGridLayout()
        self.center_lon = add_grid_lineedit(grid_center_point,
                                            0,
                                            'Longitude',
                                            LON_VALIDATOR,
                                            '°',
                                            required=True)
        self.center_lat = add_grid_lineedit(grid_center_point,
                                            1,
                                            'Latitude',
                                            LAT_VALIDATOR,
                                            '°',
                                            required=True)
        group_box_centre_point = QGroupBox("Center Point")
        group_box_centre_point.setLayout(grid_center_point)

        ## Subgroup: Advanced configuration
        grid_dims = QGridLayout()
        self.cols = add_grid_lineedit(grid_dims,
                                      0,
                                      'Horizontal',
                                      DIM_VALIDATOR,
                                      required=True)
        self.rows = add_grid_lineedit(grid_dims,
                                      1,
                                      'Vertical',
                                      DIM_VALIDATOR,
                                      required=True)
        group_box_dims = QGroupBox("Grid Extent")
        group_box_dims.setLayout(grid_dims)

        vbox_manual_domain = QVBoxLayout()
        vbox_manual_domain.addWidget(group_box_centre_point)
        vbox_manual_domain.addWidget(group_box_dims)

        self.group_box_manual_domain = QGroupBox("Advanced Configuration")
        # TODO: make this section collapsable (default state collapsed)
        # and change the checkbox to arrows like `setArrowType(Qt.RightArrow)`
        # TODO: the style should be disabled when the 'advanced configuration' box is disabled (default).
        self.group_box_manual_domain.setCheckable(True)
        self.group_box_manual_domain.setChecked(False)
        self.group_box_manual_domain.setLayout(vbox_manual_domain)

        for field in [
                self.resolution, self.center_lat, self.center_lon, self.rows,
                self.cols, self.truelat1, self.truelat2
        ]:
            # editingFinished is only emitted on user input, not via programmatic changes.
            # This is important as we want to avoid re-drawing the bbox many times when several
            # fields get changed while using the automatic domain generator.
            field.editingFinished.connect(self.on_change_any_field)

        # Group Box: Parent Domain
        self.group_box_parent_domain = QGroupBox("Enable Parenting")
        self.group_box_parent_domain.setObjectName('group_box_parent_domain')
        self.group_box_parent_domain.setCheckable(True)
        self.group_box_parent_domain.setChecked(False)

        # TODO: As it is for the single domain case the generation of the domain should be automatic.
        # For now leave placeholder values of '3' for Child to Parent Ratio and '2' for padding.
        # use `calc_parent_lonlat_from_child` in `routines.py` to calculate the coordinate of the domain given the child domain coordinate,
        # grid_ratio and padding.
        hbox_parent_num = QHBoxLayout()
        hbox_parent_num.addWidget(QLabel('Number of Parent Domains:'))
        self.parent_spin = QSpinBox()
        self.parent_spin.setObjectName('parent_spin')
        self.parent_spin.setRange(1, MAX_PARENTS)
        hbox_parent_num.addWidget(self.parent_spin)

        self.group_box_parent_domain.setLayout(hbox_parent_num)

        self.parent_domains = []  # type: list
        self.parent_vbox = QVBoxLayout()
        self.parent_vbox.setSizeConstraint(QLayout.SetMinimumSize)

        go_to_data_tab_btn = QPushButton('Continue to Datasets')
        go_to_data_tab_btn.clicked.connect(self.go_to_data_tab)

        # Tabs
        dom_mgr_layout = QVBoxLayout()
        dom_mgr_layout.addWidget(self.gbox_import_export)
        dom_mgr_layout.addWidget(self.group_box_map_type)
        dom_mgr_layout.addWidget(self.group_box_resol)
        dom_mgr_layout.addWidget(self.group_box_auto_domain)
        dom_mgr_layout.addWidget(self.group_box_manual_domain)
        dom_mgr_layout.addWidget(self.group_box_parent_domain)
        dom_mgr_layout.addLayout(self.parent_vbox)
        dom_mgr_layout.addWidget(go_to_data_tab_btn)
        self.setLayout(dom_mgr_layout)

        QMetaObject.connectSlotsByName(self)

        # trigger event for initial layout
        self.projection.currentIndexChanged.emit(
            self.projection.currentIndex())

    @property
    def project(self) -> Project:
        return self._project

    @project.setter
    def project(self, val: Project) -> None:
        ''' Sets the currently active project. See tab_simulation. '''
        self._project = val
        self.populate_ui_from_project()

    def populate_ui_from_project(self) -> None:
        project = self.project
        try:
            domains = project.data['domains']
        except KeyError:
            return

        main_domain = domains[0]

        idx = self.projection.findData(main_domain['map_proj'])
        self.projection.setCurrentIndex(idx)
        try:
            truelat1 = main_domain['truelat1']
            self.truelat1.set_value(truelat1)

            truelat2 = main_domain['truelat2']
            self.truelat2.set_value(truelat2)
        except KeyError:
            pass

        self.resolution.set_value(main_domain['cell_size'][0])

        lon, lat = main_domain['center_lonlat']
        self.center_lat.set_value(lat)
        self.center_lon.set_value(lon)

        cols, rows = main_domain['domain_size']
        self.rows.set_value(rows)
        self.cols.set_value(cols)

        if len(domains) > 1:
            self.group_box_parent_domain.setChecked(True)
            self.parent_spin.setValue(len(domains) - 1)
            # We call the signal handler explicitly as we need the widgets ready immediately
            # and otherwise this is delayed until the signals are processed (queueing etc.).
            self.on_parent_spin_valueChanged(len(domains) - 1)

            for idx, parent_domain in enumerate(domains[1:]):
                fields, _ = self.parent_domains[idx]
                fields = fields['inputs']

                field_to_key = {
                    'ratio': 'parent_cell_size_ratio',
                    'top': 'padding_top',
                    'left': 'padding_left',
                    'right': 'padding_right',
                    'bottom': 'padding_bottom'
                }

                for field_name, key in field_to_key.items():
                    field = fields[field_name]
                    val = parent_domain[key]
                    field.set_value(val)

        self.draw_bbox_and_grids(zoom_out=True)

    @pyqtSlot()
    def on_export_geogrid_namelist_button_clicked(self):
        if not self.update_project():
            raise ValueError('Input invalid, check fields')
        file_path, _ = QFileDialog.getSaveFileName(caption='Save wps namelist as', \
                                                   directory='namelist.wps')
        if not file_path:
            return
        wps_namelist = convert_project_to_wps_namelist(self.project)
        write_namelist(wps_namelist, file_path)

    @pyqtSlot()
    def on_set_projection_button_clicked(self):
        crs = self.create_domain_crs()

        qgsProject = QgsProject.instance()  # type: QgsProject
        qgsProject.setCrs(get_qgis_crs(crs.proj4))

    def create_domain_crs(self) -> CRS:
        proj = self.get_proj_kwargs()
        if proj is None:
            raise ValueError('Incomplete projection definition')

        origin_valid = all(
            map(lambda w: w.is_valid(), [self.center_lat, self.center_lon]))
        if origin_valid:
            origin = LonLat(self.center_lon.value(), self.center_lat.value())
        else:
            origin = LonLat(0, 0)

        if proj['map_proj'] == 'lambert':
            crs = CRS.create_lambert(proj['truelat1'], proj['truelat2'],
                                     origin)
        elif proj['map_proj'] == 'lat-lon':
            crs = CRS.create_lonlat()
        else:
            raise NotImplementedError('unknown proj: ' + proj['map_proj'])
        return crs

    @pyqtSlot()
    def on_set_canvas_extent_button_clicked(self):
        if not self.resolution.is_valid():
            return
        canvas = self.iface.mapCanvas()  # type: QgsMapCanvas

        settings = canvas.mapSettings()  # type: QgsMapSettings
        map_crs = settings.destinationCrs(
        )  # type: QgsCoordinateReferenceSystem

        extent = canvas.extent()  # type: QgsRectangle
        self.set_domain_to_extent(map_crs, extent)

    @pyqtSlot()
    def on_set_layer_extent_button_clicked(self):
        if not self.resolution.is_valid():
            return
        layer = self.iface.activeLayer()  # type: QgsMapLayer

        layer_crs = layer.crs()  # type: QgsCoordinateReferenceSystem

        extent = layer.extent()  # type: QgsRectangle
        self.set_domain_to_extent(layer_crs, extent)

    def set_domain_to_extent(self, crs: QgsCoordinateReferenceSystem,
                             extent: QgsRectangle) -> None:
        resolution = self.resolution.value()

        bbox = rect_to_bbox(extent)

        extent_crs = CRS(crs.toProj4())
        domain_crs = self.create_domain_crs()
        domain_srs = domain_crs.srs

        domain_bbox = domain_crs.transform_bbox(bbox, domain_srs)

        # TODO disallow creation of bounding box outside projection range (e.g. for lat-lon 360-180)

        xmin, xmax, ymin, ymax = domain_bbox.minx, domain_bbox.maxx, domain_bbox.miny, domain_bbox.maxy

        center_x = xmin + (xmax - xmin) / 2
        center_y = ymin + (ymax - ymin) / 2
        center_lonlat = domain_crs.to_lonlat(Coordinate2D(center_x, center_y))
        self.center_lat.set_value(center_lonlat.lat)
        self.center_lon.set_value(center_lonlat.lon)
        self.resolution.set_value(resolution)
        cols = ceil((xmax - xmin) / resolution)
        rows = ceil((ymax - ymin) / resolution)
        self.cols.set_value(cols)
        self.rows.set_value(rows)

        self.on_change_any_field(zoom_out=True)

    @pyqtSlot()
    def on_group_box_parent_domain_clicked(self):
        if self.group_box_parent_domain.isChecked():
            self.add_parent_domain()
        else:
            self.parent_spin.setValue(1)
            while self.parent_domains:
                self.remove_last_parent_domain()

    def add_parent_domain(self):
        idx = len(self.parent_domains) + 1
        fields, group_box_parent = create_parent_group_box('Parent ' +
                                                           str(idx),
                                                           '?',
                                                           self.proj_res_unit,
                                                           required=True)
        self.parent_vbox.addWidget(group_box_parent)
        # "If you add a child widget to an already visible widget you must
        #  explicitly show the child to make it visible."
        # (http://doc.qt.io/qt-5/qwidget.html#QWidget)
        group_box_parent.show()
        self.parent_domains.append((fields, group_box_parent))
        # After adding/removing widgets, we need to tell Qt to recompute the sizes.
        # This always has to be done on the widget where the child widgets have been changed,
        # here self.subtab_parenting (which contains self.parent_vbox).
        self.adjustSize()

        for field in fields['inputs'].values():
            field.editingFinished.connect(self.on_change_any_field)

    def remove_last_parent_domain(self):
        _, group_box_parent = self.parent_domains.pop()
        group_box_parent.deleteLater()
        self.parent_vbox.removeWidget(group_box_parent)
        self.on_change_any_field()

    @pyqtSlot(int)
    def on_parent_spin_valueChanged(self, value: int) -> None:
        count = len(self.parent_domains)
        for _ in range(value, count):
            self.remove_last_parent_domain()
        for _ in range(count, value):
            self.add_parent_domain()

    @pyqtSlot(int)
    def on_projection_currentIndexChanged(self, index: int) -> None:
        proj_id = self.projection.currentData()
        is_lambert = proj_id == 'lambert'
        is_undefined = proj_id == 'undefined'
        is_lat_lon = proj_id == 'lat-lon'

        self.group_box_resol.setDisabled(is_undefined)
        self.group_box_auto_domain.setDisabled(is_undefined)
        self.group_box_manual_domain.setDisabled(is_undefined)
        self.group_box_parent_domain.setDisabled(is_undefined)

        self.widget_true_lats.setVisible(is_lambert)

        if is_undefined:
            self.proj_res_unit = ''
        elif is_lat_lon:
            self.proj_res_unit = '°'
        elif is_lambert:
            self.proj_res_unit = 'm'
        self.resolution_label.setText(self.proj_res_unit)

        # If the projection is changed the parent domains are removed
        self.group_box_parent_domain.setChecked(False)
        for _ in self.parent_domains:
            self.remove_last_parent_domain()

        self.adjustSize()

    def get_proj_kwargs(self) -> dict:
        proj_id = self.projection.currentData()
        kwargs = {'map_proj': proj_id}
        if proj_id == 'lambert':
            valid = all(
                map(lambda w: w.is_valid(), [self.truelat1, self.truelat2]))
            if not valid:
                return None
            kwargs = {
                'map_proj': proj_id,
                'truelat1': self.truelat1.value(),
                'truelat2': self.truelat2.value(),
            }
        return kwargs

    def update_project(self) -> bool:
        proj_kwargs = self.get_proj_kwargs()
        if proj_kwargs is None:
            return False

        valid = all(
            map(lambda w: w.is_valid(), [
                self.center_lat, self.center_lon, self.resolution, self.cols,
                self.rows
            ]))
        if not valid:
            return False
        center_lonlat = LonLat(lon=self.center_lon.value(),
                               lat=self.center_lat.value())
        resolution = self.resolution.value()
        domain_size = (self.cols.value(), self.rows.value())

        parent_domains = []

        for parent_domain in self.parent_domains:
            fields, _ = parent_domain
            inputs = fields['inputs']
            valid = all(map(lambda w: w.is_valid(), inputs.values()))
            if not valid:
                return False
            ratio, top, left, right, bottom = [
                inputs[name].value()
                for name in ['ratio', 'top', 'left', 'right', 'bottom']
            ]

            parent_domains.append({
                'parent_cell_size_ratio': ratio,
                'padding_left': left,
                'padding_right': right,
                'padding_bottom': bottom,
                'padding_top': top
            })

        self.project.set_domains(cell_size=(resolution, resolution),
                                 domain_size=domain_size,
                                 center_lonlat=center_lonlat,
                                 parent_domains=parent_domains,
                                 **proj_kwargs)
        return True

    def on_change_any_field(self, zoom_out=False):
        if not self.update_project():
            return

        domains = self.project.data['domains']

        # update main domain size as it may have been adjusted
        main_domain_size = domains[0]['domain_size']
        self.cols.set_value(main_domain_size[0])
        self.rows.set_value(main_domain_size[1])

        for (fields, _), domain in zip(self.parent_domains, domains[1:]):
            # update the parent resolutions
            res_label = fields['other']['resolution']
            res_label.setText(
                HORIZONTAL_RESOLUTION_LABEL.format(
                    resolution=domain['cell_size'][0],
                    unit=self.proj_res_unit))

            # update any padding as it may have been adjusted
            for name in ['left', 'right', 'top', 'bottom']:
                field = fields['inputs'][name]
                field.set_value(domain['padding_' + name])

        self.draw_bbox_and_grids(zoom_out)

    def draw_bbox_and_grids(self, zoom_out: bool) -> None:
        project = self.project

        update_domain_grid_layers(project)
        update_domain_outline_layers(self.iface.mapCanvas(),
                                     project,
                                     zoom_out=zoom_out)
Beispiel #3
0
    def setupUI(self, param_type, param_dict, form):
        """
        Build UI elements using parameters dict of scikit models

            # Attributes:
                param_type: String, type of param to update
                param_dict: dict, dictionary of parameter/default values from model.
                default_params: dict, dictionary of default parameters defined by model spec json.
                form: QtWidget.Groupbox, container to hold UI structs
        """
        try:
            for k, v in param_dict.items():
                label_string = k
                label = QLabel(label_string)
                val_type = v['type']
                if val_type == 'dropdown':
                    input_field = QComboBox(objectName=k)
                    for name, value in v['options'].items():
                        input_field.addItem(name, value)
                    idx = input_field.findData(v['default'])
                    if idx != -1:
                        input_field.setCurrentIndex(idx)
                    input_field.currentIndexChanged.connect(
                        lambda state, x=k, y=input_field: self._update_param(
                            param_type, x, y.currentData()))

                elif val_type == 'double':
                    input_field = QDoubleSpinBox(objectName=k)
                    input_field.setDecimals(v['decimal_len'])
                    input_field.setRange(v['min'], v['max'])
                    if v['default'] is not None:
                        input_field.setValue(v['default'])
                    input_field.setSingleStep(v['step_size'])
                    input_field.valueChanged.connect(
                        lambda state, x=k, y=input_field: self._update_param(
                            param_type, x, y.value()))

                elif val_type == 'int':
                    input_field = QSpinBox(objectName=k)
                    input_field.setRange(v['min'], v['max'])
                    if v['default'] is not None:
                        input_field.setValue(v['default'])
                    input_field.setSingleStep(v['step_size'])
                    input_field.valueChanged.connect(
                        lambda state, x=k, y=input_field: self._update_param(
                            param_type, x, y.value()))
                elif val_type == 'range':
                    label_string = k
                    label = QLabel(label_string + ' : 1,')
                    input_field = QSpinBox(objectName=k)
                    input_field.setRange(v['min'], v['max'])
                    if v['default'] is not None:
                        input_field.setValue(v['default'][-1])
                    input_field.valueChanged.connect(
                        lambda state, x=k, y=input_field: self._update_param(
                            param_type, x, [1, y.value()]))
                elif val_type == 'static':
                    label_string = k
                    input_field = QLineEdit(objectName=k)
                    input_field.setText(str(v['default']))
                    # input_field.textColor(QColor.red())
                    input_field.setEnabled(False)
                    self._update_param(param_type, k, v['default'])
                if v['tooltip'] is not None:
                    input_field.setToolTip(v['tooltip'])
                form.addRow(label, input_field)
                self.input_widgets[k] = input_field
        except Exception as e:
            self.logger.error("Error generating {} dialog.", exc_info=True)
            print("Exception {} occured with key {} and value {}".format(
                e, k, v))
            tb = traceback.format_exc()
            print(tb)
class SerialPortSelector(QWidget):
    serialport_combobox = None
    settings = None

    DARWIN_SERIAL_PORT_PATH = "/dev/tty.Orksokopter-DevB"

    EMULATOR = "__EMULATOR__"

    accepted = pyqtSignal(['QString'])
    rejected = pyqtSignal()

    def __init__(self, settings, parent=None):
        super().__init__(parent)

        self.setWindowTitle('Seriellen Port wählen')
        self.setWindowIcon(QIcon(':/icons/glyph-router'))

        self.serialport_combobox = QComboBox()
        self.serialport_combobox.setEditable(True)

        buttonbox = QDialogButtonBox(self)
        buttonbox.setStandardButtons(QDialogButtonBox.Ok |
                                     QDialogButtonBox.Close)

        layout = QVBoxLayout()
        layout.addWidget(self.serialport_combobox)
        layout.addWidget(buttonbox)

        self.setLayout(layout)
        buttonbox.rejected.connect(self.reject)
        buttonbox.accepted.connect(self.accept)

        self.settings = settings

        comports = list(list_ports.comports())

        if sys.platform.lower() == "darwin":
            if os.path.exists(self.DARWIN_SERIAL_PORT_PATH):
                comports.append((self.DARWIN_SERIAL_PORT_PATH,
                                 "Orksokopter-DevB"))

        comports.append((self.EMULATOR, "Emulator"))

        if comports:
            for port in comports:
                self.serialport_combobox.addItem(port[1], port[0])

        if self.settings.value('last_selected_com_port'):
            index = self.serialport_combobox.findData(
                self.settings.value('last_selected_com_port')
            )

            if index != -1:
                self.serialport_combobox.setCurrentIndex(index)

    def accept(self):
        self.settings.setValue('last_selected_com_port',
                               self.get_selected_serial_port())
        self.accepted.emit(self.get_selected_serial_port())

    def reject(self):
        self.rejected.emit()

    def get_selected_serial_port(self):
        selected_item_data = self.serialport_combobox.itemData(
            self.serialport_combobox.currentIndex()
        )

        if selected_item_data:
            return selected_item_data
        else:
            return self.serialport_combobox.currentText()
Beispiel #5
0
class GuiPreferencesGeneral(QWidget):
    def __init__(self, theParent):
        QWidget.__init__(self, theParent)

        self.mainConf = novelwriter.CONFIG
        self.theParent = theParent
        self.theTheme = theParent.theTheme

        # The Form
        self.mainForm = QConfigLayout()
        self.mainForm.setHelpTextStyle(self.theTheme.helpText)
        self.setLayout(self.mainForm)

        # Look and Feel
        # =============
        self.mainForm.addGroupLabel(self.tr("Look and Feel"))
        minWidth = self.mainConf.pxInt(200)

        # Select Locale
        self.guiLang = QComboBox()
        self.guiLang.setMinimumWidth(minWidth)
        theLangs = self.mainConf.listLanguages(self.mainConf.LANG_NW)
        for lang, langName in theLangs:
            self.guiLang.addItem(langName, lang)
        langIdx = self.guiLang.findData(self.mainConf.guiLang)
        if langIdx != -1:
            self.guiLang.setCurrentIndex(langIdx)

        self.mainForm.addRow(self.tr("Main GUI language"), self.guiLang,
                             self.tr("Requires restart."))

        # Select Theme
        self.guiTheme = QComboBox()
        self.guiTheme.setMinimumWidth(minWidth)
        self.theThemes = self.theTheme.listThemes()
        for themeDir, themeName in self.theThemes:
            self.guiTheme.addItem(themeName, themeDir)
        themeIdx = self.guiTheme.findData(self.mainConf.guiTheme)
        if themeIdx != -1:
            self.guiTheme.setCurrentIndex(themeIdx)

        self.mainForm.addRow(self.tr("Main GUI theme"), self.guiTheme,
                             self.tr("Requires restart."))

        # Select Icon Theme
        self.guiIcons = QComboBox()
        self.guiIcons.setMinimumWidth(minWidth)
        self.theIcons = self.theTheme.theIcons.listThemes()
        for iconDir, iconName in self.theIcons:
            self.guiIcons.addItem(iconName, iconDir)
        iconIdx = self.guiIcons.findData(self.mainConf.guiIcons)
        if iconIdx != -1:
            self.guiIcons.setCurrentIndex(iconIdx)

        self.mainForm.addRow(self.tr("Main icon theme"), self.guiIcons,
                             self.tr("Requires restart."))

        # Font Family
        self.guiFont = QLineEdit()
        self.guiFont.setReadOnly(True)
        self.guiFont.setFixedWidth(self.mainConf.pxInt(162))
        self.guiFont.setText(self.mainConf.guiFont)
        self.fontButton = QPushButton("...")
        self.fontButton.setMaximumWidth(
            int(2.5 * self.theTheme.getTextWidth("...")))
        self.fontButton.clicked.connect(self._selectFont)
        self.mainForm.addRow(self.tr("Font family"),
                             self.guiFont,
                             self.tr("Requires restart."),
                             theButton=self.fontButton)

        # Font Size
        self.guiFontSize = QSpinBox(self)
        self.guiFontSize.setMinimum(8)
        self.guiFontSize.setMaximum(60)
        self.guiFontSize.setSingleStep(1)
        self.guiFontSize.setValue(self.mainConf.guiFontSize)
        self.mainForm.addRow(self.tr("Font size"),
                             self.guiFontSize,
                             self.tr("Requires restart."),
                             theUnit=self.tr("pt"))

        # GUI Settings
        # ============
        self.mainForm.addGroupLabel(self.tr("GUI Settings"))

        self.emphLabels = QSwitch()
        self.emphLabels.setChecked(self.mainConf.emphLabels)
        self.mainForm.addRow(
            self.tr("Emphasise partition and chapter labels"),
            self.emphLabels,
            self.tr("Makes them stand out in the project tree."),
        )

        self.showFullPath = QSwitch()
        self.showFullPath.setChecked(self.mainConf.showFullPath)
        self.mainForm.addRow(
            self.tr("Show full path in document header"), self.showFullPath,
            self.tr("Add the parent folder names to the header."))

        self.hideVScroll = QSwitch()
        self.hideVScroll.setChecked(self.mainConf.hideVScroll)
        self.mainForm.addRow(
            self.tr("Hide vertical scroll bars in main windows"),
            self.hideVScroll,
            self.tr("Scrolling available with mouse wheel and keys only."))

        self.hideHScroll = QSwitch()
        self.hideHScroll.setChecked(self.mainConf.hideHScroll)
        self.mainForm.addRow(
            self.tr("Hide horizontal scroll bars in main windows"),
            self.hideHScroll,
            self.tr("Scrolling available with mouse wheel and keys only."))

        return

    def saveValues(self):
        """Save the values set for this tab.
        """
        guiLang = self.guiLang.currentData()
        guiTheme = self.guiTheme.currentData()
        guiIcons = self.guiIcons.currentData()
        guiFont = self.guiFont.text()
        guiFontSize = self.guiFontSize.value()
        emphLabels = self.emphLabels.isChecked()

        # Check if restart is needed
        needsRestart = False
        needsRestart |= self.mainConf.guiLang != guiLang
        needsRestart |= self.mainConf.guiTheme != guiTheme
        needsRestart |= self.mainConf.guiIcons != guiIcons
        needsRestart |= self.mainConf.guiFont != guiFont
        needsRestart |= self.mainConf.guiFontSize != guiFontSize

        # Check if refreshing project tree is needed
        refreshTree = False
        refreshTree |= self.mainConf.emphLabels != emphLabels

        self.mainConf.guiLang = guiLang
        self.mainConf.guiTheme = guiTheme
        self.mainConf.guiIcons = guiIcons
        self.mainConf.guiFont = guiFont
        self.mainConf.guiFontSize = guiFontSize
        self.mainConf.emphLabels = emphLabels
        self.mainConf.showFullPath = self.showFullPath.isChecked()
        self.mainConf.hideVScroll = self.hideVScroll.isChecked()
        self.mainConf.hideHScroll = self.hideHScroll.isChecked()

        self.mainConf.confChanged = True

        return needsRestart, refreshTree

    ##
    #  Slots
    ##

    def _selectFont(self):
        """Open the QFontDialog and set a font for the font style.
        """
        currFont = QFont()
        currFont.setFamily(self.mainConf.guiFont)
        currFont.setPointSize(self.mainConf.guiFontSize)
        theFont, theStatus = QFontDialog.getFont(currFont, self)
        if theStatus:
            self.guiFont.setText(theFont.family())
            self.guiFontSize.setValue(theFont.pointSize())
        return
    def __init__(self, parent: 'ElectrumWindow', config: 'SimpleConfig'):
        WindowModalDialog.__init__(self, parent, _('Preferences'))
        self.config = config
        self.window = parent
        self.need_restart = False
        self.fx = self.window.fx
        self.wallet = self.window.wallet

        vbox = QVBoxLayout()
        tabs = QTabWidget()
        gui_widgets = []
        tx_widgets = []
        oa_widgets = []

        # language
        lang_help = _(
            'Select which language is used in the GUI (after restart).')
        lang_label = HelpLabel(_('Language') + ':', lang_help)
        lang_combo = QComboBox()
        lang_combo.addItems(list(languages.values()))
        lang_keys = list(languages.keys())
        lang_cur_setting = self.config.get("language", '')
        try:
            index = lang_keys.index(lang_cur_setting)
        except ValueError:  # not in list
            index = 0
        lang_combo.setCurrentIndex(index)
        if not self.config.is_modifiable('language'):
            for w in [lang_combo, lang_label]:
                w.setEnabled(False)

        def on_lang(x):
            lang_request = list(languages.keys())[lang_combo.currentIndex()]
            if lang_request != self.config.get('language'):
                self.config.set_key("language", lang_request, True)
                self.need_restart = True

        lang_combo.currentIndexChanged.connect(on_lang)
        gui_widgets.append((lang_label, lang_combo))

        nz_help = _(
            'Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"'
        )
        nz_label = HelpLabel(_('Zeros after decimal point') + ':', nz_help)
        nz = QSpinBox()
        nz.setMinimum(0)
        nz.setMaximum(self.config.decimal_point)
        nz.setValue(self.config.num_zeros)
        if not self.config.is_modifiable('num_zeros'):
            for w in [nz, nz_label]:
                w.setEnabled(False)

        def on_nz():
            value = nz.value()
            if self.config.num_zeros != value:
                self.config.num_zeros = value
                self.config.set_key('num_zeros', value, True)
                self.window.history_list.update()
                self.window.address_list.update()

        nz.valueChanged.connect(on_nz)
        gui_widgets.append((nz_label, nz))

        use_rbf = bool(self.config.get('use_rbf', True))
        use_rbf_cb = QCheckBox(_('Use Replace-By-Fee'))
        use_rbf_cb.setChecked(use_rbf)
        use_rbf_cb.setToolTip(
            _('If you check this box, your transactions will be marked as non-final,') + '\n' + \
            _('and you will have the possibility, while they are unconfirmed, to replace them with transactions that pay higher fees.') + '\n' + \
            _('Note that some merchants do not accept non-final transactions until they are confirmed.'))

        def on_use_rbf(x):
            self.config.set_key('use_rbf', bool(x))
            batch_rbf_cb.setEnabled(bool(x))

        use_rbf_cb.stateChanged.connect(on_use_rbf)
        tx_widgets.append((use_rbf_cb, None))

        batch_rbf_cb = QCheckBox(_('Batch RBF transactions'))
        batch_rbf_cb.setChecked(bool(self.config.get('batch_rbf', False)))
        batch_rbf_cb.setEnabled(use_rbf)
        batch_rbf_cb.setToolTip(
            _('If you check this box, your unconfirmed transactions will be consolidated into a single transaction.') + '\n' + \
            _('This will save fees.'))

        def on_batch_rbf(x):
            self.config.set_key('batch_rbf', bool(x))

        batch_rbf_cb.stateChanged.connect(on_batch_rbf)
        tx_widgets.append((batch_rbf_cb, None))

        # lightning
        lightning_widgets = []

        help_local_wt = _("""If this option is checked, Electrum will
run a local watchtower and protect your channels even if your wallet is not
open. For this to work, your computer needs to be online regularly.""")
        local_wt_cb = QCheckBox(_("Run a local watchtower"))
        local_wt_cb.setToolTip(help_local_wt)
        local_wt_cb.setChecked(
            bool(self.config.get('run_local_watchtower', False)))

        def on_local_wt_checked(x):
            self.config.set_key('run_local_watchtower', bool(x))

        local_wt_cb.stateChanged.connect(on_local_wt_checked)
        lightning_widgets.append((local_wt_cb, None))

        help_persist = _(
            """If this option is checked, Electrum will persist after
you close all your wallet windows, and the Electrum icon will be visible in the taskbar.
Use this if you want your local watchtower to keep running after you close your wallet."""
        )
        persist_cb = QCheckBox(_("Persist after all windows are closed"))
        persist_cb.setToolTip(help_persist)
        persist_cb.setChecked(bool(self.config.get('persist_daemon', False)))

        def on_persist_checked(x):
            self.config.set_key('persist_daemon', bool(x))

        persist_cb.stateChanged.connect(on_persist_checked)
        lightning_widgets.append((persist_cb, None))

        help_remote_wt = _(
            """To use a remote watchtower, enter the corresponding URL here""")
        remote_wt_cb = QCheckBox(_("Use a remote watchtower"))
        remote_wt_cb.setToolTip(help_remote_wt)
        remote_wt_cb.setChecked(bool(self.config.get('use_watchtower', False)))

        def on_remote_wt_checked(x):
            self.config.set_key('use_watchtower', bool(x))
            self.watchtower_url_e.setEnabled(bool(x))

        remote_wt_cb.stateChanged.connect(on_remote_wt_checked)
        watchtower_url = self.config.get('watchtower_url')
        self.watchtower_url_e = QLineEdit(watchtower_url)
        self.watchtower_url_e.setEnabled(
            self.config.get('use_watchtower', False))

        def on_wt_url():
            url = self.watchtower_url_e.text() or None
            watchtower_url = self.config.set_key('watchtower_url', url)

        self.watchtower_url_e.editingFinished.connect(on_wt_url)
        lightning_widgets.append((remote_wt_cb, self.watchtower_url_e))

        msg = _('OpenAlias record, used to receive coins and to sign payment requests.') + '\n\n'\
              + _('The following alias providers are available:') + '\n'\
              + '\n'.join(['https://cryptoname.co/', 'http://xmr.link']) + '\n\n'\
              + 'For more information, see https://openalias.org'
        alias_label = HelpLabel(_('OpenAlias') + ':', msg)
        alias = self.config.get('alias', '')
        self.alias_e = QLineEdit(alias)
        self.set_alias_color()
        self.alias_e.editingFinished.connect(self.on_alias_edit)
        oa_widgets.append((alias_label, self.alias_e))

        # units
        units = base_units_list
        msg = (
            _('Base unit of your wallet.') +
            '\n1 QTUM = 1000 mQTUM. 1 mQTUM = 1000 bits. 1 bit = 100 sat.\n' +
            _('This setting affects the Send tab, and all balance related fields.'
              ))
        unit_label = HelpLabel(_('Base unit') + ':', msg)
        unit_combo = QComboBox()
        unit_combo.addItems(units)
        unit_combo.setCurrentIndex(units.index(self.window.base_unit()))

        def on_unit(x, nz):
            unit_result = units[unit_combo.currentIndex()]
            if self.window.base_unit() == unit_result:
                return
            edits = self.window.amount_e, self.window.receive_amount_e
            amounts = [edit.get_amount() for edit in edits]
            self.config.set_base_unit(unit_result)
            nz.setMaximum(self.config.decimal_point)
            self.window.history_list.update()
            self.window.request_list.update()
            self.window.address_list.update()
            for edit, amount in zip(edits, amounts):
                edit.setAmount(amount)
            self.window.update_status()

        unit_combo.currentIndexChanged.connect(lambda x: on_unit(x, nz))
        gui_widgets.append((unit_label, unit_combo))

        system_cameras = qrscanner._find_system_cameras()
        qr_combo = QComboBox()
        qr_combo.addItem("Default", "default")
        for camera, device in system_cameras.items():
            qr_combo.addItem(camera, device)
        #combo.addItem("Manually specify a device", config.get("video_device"))
        index = qr_combo.findData(self.config.get("video_device"))
        qr_combo.setCurrentIndex(index)
        msg = _("Install the zbar package to enable this.")
        qr_label = HelpLabel(_('Video Device') + ':', msg)
        qr_combo.setEnabled(qrscanner.libzbar is not None)
        on_video_device = lambda x: self.config.set_key(
            "video_device", qr_combo.itemData(x), True)
        qr_combo.currentIndexChanged.connect(on_video_device)
        gui_widgets.append((qr_label, qr_combo))

        colortheme_combo = QComboBox()
        colortheme_combo.addItem(_('Light'), 'default')
        colortheme_combo.addItem(_('Dark'), 'dark')
        index = colortheme_combo.findData(
            self.config.get('qt_gui_color_theme', 'default'))
        colortheme_combo.setCurrentIndex(index)
        colortheme_label = QLabel(_('Color theme') + ':')

        def on_colortheme(x):
            self.config.set_key('qt_gui_color_theme',
                                colortheme_combo.itemData(x), True)
            self.need_restart = True

        colortheme_combo.currentIndexChanged.connect(on_colortheme)
        gui_widgets.append((colortheme_label, colortheme_combo))

        updatecheck_cb = QCheckBox(
            _("Automatically check for software updates"))
        updatecheck_cb.setChecked(bool(self.config.get('check_updates',
                                                       False)))

        def on_set_updatecheck(v):
            self.config.set_key('check_updates', v == Qt.Checked, save=True)

        updatecheck_cb.stateChanged.connect(on_set_updatecheck)
        gui_widgets.append((updatecheck_cb, None))

        filelogging_cb = QCheckBox(_("Write logs to file"))
        filelogging_cb.setChecked(bool(self.config.get('log_to_file', False)))

        def on_set_filelogging(v):
            self.config.set_key('log_to_file', v == Qt.Checked, save=True)
            self.need_restart = True

        filelogging_cb.stateChanged.connect(on_set_filelogging)
        filelogging_cb.setToolTip(
            _('Debug logs can be persisted to disk. These are useful for troubleshooting.'
              ))
        gui_widgets.append((filelogging_cb, None))

        preview_cb = QCheckBox(_('Advanced preview'))
        preview_cb.setChecked(bool(self.config.get('advanced_preview', False)))
        preview_cb.setToolTip(
            _("Open advanced transaction preview dialog when 'Pay' is clicked."
              ))

        def on_preview(x):
            self.config.set_key('advanced_preview', x == Qt.Checked)

        preview_cb.stateChanged.connect(on_preview)
        tx_widgets.append((preview_cb, None))

        usechange_cb = QCheckBox(_('Use change addresses'))
        usechange_cb.setChecked(self.window.wallet.use_change)
        if not self.config.is_modifiable('use_change'):
            usechange_cb.setEnabled(False)

        def on_usechange(x):
            usechange_result = x == Qt.Checked
            if self.window.wallet.use_change != usechange_result:
                self.window.wallet.use_change = usechange_result
                self.window.wallet.db.put('use_change',
                                          self.window.wallet.use_change)
                multiple_cb.setEnabled(self.window.wallet.use_change)

        usechange_cb.stateChanged.connect(on_usechange)
        usechange_cb.setToolTip(
            _('Using change addresses makes it more difficult for other people to track your transactions.'
              ))
        tx_widgets.append((usechange_cb, None))

        def on_multiple(x):
            multiple = x == Qt.Checked
            if self.wallet.multiple_change != multiple:
                self.wallet.multiple_change = multiple
                self.wallet.db.put('multiple_change', multiple)

        multiple_change = self.wallet.multiple_change
        multiple_cb = QCheckBox(_('Use multiple change addresses'))
        multiple_cb.setEnabled(self.wallet.use_change)
        multiple_cb.setToolTip('\n'.join([
            _('In some cases, use up to 3 change addresses in order to break '
              'up large coin amounts and obfuscate the recipient address.'),
            _('This may result in higher transactions fees.')
        ]))
        multiple_cb.setChecked(multiple_change)
        multiple_cb.stateChanged.connect(on_multiple)
        tx_widgets.append((multiple_cb, None))

        def fmt_docs(key, klass):
            lines = [ln.lstrip(" ") for ln in klass.__doc__.split("\n")]
            return '\n'.join([key, "", " ".join(lines)])

        choosers = sorted(coinchooser.COIN_CHOOSERS.keys())
        if len(choosers) > 1:
            chooser_name = coinchooser.get_name(self.config)
            msg = _(
                'Choose coin (UTXO) selection method.  The following are available:\n\n'
            )
            msg += '\n\n'.join(
                fmt_docs(*item) for item in coinchooser.COIN_CHOOSERS.items())
            chooser_label = HelpLabel(_('Coin selection') + ':', msg)
            chooser_combo = QComboBox()
            chooser_combo.addItems(choosers)
            i = choosers.index(chooser_name) if chooser_name in choosers else 0
            chooser_combo.setCurrentIndex(i)

            def on_chooser(x):
                chooser_name = choosers[chooser_combo.currentIndex()]
                self.config.set_key('coin_chooser', chooser_name)

            chooser_combo.currentIndexChanged.connect(on_chooser)
            tx_widgets.append((chooser_label, chooser_combo))

        def on_unconf(x):
            self.config.set_key('confirmed_only', bool(x))

        conf_only = bool(self.config.get('confirmed_only', False))
        unconf_cb = QCheckBox(_('Spend only confirmed coins'))
        unconf_cb.setToolTip(_('Spend only confirmed inputs.'))
        unconf_cb.setChecked(conf_only)
        unconf_cb.stateChanged.connect(on_unconf)
        tx_widgets.append((unconf_cb, None))

        def on_outrounding(x):
            self.config.set_key('coin_chooser_output_rounding', bool(x))

        enable_outrounding = bool(
            self.config.get('coin_chooser_output_rounding', True))
        outrounding_cb = QCheckBox(_('Enable output value rounding'))
        outrounding_cb.setToolTip(
            _('Set the value of the change output so that it has similar precision to the other outputs.'
              ) + '\n' + _('This might improve your privacy somewhat.') +
            '\n' +
            _('If enabled, at most 100 satoshis might be lost due to this, per transaction.'
              ))
        outrounding_cb.setChecked(enable_outrounding)
        outrounding_cb.stateChanged.connect(on_outrounding)
        tx_widgets.append((outrounding_cb, None))

        def on_opsender(x):
            self.config.set_key('disable_opsender', bool(x))

        disable_opsender = bool(self.config.get('disable_opsender', True))
        opsender_cb = QCheckBox(_('Disable OP_SENDER'))
        opsender_cb.setToolTip(_("Don't use op_sender at any time"))
        opsender_cb.setChecked(disable_opsender)
        opsender_cb.stateChanged.connect(on_opsender)
        tx_widgets.append((opsender_cb, None))

        block_explorers = sorted(util.block_explorer_info().keys())
        msg = _(
            'Choose which online block explorer to use for functions that open a web browser'
        )
        block_ex_label = HelpLabel(_('Online Block Explorer') + ':', msg)
        block_ex_combo = QComboBox()
        block_ex_combo.addItems(block_explorers)
        block_ex_combo.setCurrentIndex(
            block_ex_combo.findText(util.block_explorer(self.config)))

        def on_be(x):
            be_result = block_explorers[block_ex_combo.currentIndex()]
            self.config.set_key('block_explorer', be_result, True)

        block_ex_combo.currentIndexChanged.connect(on_be)
        tx_widgets.append((block_ex_label, block_ex_combo))

        # Fiat Currency
        hist_checkbox = QCheckBox()
        hist_capgains_checkbox = QCheckBox()
        fiat_address_checkbox = QCheckBox()
        ccy_combo = QComboBox()
        ex_combo = QComboBox()

        def update_currencies():
            if not self.window.fx: return
            currencies = sorted(
                self.fx.get_currencies(self.fx.get_history_config()))
            ccy_combo.clear()
            ccy_combo.addItems([_('None')] + currencies)
            if self.fx.is_enabled():
                ccy_combo.setCurrentIndex(
                    ccy_combo.findText(self.fx.get_currency()))

        def update_history_cb():
            if not self.fx: return
            hist_checkbox.setChecked(self.fx.get_history_config())
            hist_checkbox.setEnabled(self.fx.is_enabled())

        def update_fiat_address_cb():
            if not self.fx: return
            fiat_address_checkbox.setChecked(self.fx.get_fiat_address_config())

        def update_history_capgains_cb():
            if not self.fx: return
            hist_capgains_checkbox.setChecked(
                self.fx.get_history_capital_gains_config())
            hist_capgains_checkbox.setEnabled(hist_checkbox.isChecked())

        def update_exchanges():
            if not self.fx: return
            b = self.fx.is_enabled()
            ex_combo.setEnabled(b)
            if b:
                h = self.fx.get_history_config()
                c = self.fx.get_currency()
                exchanges = self.fx.get_exchanges_by_ccy(c, h)
            else:
                exchanges = self.fx.get_exchanges_by_ccy('USD', False)
            ex_combo.blockSignals(True)
            ex_combo.clear()
            ex_combo.addItems(sorted(exchanges))
            ex_combo.setCurrentIndex(
                ex_combo.findText(self.fx.config_exchange()))
            ex_combo.blockSignals(False)

        def on_currency(hh):
            if not self.fx: return
            b = bool(ccy_combo.currentIndex())
            ccy = str(ccy_combo.currentText()) if b else None
            self.fx.set_enabled(b)
            if b and ccy != self.fx.ccy:
                self.fx.set_currency(ccy)
            update_history_cb()
            update_exchanges()
            self.window.update_fiat()

        def on_exchange(idx):
            exchange = str(ex_combo.currentText())
            if self.fx and self.fx.is_enabled(
            ) and exchange and exchange != self.fx.exchange.name():
                self.fx.set_exchange(exchange)

        def on_history(checked):
            if not self.fx: return
            self.fx.set_history_config(checked)
            update_exchanges()
            self.window.history_model.refresh('on_history')
            if self.fx.is_enabled() and checked:
                self.fx.trigger_update()
            update_history_capgains_cb()

        def on_history_capgains(checked):
            if not self.fx: return
            self.fx.set_history_capital_gains_config(checked)
            self.window.history_model.refresh('on_history_capgains')

        def on_fiat_address(checked):
            if not self.fx: return
            self.fx.set_fiat_address_config(checked)
            self.window.address_list.refresh_headers()
            self.window.address_list.update()

        update_currencies()
        update_history_cb()
        update_history_capgains_cb()
        update_fiat_address_cb()
        update_exchanges()
        ccy_combo.currentIndexChanged.connect(on_currency)
        hist_checkbox.stateChanged.connect(on_history)
        hist_capgains_checkbox.stateChanged.connect(on_history_capgains)
        fiat_address_checkbox.stateChanged.connect(on_fiat_address)
        ex_combo.currentIndexChanged.connect(on_exchange)

        fiat_widgets = []
        fiat_widgets.append((QLabel(_('Fiat currency')), ccy_combo))
        fiat_widgets.append((QLabel(_('Source')), ex_combo))
        fiat_widgets.append((QLabel(_('Show history rates')), hist_checkbox))
        fiat_widgets.append((QLabel(_('Show capital gains in history')),
                             hist_capgains_checkbox))
        fiat_widgets.append((QLabel(_('Show Fiat balance for addresses')),
                             fiat_address_checkbox))

        tabs_info = [
            (gui_widgets, _('General')),
            (tx_widgets, _('Transactions')),
            # (lightning_widgets, _('Lightning')),
            (fiat_widgets, _('Fiat')),
            (oa_widgets, _('OpenAlias')),
        ]
        for widgets, name in tabs_info:
            tab = QWidget()
            tab_vbox = QVBoxLayout(tab)
            grid = QGridLayout()
            for a, b in widgets:
                i = grid.rowCount()
                if b:
                    if a:
                        grid.addWidget(a, i, 0)
                    grid.addWidget(b, i, 1)
                else:
                    grid.addWidget(a, i, 0, 1, 2)
            tab_vbox.addLayout(grid)
            tab_vbox.addStretch(1)
            tabs.addTab(tab, name)

        vbox.addWidget(tabs)
        vbox.addStretch(1)
        vbox.addLayout(Buttons(CloseButton(self)))
        self.setLayout(vbox)
Beispiel #7
0
class ColorSet:
    """Stores color settings and provides dialogs for user changes.
    """
    def __init__(self, option):
        self.option = option
        self.sysPalette = QApplication.palette()
        self.colors = [Color(roleKey) for roleKey in roles.keys()]
        self.theme = ThemeSetting[self.option.strData('ColorTheme')]
        for color in self.colors:
            color.colorChanged.connect(self.setCustomTheme)
            color.setFromPalette(self.sysPalette)
            if self.theme == ThemeSetting.dark:
                color.setFromTheme(darkColors)
            elif self.theme == ThemeSetting.custom:
                color.setFromOption(self.option)

    def setAppColors(self):
        """Set application to current colors.
        """
        newPalette = QApplication.palette()
        for color in self.colors:
            color.updatePalette(newPalette)
        qApp.setPalette(newPalette)

    def showDialog(self, parent):
        """Show a dialog for user color changes.

        Return True if changes were made.
        """
        dialog = QDialog(parent)
        dialog.setWindowFlags(Qt.Dialog | Qt.WindowTitleHint
                              | Qt.WindowSystemMenuHint)
        dialog.setWindowTitle(_('Color Settings'))
        topLayout = QVBoxLayout(dialog)
        dialog.setLayout(topLayout)
        themeBox = QGroupBox(_('Color Theme'), dialog)
        topLayout.addWidget(themeBox)
        themeLayout = QVBoxLayout(themeBox)
        self.themeControl = QComboBox(dialog)
        self.themeControl.addItem(_('Default system theme'),
                                  ThemeSetting.system)
        self.themeControl.addItem(_('Dark theme'), ThemeSetting.dark)
        self.themeControl.addItem(_('Custom theme'), ThemeSetting.custom)
        self.themeControl.setCurrentIndex(
            self.themeControl.findData(self.theme))
        self.themeControl.currentIndexChanged.connect(self.updateThemeSetting)
        themeLayout.addWidget(self.themeControl)
        self.groupBox = QGroupBox(dialog)
        self.setBoxTitle()
        topLayout.addWidget(self.groupBox)
        gridLayout = QGridLayout(self.groupBox)
        row = 0
        for color in self.colors:
            gridLayout.addWidget(color.getLabel(), row, 0)
            gridLayout.addWidget(color.getSwatch(), row, 1)
            row += 1
        ctrlLayout = QHBoxLayout()
        topLayout.addLayout(ctrlLayout)
        ctrlLayout.addStretch(0)
        okButton = QPushButton(_('&OK'), dialog)
        ctrlLayout.addWidget(okButton)
        okButton.clicked.connect(dialog.accept)
        cancelButton = QPushButton(_('&Cancel'), dialog)
        ctrlLayout.addWidget(cancelButton)
        cancelButton.clicked.connect(dialog.reject)
        if dialog.exec_() == QDialog.Accepted:
            self.theme = ThemeSetting(self.themeControl.currentData())
            self.option.changeData('ColorTheme', self.theme.name, True)
            if self.theme == ThemeSetting.system:
                qApp.setPalette(self.sysPalette)
            else:  # dark theme or custom
                if self.theme == ThemeSetting.custom:
                    for color in self.colors:
                        color.updateOption(self.option)
                self.setAppColors()
        else:
            for color in self.colors:
                color.setFromPalette(self.sysPalette)
                if self.theme == ThemeSetting.dark:
                    color.setFromTheme(darkColors)
                elif self.theme == ThemeSetting.custom:
                    color.setFromOption(self.option)

    def setBoxTitle(self):
        """Set title of group box to standard or custom.
        """
        if self.themeControl.currentData() == ThemeSetting.custom:
            title = _('Custom Colors')
        else:
            title = _('Theme Colors')
        self.groupBox.setTitle(title)

    def updateThemeSetting(self):
        """Update the colors based on a theme control change.
        """
        if self.themeControl.currentData() == ThemeSetting.system:
            for color in self.colors:
                color.setFromPalette(self.sysPalette)
                color.changeSwatchColor()
        elif self.themeControl.currentData() == ThemeSetting.dark:
            for color in self.colors:
                color.setFromTheme(darkColors)
                color.changeSwatchColor()
        else:
            for color in self.colors:
                color.setFromOption(self.option)
                color.changeSwatchColor()
        self.setBoxTitle()

    def setCustomTheme(self):
        """Set to custom theme setting after user color change.
        """
        if self.themeControl.currentData != ThemeSetting.custom:
            self.themeControl.blockSignals(True)
            self.themeControl.setCurrentIndex(2)
            self.themeControl.blockSignals(False)
            self.setBoxTitle()
Beispiel #8
0
class SchemeSelector(QWidget):

    currentChanged = pyqtSignal()
    changed = pyqtSignal()

    def __init__(self, parent=None):
        super(SchemeSelector, self).__init__(parent)
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        self.label = QLabel()
        self.scheme = QComboBox()
        self.menuButton = QPushButton(flat=True)
        menu = QMenu(self.menuButton)
        self.menuButton.setMenu(menu)
        layout.addWidget(self.label)
        layout.addWidget(self.scheme)
        layout.addWidget(self.menuButton)
        layout.addStretch(1)
        # action generator
        def act(slot, icon=None):
            a = QAction(self, triggered=slot)
            self.addAction(a)
            icon and a.setIcon(icons.get(icon))
            return a

        # add action
        a = self.addAction_ = act(self.slotAdd, 'list-add')
        menu.addAction(a)

        # remove action
        a = self.removeAction = act(self.slotRemove, 'list-remove')
        menu.addAction(a)


        # rename action
        a = self.renameAction = act(self.slotRename, 'document-edit')
        menu.addAction(a)

        menu.addSeparator()

        # import action
        a = self.importAction = act(self.slotImport, 'document-open')
        menu.addAction(a)

        # export action
        a = self.exportAction = act(self.slotExport, 'document-save-as')
        menu.addAction(a)


        self.scheme.currentIndexChanged.connect(self.slotSchemeChanged)
        app.translateUI(self)

    def translateUI(self):
        self.label.setText(_("Scheme:"))
        self.menuButton.setText(_("&Menu"))
        self.addAction_.setText(_("&Add..."))
        self.removeAction.setText(_("&Remove"))
        self.renameAction.setText(_("Re&name..."))
        self.importAction.setText(_("&Import..."))
        self.exportAction.setText(_("&Export..."))

    def slotSchemeChanged(self, index):
        """Called when the Scheme combobox is changed by the user."""
        self.disableDefault(self.scheme.itemData(index) == 'default')
        self.currentChanged.emit()
        self.changed.emit()

    def disableDefault(self, val):
        self.removeAction.setDisabled(val)
        self.renameAction.setDisabled(val)

    def schemes(self):
        """Returns the list with internal names of currently available schemes."""
        return [self.scheme.itemData(i) for i in range(self.scheme.count())]

    def currentScheme(self):
        """Returns the internal name of the currently selected scheme"""
        return self.scheme.itemData(self.scheme.currentIndex())

    def insertSchemeItem(self, name, scheme):
        for i in range(1, self.scheme.count()):
            n = self.scheme.itemText(i)
            if n.lower() > name.lower():
                self.scheme.insertItem(i, name, scheme)
                break
        else:
            self.scheme.addItem(name, scheme)

    def addScheme(self, name):
        num, key = 1, 'user1'
        while key in self.schemes() or key in self._schemesToRemove:
            num += 1
            key = 'user{0}'.format(num)
        self.insertSchemeItem(name, key)
        self.scheme.setCurrentIndex(self.scheme.findData(key))
        return key

    def slotAdd(self):
        name, ok = QInputDialog.getText(self,
            app.caption(_("Add Scheme")),
            _("Please enter a name for the new scheme:"))
        if ok:
            self.addScheme(name)


    def slotRemove(self):
        index = self.scheme.currentIndex()
        scheme = self.scheme.itemData(index)
        if scheme == 'default':
            return # default can not be removed

        self._schemesToRemove.add(scheme)
        self.scheme.removeItem(index)

    def slotRename(self):
        index = self.scheme.currentIndex()
        name = self.scheme.itemText(index)
        scheme = self.scheme.itemData(index)
        newName, ok = QInputDialog.getText(self, _("Rename"), _("New name:"), text=name)
        if ok:
            self.scheme.blockSignals(True)
            self.scheme.removeItem(index)
            self.insertSchemeItem(newName, scheme)
            self.scheme.setCurrentIndex(self.scheme.findData(scheme))
            self.scheme.blockSignals(False)
            self.changed.emit()

    def slotImport(self):
        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"), _("All Files"))
        caption = app.caption(_("dialog title", "Import color theme"))
        filename = QFileDialog.getOpenFileName(self, caption, QDir.homePath(), filetypes)[0]
        if filename:
            self.parent().import_(filename)

    def slotExport(self):
        name = self.scheme.currentText()
        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"), _("All Files"))
        caption = app.caption(_("dialog title",
            "Export {name}").format(name=name))
        path = os.path.join(QDir.homePath(), name+'.xml')
        filename = QFileDialog.getSaveFileName(self, caption, path, filetypes)[0]
        if filename:
            if os.path.splitext(filename)[1] != '.xml':
                filename += '.xml'
            self.parent().export(name, filename)


    def loadSettings(self, currentKey, namesGroup):
        # don't mark schemes for removal anymore
        self._schemesToRemove = set()

        s = QSettings()
        cur = s.value(currentKey, "default", str)

        # load the names for the shortcut schemes
        s.beginGroup(namesGroup)
        block = self.scheme.blockSignals(True)
        self.scheme.clear()
        self.scheme.addItem(_("Default"), "default")
        lst = [(s.value(key, key, str), key) for key in s.childKeys()]
        for name, key in sorted(lst, key=lambda f: f[0].lower()):
            self.scheme.addItem(name, key)

        # find out index
        index = self.scheme.findData(cur)
        self.disableDefault(cur == 'default')
        self.scheme.setCurrentIndex(index)
        self.scheme.blockSignals(block)
        self.currentChanged.emit()

    def saveSettings(self, currentKey, namesGroup, removePrefix=None):
        # first save new scheme names
        s = QSettings()
        s.beginGroup(namesGroup)
        for i in range(self.scheme.count()):
            if self.scheme.itemData(i) != 'default':
                s.setValue(self.scheme.itemData(i), self.scheme.itemText(i))

        for scheme in self._schemesToRemove:
            s.remove(scheme)
        s.endGroup()
        if removePrefix:
            for scheme in self._schemesToRemove:
                s.remove("{0}/{1}".format(removePrefix, scheme))
        # then save current
        scheme = self.currentScheme()
        s.setValue(currentKey, scheme)
        # clean up
        self._schemesToRemove = set()
Beispiel #9
0
class HeapPluginForm(PluginForm):
    def __init__(self):
        super(HeapPluginForm, self).__init__()
        self.parent      = None
        self.config_path = None
        self.config      = None
        self.tracer      = None
        self.heap        = None
        self.cur_arena   = None # default: main_arena
        self.ptr_size    = get_arch_ptrsize()

    def OnCreate(self, form):
        self.parent = self.FormToPyQtWidget(form)     
        self.setup_gui()
        self.init_heap()
        self.populate_gui()

    def setup_gui(self):
        self.chunk_widget = ChunkWidget(self)
        self.tracer_tab = TracerWidget(self)
        self.arena_widget = ArenaWidget(self)
        self.bins_widget = BinsWidget(self)
        self.tcache_widget = TcacheWidget(self)
        self.magic_widget = MagicWidget(self)
        self.config_widget = ConfigWidget(self)

        self.tabs = QTabWidget()
        self.tabs.addTab(self.tracer_tab, "Tracer")
        self.tabs.addTab(self.arena_widget, "Arena")
        self.tabs.addTab(self.bins_widget, "Bins")
        self.tabs.addTab(self.tcache_widget, "Tcache")
        self.tabs.addTab(self.magic_widget, "Magic")
        self.tabs.addTab(self.config_widget, "Config")

        self.btn_reload = QPushButton("Reload info")
        icon = QtGui.QIcon(os.path.normpath(ICONS_DIR + '/refresh.png'))
        self.btn_reload.setIcon(icon)
        self.btn_reload.setFixedWidth(120)
        self.btn_reload.setEnabled(False)
        self.btn_reload.clicked.connect(self.reload_gui_info)

        self.cb_arenas = QComboBox()
        self.cb_arenas.setFixedWidth(150)
        self.cb_arenas.currentIndexChanged[int].connect(self.cb_arenas_changed)

        hbox_arenas = QHBoxLayout()        
        hbox_arenas.addWidget(QLabel('Switch arena: '))
        hbox_arenas.addWidget(self.cb_arenas)
        hbox_arenas.setContentsMargins(0, 0, 0, 0)

        self.arenas_widget = QWidget()
        self.arenas_widget.setLayout(hbox_arenas)
        self.arenas_widget.setVisible(False)

        self.txt_warning = QLabel()
        self.txt_warning.setStyleSheet("font-weight: bold; color: red")
        self.txt_warning.setVisible(False)

        hbox_top = QHBoxLayout()
        hbox_top.addWidget(self.btn_reload)
        hbox_top.addWidget(self.arenas_widget)
        hbox_top.addWidget(self.txt_warning)
        hbox_top.setContentsMargins(0, 0, 0, 0)
        hbox_top.addStretch(1)

        vbox_left_panel = QVBoxLayout()        
        vbox_left_panel.addLayout(hbox_top)
        vbox_left_panel.addWidget(self.tabs)
        vbox_left_panel.setContentsMargins(0, 0, 0, 0)

        left_panel = QWidget()
        left_panel.setLayout(vbox_left_panel)

        self.splitter = QSplitter(QtCore.Qt.Horizontal)
        self.splitter.addWidget(left_panel)
        self.splitter.addWidget(self.chunk_widget)
        self.splitter.setStretchFactor(0, 1)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.splitter)
        self.parent.setLayout(main_layout)

    def populate_gui(self):
        self.magic_widget.populate_libc_offsets()
        self.reload_gui_info()

    def reload_gui_info(self, from_arena_cb=False):
        if not self.heap:
            return

        if not self.heap.get_heap_base():
            self.show_warning('Heap not initialized')
            return

        self.hide_warning()
        self.arenas_widget.setVisible(True)

        if not from_arena_cb:
            self.populate_arenas()

        self.arena_widget.populate_table()
        self.tcache_widget.populate_table()
        self.bins_widget.populate_tables()

    def init_heap(self):
        try:            
            self.config_path = CONFIG_PATH
            self.config = HeapConfig(self.config_path)
            self.config_widget.load_config()

        except Exception as e:
            self.config = None
            self.show_warning('Please, update the config file')
            warning(str(e))
            return

        if not self.config.offsets:
            arch_bits = self.ptr_size * 8
            self.show_warning('Config: Libc offsets for %d bits not found.' % arch_bits)
            return

        try:
            current_libc_version = get_libc_version()
            if self.config.libc_version == current_libc_version or current_libc_version == None:
                self.heap = Heap(self.config)
                self.btn_reload.setEnabled(True)
                self.tabs.setTabEnabled(3, self.heap.tcache_enabled)
            else:
                self.show_warning('Config: glibc version mismatched: %s != %s' % \
                    (str(self.config.libc_version), str(current_libc_version)))

        except AttributeError:
            self.show_warning('Invalid config file content')
         

    def populate_arenas(self):
        old_arena = self.cur_arena
        self.cb_arenas.clear()

        for addr, arena in self.heap.arenas():
            if addr == self.heap.main_arena_addr:
                self.cb_arenas.addItem("main_arena", None)
            else:
                self.cb_arenas.addItem("0x%x" % addr, addr)

        idx = self.cb_arenas.findData(old_arena)
        if idx != -1:
            self.cb_arenas.setCurrentIndex(idx)

    def show_warning(self, txt):
        self.txt_warning.setText(txt)
        self.txt_warning.setVisible(True)

    def hide_warning(self):
        self.txt_warning.setVisible(False)

    def cb_arenas_changed(self, idx):
        self.cur_arena = self.cb_arenas.itemData(idx)
        self.reload_gui_info(True)

    def show_chunk_info(self, address):
        self.chunk_widget.show_chunk(address)

    def Show(self):
        return PluginForm.Show(self, PLUGNAME, options = (
            PluginForm.FORM_TAB | PluginForm.FORM_CLOSE_LATER
        ))
    
    def OnClose(self, form):
        if self.tracer:
            self.tracer.unhook()
            log("Tracer disabled")
        log("Form closed")
Beispiel #10
0
class Thermocouple(PluginBase):
    qtcb_error_state = pyqtSignal(bool, bool)

    def __init__(self, *args):
        super().__init__(BrickletThermocouple, *args)

        self.thermo = self.device

        self.qtcb_error_state.connect(self.cb_error_state)
        self.thermo.register_callback(self.thermo.CALLBACK_ERROR_STATE,
                                      self.qtcb_error_state.emit)

        self.cbe_temperature = CallbackEmulator(self.thermo.get_temperature,
                                                None,
                                                self.cb_temperature,
                                                self.increase_error_count)

        self.current_temperature = CurveValueWrapper() # float, °C

        self.error_label = QLabel('Current Errors: None')
        self.error_label.setAlignment(Qt.AlignVCenter | Qt.AlignRight)

        plots = [('Temperature', Qt.red, self.current_temperature, '{:.2f} °C'.format)]
        self.plot_widget = PlotWidget('Temperature [°C]', plots, extra_key_widgets=[self.error_label], y_resolution=0.01)

        self.averaging_label = QLabel('Averaging:')
        self.averaging_combo = QComboBox()
        self.averaging_combo.addItem('1', BrickletThermocouple.AVERAGING_1)
        self.averaging_combo.addItem('2', BrickletThermocouple.AVERAGING_2)
        self.averaging_combo.addItem('4', BrickletThermocouple.AVERAGING_4)
        self.averaging_combo.addItem('8', BrickletThermocouple.AVERAGING_8)
        self.averaging_combo.addItem('16', BrickletThermocouple.AVERAGING_16)

        self.type_label = QLabel('Thermocouple Type:')
        self.type_combo = QComboBox()
        self.type_combo.addItem('B', BrickletThermocouple.TYPE_B)
        self.type_combo.addItem('E', BrickletThermocouple.TYPE_E)
        self.type_combo.addItem('J', BrickletThermocouple.TYPE_J)
        self.type_combo.addItem('K', BrickletThermocouple.TYPE_K)
        self.type_combo.addItem('N', BrickletThermocouple.TYPE_N)
        self.type_combo.addItem('R', BrickletThermocouple.TYPE_R)
        self.type_combo.addItem('S', BrickletThermocouple.TYPE_S)
        self.type_combo.addItem('T', BrickletThermocouple.TYPE_T)
        self.type_combo.addItem('Gain 8', BrickletThermocouple.TYPE_G8)
        self.type_combo.addItem('Gain 32', BrickletThermocouple.TYPE_G32)

        self.filter_label = QLabel('Noise Rejection Filter:')
        self.filter_combo = QComboBox()
        self.filter_combo.addItem('50 Hz', BrickletThermocouple.FILTER_OPTION_50HZ)
        self.filter_combo.addItem('60 Hz', BrickletThermocouple.FILTER_OPTION_60HZ)

        hlayout = QHBoxLayout()
        hlayout.addWidget(self.averaging_label)
        hlayout.addWidget(self.averaging_combo)
        hlayout.addStretch()
        hlayout.addWidget(self.type_label)
        hlayout.addWidget(self.type_combo)
        hlayout.addStretch()
        hlayout.addWidget(self.filter_label)
        hlayout.addWidget(self.filter_combo)

        line = QFrame()
        line.setObjectName("line")
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)

        layout = QVBoxLayout(self)
        layout.addWidget(self.plot_widget)
        layout.addWidget(line)
        layout.addLayout(hlayout)

        self.averaging_combo.currentIndexChanged.connect(self.configuration_changed)
        self.type_combo.currentIndexChanged.connect(self.configuration_changed)
        self.filter_combo.currentIndexChanged.connect(self.configuration_changed)

    def start(self):
        async_call(self.thermo.get_configuration, None, self.get_configuration_async, self.increase_error_count)
        async_call(self.thermo.get_error_state, None, self.cb_error_state, self.increase_error_count,
                   expand_result_tuple_for_callback=True)

        self.cbe_temperature.set_period(100)

        self.plot_widget.stop = False

    def stop(self):
        self.cbe_temperature.set_period(0)

        self.plot_widget.stop = True

    def destroy(self):
        pass

    @staticmethod
    def has_device_identifier(device_identifier):
        return device_identifier == BrickletThermocouple.DEVICE_IDENTIFIER

    def configuration_changed(self, _):
        conf_averaging = self.averaging_combo.itemData(self.averaging_combo.currentIndex())
        conf_type = self.type_combo.itemData(self.type_combo.currentIndex())
        conf_filter = self.filter_combo.itemData(self.filter_combo.currentIndex())

        self.thermo.set_configuration(conf_averaging, conf_type, conf_filter)

    def cb_temperature(self, temperature):
        self.current_temperature.value = temperature / 100.0

    def get_configuration_async(self, conf):
        self.averaging_combo.blockSignals(True)
        self.averaging_combo.setCurrentIndex(self.averaging_combo.findData(conf.averaging))
        self.averaging_combo.blockSignals(False)

        self.type_combo.blockSignals(True)
        self.type_combo.setCurrentIndex(self.type_combo.findData(conf.thermocouple_type))
        self.type_combo.blockSignals(False)

        self.filter_combo.blockSignals(True)
        self.filter_combo.setCurrentIndex(self.filter_combo.findData(conf.filter))
        self.filter_combo.blockSignals(False)

    def cb_error_state(self, over_under, open_circuit):
        if over_under or open_circuit:
            text = 'Current Errors: '

            if over_under:
                text += 'Over/Under Voltage'

            if over_under and open_circuit:
                text += ' and '

            if open_circuit:
                text += 'Open Circuit\n(defective thermocouple or nothing connected)'

            self.error_label.setStyleSheet('QLabel { color : red }')
            self.error_label.setText(text)
        else:
            self.error_label.setStyleSheet('')
            self.error_label.setText('Current Errors: None')
Beispiel #11
0
class SearchWidget(QWidget, WidgetMixin):
	def __init__(self, **kwargs):
		super(SearchWidget, self).__init__(**kwargs)

		layout = QGridLayout()
		self.setLayout(layout)

		self.exprEdit = QLineEdit()
		self.exprEdit.returnPressed.connect(self.returnPressed)
		self.setFocusProxy(self.exprEdit)

		self.optionsButton = SearchOptionsButton()

		self.pluginChoice = QComboBox()
		plugins = sorted(file_search.enabledPlugins(), key=lambda p: p.name())
		for plugin in plugins:
			self.pluginChoice.addItem(plugin.name(), plugin.id)

		self.results = LocationList()
		self.results.setColumns(['path', 'line', 'snippet'])

		self.searcher = None

		layout.addWidget(self.exprEdit, 0, 0)
		layout.addWidget(self.optionsButton, 0, 1)
		layout.addWidget(self.pluginChoice, 0, 2)
		layout.addWidget(self.results, 1, 0, 1, -1)

		self.addCategory('file_search_widget')

	def setPlugin(self, id):
		index = self.pluginChoice.findData(id)
		if index >= 0:
			self.pluginChoice.setCurrentIndex(index)

	def setText(self, text):
		self.exprEdit.setText(text)

	def selectedPlugin(self):
		return self.pluginChoice.itemData(self.pluginChoice.currentIndex())

	def regexp(self):
		re = QRegExp(self.exprEdit.text())
		re.setCaseSensitivity(csToQtEnum(self.optionsButton.caseSensitive()))
		re.setPatternSyntax(self.optionsButton.reFormat())
		return re

	def shouldFindRoot(self):
		return self.optionsButton.shouldFindRoot()

	def makeArgs(self, plugin):
		ed = buffers.currentBuffer()

		if self.shouldFindRoot():
			path = plugin.searchRootPath(ed.path)
		else:
			path = os.path.dirname(ed.path)
		pattern = self.exprEdit.text()
		ci = self.optionsButton.caseSensitive()
		return (path, pattern, ci)

	@Slot()
	def doSearch(self):
		self.results.clear()
		plugin_type = file_search.getPlugin(self.selectedPlugin())
		self.searcher = plugin_type()
		file_search.setupLocationList(self.searcher, self.results)
		args = self.makeArgs(self.searcher)
		self.searcher.search(*args)

	returnPressed = Signal()
Beispiel #12
0
class AccountAddDialog(QDialog):
    def __init__(self, parent, icons, edit=False, username='', password='', api=''):
        QDialog.__init__(self, parent)
        self.edit = edit

        # Build UI
        layout = QVBoxLayout()

        formlayout = QFormLayout()
        self.lbl_username = QLabel('Username:'******'Password:'******'Request PIN')
        self.api_auth.setTextFormat(QtCore.Qt.RichText)
        self.api_auth.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
        self.api_auth.setOpenExternalLinks(True)
        pin_layout.addWidget(self.password)
        pin_layout.addWidget(self.api_auth)

        formlayout.addRow(QLabel('Site:'), self.api)
        formlayout.addRow(self.lbl_username, self.username)
        formlayout.addRow(self.lbl_password, pin_layout)

        bottombox = QDialogButtonBox()
        bottombox.addButton(QDialogButtonBox.Save)
        bottombox.addButton(QDialogButtonBox.Cancel)
        bottombox.accepted.connect(self.validate)
        bottombox.rejected.connect(self.reject)

        # Populate APIs
        for libname, lib in sorted(utils.available_libs.items()):
            self.api.addItem(icons[libname], lib[0], libname)

        if self.edit:
            self.username.setEnabled(False)
            self.api.setCurrentIndex(self.api.findData(api, QtCore.Qt.UserRole))
            self.api.setEnabled(False)

        # Finish layouts
        layout.addLayout(formlayout)
        layout.addWidget(bottombox)

        self.setLayout(layout)

    def validate(self):
        if len(self.username.text()) is 0:
            if len(self.password.text()) is 0:
                self._error('Please fill the credentials fields.')
            else:
                self._error('Please fill the username field.')
        elif len(self.password.text()) is 0:
            self._error('Please fill the password/PIN field.')
        else:
            self.accept()

    def s_refresh(self, index):
        if not self.edit:
            self.username.setText("")
            self.password.setText("")

        if pyqt_version is 5:
            apiname = self.api.itemData(index)
        else:
            apiname = str(self.api.itemData(index))
        api = utils.available_libs[apiname]
        if api[2] == utils.LOGIN_OAUTH:
            apiname = str(self.api.itemData(index))
            url = utils.available_libs[apiname][4]
            self.api_auth.setText( "<a href=\"{}\">Request PIN</a>".format(url) )
            self.api_auth.show()

            self.lbl_username.setText('Name:')
            self.lbl_password.setText('PIN:')
            self.password.setEchoMode(QLineEdit.Normal)
        else:
            self.lbl_username.setText('Username:'******'Password:'******'Error', msg, QMessageBox.Ok)

    @staticmethod
    def do(parent=None, icons=None, edit=False, username='', password='', api=''):
        dialog = AccountAddDialog(parent, icons, edit, username, password, api)
        result = dialog.exec_()

        if result == QDialog.Accepted:
            currentIndex = dialog.api.currentIndex()
            return (
                    str(dialog.username.text()),
                    str(dialog.password.text()),
                    str(dialog.api.itemData(currentIndex))
                   )
        else:
            return None
class fullScreenEditor(QWidget):
    def __init__(self, index, parent=None):
        QWidget.__init__(self, parent)
        self._background = None
        self._index = index
        self._theme = findThemePath(settings.fullScreenTheme)
        self._themeDatas = loadThemeDatas(self._theme)
        self.setMouseTracking(True)
        self._geometries = {}

        # Text editor
        self.editor = MDEditView(self,
                                index=index,
                                spellcheck=settings.spellcheck,
                                highlighting=True,
                                dict=settings.dict)
        self.editor.setFrameStyle(QFrame.NoFrame)
        self.editor.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.editor.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.editor.installEventFilter(self)
        self.editor.setMouseTracking(True)
        self.editor.setVerticalScrollBar(myScrollBar())
        self.scrollBar = self.editor.verticalScrollBar()
        self.scrollBar.setParent(self)

        # Top Panel
        self.topPanel = myPanel(parent=self)
        # self.topPanel.layout().addStretch(1)

        # Spell checking
        if enchant:
            self.btnSpellCheck = QPushButton(self)
            self.btnSpellCheck.setFlat(True)
            self.btnSpellCheck.setIcon(QIcon.fromTheme("tools-check-spelling"))
            self.btnSpellCheck.setCheckable(True)
            self.btnSpellCheck.setChecked(self.editor.spellcheck)
            self.btnSpellCheck.toggled.connect(self.editor.toggleSpellcheck)
        else:
            self.btnSpellCheck = None

        # Navigation Buttons
        self.btnPrevious = QPushButton(self)
        self.btnPrevious.setFlat(True)
        self.btnPrevious.setIcon(QIcon.fromTheme("arrow-left"))
        self.btnPrevious.clicked.connect(self.switchPreviousItem)
        self.btnNext = QPushButton(self)
        self.btnNext.setFlat(True)
        self.btnNext.setIcon(QIcon.fromTheme("arrow-right"))
        self.btnNext.clicked.connect(self.switchNextItem)
        self.btnNew = QPushButton(self)
        self.btnNew.setFlat(True)
        self.btnNew.setIcon(QIcon.fromTheme("document-new"))
        self.btnNew.clicked.connect(self.createNewText)

        # Path and New Text Buttons
        self.wPath = myPath(self)

        # Close
        self.btnClose = QPushButton(self)
        self.btnClose.setIcon(qApp.style().standardIcon(QStyle.SP_DialogCloseButton))
        self.btnClose.clicked.connect(self.close)
        self.btnClose.setFlat(True)

        # Top panel Layout
        if self.btnSpellCheck:
            self.topPanel.layout().addWidget(self.btnSpellCheck)
        self.topPanel.layout().addSpacing(15)
        self.topPanel.layout().addWidget(self.btnPrevious)
        self.topPanel.layout().addWidget(self.btnNext)
        self.topPanel.layout().addWidget(self.btnNew)

        self.topPanel.layout().addStretch(1)
        self.topPanel.layout().addWidget(self.wPath)
        self.topPanel.layout().addStretch(1)

        self.topPanel.layout().addWidget(self.btnClose)
        self.updateTopBar()

        # Left Panel
        self._locked = False
        self.leftPanel = myPanel(vertical=True, parent=self)
        self.locker = locker(self)
        self.locker.lockChanged.connect(self.setLocked)
        self.leftPanel.layout().addWidget(self.locker)

        # Bottom Panel
        self.bottomPanel = myPanel(parent=self)

        self.bottomPanel.layout().addSpacing(24)
        self.lstThemes = QComboBox(self)
        self.lstThemes.setAttribute(Qt.WA_TranslucentBackground)
        paths = allPaths("resources/themes")
        for p in paths:
            lst = [i for i in os.listdir(p) if os.path.splitext(i)[1] == ".theme"]
            for t in lst:
                themeIni = os.path.join(p, t)
                name = loadThemeDatas(themeIni)["Name"]
                # self.lstThemes.addItem(os.path.splitext(t)[0])
                self.lstThemes.addItem(name)
                self.lstThemes.setItemData(self.lstThemes.count()-1, os.path.splitext(t)[0])

        self.lstThemes.setCurrentIndex(self.lstThemes.findData(settings.fullScreenTheme))
        # self.lstThemes.setCurrentText(settings.fullScreenTheme)
        self.lstThemes.currentTextChanged.connect(self.setTheme)
        self.lstThemes.setMaximumSize(QSize(300, QFontMetrics(qApp.font()).height()))
        themeLabel = QLabel(self.tr("Theme:"), self)
        self.bottomPanel.layout().addWidget(themeLabel)
        self.bottomPanel.layout().addWidget(self.lstThemes)
        self.bottomPanel.layout().addStretch(1)

        self.lblProgress = QLabel(self)
        self.lblProgress.setMaximumSize(QSize(200, 14))
        self.lblProgress.setMinimumSize(QSize(100, 14))
        self.lblWC = QLabel(self)
        self.lblClock = myClockLabel(self)
        self.bottomPanel.layout().addWidget(self.lblWC)
        self.bottomPanel.layout().addWidget(self.lblProgress)
        self.bottomPanel.layout().addSpacing(15)
        self.bottomPanel.layout().addWidget(self.lblClock)
        self.updateStatusBar()

        self.bottomPanel.layout().addSpacing(24)

        # Add Widget Settings
        if self.btnSpellCheck:
            self.topPanel.addWidgetSetting(self.tr("Spellcheck"), 'top-spellcheck', (self.btnSpellCheck, ))
        self.topPanel.addWidgetSetting(self.tr("Navigation"), 'top-navigation', (self.btnPrevious, self.btnNext))
        self.topPanel.addWidgetSetting(self.tr("New Text"), 'top-new-doc', (self.btnNew, ))
        self.topPanel.addWidgetSetting(self.tr("Title"), 'top-title', (self.wPath, ))
        self.topPanel.addSetting(self.tr("Title: Show Full Path"), 'title-show-full-path', True)
        self.topPanel.setSettingCallback('title-show-full-path', lambda var, val: self.updateTopBar())
        self.bottomPanel.addWidgetSetting(self.tr("Theme selector"), 'bottom-theme', (self.lstThemes, themeLabel))
        self.bottomPanel.addWidgetSetting(self.tr("Word count"), 'bottom-wc', (self.lblWC, ))
        self.bottomPanel.addWidgetSetting(self.tr("Progress"), 'bottom-progress', (self.lblProgress, ))
        self.bottomPanel.addSetting(self.tr("Progress: Auto Show/Hide"), 'progress-auto-show', True)
        self.bottomPanel.addWidgetSetting(self.tr("Clock"), 'bottom-clock', (self.lblClock, ))
        self.bottomPanel.addSetting(self.tr("Clock: Show Seconds"), 'clock-show-seconds', True)
        self.bottomPanel.setAutoHideVariable('autohide-bottom')
        self.topPanel.setAutoHideVariable('autohide-top')
        self.leftPanel.setAutoHideVariable('autohide-left')

        # Connection
        self._index.model().dataChanged.connect(self.dataChanged)

        # self.updateTheme()
        self.showFullScreen()
        # self.showMaximized()
        # self.show()

    def __del__(self):
        # print("Leaving fullScreenEditor via Destructor event", flush=True)
        self.showNormal()
        self.close()

    def setLocked(self, val):
        self._locked = val
        self.btnClose.setVisible(not val)

    def setTheme(self, themeName):
        themeName = self.lstThemes.currentData()
        settings.fullScreenTheme = themeName
        self._theme = findThemePath(themeName)
        self._themeDatas = loadThemeDatas(self._theme)
        self.updateTheme()

    def updateTheme(self):
        # Reinit stored geometries for hiding widgets
        self._geometries = {}
        rect = self.geometry()
        self._background = generateTheme(self._themeDatas, rect)

        setThemeEditorDatas(self.editor, self._themeDatas, self._background, rect)

        # Colors
        if self._themeDatas["Foreground/Color"] == self._themeDatas["Background/Color"] or \
                        self._themeDatas["Foreground/Opacity"] < 5:
            self._fgcolor = QColor(self._themeDatas["Text/Color"])
            self._bgcolor = QColor(self._themeDatas["Background/Color"])
        else:
            self._bgcolor = QColor(self._themeDatas["Foreground/Color"])
            self._bgcolor.setAlpha(self._themeDatas["Foreground/Opacity"] * 255 / 100)
            self._fgcolor = QColor(self._themeDatas["Text/Color"])
            if self._themeDatas["Text/Color"] == self._themeDatas["Foreground/Color"]:
                self._fgcolor = QColor(self._themeDatas["Background/Color"])

        # ScrollBar
        r = self.editor.geometry()
        w = qApp.style().pixelMetric(QStyle.PM_ScrollBarExtent)
        r.setWidth(w)
        r.moveRight(rect.right() - rect.left())
        self.scrollBar.setGeometry(r)
        # self.scrollBar.setVisible(False)
        self.hideWidget(self.scrollBar)
        p = self.scrollBar.palette()
        b = QBrush(self._background.copy(self.scrollBar.geometry()))
        p.setBrush(QPalette.Base, b)
        self.scrollBar.setPalette(p)

        self.scrollBar.setColor(self._bgcolor)

        # Left Panel
        r = self.locker.geometry()
        r.moveTopLeft(QPoint(
                0,
                self.geometry().height() / 2 - r.height() / 2
        ))
        self.leftPanel.setGeometry(r)
        self.hideWidget(self.leftPanel)
        self.leftPanel.setColor(self._bgcolor)

        # Top / Bottom Panels
        r = QRect(0, 0, 0, 24)
        r.setWidth(rect.width())
        # r.moveLeft(rect.center().x() - r.width() / 2)
        self.topPanel.setGeometry(r)
        # self.topPanel.setVisible(False)
        self.hideWidget(self.topPanel)
        r.moveBottom(rect.bottom() - rect.top())
        self.bottomPanel.setGeometry(r)
        # self.bottomPanel.setVisible(False)
        self.hideWidget(self.bottomPanel)
        self.topPanel.setColor(self._bgcolor)
        self.bottomPanel.setColor(self._bgcolor)

        # Lst theme
        # p = self.lstThemes.palette()
        p = self.palette()
        p.setBrush(QPalette.Button, self._bgcolor)
        p.setBrush(QPalette.ButtonText, self._fgcolor)
        p.setBrush(QPalette.WindowText, self._fgcolor)

        for panel in (self.bottomPanel, self.topPanel, self.leftPanel):
            for i in range(panel.layout().count()):
                item = panel.layout().itemAt(i)
                if item.widget():
                    item.widget().setPalette(p)
        # self.lstThemes.setPalette(p)
        # self.lblWC.setPalette(p)

        self.update()
        self.editor.centerCursor()

    def paintEvent(self, event):
        if self._background:
            painter = QPainter(self)
            painter.setClipRegion(event.region())
            painter.drawPixmap(event.rect(), self._background, event.rect())
            painter.end()

    def resizeEvent(self, event):
        self.updateTheme()

    def keyPressEvent(self, event):
        if event.key() in [Qt.Key_Escape, Qt.Key_F11] and \
                not self._locked:
            # print("Leaving fullScreenEditor via keyPressEvent", flush=True)
            self.showNormal()
            self.close()
        elif (event.modifiers() & Qt.AltModifier) and \
                event.key() in [Qt.Key_PageUp, Qt.Key_PageDown, Qt.Key_Left, Qt.Key_Right]:
            if event.key() in [Qt.Key_PageUp, Qt.Key_Left]:
                success = self.switchPreviousItem()
            if event.key() in [Qt.Key_PageDown, Qt.Key_Right]:
                success = self.switchNextItem()
            if not success:
                QWidget.keyPressEvent(self, event)
        else:
            QWidget.keyPressEvent(self, event)

    def mouseMoveEvent(self, event):
        r = self.geometry()

        for w in [self.scrollBar, self.topPanel,
                  self.bottomPanel, self.leftPanel]:
            # w.setVisible(w.geometry().contains(event.pos()))
            if self._geometries[w].contains(event.pos()):
                self.showWidget(w)
            else:
                self.hideWidget(w)

    def hideWidget(self, widget):
        if widget not in self._geometries:
            self._geometries[widget] = widget.geometry()

        if hasattr(widget, "_autoHide") and not widget._autoHide:
            return

        # Hides widget in the bottom right corner
        widget.move(self.geometry().bottomRight() + QPoint(1, 1))

    def showWidget(self, widget):
        if widget in self._geometries:
            widget.move(self._geometries[widget].topLeft())

    def eventFilter(self, obj, event):
        if obj == self.editor and event.type() == QEvent.Enter:
            for w in [self.scrollBar, self.topPanel,
                      self.bottomPanel, self.leftPanel]:
                # w.setVisible(False)
                self.hideWidget(w)
        return QWidget.eventFilter(self, obj, event)

    def dataChanged(self, topLeft, bottomRight):
        # This is called sometimes after self has been destroyed. Don't know why.
        if not self or not self._index:
            return
        if topLeft.row() <= self._index.row() <= bottomRight.row():
            self.updateStatusBar()

    def updateTopBar(self):
        item = self._index.internalPointer()
        previousItem = self.previousTextItem(item)
        nextItem = self.nextTextItem(item)
        self.btnPrevious.setEnabled(previousItem is not None)
        self.btnNext.setEnabled(nextItem is not None)
        self.wPath.setItem(item)

    def updateStatusBar(self):
        if self._index:
            item = self._index.internalPointer()

        wc = item.data(Outline.wordCount)
        goal = item.data(Outline.goal)
        pg = item.data(Outline.goalPercentage)

        if goal:
            if settings.fullscreenSettings.get("progress-auto-show", True):
                self.lblProgress.show()
            self.lblWC.setText(self.tr("{} words / {}").format(wc, goal))
        else:
            if settings.fullscreenSettings.get("progress-auto-show", True):
                self.lblProgress.hide()
            self.lblWC.setText(self.tr("{} words").format(wc))
            pg = 0
        rect = self.lblProgress.geometry()
        rect = QRect(QPoint(0, 0), rect.size())
        self.px = QPixmap(rect.size())
        self.px.fill(Qt.transparent)
        p = QPainter(self.px)
        drawProgress(p, rect, pg, 2)
        p.end()
        self.lblProgress.setPixmap(self.px)

        self.locker.setWordCount(wc)
        # If there's a goal, then we update the locker target's number of word accordingly
        # (also if there is a word count, we deduce it.
        if goal and not self.locker.isLocked():
            if wc and goal - wc > 0:
                self.locker.spnWordTarget.setValue(goal - wc)
            elif not wc:
                self.locker.spnWordTarget.setValue(goal)

    def setCurrentModelIndex(self, index):
        self._index = index
        self.editor.setCurrentModelIndex(index)
        self.updateTopBar()
        self.updateStatusBar()

    def switchPreviousItem(self):
        item = self._index.internalPointer()
        previousItem = self.previousTextItem(item)
        if previousItem:
            self.setCurrentModelIndex(previousItem.index())
            return True
        return False

    def switchNextItem(self):
        item = self._index.internalPointer()
        nextItem = self.nextTextItem(item)
        if nextItem:
            self.setCurrentModelIndex(nextItem.index())
            return True
        return False

    def switchToItem(self, item):
        item = self.firstTextItem(item)
        if item:
            self.setCurrentModelIndex(item.index())
        
    def createNewText(self):
        item = self._index.internalPointer()
        newItem = outlineItem(title=qApp.translate("outlineBasics", "New"), _type=settings.defaultTextType)
        self._index.model().insertItem(newItem, item.row() + 1, item.parent().index())
        self.setCurrentModelIndex(newItem.index())

    def previousModelItem(self, item):
        parent = item.parent()
        if not parent:
            # Root has no sibling
            return None

        row = parent.childItems.index(item)
        if row > 0:
            return parent.child(row - 1)
        return self.previousModelItem(parent)

    def nextModelItem(self, item):
        parent = item.parent()
        if not parent:
            # Root has no sibling
            return None

        row = parent.childItems.index(item)
        if row + 1 < parent.childCount():
            return parent.child(row + 1)
        return self.nextModelItem(parent)

    def previousTextItem(self, item):
        previous = self.previousModelItem(item)

        while previous:
            last = self.lastTextItem(previous)
            if last:
                return last
            previous = self.previousModelItem(previous)
        return None
        
    def nextTextItem(self, item):
        if item.isFolder() and item.childCount() > 0:
            next = item.child(0)
        else:
            next = self.nextModelItem(item)

        while next:
            first = self.firstTextItem(next)
            if first:
                return first
            next = self.nextModelItem(next)
        return None

    def firstTextItem(self, item):
        if item.isText():
            return item
        for child in item.children():
            first = self.firstTextItem(child)
            if first:
                return first
        return None

    def lastTextItem(self, item):
        if item.isText():
            return item
        for child in reversed(item.children()):
            last = self.lastTextItem(child)
            if last:
                return last
        return None
Beispiel #14
0
class UVLightV2(COMCUPluginBase):
    def __init__(self, *args):
        super().__init__(BrickletUVLightV2, *args)

        self.uv_light = self.device

        self.cbe_uva = CallbackEmulator(self.uv_light.get_uva,
                                        None,
                                        self.cb_uva,
                                        self.increase_error_count)

        self.cbe_uvb = CallbackEmulator(self.uv_light.get_uvb,
                                        None,
                                        self.cb_uvb,
                                        self.increase_error_count)

        self.cbe_uvi = CallbackEmulator(self.uv_light.get_uvi,
                                        None,
                                        self.cb_uvi,
                                        self.increase_error_count)

        self.index_label = IndexLabel(' UVI: ? ')
        self.index_label.setText('0.0')

        self.current_uva = CurveValueWrapper()
        self.current_uvb = CurveValueWrapper()

        plots = [('UVA', Qt.red, self.current_uva, '{} mW/m²'.format),
                 ('UVB', Qt.darkGreen, self.current_uvb, '{} mW/m²'.format)]

        self.plot_widget = PlotWidget('UV [mW/m²]', plots, extra_key_widgets=[self.index_label], y_resolution=0.1)

        self.time_label = QLabel('Integration Time:')
        self.time_combo = QComboBox()
        self.time_combo.addItem("50 ms", BrickletUVLightV2.INTEGRATION_TIME_50MS)
        self.time_combo.addItem("100 ms", BrickletUVLightV2.INTEGRATION_TIME_100MS)
        self.time_combo.addItem("200 ms", BrickletUVLightV2.INTEGRATION_TIME_200MS)
        self.time_combo.addItem("400 ms", BrickletUVLightV2.INTEGRATION_TIME_400MS)
        self.time_combo.addItem("800 ms", BrickletUVLightV2.INTEGRATION_TIME_800MS)
        self.time_combo.currentIndexChanged.connect(self.new_config)

        self.saturation_label = QLabel('Sensor is saturated, choose a shorter integration time!')
        self.saturation_label.setStyleSheet('QLabel { color : red }')
        self.saturation_label.hide()

        hlayout = QHBoxLayout()
        hlayout.addWidget(self.time_label)
        hlayout.addWidget(self.time_combo)
        hlayout.addStretch()
        hlayout.addWidget(self.saturation_label)

        line = QFrame()
        line.setObjectName("line")
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)

        layout = QVBoxLayout(self)
        layout.addWidget(self.plot_widget)
        layout.addWidget(line)
        layout.addLayout(hlayout)

    def start(self):
        async_call(self.uv_light.get_configuration, None, self.get_configucation_async, self.increase_error_count)

        self.cbe_uva.set_period(100)
        self.cbe_uvb.set_period(100)
        self.cbe_uvi.set_period(100)

        self.plot_widget.stop = False

    def stop(self):
        self.cbe_uva.set_period(0)
        self.cbe_uvb.set_period(0)
        self.cbe_uvi.set_period(0)

        self.plot_widget.stop = True

    def destroy(self):
        pass

    @staticmethod
    def has_device_identifier(device_identifier):
        return device_identifier == BrickletUVLightV2.DEVICE_IDENTIFIER

    def get_configucation_async(self, integration_time):
        self.time_combo.setCurrentIndex(self.time_combo.findData(integration_time))

    def new_config(self, value):
        try:
            self.uv_light.set_configuration(self.time_combo.itemData(self.time_combo.currentIndex()))
        except:
            pass

    def cb_uva(self, uva):
        self.saturation_label.setVisible(uva < 0)

        if uva < 0: # saturated
            return

        self.current_uva.value = uva / 10.0

    def cb_uvb(self, uvb):
        self.saturation_label.setVisible(uvb < 0)

        if uvb < 0: # saturated
            return

        self.current_uvb.value = uvb / 10.0

    def cb_uvi(self, uvi):
        self.saturation_label.setVisible(uvi < 0)

        if uvi < 0: # saturated
            return

        uvi = round(uvi / 10.0, 1)

        self.index_label.setText(str(uvi))

        if uvi < 2.5:
            background = 'green'
            color = 'white'
        elif uvi < 5.5:
            background = 'yellow'
            color = 'black'
        elif uvi < 7.5:
            background = 'orange'
            color = 'black'
        elif uvi < 10.5:
            background = 'red'
            color = 'white'
        else:
            background = 'magenta'
            color = 'white'

        self.index_label.setStyleSheet('QLabel {{ background : {0}; color : {1} }}'.format(background, color))
Beispiel #15
0
class BarometerV2(COMCUPluginBase):
    def __init__(self, *args):
        super().__init__(BrickletBarometerV2, *args)

        self.barometer = self.device

        self.cbe_air_pressure = CallbackEmulator(
            self, self.barometer.get_air_pressure, None, self.cb_air_pressure,
            self.increase_error_count)

        self.cbe_altitude = CallbackEmulator(self, self.barometer.get_altitude,
                                             None, self.cb_altitude,
                                             self.increase_error_count)

        self.cbe_temperature = CallbackEmulator(self,
                                                self.barometer.get_temperature,
                                                None, self.cb_temperature,
                                                self.increase_error_count)

        self.current_altitude = CurveValueWrapper()
        self.current_air_pressure = CurveValueWrapper()

        self.calibration = None
        self.btn_clear_graphs = QPushButton('Clear Graphs')
        self.btn_calibration = QPushButton('Calibration...')
        self.btn_calibration.clicked.connect(self.btn_calibration_clicked)

        self.lbl_temperature_value = QLabel('-')

        self.sbox_reference_air_pressure = QDoubleSpinBox()
        self.sbox_reference_air_pressure.setMinimum(260)
        self.sbox_reference_air_pressure.setMaximum(1260)
        self.sbox_reference_air_pressure.setDecimals(3)
        self.sbox_reference_air_pressure.setValue(1013.25)
        self.sbox_reference_air_pressure.setSingleStep(1)
        self.btn_use_current = QPushButton('Use Current')
        self.btn_use_current.clicked.connect(self.btn_use_current_clicked)
        self.sbox_reference_air_pressure.editingFinished.connect(
            self.sbox_reference_air_pressure_editing_finished)

        self.sbox_moving_avg_len_air_pressure = QSpinBox()
        self.sbox_moving_avg_len_air_pressure.setMinimum(1)
        self.sbox_moving_avg_len_air_pressure.setMaximum(1000)
        self.sbox_moving_avg_len_air_pressure.setSingleStep(1)
        self.sbox_moving_avg_len_air_pressure.setValue(100)
        self.sbox_moving_avg_len_air_pressure.editingFinished.connect(
            self.sbox_moving_avg_len_editing_finished)

        self.sbox_moving_avg_len_temperature = QSpinBox()
        self.sbox_moving_avg_len_temperature.setMinimum(1)
        self.sbox_moving_avg_len_temperature.setMaximum(1000)
        self.sbox_moving_avg_len_temperature.setSingleStep(1)
        self.sbox_moving_avg_len_temperature.setValue(100)
        self.sbox_moving_avg_len_temperature.editingFinished.connect(
            self.sbox_moving_avg_len_editing_finished)

        plot_config_air_pressure = [
            ('Air Pressure', Qt.red, self.current_air_pressure,
             '{:.3f} hPa (QFE)'.format)
        ]

        plot_config_altitude = [
            ('Altitude', Qt.darkGreen, self.current_altitude,
             lambda value: '{:.3f} m ({:.3f} ft)'.format(
                 value, value / METER_TO_FEET_DIVISOR))
        ]

        self.plot_widget_air_pressure = PlotWidget('Air Pressure [hPa]',
                                                   plot_config_air_pressure,
                                                   self.btn_clear_graphs,
                                                   y_resolution=0.001)

        self.plot_widget_altitude = PlotWidget('Altitude [m]',
                                               plot_config_altitude,
                                               self.btn_clear_graphs,
                                               y_resolution=0.001)

        self.combo_data_rate = QComboBox()
        self.combo_data_rate.addItem('Off', BrickletBarometerV2.DATA_RATE_OFF)
        self.combo_data_rate.addItem('1 Hz', BrickletBarometerV2.DATA_RATE_1HZ)
        self.combo_data_rate.addItem('10 Hz',
                                     BrickletBarometerV2.DATA_RATE_10HZ)
        self.combo_data_rate.addItem('25 Hz',
                                     BrickletBarometerV2.DATA_RATE_25HZ)
        self.combo_data_rate.addItem('50 Hz',
                                     BrickletBarometerV2.DATA_RATE_50HZ)
        self.combo_data_rate.addItem('75 Hz',
                                     BrickletBarometerV2.DATA_RATE_75HZ)
        self.combo_data_rate.currentIndexChanged.connect(
            self.new_sensor_config)

        self.combo_air_pressure_low_pass_filter = QComboBox()
        self.combo_air_pressure_low_pass_filter.addItem(
            'Off', BrickletBarometerV2.LOW_PASS_FILTER_OFF)
        self.combo_air_pressure_low_pass_filter.addItem(
            '1/9th', BrickletBarometerV2.LOW_PASS_FILTER_1_9TH)
        self.combo_air_pressure_low_pass_filter.addItem(
            '1/20th', BrickletBarometerV2.LOW_PASS_FILTER_1_20TH)
        self.combo_air_pressure_low_pass_filter.currentIndexChanged.connect(
            self.new_sensor_config)

        # Layout
        layout_h1 = QHBoxLayout()
        layout_h1.addWidget(self.plot_widget_air_pressure)
        layout_h1.addWidget(self.plot_widget_altitude)

        layout = QVBoxLayout(self)
        layout.addLayout(layout_h1)

        line = QFrame()
        line.setObjectName("line")
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)

        layout.addWidget(line)

        layout_h2 = QHBoxLayout()
        layout_h2.addWidget(QLabel('Reference Air Pressure [hPa]:'))
        layout_h2.addWidget(self.sbox_reference_air_pressure)
        layout_h2.addWidget(self.btn_use_current)
        layout_h2.addStretch()
        layout_h2.addWidget(QLabel('Temperature:'))
        layout_h2.addWidget(self.lbl_temperature_value)
        layout_h2.addStretch()
        layout_h2.addWidget(self.btn_clear_graphs)

        layout.addLayout(layout_h2)

        layout_h3 = QHBoxLayout()
        layout_h3.addWidget(QLabel('Air Pressure Moving Average Length:'))
        layout_h3.addWidget(self.sbox_moving_avg_len_air_pressure)
        layout_h3.addStretch()
        layout_h3.addWidget(QLabel('Temperature Moving Average Length:'))
        layout_h3.addWidget(self.sbox_moving_avg_len_temperature)

        layout.addLayout(layout_h3)

        layout_h4 = QHBoxLayout()
        layout_h4.addWidget(QLabel('Data Rate:'))
        layout_h4.addWidget(self.combo_data_rate)
        layout_h4.addStretch()
        layout_h4.addWidget(QLabel('Air Pressure Low Pass Filter:'))
        layout_h4.addWidget(self.combo_air_pressure_low_pass_filter)
        layout_h4.addStretch()
        layout_h4.addWidget(self.btn_calibration)

        layout.addLayout(layout_h4)

    def start(self):
        async_call(self.barometer.get_reference_air_pressure, None,
                   self.get_reference_air_pressure_async,
                   self.increase_error_count)
        async_call(self.barometer.get_moving_average_configuration, None,
                   self.get_moving_average_configuration_async,
                   self.increase_error_count)
        async_call(self.barometer.get_sensor_configuration, None,
                   self.get_sensor_configuration_async,
                   self.increase_error_count)

        self.cbe_air_pressure.set_period(50)
        self.cbe_altitude.set_period(50)
        self.cbe_temperature.set_period(100)

        self.plot_widget_air_pressure.stop = False
        self.plot_widget_altitude.stop = False

    def stop(self):
        self.cbe_air_pressure.set_period(0)
        self.cbe_altitude.set_period(0)
        self.cbe_temperature.set_period(0)

        self.plot_widget_air_pressure.stop = True
        self.plot_widget_altitude.stop = True

    def destroy(self):
        if self.calibration != None:
            self.calibration.close()

    @staticmethod
    def has_device_identifier(device_identifier):
        return device_identifier == BrickletBarometerV2.DEVICE_IDENTIFIER

    def cb_air_pressure(self, air_pressure):
        self.current_air_pressure.value = air_pressure / 1000.0

    def cb_altitude(self, altitude):
        self.current_altitude.value = altitude / 1000.0

    def cb_temperature(self, temperature):
        self.lbl_temperature_value.setText('{:.2f} °C'.format(temperature /
                                                              100.0))

    def get_reference_air_pressure_async(self, air_pressure):
        self.sbox_reference_air_pressure.setValue(air_pressure / 1000.0)

    def btn_use_current_clicked(self):
        self.barometer.set_reference_air_pressure(0)
        async_call(self.barometer.get_reference_air_pressure, None,
                   self.get_reference_air_pressure_async,
                   self.increase_error_count)

    def sbox_reference_air_pressure_editing_finished(self):
        self.barometer.set_reference_air_pressure(
            self.sbox_reference_air_pressure.value() * 1000.0)

    def get_moving_average_configuration_async(self, avg):
        m_avg_air_pressure, m_avg_temperature = avg

        self.sbox_moving_avg_len_air_pressure.setValue(m_avg_air_pressure)
        self.sbox_moving_avg_len_temperature.setValue(m_avg_temperature)

    def sbox_moving_avg_len_editing_finished(self):
        self.barometer.set_moving_average_configuration(
            self.sbox_moving_avg_len_air_pressure.value(),
            self.sbox_moving_avg_len_temperature.value())

    def get_sensor_configuration_async(self, config):
        data_rate, air_pressure_low_pass_filter = config

        self.combo_data_rate.setCurrentIndex(
            self.combo_data_rate.findData(data_rate))
        self.combo_air_pressure_low_pass_filter.setCurrentIndex(
            self.combo_air_pressure_low_pass_filter.findData(
                air_pressure_low_pass_filter))

    def new_sensor_config(self):
        data_rate = self.combo_data_rate.itemData(
            self.combo_data_rate.currentIndex())
        air_pressure_low_pass_filter = self.combo_air_pressure_low_pass_filter.itemData(
            self.combo_air_pressure_low_pass_filter.currentIndex())

        self.barometer.set_sensor_configuration(data_rate,
                                                air_pressure_low_pass_filter)

    def btn_calibration_clicked(self):
        if self.calibration == None:
            self.calibration = Calibration(self)

        self.btn_calibration.setEnabled(False)
        self.calibration.show()
Beispiel #16
0
class BarometerV2(COMCUPluginBase):
    def __init__(self, *args):
        super().__init__(BrickletBarometerV2, *args)

        self.barometer = self.device

        self.cbe_air_pressure = CallbackEmulator(self.barometer.get_air_pressure,
                                                 None,
                                                 self.cb_air_pressure,
                                                 self.increase_error_count)

        self.cbe_altitude = CallbackEmulator(self.barometer.get_altitude,
                                             None,
                                             self.cb_altitude,
                                             self.increase_error_count)

        self.cbe_temperature = CallbackEmulator(self.barometer.get_temperature,
                                                None,
                                                self.cb_temperature,
                                                self.increase_error_count)

        self.current_altitude = CurveValueWrapper()
        self.current_air_pressure = CurveValueWrapper()

        self.calibration = None
        self.btn_clear_graphs = QPushButton('Clear Graphs')
        self.btn_calibration = QPushButton('Calibration...')
        self.btn_calibration.clicked.connect(self.btn_calibration_clicked)

        self.lbl_temperature_value = QLabel('-')

        self.sbox_reference_air_pressure = QDoubleSpinBox()
        self.sbox_reference_air_pressure.setMinimum(260)
        self.sbox_reference_air_pressure.setMaximum(1260)
        self.sbox_reference_air_pressure.setDecimals(3)
        self.sbox_reference_air_pressure.setValue(1013.25)
        self.sbox_reference_air_pressure.setSingleStep(1)
        self.btn_use_current = QPushButton('Use Current')
        self.btn_use_current.clicked.connect(self.btn_use_current_clicked)
        self.sbox_reference_air_pressure.editingFinished.connect(self.sbox_reference_air_pressure_editing_finished)

        self.sbox_moving_avg_len_air_pressure = QSpinBox()
        self.sbox_moving_avg_len_air_pressure.setMinimum(1)
        self.sbox_moving_avg_len_air_pressure.setMaximum(1000)
        self.sbox_moving_avg_len_air_pressure.setSingleStep(1)
        self.sbox_moving_avg_len_air_pressure.setValue(100)
        self.sbox_moving_avg_len_air_pressure.editingFinished.connect(self.sbox_moving_avg_len_editing_finished)

        self.sbox_moving_avg_len_temperature = QSpinBox()
        self.sbox_moving_avg_len_temperature.setMinimum(1)
        self.sbox_moving_avg_len_temperature.setMaximum(1000)
        self.sbox_moving_avg_len_temperature.setSingleStep(1)
        self.sbox_moving_avg_len_temperature.setValue(100)
        self.sbox_moving_avg_len_temperature.editingFinished.connect(self.sbox_moving_avg_len_editing_finished)

        plot_config_air_pressure = [('Air Pressure',
                                     Qt.red,
                                     self.current_air_pressure,
                                     '{:.3f} mbar (QFE)'.format)]

        plot_config_altitude = [('Altitude',
                                 Qt.darkGreen,
                                 self.current_altitude,
                                 lambda value: '{:.3f} m ({:.3f} ft)'.format(value, value / METER_TO_FEET_DIVISOR))]

        self.plot_widget_air_pressure = PlotWidget('Air Pressure [mbar]',
                                                   plot_config_air_pressure,
                                                   self.btn_clear_graphs,
                                                   y_resolution=0.001)

        self.plot_widget_altitude = PlotWidget('Altitude [m]',
                                               plot_config_altitude,
                                               self.btn_clear_graphs,
                                               y_resolution=0.001)

        self.combo_data_rate = QComboBox()
        self.combo_data_rate.addItem('Off', BrickletBarometerV2.DATA_RATE_OFF)
        self.combo_data_rate.addItem('1 Hz', BrickletBarometerV2.DATA_RATE_1HZ)
        self.combo_data_rate.addItem('10 Hz', BrickletBarometerV2.DATA_RATE_10HZ)
        self.combo_data_rate.addItem('25 Hz', BrickletBarometerV2.DATA_RATE_25HZ)
        self.combo_data_rate.addItem('50 Hz', BrickletBarometerV2.DATA_RATE_50HZ)
        self.combo_data_rate.addItem('75 Hz', BrickletBarometerV2.DATA_RATE_75HZ)
        self.combo_data_rate.currentIndexChanged.connect(self.new_sensor_config)

        self.combo_air_pressure_low_pass_filter = QComboBox()
        self.combo_air_pressure_low_pass_filter.addItem('Off', BrickletBarometerV2.LOW_PASS_FILTER_OFF)
        self.combo_air_pressure_low_pass_filter.addItem('1/9th', BrickletBarometerV2.LOW_PASS_FILTER_1_9TH)
        self.combo_air_pressure_low_pass_filter.addItem('1/20th', BrickletBarometerV2.LOW_PASS_FILTER_1_20TH)
        self.combo_air_pressure_low_pass_filter.currentIndexChanged.connect(self.new_sensor_config)

        # Layout
        layout_h1 = QHBoxLayout()
        layout_h1.addWidget(self.plot_widget_air_pressure)
        layout_h1.addWidget(self.plot_widget_altitude)

        layout = QVBoxLayout(self)
        layout.addLayout(layout_h1)

        line = QFrame()
        line.setObjectName("line")
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)

        layout.addWidget(line)

        layout_h2 = QHBoxLayout()
        layout_h2.addWidget(QLabel('Reference Air Pressure [mbar]:'))
        layout_h2.addWidget(self.sbox_reference_air_pressure)
        layout_h2.addWidget(self.btn_use_current)
        layout_h2.addStretch()
        layout_h2.addWidget(QLabel('Temperature:'))
        layout_h2.addWidget(self.lbl_temperature_value)
        layout_h2.addStretch()
        layout_h2.addWidget(self.btn_clear_graphs)

        layout.addLayout(layout_h2)

        layout_h3 = QHBoxLayout()
        layout_h3.addWidget(QLabel('Air Pressure Moving Average Length:'))
        layout_h3.addWidget(self.sbox_moving_avg_len_air_pressure)
        layout_h3.addStretch()
        layout_h3.addWidget(QLabel('Temperature Moving Average Length:'))
        layout_h3.addWidget(self.sbox_moving_avg_len_temperature)

        layout.addLayout(layout_h3)

        layout_h4 = QHBoxLayout()
        layout_h4.addWidget(QLabel('Data Rate:'))
        layout_h4.addWidget(self.combo_data_rate)
        layout_h4.addStretch()
        layout_h4.addWidget(QLabel('Air Pressure Low Pass Filter:'))
        layout_h4.addWidget(self.combo_air_pressure_low_pass_filter)
        layout_h4.addStretch()
        layout_h4.addWidget(self.btn_calibration)

        layout.addLayout(layout_h4)

    def start(self):
        async_call(self.barometer.get_reference_air_pressure, None, self.get_reference_air_pressure_async, self.increase_error_count)
        async_call(self.barometer.get_moving_average_configuration, None, self.get_moving_average_configuration_async, self.increase_error_count)
        async_call(self.barometer.get_sensor_configuration, None, self.get_sensor_configuration_async, self.increase_error_count)

        self.cbe_air_pressure.set_period(50)
        self.cbe_altitude.set_period(50)
        self.cbe_temperature.set_period(100)

        self.plot_widget_air_pressure.stop = False
        self.plot_widget_altitude.stop = False

    def stop(self):
        self.cbe_air_pressure.set_period(0)
        self.cbe_altitude.set_period(0)
        self.cbe_temperature.set_period(0)

        self.plot_widget_air_pressure.stop = True
        self.plot_widget_altitude.stop = True

    def destroy(self):
        if self.calibration != None:
            self.calibration.close()

    @staticmethod
    def has_device_identifier(device_identifier):
        return device_identifier == BrickletBarometerV2.DEVICE_IDENTIFIER

    def cb_air_pressure(self, air_pressure):
        self.current_air_pressure.value = air_pressure / 1000.0

    def cb_altitude(self, altitude):
        self.current_altitude.value = altitude / 1000.0

    def cb_temperature(self, temperature):
        self.lbl_temperature_value.setText('{:.2f} °C'.format(temperature / 100.0))

    def get_reference_air_pressure_async(self, air_pressure):
        self.sbox_reference_air_pressure.setValue(air_pressure / 1000.0)

    def btn_use_current_clicked(self):
        self.barometer.set_reference_air_pressure(0)
        async_call(self.barometer.get_reference_air_pressure, None, self.get_reference_air_pressure_async, self.increase_error_count)

    def sbox_reference_air_pressure_editing_finished(self):
        self.barometer.set_reference_air_pressure(self.sbox_reference_air_pressure.value() * 1000.0)

    def get_moving_average_configuration_async(self, avg):
        m_avg_air_pressure, m_avg_temperature = avg

        self.sbox_moving_avg_len_air_pressure.setValue(m_avg_air_pressure)
        self.sbox_moving_avg_len_temperature.setValue(m_avg_temperature)

    def sbox_moving_avg_len_editing_finished(self):
        self.barometer.set_moving_average_configuration(self.sbox_moving_avg_len_air_pressure.value(),
                                                        self.sbox_moving_avg_len_temperature.value())

    def get_sensor_configuration_async(self, config):
        data_rate, air_pressure_low_pass_filter = config

        self.combo_data_rate.setCurrentIndex(self.combo_data_rate.findData(data_rate))
        self.combo_air_pressure_low_pass_filter.setCurrentIndex(self.combo_air_pressure_low_pass_filter.findData(air_pressure_low_pass_filter))

    def new_sensor_config(self):
        data_rate = self.combo_data_rate.itemData(self.combo_data_rate.currentIndex())
        air_pressure_low_pass_filter = self.combo_air_pressure_low_pass_filter.itemData(self.combo_air_pressure_low_pass_filter.currentIndex())

        self.barometer.set_sensor_configuration(data_rate, air_pressure_low_pass_filter)

    def btn_calibration_clicked(self):
        if self.calibration == None:
            self.calibration = Calibration(self)

        self.btn_calibration.setEnabled(False)
        self.calibration.show()
Beispiel #17
0
class SettingsDialog(QDialog):
    """
    The dialog allowing an user to configure the plugin. It has multiple tabs
    used to group the settings by category (general, network, etc.).
    """
    def __init__(self, plugin):
        super(SettingsDialog, self).__init__()
        self._plugin = plugin

        # General setup of the dialog
        self._plugin.logger.debug("Showing settings dialog")
        self.setWindowTitle("Settings")
        icon_path = self._plugin.plugin_resource("settings.png")
        self.setWindowIcon(QIcon(icon_path))
        self.setWindowFlags(self.windowFlags() & ~Qt.WindowCloseButtonHint)

        window_widget = QWidget(self)
        window_layout = QVBoxLayout(window_widget)
        tabs = QTabWidget(window_widget)
        window_layout.addWidget(tabs)

        # "General Settings" tab
        tab = QWidget(tabs)
        layout = QFormLayout(tab)
        layout.setFormAlignment(Qt.AlignVCenter)
        tabs.addTab(tab, "General Settings")

        user_widget = QWidget(tab)
        user_layout = QHBoxLayout(user_widget)
        layout.addRow(user_widget)

        # User color
        self._color_button = QPushButton("")
        self._color_button.setFixedSize(50, 30)

        def color_button_activated(_):
            self._set_color(qt_color=QColorDialog.getColor().rgb())

        self._color = self._plugin.config["user"]["color"]
        self._set_color(ida_color=self._color)
        self._color_button.clicked.connect(color_button_activated)
        user_layout.addWidget(self._color_button)

        # User name
        self._name_line_edit = QLineEdit()
        name = self._plugin.config["user"]["name"]
        self._name_line_edit.setText(name)
        user_layout.addWidget(self._name_line_edit)

        text = "Disable all user cursors"
        self._disable_all_cursors_checkbox = QCheckBox(text)
        layout.addRow(self._disable_all_cursors_checkbox)
        navbar_checked = not self._plugin.config["cursors"]["navbar"]
        funcs_checked = not self._plugin.config["cursors"]["funcs"]
        disasm_checked = not self._plugin.config["cursors"]["disasm"]
        all_checked = navbar_checked and funcs_checked and disasm_checked
        self._disable_all_cursors_checkbox.setChecked(all_checked)

        def state_changed(state):
            enabled = state == Qt.Unchecked
            self._disable_navbar_cursors_checkbox.setChecked(not enabled)
            self._disable_navbar_cursors_checkbox.setEnabled(enabled)
            self._disable_funcs_cursors_checkbox.setChecked(not enabled)
            self._disable_funcs_cursors_checkbox.setEnabled(enabled)
            self._disable_disasm_cursors_checkbox.setChecked(not enabled)
            self._disable_disasm_cursors_checkbox.setEnabled(enabled)

        self._disable_all_cursors_checkbox.stateChanged.connect(state_changed)

        style_sheet = """QCheckBox{ margin-left: 20px; }"""

        text = "Disable navigation bar user cursors"
        self._disable_navbar_cursors_checkbox = QCheckBox(text)
        layout.addRow(self._disable_navbar_cursors_checkbox)
        self._disable_navbar_cursors_checkbox.setChecked(navbar_checked)
        self._disable_navbar_cursors_checkbox.setEnabled(not all_checked)
        self._disable_navbar_cursors_checkbox.setStyleSheet(style_sheet)

        text = "Disable functions window user cursors"
        self._disable_funcs_cursors_checkbox = QCheckBox(text)
        layout.addRow(self._disable_funcs_cursors_checkbox)
        self._disable_funcs_cursors_checkbox.setChecked(funcs_checked)
        self._disable_funcs_cursors_checkbox.setEnabled(not all_checked)
        self._disable_funcs_cursors_checkbox.setStyleSheet(style_sheet)

        text = "Disable disassembly view user cursors"
        self._disable_disasm_cursors_checkbox = QCheckBox(text)
        layout.addRow(self._disable_disasm_cursors_checkbox)
        self._disable_disasm_cursors_checkbox.setChecked(disasm_checked)
        self._disable_disasm_cursors_checkbox.setEnabled(not all_checked)
        self._disable_disasm_cursors_checkbox.setStyleSheet(style_sheet)

        text = "Allow other users to send notifications"
        self._notifications_checkbox = QCheckBox(text)
        layout.addRow(self._notifications_checkbox)
        checked = self._plugin.config["user"]["notifications"]
        self._notifications_checkbox.setChecked(checked)

        # Log level
        debug_level_label = QLabel("Logging level: ")
        self._debug_level_combo_box = QComboBox()
        self._debug_level_combo_box.addItem("CRITICAL", logging.CRITICAL)
        self._debug_level_combo_box.addItem("ERROR", logging.ERROR)
        self._debug_level_combo_box.addItem("WARNING", logging.WARNING)
        self._debug_level_combo_box.addItem("INFO", logging.INFO)
        self._debug_level_combo_box.addItem("DEBUG", logging.DEBUG)
        self._debug_level_combo_box.addItem("TRACE", logging.TRACE)
        level = self._plugin.config["level"]
        index = self._debug_level_combo_box.findData(level)
        self._debug_level_combo_box.setCurrentIndex(index)
        layout.addRow(debug_level_label, self._debug_level_combo_box)

        # "Network Settings" tab
        tab = QWidget(tabs)
        layout = QVBoxLayout(tab)
        tab.setLayout(layout)
        tabs.addTab(tab, "Network Settings")

        top_widget = QWidget(tab)
        layout.addWidget(top_widget)
        top_layout = QHBoxLayout(top_widget)

        self._servers = list(self._plugin.config["servers"])
        self._servers_table = QTableWidget(len(self._servers), 3, self)
        top_layout.addWidget(self._servers_table)
        for i, server in enumerate(self._servers):
            # Server host and port
            item = QTableWidgetItem("%s:%d" % (server["host"], server["port"]))
            item.setData(Qt.UserRole, server)
            item.setFlags(item.flags() & ~Qt.ItemIsEditable)
            # XXX - This prevented editing a server entry for your current
            # server because the row cannot be selected properly with
            # SingleSelection option selected
            #if self._plugin.network.server == server:
            #    item.setFlags((item.flags() & ~Qt.ItemIsSelectable))
            self._servers_table.setItem(i, 0, item)

            # Server has SSL enabled?
            ssl_checkbox = QTableWidgetItem()
            state = Qt.Unchecked if server["no_ssl"] else Qt.Checked
            ssl_checkbox.setCheckState(state)
            ssl_checkbox.setFlags((ssl_checkbox.flags() & ~Qt.ItemIsEditable))
            ssl_checkbox.setFlags(
                (ssl_checkbox.flags() & ~Qt.ItemIsUserCheckable))
            self._servers_table.setItem(i, 1, ssl_checkbox)

            # Auto-connect enabled?
            auto_checkbox = QTableWidgetItem()
            state = Qt.Unchecked if not server["auto_connect"] else Qt.Checked
            auto_checkbox.setCheckState(state)
            auto_checkbox.setFlags(
                (auto_checkbox.flags() & ~Qt.ItemIsEditable))
            auto_checkbox.setFlags(
                (auto_checkbox.flags() & ~Qt.ItemIsUserCheckable))
            self._servers_table.setItem(i, 2, auto_checkbox)

        self._servers_table.setHorizontalHeaderLabels(
            ("Servers", "SSL", "Auto"))
        horizontal_header = self._servers_table.horizontalHeader()
        horizontal_header.setSectionsClickable(False)
        horizontal_header.setSectionResizeMode(0, QHeaderView.Stretch)
        horizontal_header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
        horizontal_header.setSectionResizeMode(2, QHeaderView.ResizeToContents)
        self._servers_table.verticalHeader().setVisible(False)
        self._servers_table.setSelectionBehavior(QTableWidget.SelectRows)
        self._servers_table.setSelectionMode(QTableWidget.SingleSelection)
        self._servers_table.itemClicked.connect(self._server_clicked)
        self._servers_table.itemDoubleClicked.connect(
            self._server_double_clicked)
        self._servers_table.setMaximumHeight(100)

        buttons_widget = QWidget(top_widget)
        buttons_layout = QVBoxLayout(buttons_widget)
        top_layout.addWidget(buttons_widget)

        # Add server button
        self._add_button = QPushButton("Add Server")
        self._add_button.clicked.connect(self._add_button_clicked)
        buttons_layout.addWidget(self._add_button)

        # Edit server button
        self._edit_button = QPushButton("Edit Server")
        self._edit_button.setEnabled(False)
        self._edit_button.clicked.connect(self._edit_button_clicked)
        buttons_layout.addWidget(self._edit_button)

        # Delete server button
        self._delete_button = QPushButton("Delete Server")
        self._delete_button.setEnabled(False)
        self._delete_button.clicked.connect(self._delete_button_clicked)
        buttons_layout.addWidget(self._delete_button)

        bottom_widget = QWidget(tab)
        bottom_layout = QFormLayout(bottom_widget)
        layout.addWidget(bottom_widget)

        # TCP Keep-Alive settings
        keep_cnt_label = QLabel("Keep-Alive Count: ")
        self._keep_cnt_spin_box = QSpinBox(bottom_widget)
        self._keep_cnt_spin_box.setRange(0, 86400)
        self._keep_cnt_spin_box.setValue(self._plugin.config["keep"]["cnt"])
        self._keep_cnt_spin_box.setSuffix(" packets")
        bottom_layout.addRow(keep_cnt_label, self._keep_cnt_spin_box)

        keep_intvl_label = QLabel("Keep-Alive Interval: ")
        self._keep_intvl_spin_box = QSpinBox(bottom_widget)
        self._keep_intvl_spin_box.setRange(0, 86400)
        self._keep_intvl_spin_box.setValue(
            self._plugin.config["keep"]["intvl"])
        self._keep_intvl_spin_box.setSuffix(" seconds")
        bottom_layout.addRow(keep_intvl_label, self._keep_intvl_spin_box)

        keep_idle_label = QLabel("Keep-Alive Idle: ")
        self._keep_idle_spin_box = QSpinBox(bottom_widget)
        self._keep_idle_spin_box.setRange(0, 86400)
        self._keep_idle_spin_box.setValue(self._plugin.config["keep"]["idle"])
        self._keep_idle_spin_box.setSuffix(" seconds")
        bottom_layout.addRow(keep_idle_label, self._keep_idle_spin_box)

        # Buttons commons to all tabs
        actions_widget = QWidget(self)
        actions_layout = QHBoxLayout(actions_widget)

        # Cancel = do not save the changes and close the dialog
        def cancel(_):
            self.reject()

        cancel_button = QPushButton("Cancel")
        cancel_button.clicked.connect(cancel)
        actions_layout.addWidget(cancel_button)

        # Reset = reset all settings from all tabs to default values
        reset_button = QPushButton("Reset")
        reset_button.clicked.connect(self._reset)
        actions_layout.addWidget(reset_button)

        # Save = save the changes and close the dialog
        def save(_):
            self._commit()
            self.accept()

        save_button = QPushButton("Save")
        save_button.clicked.connect(save)
        actions_layout.addWidget(save_button)
        window_layout.addWidget(actions_widget)

        # Do not allow the user to resize the dialog
        self.setFixedSize(window_widget.sizeHint().width(),
                          window_widget.sizeHint().height())

    def _set_color(self, ida_color=None, qt_color=None):
        """Sets the color of the user color button."""
        # IDA represents colors as 0xBBGGRR
        if ida_color is not None:
            r = ida_color & 255
            g = (ida_color >> 8) & 255
            b = (ida_color >> 16) & 255

        # Qt represents colors as 0xRRGGBB
        if qt_color is not None:
            r = (qt_color >> 16) & 255
            g = (qt_color >> 8) & 255
            b = qt_color & 255

        ida_color = r | g << 8 | b << 16
        qt_color = r << 16 | g << 8 | b

        # Set the stylesheet of the button
        css = "QPushButton {background-color: #%06x; color: #%06x;}"
        self._color_button.setStyleSheet(css % (qt_color, qt_color))
        self._color = ida_color

    def _server_clicked(self, _):
        self._edit_button.setEnabled(True)
        self._delete_button.setEnabled(True)

    def _server_double_clicked(self, _):
        item = self._servers_table.selectedItems()[0]
        server = item.data(Qt.UserRole)
        # If not the current server, connect to it
        if (not self._plugin.network.connected
                or self._plugin.network.server != server):
            self._plugin.network.stop_server()
            self._plugin.network.connect(server)
        self.accept()

    def _add_button_clicked(self, _):
        dialog = ServerInfoDialog(self._plugin, "Add server")
        dialog.accepted.connect(partial(self._add_dialog_accepted, dialog))
        dialog.exec_()

    def _edit_button_clicked(self, _):
        selected = self._servers_table.selectedItems()
        if len(selected) == 0:
            self._plugin.logger.warning("No server selected")
            return
        item = selected[0]
        server = item.data(Qt.UserRole)
        dialog = ServerInfoDialog(self._plugin, "Edit server", server)
        dialog.accepted.connect(partial(self._edit_dialog_accepted, dialog))
        dialog.exec_()

    def _delete_button_clicked(self, _):
        item = self._servers_table.selectedItems()[0]
        server = item.data(Qt.UserRole)
        self._servers.remove(server)
        self._plugin.save_config()
        self._servers_table.removeRow(item.row())
        self.update()

    def _add_dialog_accepted(self, dialog):
        """Called when the dialog to add a server is accepted."""
        server = dialog.get_result()
        self._servers.append(server)
        row_count = self._servers_table.rowCount()
        self._servers_table.insertRow(row_count)

        new_server = QTableWidgetItem("%s:%d" %
                                      (server["host"], server["port"]))
        new_server.setData(Qt.UserRole, server)
        new_server.setFlags(new_server.flags() & ~Qt.ItemIsEditable)
        self._servers_table.setItem(row_count, 0, new_server)

        new_checkbox = QTableWidgetItem()
        state = Qt.Unchecked if server["no_ssl"] else Qt.Checked
        new_checkbox.setCheckState(state)
        new_checkbox.setFlags((new_checkbox.flags() & ~Qt.ItemIsEditable))
        new_checkbox.setFlags(new_checkbox.flags() & ~Qt.ItemIsUserCheckable)
        self._servers_table.setItem(row_count, 1, new_checkbox)
        self.update()

    def _edit_dialog_accepted(self, dialog):
        """Called when the dialog to edit a server is accepted."""
        server = dialog.get_result()
        item = self._servers_table.selectedItems()[0]
        self._servers[item.row()] = server

        item.setText("%s:%d" % (server["host"], server["port"]))
        item.setData(Qt.UserRole, server)
        item.setFlags(item.flags() & ~Qt.ItemIsEditable)

        checkbox = self._servers_table.item(item.row(), 1)
        state = Qt.Unchecked if server["no_ssl"] else Qt.Checked
        checkbox.setCheckState(state)
        self.update()

    def _reset(self, _):
        """Resets all the form elements to their default value."""
        config = self._plugin.default_config()

        self._name_line_edit.setText(config["user"]["name"])
        self._set_color(ida_color=config["user"]["color"])

        navbar_checked = not config["cursors"]["navbar"]
        funcs_checked = not config["cursors"]["funcs"]
        disasm_checked = not config["cursors"]["disasm"]
        all_checked = navbar_checked and funcs_checked and disasm_checked
        self._disable_all_cursors_checkbox.setChecked(all_checked)

        self._disable_navbar_cursors_checkbox.setChecked(navbar_checked)
        self._disable_navbar_cursors_checkbox.setEnabled(not all_checked)
        self._disable_funcs_cursors_checkbox.setChecked(funcs_checked)
        self._disable_funcs_cursors_checkbox.setEnabled(not all_checked)
        self._disable_disasm_cursors_checkbox.setChecked(disasm_checked)
        self._disable_disasm_cursors_checkbox.setEnabled(not all_checked)

        checked = config["user"]["notifications"]
        self._notifications_checkbox.setChecked(checked)

        index = self._debug_level_combo_box.findData(config["level"])
        self._debug_level_combo_box.setCurrentIndex(index)

        del self._servers[:]
        self._servers_table.clearContents()
        self._keep_cnt_spin_box.setValue(config["keep"]["cnt"])
        self._keep_intvl_spin_box.setValue(config["keep"]["intvl"])
        self._keep_idle_spin_box.setValue(config["keep"]["idle"])

    def _commit(self):
        """Commits all the changes made to the form elements."""
        name = self._name_line_edit.text()
        if self._plugin.config["user"]["name"] != name:
            old_name = self._plugin.config["user"]["name"]
            self._plugin.network.send_packet(UpdateUserName(old_name, name))
            self._plugin.config["user"]["name"] = name

        if self._plugin.config["user"]["color"] != self._color:
            name = self._plugin.config["user"]["name"]
            old_color = self._plugin.config["user"]["color"]
            packet = UpdateUserColor(name, old_color, self._color)
            self._plugin.network.send_packet(packet)
            self._plugin.config["user"]["color"] = self._color
            self._plugin.interface.widget.refresh()

        all_ = self._disable_all_cursors_checkbox.isChecked()
        checked = self._disable_navbar_cursors_checkbox.isChecked()
        self._plugin.config["cursors"]["navbar"] = not all_ and not checked
        checked = self._disable_funcs_cursors_checkbox.isChecked()
        self._plugin.config["cursors"]["funcs"] = not all_ and not checked
        checked = self._disable_disasm_cursors_checkbox.isChecked()
        self._plugin.config["cursors"]["disasm"] = not all_ and not checked

        checked = self._notifications_checkbox.isChecked()
        self._plugin.config["user"]["notifications"] = checked

        index = self._debug_level_combo_box.currentIndex()
        level = self._debug_level_combo_box.itemData(index)
        self._plugin.logger.setLevel(level)
        self._plugin.config["level"] = level

        self._plugin.config["servers"] = self._servers
        cnt = self._keep_cnt_spin_box.value()
        self._plugin.config["keep"]["cnt"] = cnt
        intvl = self._keep_intvl_spin_box.value()
        self._plugin.config["keep"]["intvl"] = intvl
        idle = self._keep_idle_spin_box.value()
        self._plugin.config["keep"]["idle"] = idle
        if self._plugin.network.client:
            self._plugin.network.client.set_keep_alive(cnt, intvl, idle)

        self._plugin.save_config()
class OWCSVFileImport(widget.OWWidget):
    name = "CSV File Import"
    description = "Import a data table from a CSV formatted file."
    icon = "icons/CSVFile.svg"

    outputs = [
        widget.OutputSignal(
            name="Data",
            type=Orange.data.Table,
            doc="Loaded data set."),
        widget.OutputSignal(
            name="Data Frame",
            type=pd.DataFrame,
            doc=""
        )
    ]

    class Error(widget.OWWidget.Error):
        error = widget.Msg(
            "Unexpected error"
        )
        encoding_error = widget.Msg(
            "Encoding error\n"
            "The file might be encoded in an unsupported encoding or it "
            "might be binary"
        )

    #: Paths and options of files accessed in a 'session'
    _session_items = settings.Setting(
        [], schema_only=True)  # type: List[Tuple[str, dict]]

    #: Saved dialog state (last directory and selected filter)
    dialog_state = settings.Setting({
        "directory": "",
        "filter": ""
    })  # type: Dict[str, str]
    MaxHistorySize = 50

    want_main_area = False
    buttons_area_orientation = None

    def __init__(self, *args, **kwargs):
        super().__init__(self, *args, **kwargs)

        self.__committimer = QTimer(self, singleShot=True)
        self.__committimer.timeout.connect(self.commit)

        self.__executor = qconcurrent.ThreadExecutor()
        self.__watcher = None  # type: Optional[qconcurrent.FutureWatcher]

        self.controlArea.layout().setSpacing(-1)  # reset spacing
        grid = QGridLayout()
        grid.addWidget(QLabel("File:", self), 0, 0, 1, 1)

        self.import_items_model = QStandardItemModel(self)
        self.recent_combo = QComboBox(
            self, objectName="recent-combo", toolTip="Recent files.",
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,
            minimumContentsLength=16,
        )
        self.recent_combo.setModel(self.import_items_model)
        self.recent_combo.activated.connect(self.activate_recent)
        self.recent_combo.setSizePolicy(
            QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
        self.browse_button = QPushButton(
            "…", icon=self.style().standardIcon(QStyle.SP_DirOpenIcon),
            toolTip="Browse filesystem", autoDefault=False,
        )
        self.browse_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        self.browse_button.clicked.connect(self.browse)
        grid.addWidget(self.recent_combo, 0, 1, 1, 1)
        grid.addWidget(self.browse_button, 0, 2, 1, 1)
        self.controlArea.layout().addLayout(grid)

        ###########
        # Info text
        ###########
        box = gui.widgetBox(self.controlArea, "Info", addSpace=False)
        self.summary_text = QTextBrowser(
            verticalScrollBarPolicy=Qt.ScrollBarAsNeeded,
            readOnly=True,
        )
        self.summary_text.viewport().setBackgroundRole(QPalette.NoRole)
        self.summary_text.setFrameStyle(QTextBrowser.NoFrame)
        self.summary_text.setMinimumHeight(self.fontMetrics().ascent() * 2 + 4)
        self.summary_text.viewport().setAutoFillBackground(False)
        box.layout().addWidget(self.summary_text)

        button_box = QDialogButtonBox(
            orientation=Qt.Horizontal,
            standardButtons=QDialogButtonBox.Cancel | QDialogButtonBox.Retry
        )
        self.load_button = b = button_box.button(QDialogButtonBox.Retry)
        b.setText("Load")
        b.clicked.connect(self.__committimer.start)
        b.setEnabled(False)
        b.setDefault(True)

        self.cancel_button = b = button_box.button(QDialogButtonBox.Cancel)
        b.clicked.connect(self.cancel)
        b.setEnabled(False)
        b.setAutoDefault(False)

        self.import_options_button = QPushButton(
            "Import Options…", enabled=False, autoDefault=False,
            clicked=self._activate_import_dialog
        )

        self.recent_combo.currentIndexChanged.connect(
            lambda idx:
                self.import_options_button.setEnabled(idx != -1) or
                self.load_button.setEnabled(idx != -1)
        )
        button_box.addButton(
            self.import_options_button, QDialogButtonBox.ActionRole
        )
        button_box.setStyleSheet(
            "button-layout: {:d};".format(QDialogButtonBox.MacLayout)
        )
        self.controlArea.layout().addWidget(button_box)

        self._restoreState()
        if self.current_item() is not None:
            self._invalidate()
        self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)

    @Slot(int)
    def activate_recent(self, index):
        """
        Activate an item from the recent list.
        """
        if 0 <= index < self.import_items_model.rowCount():
            item = self.import_items_model.item(index)
            assert item is not None
            path = item.data(ImportItem.PathRole)
            opts = item.data(ImportItem.OptionsRole)
            if not isinstance(opts, Options):
                opts = None
            self.set_selected_file(path, opts)
        else:
            self.recent_combo.setCurrentIndex(-1)

    @Slot()
    def browse(self):
        """
        Open a file dialog and select a user specified file.
        """
        formats = [
            "Text - comma separated (*.csv, *)",
            "Text - tab separated (*.tsv, *)",
            "Text - all files (*)"
        ]

        dlg = QFileDialog(
            self, windowTitle="Open Data File",
            acceptMode=QFileDialog.AcceptOpen,
            fileMode=QFileDialog.ExistingFile
        )
        dlg.setNameFilters(formats)
        state = self.dialog_state
        lastdir = state.get("directory", "")
        lastfilter = state.get("filter", "")

        if lastdir and os.path.isdir(lastdir):
            dlg.setDirectory(lastdir)
        if lastfilter:
            dlg.selectNameFilter(lastfilter)

        status = dlg.exec_()
        dlg.deleteLater()
        if status == QFileDialog.Accepted:
            self.dialog_state["directory"] = dlg.directory().absolutePath()
            self.dialog_state["filter"] = dlg.selectedNameFilter()

            selected_filter = dlg.selectedNameFilter()
            path = dlg.selectedFiles()[0]
            # pre-flight check; try to determine the nature of the file
            mtype = _mime_type_for_path(path)
            if not mtype.inherits("text/plain"):
                mb = QMessageBox(
                    parent=self,
                    windowTitle="",
                    icon=QMessageBox.Question,
                    text="The '{basename}' may be a binary file.\n"
                         "Are you sure you want to continue?".format(
                            basename=os.path.basename(path)),
                    standardButtons=QMessageBox.Cancel | QMessageBox.Yes
                )
                mb.setWindowModality(Qt.WindowModal)
                if mb.exec() == QMessageBox.Cancel:
                    return

            # initialize dialect based on selected extension
            if selected_filter in formats[:-1]:
                filter_idx = formats.index(selected_filter)
                if filter_idx == 0:
                    dialect = csv.excel()
                elif filter_idx == 1:
                    dialect = csv.excel_tab()
                else:
                    dialect = csv.excel_tab()
                header = True
            else:
                try:
                    dialect, header = sniff_csv_with_path(path)
                except Exception:
                    dialect, header = csv.excel(), True

            options = None
            # Search for path in history.
            # If found use the stored params to initialize the import dialog
            items = self.itemsFromSettings()
            idx = index_where(items, lambda t: samepath(t[0], path))
            if idx is not None:
                _, options_ = items[idx]
                if options_ is not None:
                    options = options_

            if options is None:
                if not header:
                    rowspec = []
                else:
                    rowspec = [(range(0, 1), RowSpec.Header)]
                options = Options(
                    encoding="utf-8", dialect=dialect, rowspec=rowspec)

            dlg = CSVImportDialog(
                self, windowTitle="Import Options",  sizeGripEnabled=True)
            dlg.setWindowModality(Qt.WindowModal)
            dlg.setPath(path)
            dlg.setOptions(options)
            status = dlg.exec_()
            dlg.deleteLater()
            if status == QDialog.Accepted:
                self.set_selected_file(path, dlg.options())

    def current_item(self):
        # type: () -> Optional[ImportItem]
        """
        Return the current selected item (file) or None if there is no
        current item.
        """
        idx = self.recent_combo.currentIndex()
        if idx == -1:
            return None

        item = self.recent_combo.model().item(idx)  # type: QStandardItem
        if isinstance(item, ImportItem):
            return item
        else:
            return None

    def _activate_import_dialog(self):
        """Activate the Import Options dialog for the  current item."""
        item = self.current_item()
        assert item is not None
        dlg = CSVImportDialog(
            self, windowTitle="Import Options", sizeGripEnabled=True,
        )
        dlg.setWindowModality(Qt.WindowModal)
        dlg.setAttribute(Qt.WA_DeleteOnClose)
        settings = QSettings()
        qualname = qname(type(self))
        settings.beginGroup(qualname)
        size = settings.value("size", QSize(), type=QSize)  # type: QSize
        if size.isValid():
            dlg.resize(size)

        path = item.data(ImportItem.PathRole)
        options = item.data(ImportItem.OptionsRole)
        dlg.setPath(path)  # Set path before options so column types can
        if isinstance(options, Options):
            dlg.setOptions(options)

        def update():
            newoptions = dlg.options()
            item.setData(newoptions, ImportItem.OptionsRole)
            # update the stored item
            self._add_recent(path, newoptions)
            if newoptions != options:
                self._invalidate()
        dlg.accepted.connect(update)

        def store_size():
            settings.setValue("size", dlg.size())
        dlg.finished.connect(store_size)
        dlg.show()

    def set_selected_file(self, filename, options=None):
        """
        Set the current selected filename path.
        """
        self._add_recent(filename, options)
        self._invalidate()

    #: Saved options for a filename
    SCHEMA = {
        "path": str,  # Local filesystem path
        "options": str,  # json encoded 'Options'
    }

    @classmethod
    def _local_settings(cls):
        # type: () -> QSettings
        """Return a QSettings instance with local persistent settings."""
        filename = "{}.ini".format(qname(cls))
        fname = os.path.join(settings.widget_settings_dir(), filename)
        return QSettings(fname, QSettings.IniFormat)

    def _add_recent(self, filename, options=None):
        # type: (str, Optional[Options]) -> None
        """
        Add filename to the list of recent files.
        """
        model = self.import_items_model
        index = index_where(
            (model.index(i, 0).data(ImportItem.PathRole)
             for i in range(model.rowCount())),
            lambda path: isinstance(path, str) and samepath(path, filename)
        )
        if index is not None:
            item, *_ = model.takeRow(index)
        else:
            item = ImportItem.fromPath(filename)

        model.insertRow(0, item)

        if options is not None:
            item.setOptions(options)

        self.recent_combo.setCurrentIndex(0)
        # store items to local persistent settings
        s = self._local_settings()
        arr = QSettings_readArray(s, "recent", OWCSVFileImport.SCHEMA)
        item = {"path": filename}
        if options is not None:
            item["options"] = json.dumps(options.as_dict())

        arr = [item for item in arr if item.get("path") != filename]
        arr.append(item)
        QSettings_writeArray(s, "recent", arr)

        # update workflow session items
        items = self._session_items[:]
        idx = index_where(items, lambda t: samepath(t[0], filename))
        if idx is not None:
            del items[idx]
        items.insert(0, (filename, options.as_dict()))
        self._session_items = items[:OWCSVFileImport.MaxHistorySize]

    def _invalidate(self):
        # Invalidate the current output and schedule a new commit call.
        # (NOTE: The widget enters a blocking state)
        self.__committimer.start()
        if self.__watcher is not None:
            self.__cancel_task()
        self.setBlocking(True)

    def commit(self):
        """
        Commit the current state and submit the load task for execution.

        Note
        ----
        Any existing pending task is canceled.
        """
        self.__committimer.stop()
        if self.__watcher is not None:
            self.__cancel_task()
        self.error()

        item = self.current_item()
        if item is None:
            return
        path = item.data(ImportItem.PathRole)
        opts = item.data(ImportItem.OptionsRole)
        if not isinstance(opts, Options):
            return

        task = state = TaskState()
        state.future = ...
        state.watcher = qconcurrent.FutureWatcher()
        state.progressChanged.connect(self.__set_read_progress,
                                      Qt.QueuedConnection)

        def progress_(i, j):
            task.emitProgressChangedOrCancel(i, j)

        task.future = self.__executor.submit(
            clear_stack_on_cancel(load_csv),
            path, opts, progress_,
        )
        task.watcher.setFuture(task.future)
        w = task.watcher
        w.done.connect(self.__handle_result)
        w.progress = state
        self.__watcher = w
        self.__set_running_state()

    @Slot('qint64', 'qint64')
    def __set_read_progress(self, read, count):
        if count > 0:
            self.progressBarSet(100 * read / count)

    def __cancel_task(self):
        # Cancel and dispose of the current task
        assert self.__watcher is not None
        w = self.__watcher
        w.future().cancel()
        w.progress.cancel = True
        w.done.disconnect(self.__handle_result)
        w.progress.progressChanged.disconnect(self.__set_read_progress)
        w.progress.deleteLater()
        # wait until completion
        futures.wait([w.future()])
        self.__watcher = None

    def cancel(self):
        """
        Cancel current pending or executing task.
        """
        if self.__watcher is not None:
            self.__cancel_task()
            self.__clear_running_state()
            self.setStatusMessage("Cancelled")
            self.summary_text.setText(
                "<div>Cancelled<br/><small>Press 'Reload' to try again</small></div>"
            )

    def __set_running_state(self):
        self.progressBarInit()
        self.setBlocking(True)
        self.setStatusMessage("Running")
        self.cancel_button.setEnabled(True)
        self.load_button.setText("Restart")
        path = self.current_item().path()
        self.Error.clear()
        self.summary_text.setText(
            "<div>Loading: <i>{}</i><br/>".format(prettyfypath(path))
        )

    def __clear_running_state(self, ):
        self.progressBarFinished()
        self.setStatusMessage("")
        self.setBlocking(False)
        self.cancel_button.setEnabled(False)
        self.load_button.setText("Reload")

    def __set_error_state(self, err):
        self.Error.clear()
        if isinstance(err, UnicodeDecodeError):
            self.Error.encoding_error(exc_info=err)
        else:
            self.Error.error(exc_info=err)

        path = self.current_item().path()
        basename = os.path.basename(path)
        if isinstance(err, UnicodeDecodeError):
            text = (
                "<div><i>{basename}</i> was not loaded due to a text encoding "
                "error. The file might be saved in an unknown or invalid "
                "encoding, or it might be a binary file.</div>"
            ).format(
                basename=escape(basename)
            )
        else:
            text = (
                "<div><i>{basename}</i> was not loaded due to an error:"
                "<p style='white-space: pre;'>{err}</p>"
            ).format(
                basename=escape(basename),
                err="".join(traceback.format_exception_only(type(err), err))
            )
        self.summary_text.setText(text)

    def __clear_error_state(self):
        self.Error.error.clear()
        self.summary_text.setText("")

    def onDeleteWidget(self):
        """Reimplemented."""
        if self.__watcher is not None:
            self.__cancel_task()
            self.__executor.shutdown()
        super().onDeleteWidget()

    @Slot(object)
    def __handle_result(self, f):
        # type: (qconcurrent.Future[pd.DataFrame]) -> None
        assert f.done()
        assert f is self.__watcher.future()
        self.__watcher = None
        self.__clear_running_state()

        try:
            df = f.result()
            assert isinstance(df, pd.DataFrame)
        except pd.errors.EmptyDataError:
            df = pd.DataFrame({})
        except Exception as e:  # pylint: disable=broad-except
            self.__set_error_state(e)
            df = None
        else:
            self.__clear_error_state()

        if df is not None:
            table = pandas_to_table(df)
        else:
            table = None
        self.send("Data Frame", df)
        self.send('Data', table)
        self._update_status_messages(table)

    def _update_status_messages(self, data):
        if data is None:
            return

        def pluralize(seq):
            return "s" if len(seq) != 1 else ""

        summary = ("{n_instances} row{plural_1}, "
                   "{n_features} feature{plural_2}, "
                   "{n_meta} meta{plural_3}").format(
                        n_instances=len(data), plural_1=pluralize(data),
                        n_features=len(data.domain.attributes),
                        plural_2=pluralize(data.domain.attributes),
                        n_meta=len(data.domain.metas),
                        plural_3=pluralize(data.domain.metas))
        self.summary_text.setText(summary)

    def itemsFromSettings(self):
        # type: () -> List[Tuple[str, Options]]
        """
        Return items from local history.
        """
        s = self._local_settings()
        items_ = QSettings_readArray(s, "recent", OWCSVFileImport.SCHEMA)
        items = []  # type: List[Tuple[str, Options]]
        for item in items_:
            path = item.get("path", "")
            if not path:
                continue
            opts_json = item.get("options", "")
            try:
                opts = Options.from_dict(json.loads(opts_json))
            except (csv.Error, LookupError, TypeError, json.JSONDecodeError):
                _log.error("Could not reconstruct options for '%s'", path,
                           exc_info=True)
                pass
            else:
                items.append((path, opts))
        return items[::-1]

    def _restoreState(self):
        # Restore the state. Merge session (workflow) items with the
        # local history.
        model = self.import_items_model
        # local history
        items = self.itemsFromSettings()
        # stored session items
        sitems = []
        for p, m in self._session_items:
            try:
                item_ = (p, Options.from_dict(m))
            except (csv.Error, LookupError) as e:
                # Is it better to fail then to lose a item slot?
                _log.error("Failed to restore '%s'", p, exc_info=True)
            else:
                sitems.append(item_)

        items = sitems + items
        items = unique(items, key=lambda t: pathnormalize(t[0]))

        curr = self.recent_combo.currentIndex()
        if curr != -1:
            currentpath = self.recent_combo.currentData(ImportItem.PathRole)
        else:
            currentpath = None
        for path, options in items:
            item = ImportItem.fromPath(path)
            item.setOptions(options)
            model.appendRow(item)

        if currentpath is not None:
            idx = self.recent_combo.findData(currentpath, ImportItem.PathRole)
            if idx != -1:
                self.recent_combo.setCurrentIndex(idx)
Beispiel #19
0
class GuiItemEditor(QDialog):

    def __init__(self, theParent, theProject, tHandle):
        QDialog.__init__(self, theParent)

        logger.debug("Initialising ItemEditor ...")

        self.mainConf   = nw.CONFIG
        self.theProject = theProject
        self.theParent  = theParent
        self.theItem    = self.theProject.getItem(tHandle)

        self.outerBox   = QHBoxLayout()
        self.innerBox   = QVBoxLayout()

        self.setWindowTitle("Item Settings")

        self.gradPath = path.abspath(path.join(self.mainConf.appPath,"graphics","gear.svg"))
        self.svgGradient = QSvgWidget(self.gradPath)
        self.svgGradient.setFixedSize(QSize(64,64))

        self.setLayout(self.outerBox)
        self.outerBox.addWidget(self.svgGradient, 0, Qt.AlignTop)
        self.outerBox.addLayout(self.innerBox)

        self.mainGroup = QGroupBox("Item Settings")
        self.mainForm  = QFormLayout()

        self.editName   = QLineEdit()
        self.editStatus = QComboBox()
        self.editLayout = QComboBox()

        if self.theItem.itemClass == nwItemClass.NOVEL:
            for sLabel, _, _ in self.theProject.statusItems:
                self.editStatus.addItem(
                    self.theParent.statusIcons[sLabel], sLabel, sLabel
                )
        else:
            for sLabel, _, _ in self.theProject.importItems:
                self.editStatus.addItem(
                    self.theParent.importIcons[sLabel], sLabel, sLabel
                )

        self.validLayouts = []
        if self.theItem.itemType == nwItemType.FILE:
            if self.theItem.itemClass == nwItemClass.NOVEL:
                self.validLayouts.append(nwItemLayout.TITLE)
                self.validLayouts.append(nwItemLayout.BOOK)
                self.validLayouts.append(nwItemLayout.PAGE)
                self.validLayouts.append(nwItemLayout.PARTITION)
                self.validLayouts.append(nwItemLayout.UNNUMBERED)
                self.validLayouts.append(nwItemLayout.CHAPTER)
                self.validLayouts.append(nwItemLayout.SCENE)
                self.validLayouts.append(nwItemLayout.NOTE)
            else:
                self.validLayouts.append(nwItemLayout.NOTE)
        else:
            self.validLayouts.append(nwItemLayout.NO_LAYOUT)

        for itemLayout in nwItemLayout:
            if itemLayout in self.validLayouts:
                self.editLayout.addItem(nwLabels.LAYOUT_NAME[itemLayout],itemLayout)

        self.mainForm.addRow("Name",   self.editName)
        self.mainForm.addRow("Status", self.editStatus)
        self.mainForm.addRow("Layout", self.editLayout)

        self.editName.setMinimumWidth(200)

        self.editName.setText(self.theItem.itemName)
        statusIdx = self.editStatus.findData(self.theItem.itemStatus)
        if statusIdx != -1:
            self.editStatus.setCurrentIndex(statusIdx)
        layoutIdx = self.editLayout.findData(self.theItem.itemLayout)
        if layoutIdx != -1:
            self.editLayout.setCurrentIndex(layoutIdx)

        self.buttonBox = QHBoxLayout()
        self.closeButton = QPushButton("Close")
        self.closeButton.clicked.connect(self._doClose)
        self.saveButton = QPushButton("Save")
        self.saveButton.setDefault(True)
        self.saveButton.clicked.connect(self._doSave)
        self.buttonBox.addStretch(1)
        self.buttonBox.addWidget(self.closeButton)
        self.buttonBox.addWidget(self.saveButton)

        self.mainGroup.setLayout(self.mainForm)
        self.innerBox.addWidget(self.mainGroup)
        self.innerBox.addLayout(self.buttonBox)

        self.show()

        self.editName.selectAll()

        logger.debug("ItemEditor initialisation complete")

        return

    def _doSave(self):
        logger.verbose("ItemEditor save button clicked")
        itemName   = self.editName.text()
        itemStatus = self.editStatus.currentData()
        itemLayout = self.editLayout.currentData()
        self.theItem.setName(itemName)
        self.theItem.setStatus(itemStatus)
        self.theItem.setLayout(itemLayout)
        self.theProject.setProjectChanged(True)
        self.accept()
        self.close()
        return

    def _doClose(self):
        logger.verbose("ItemEditor close button clicked")
        self.reject()
        self.close()
        return
    def __init__(self, parent, config):
        WindowModalDialog.__init__(self, parent, _('Preferences'))
        self.config = config
        self.window = parent
        self.need_restart = False
        self.fx = self.window.fx
        self.wallet = self.window.wallet

        vbox = QVBoxLayout()
        tabs = QTabWidget()
        gui_widgets = []
        fee_widgets = []
        tx_widgets = []
        id_widgets = []

        # language
        lang_help = _(
            'Select which language is used in the GUI (after restart).')
        lang_label = HelpLabel(_('Language') + ':', lang_help)
        lang_combo = QComboBox()
        from qtum_electrum.i18n import languages
        lang_combo.addItems(list(languages.values()))
        lang_keys = list(languages.keys())
        lang_cur_setting = self.config.get("language", '')
        try:
            index = lang_keys.index(lang_cur_setting)
        except ValueError:  # not in list
            index = 0
        lang_combo.setCurrentIndex(index)
        if not self.config.is_modifiable('language'):
            for w in [lang_combo, lang_label]:
                w.setEnabled(False)

        def on_lang(x):
            lang_request = list(languages.keys())[lang_combo.currentIndex()]
            if lang_request != self.config.get('language'):
                self.config.set_key("language", lang_request, True)
                self.need_restart = True

        lang_combo.currentIndexChanged.connect(on_lang)
        gui_widgets.append((lang_label, lang_combo))

        nz_help = _(
            'Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"'
        )
        nz_label = HelpLabel(_('Zeros after decimal point') + ':', nz_help)
        nz = QSpinBox()
        nz.setMinimum(0)
        nz.setMaximum(self.window.decimal_point)
        nz.setValue(self.window.num_zeros)
        if not self.config.is_modifiable('num_zeros'):
            for w in [nz, nz_label]:
                w.setEnabled(False)

        def on_nz():
            value = nz.value()
            if self.window.num_zeros != value:
                self.window.num_zeros = value
                self.config.set_key('num_zeros', value, True)
                self.window.history_list.update()
                self.window.address_list.update()

        nz.valueChanged.connect(on_nz)
        gui_widgets.append((nz_label, nz))

        def on_dynfee(x):
            self.config.set_key('dynamic_fees', x == Qt.Checked)
            self.window.fee_slider.update()

        dynfee_cb = QCheckBox(_('Use dynamic fees'))
        dynfee_cb.setChecked(self.config.is_dynfee())
        dynfee_cb.setToolTip(_("Use fees recommended by the server."))
        fee_widgets.append((dynfee_cb, None))
        dynfee_cb.stateChanged.connect(on_dynfee)

        feebox_cb = QCheckBox(_('Edit fees manually'))
        feebox_cb.setChecked(self.config.get('show_fee', False))
        feebox_cb.setToolTip(_("Show fee edit box in send tab."))

        def on_feebox(x):
            self.config.set_key('show_fee', x == Qt.Checked)
            self.window.fee_e.setVisible(bool(x))

        feebox_cb.stateChanged.connect(on_feebox)
        fee_widgets.append((feebox_cb, None))

        use_rbf = self.config.get('use_rbf', True)
        use_rbf_cb = QCheckBox(_('Use Replace-By-Fee'))
        use_rbf_cb.setChecked(use_rbf)
        use_rbf_cb.setToolTip(
            _('If you check this box, your transactions will be marked as non-final,') + '\n' + \
            _(
                'and you will have the possibility, while they are unconfirmed, to replace them with transactions that pay higher fees.') + '\n' + \
            _('Note that some merchants do not accept non-final transactions until they are confirmed.'))

        def on_use_rbf(x):
            self.config.set_key('use_rbf', x == Qt.Checked)

        use_rbf_cb.stateChanged.connect(on_use_rbf)
        fee_widgets.append((use_rbf_cb, None))

        batch_rbf_cb = QCheckBox(_('Batch RBF transactions'))
        batch_rbf_cb.setChecked(self.config.get('batch_rbf', False))
        batch_rbf_cb.setEnabled(use_rbf)
        batch_rbf_cb.setToolTip(
            _(
                'If you check this box, your unconfirmed transactions will be consolidated into a single transaction.') + '\n' + \
            _('This will save fees.'))

        def on_batch_rbf(x):
            self.config.set_key('batch_rbf', bool(x))

        batch_rbf_cb.stateChanged.connect(on_batch_rbf)
        fee_widgets.append((batch_rbf_cb, None))

        msg = _('OpenAlias record, used to receive coins and to sign payment requests.') + '\n\n' \
              + _('The following alias providers are available:') + '\n' \
              + '\n'.join(['https://cryptoname.co/', 'http://xmr.link']) + '\n\n' \
              + 'For more information, see http://openalias.org'
        alias_label = HelpLabel(_('OpenAlias') + ':', msg)
        alias = self.config.get('alias', '')
        self.alias_e = QLineEdit(alias)

        self.set_alias_color()
        self.window.alias_received_signal.connect(self.set_alias_color)
        self.alias_e.editingFinished.connect(self.on_alias_edit)
        id_widgets.append((alias_label, self.alias_e))

        # SSL certificate
        msg = ' '.join([
            _('SSL certificate used to sign payment requests.'),
            _('Use setconfig to set ssl_chain and ssl_privkey.'),
        ])
        if self.config.get('ssl_privkey') or self.config.get('ssl_chain'):
            try:
                SSL_identity = paymentrequest.check_ssl_config(self.config)
                SSL_error = None
            except BaseException as e:
                SSL_identity = "error"
                SSL_error = str(e)
        else:
            SSL_identity = ""
            SSL_error = None
        SSL_id_label = HelpLabel(_('SSL certificate') + ':', msg)
        SSL_id_e = QLineEdit(SSL_identity)
        SSL_id_e.setStyleSheet(
            RED_BG if SSL_error else GREEN_BG if SSL_identity else '')
        if SSL_error:
            SSL_id_e.setToolTip(SSL_error)
        SSL_id_e.setReadOnly(True)
        id_widgets.append((SSL_id_label, SSL_id_e))

        units = ['QTUM', 'mQTUM', 'bits']
        msg = _('Base unit of your wallet.') \
              + '\n1QTUM=1000mQTUM.\n' \
              + _(' These settings affects the fields in the Send tab') + ' '
        unit_label = HelpLabel(_('Base unit') + ':', msg)
        unit_combo = QComboBox()
        unit_combo.addItems(units)
        unit_combo.setCurrentIndex(units.index(self.window.base_unit()))

        def on_unit(x, nz):
            unit_result = units[unit_combo.currentIndex()]
            if self.window.base_unit() == unit_result:
                return
            edits = self.amount_e, self.fee_e, self.receive_amount_e
            amounts = [edit.get_amount() for edit in edits]
            if unit_result == 'QTUM':
                self.window.decimal_point = 8
            elif unit_result == 'mQTUM':
                self.window.decimal_point = 5
            elif unit_result == 'bits':
                self.window.decimal_point = 2
            else:
                raise Exception('Unknown base unit')
            self.config.set_key('decimal_point', self.window.decimal_point,
                                True)
            nz.setMaximum(self.window.decimal_point)
            self.window.history_list.update()
            self.window.request_list.update()
            self.window.address_list.update()
            for edit, amount in zip(edits, amounts):
                edit.setAmount(amount)
            self.window.update_status()

        unit_combo.currentIndexChanged.connect(lambda x: on_unit(x, nz))
        gui_widgets.append((unit_label, unit_combo))

        block_explorers = sorted(util.block_explorer_info().keys())
        msg = _(
            'Choose which online block explorer to use for functions that open a web browser'
        )
        block_ex_label = HelpLabel(_('Online Block Explorer') + ':', msg)
        block_ex_combo = QComboBox()
        block_ex_combo.addItems(block_explorers)
        block_ex_combo.setCurrentIndex(
            block_ex_combo.findText(util.block_explorer(self.config)))

        def on_be(x):
            be_result = block_explorers[block_ex_combo.currentIndex()]
            self.config.set_key('block_explorer', be_result, True)

        block_ex_combo.currentIndexChanged.connect(on_be)
        gui_widgets.append((block_ex_label, block_ex_combo))

        from qtum_electrum import qrscanner
        system_cameras = qrscanner._find_system_cameras()
        qr_combo = QComboBox()
        qr_combo.addItem("Default", "default")
        for camera, device in system_cameras.items():
            qr_combo.addItem(camera, device)
        # combo.addItem("Manually specify a device", config.get("video_device"))
        index = qr_combo.findData(self.config.get("video_device"))
        qr_combo.setCurrentIndex(index)
        msg = _("Install the zbar package to enable this.")
        qr_label = HelpLabel(_('Video Device') + ':', msg)
        qr_combo.setEnabled(qrscanner.libzbar is not None)
        on_video_device = lambda x: self.config.set_key(
            "video_device", qr_combo.itemData(x), True)
        qr_combo.currentIndexChanged.connect(on_video_device)
        gui_widgets.append((qr_label, qr_combo))

        filelogging_cb = QCheckBox(_("Write logs to file"))
        filelogging_cb.setChecked(bool(self.config.get('log_to_file', False)))

        def on_set_filelogging(v):
            self.config.set_key('log_to_file', v == Qt.Checked, save=True)
            self.need_restart = True

        filelogging_cb.stateChanged.connect(on_set_filelogging)
        filelogging_cb.setToolTip(
            _('Debug logs can be persisted to disk. These are useful for troubleshooting.'
              ))
        gui_widgets.append((filelogging_cb, None))

        usechange_cb = QCheckBox(_('Use change addresses'))
        usechange_cb.setChecked(self.window.wallet.use_change)
        if not self.config.is_modifiable('use_change'):
            usechange_cb.setEnabled(False)

        def on_usechange(x):
            usechange_result = x == Qt.Checked
            if self.window.wallet.use_change != usechange_result:
                self.window.wallet.use_change = usechange_result
                self.window.wallet.storage.put('use_change',
                                               self.window.wallet.use_change)
                multiple_cb.setEnabled(self.window.wallet.use_change)

        usechange_cb.stateChanged.connect(on_usechange)
        usechange_cb.setToolTip(
            _('Using change addresses makes it more difficult for other people to track your transactions.'
              ))
        tx_widgets.append((usechange_cb, None))

        def on_multiple(x):
            multiple = x == Qt.Checked
            if self.wallet.multiple_change != multiple:
                self.wallet.multiple_change = multiple
                self.wallet.storage.put('multiple_change', multiple)

        multiple_change = self.wallet.multiple_change
        multiple_cb = QCheckBox(_('Use multiple change addresses'))
        multiple_cb.setEnabled(self.wallet.use_change)
        multiple_cb.setToolTip('\n'.join([
            _('In some cases, use up to 3 change addresses in order to break '
              'up large coin amounts and obfuscate the recipient address.'),
            _('This may result in higher transactions fees.')
        ]))
        multiple_cb.setChecked(multiple_change)
        multiple_cb.stateChanged.connect(on_multiple)
        tx_widgets.append((multiple_cb, None))

        def fmt_docs(key, klass):
            lines = [ln.lstrip(" ") for ln in klass.__doc__.split("\n")]
            return '\n'.join([key, "", " ".join(lines)])

        choosers = sorted(coinchooser.COIN_CHOOSERS.keys())
        if len(choosers) > 1:
            chooser_name = coinchooser.get_name(self.config)
            msg = _(
                'Choose coin (UTXO) selection method.  The following are available:\n\n'
            )
            msg += '\n\n'.join(
                fmt_docs(*item) for item in coinchooser.COIN_CHOOSERS.items())
            chooser_label = HelpLabel(_('Coin selection') + ':', msg)
            chooser_combo = QComboBox()
            chooser_combo.addItems(choosers)
            i = choosers.index(chooser_name) if chooser_name in choosers else 0
            chooser_combo.setCurrentIndex(i)

            def on_chooser(x):
                chooser_name = choosers[chooser_combo.currentIndex()]
                self.config.set_key('coin_chooser', chooser_name)

            chooser_combo.currentIndexChanged.connect(on_chooser)
            tx_widgets.append((chooser_label, chooser_combo))

        def on_unconf(x):
            self.config.set_key('confirmed_only', bool(x))

        conf_only = self.config.get('confirmed_only', False)
        unconf_cb = QCheckBox(_('Spend only confirmed coins'))
        unconf_cb.setToolTip(_('Spend only confirmed inputs.'))
        unconf_cb.setChecked(conf_only)
        unconf_cb.stateChanged.connect(on_unconf)
        tx_widgets.append((unconf_cb, None))

        # Fiat Currency
        hist_checkbox = QCheckBox()
        fiat_address_checkbox = QCheckBox()
        ccy_combo = QComboBox()
        ex_combo = QComboBox()

        def update_currencies():
            if not self.window.fx: return
            currencies = sorted(
                self.fx.get_currencies(self.window.fx.get_history_config()))
            ccy_combo.clear()
            ccy_combo.addItems([_('None')] + currencies)
            if self.window.fx.is_enabled():
                ccy_combo.setCurrentIndex(
                    ccy_combo.findText(self.window.fx.get_currency()))

        def update_history_cb():
            if not self.window.fx: return
            hist_checkbox.setChecked(self.window.fx.get_history_config())
            hist_checkbox.setEnabled(self.window.fx.is_enabled())

        def update_fiat_address_cb():
            if not self.window.fx: return
            fiat_address_checkbox.setChecked(
                self.window.fx.get_fiat_address_config())

        def update_exchanges():
            if not self.window.fx: return
            b = self.window.fx.is_enabled()
            ex_combo.setEnabled(b)
            if b:
                h = self.window.fx.get_history_config()
                c = self.window.fx.get_currency()
                exchanges = self.window.fx.get_exchanges_by_ccy(c, h)
            else:
                exchanges = self.window.fx.get_exchanges_by_ccy('USD', False)
            ex_combo.clear()
            ex_combo.addItems(sorted(exchanges))
            ex_combo.setCurrentIndex(
                ex_combo.findText(self.window.fx.config_exchange()))

        def on_currency(hh):
            if not self.window.fx: return
            b = bool(ccy_combo.currentIndex())
            ccy = str(ccy_combo.currentText()) if b else None
            self.window.fx.set_enabled(b)
            if b and ccy != self.window.fx.ccy:
                self.window.fx.set_currency(ccy)
            update_history_cb()
            update_exchanges()
            self.window.update_fiat()

        def on_exchange(idx):
            exchange = str(ex_combo.currentText())
            if self.window.fx and self.window.fx.is_enabled(
            ) and exchange and exchange != self.window.fx.exchange.name():
                self.window.fx.set_exchange(exchange)

        def on_history(checked):
            if not self.window.fx: return
            self.window.fx.set_history_config(checked)
            update_exchanges()
            self.window.history_list.refresh_headers()
            if self.window.fx.is_enabled() and checked:
                # reset timeout to get historical rates
                self.window.fx.timeout = 0

        def on_fiat_address(checked):
            if not self.window.fx: return
            self.window.fx.set_fiat_address_config(checked)
            self.window.address_list.refresh_headers()
            self.window.address_list.update()

        update_currencies()
        update_history_cb()
        update_fiat_address_cb()
        update_exchanges()
        ccy_combo.currentIndexChanged.connect(on_currency)
        hist_checkbox.stateChanged.connect(on_history)
        fiat_address_checkbox.stateChanged.connect(on_fiat_address)
        ex_combo.currentIndexChanged.connect(on_exchange)

        fiat_widgets = []
        fiat_widgets.append((QLabel(_('Fiat currency')), ccy_combo))
        fiat_widgets.append((QLabel(_('Show history rates')), hist_checkbox))
        fiat_widgets.append((QLabel(_('Show Fiat balance for addresses')),
                             fiat_address_checkbox))
        fiat_widgets.append((QLabel(_('Source')), ex_combo))

        tabs_info = [
            (fee_widgets, _('Fees')),
            (tx_widgets, _('Transactions')),
            (gui_widgets, _('Appearance')),
            (fiat_widgets, _('Fiat')),
            (id_widgets, _('Identity')),
        ]
        for widgets, name in tabs_info:
            tab = QWidget()
            grid = QGridLayout(tab)
            grid.setColumnStretch(0, 1)
            for a, b in widgets:
                i = grid.rowCount()
                if b:
                    if a:
                        grid.addWidget(a, i, 0)
                    grid.addWidget(b, i, 1)
                else:
                    grid.addWidget(a, i, 0, 1, 2)
            tabs.addTab(tab, name)

        vbox.addWidget(tabs)
        vbox.addStretch(1)
        vbox.addLayout(Buttons(CloseButton(self)))
        self.setLayout(vbox)
Beispiel #21
0
class AnalogIn(PluginBase):
    def __init__(self, *args):
        super().__init__(BrickletAnalogIn, *args)

        self.ai = self.device

        # the firmware version of a EEPROM Bricklet can (under common circumstances)
        # not change during the lifetime of an EEPROM Bricklet plugin. therefore,
        # it's okay to make final decisions based on it here
        self.has_range = self.firmware_version >= (2, 0, 1)
        self.has_averaging = self.firmware_version >= (2, 0, 3)

        self.cbe_voltage = CallbackEmulator(self.ai.get_voltage,
                                            None,
                                            self.cb_voltage,
                                            self.increase_error_count)

        self.current_voltage = CurveValueWrapper() # float, V

        plots = [('Voltage', Qt.red, self.current_voltage, format_voltage)]
        self.plot_widget = PlotWidget('Voltage [V]', plots, y_resolution=0.001)

        layout = QVBoxLayout(self)
        layout.addWidget(self.plot_widget)

        if self.has_range:
            self.combo_range = QComboBox()
            self.combo_range.addItem('Automatic', BrickletAnalogIn.RANGE_AUTOMATIC)

            if self.has_averaging:
                self.combo_range.addItem('0 - 3.30 V', BrickletAnalogIn.RANGE_UP_TO_3V)

            self.combo_range.addItem('0 - 6.05 V', BrickletAnalogIn.RANGE_UP_TO_6V)
            self.combo_range.addItem('0 - 10.32 V', BrickletAnalogIn.RANGE_UP_TO_10V)
            self.combo_range.addItem('0 - 36.30 V', BrickletAnalogIn.RANGE_UP_TO_36V)
            self.combo_range.addItem('0 - 45.00 V', BrickletAnalogIn.RANGE_UP_TO_45V)
            self.combo_range.currentIndexChanged.connect(self.range_changed)

            hlayout = QHBoxLayout()
            hlayout.addWidget(QLabel('Range:'))
            hlayout.addWidget(self.combo_range)
            hlayout.addStretch()

            if self.has_averaging:
                self.spin_average = QSpinBox()
                self.spin_average.setMinimum(0)
                self.spin_average.setMaximum(255)
                self.spin_average.setSingleStep(1)
                self.spin_average.setValue(50)
                self.spin_average.editingFinished.connect(self.spin_average_finished)

                hlayout.addWidget(QLabel('Average Length:'))
                hlayout.addWidget(self.spin_average)

            line = QFrame()
            line.setObjectName("line")
            line.setFrameShape(QFrame.HLine)
            line.setFrameShadow(QFrame.Sunken)

            layout.addWidget(line)
            layout.addLayout(hlayout)

    def get_range_async(self, range_):
        self.combo_range.setCurrentIndex(self.combo_range.findData(range_))

    def get_averaging_async(self, average):
        self.spin_average.setValue(average)

    def start(self):
        if self.has_range:
            async_call(self.ai.get_range, None, self.get_range_async, self.increase_error_count)

        if self.has_averaging:
            async_call(self.ai.get_averaging, None, self.get_averaging_async, self.increase_error_count)

        self.cbe_voltage.set_period(100)

        self.plot_widget.stop = False

    def stop(self):
        self.cbe_voltage.set_period(0)

        self.plot_widget.stop = True

    def destroy(self):
        pass

    @staticmethod
    def has_device_identifier(device_identifier):
        return device_identifier == BrickletAnalogIn.DEVICE_IDENTIFIER

    def cb_voltage(self, voltage):
        self.current_voltage.value = voltage / 1000.0

    def range_changed(self, index):
        if index >= 0 and self.has_range:
            range_ = self.combo_range.itemData(index)
            async_call(self.ai.set_range, range_, None, self.increase_error_count)

    def spin_average_finished(self):
        self.ai.set_averaging(self.spin_average.value())
Beispiel #22
0
    def __init__(self,
                 parent: 'ElectrumWindow',
                 config: 'SimpleConfig',
                 go_tab=None):
        WindowModalDialog.__init__(self, parent, _('Preferences'))
        self.config = config
        self.window = parent
        self.need_restart = False
        self.fx = self.window.fx
        self.wallet = self.window.wallet

        vbox = QVBoxLayout()
        tabs = QTabWidget()
        tabs.setObjectName("settings_tab")
        gui_widgets = []
        tx_widgets = []
        oa_widgets = []

        # language
        lang_help = _(
            'Select which language is used in the GUI (after restart).')
        lang_label = HelpLabel(_('Language') + ':', lang_help)
        lang_combo = QComboBox()
        lang_combo.addItems(list(languages.values()))
        lang_keys = list(languages.keys())
        lang_cur_setting = self.config.get("language", '')
        try:
            index = lang_keys.index(lang_cur_setting)
        except ValueError:  # not in list
            index = 0
        lang_combo.setCurrentIndex(index)
        if not self.config.is_modifiable('language'):
            for w in [lang_combo, lang_label]:
                w.setEnabled(False)

        def on_lang(x):
            lang_request = list(languages.keys())[lang_combo.currentIndex()]
            if lang_request != self.config.get('language'):
                self.config.set_key("language", lang_request, True)
                self.need_restart = True

        lang_combo.currentIndexChanged.connect(on_lang)
        gui_widgets.append((lang_label, lang_combo))

        nz_help = _(
            'Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"'
        )
        nz_label = HelpLabel(_('Zeros after decimal point') + ':', nz_help)
        nz = QSpinBox()
        nz.setMinimum(0)
        nz.setMaximum(self.config.decimal_point)
        nz.setValue(self.config.num_zeros)
        if not self.config.is_modifiable('num_zeros'):
            for w in [nz, nz_label]:
                w.setEnabled(False)

        def on_nz():
            value = nz.value()
            if self.config.num_zeros != value:
                self.config.num_zeros = value
                self.config.set_key('num_zeros', value, True)
                self.window.history_list.update()
                self.window.address_list.update()

        nz.valueChanged.connect(on_nz)
        gui_widgets.append((nz_label, nz))

        msg = _('OpenAlias record, used to receive coins and to sign payment requests.') + '\n\n'\
              + _('The following alias providers are available:') + '\n'\
              + '\n'.join(['https://cryptoname.co/', 'http://xmr.link']) + '\n\n'\
              + 'For more information, see https://openalias.org'
        alias_label = HelpLabel(_('OpenAlias') + ':', msg)
        alias = self.config.get('alias', '')
        self.alias_e = QLineEdit(alias)
        self.set_alias_color()
        self.alias_e.editingFinished.connect(self.on_alias_edit)
        oa_widgets.append((alias_label, self.alias_e))

        # units
        units = base_units_list
        msg = (
            _('Base unit of your wallet.') +
            '\n1 FIRO = 1000 mFIRO. 1 mFiro = 1000 bits. 1 bit = 100 satoshis.\n'
            +
            _('This setting affects the Send tab, and all balance related fields.'
              ))
        unit_label = HelpLabel(_('Base unit') + ':', msg)
        unit_combo = QComboBox()
        unit_combo.addItems(units)
        unit_combo.setCurrentIndex(units.index(self.window.base_unit()))

        def on_unit(x, nz):
            unit_result = units[unit_combo.currentIndex()]
            if self.window.base_unit() == unit_result:
                return
            edits = self.window.amount_e, self.window.receive_amount_e
            amounts = [edit.get_amount() for edit in edits]
            self.config.set_base_unit(unit_result)
            nz.setMaximum(self.config.decimal_point)
            self.window.update_tabs()
            for edit, amount in zip(edits, amounts):
                edit.setAmount(amount)
            self.window.update_status()

        unit_combo.currentIndexChanged.connect(lambda x: on_unit(x, nz))
        gui_widgets.append((unit_label, unit_combo))

        qr_combo = QComboBox()
        qr_combo.addItem("Default", "default")
        msg = (_("For scanning QR codes.") + "\n" +
               _("Install the zbar package to enable this."))
        qr_label = HelpLabel(_('Video Device') + ':', msg)
        from .qrreader import find_system_cameras
        system_cameras = find_system_cameras()
        for cam_desc, cam_path in system_cameras.items():
            qr_combo.addItem(cam_desc, cam_path)
        index = qr_combo.findData(self.config.get("video_device"))
        qr_combo.setCurrentIndex(index)
        on_video_device = lambda x: self.config.set_key(
            "video_device", qr_combo.itemData(x), True)
        qr_combo.currentIndexChanged.connect(on_video_device)
        gui_widgets.append((qr_label, qr_combo))

        colortheme_combo = QComboBox()
        colortheme_combo.addItem(_('Light'), 'default')
        colortheme_combo.addItem(_('Dark'), 'dark')
        index = colortheme_combo.findData(
            self.config.get('qt_gui_color_theme', 'default'))
        colortheme_combo.setCurrentIndex(index)
        colortheme_label = QLabel(_('Color theme') + ':')

        def on_colortheme(x):
            self.config.set_key('qt_gui_color_theme',
                                colortheme_combo.itemData(x), True)
            self.need_restart = True

        colortheme_combo.currentIndexChanged.connect(on_colortheme)
        gui_widgets.append((colortheme_label, colortheme_combo))

        show_dip2_cb = QCheckBox(_('Show transaction type in wallet history'))
        def_dip2 = not self.window.wallet.psman.unsupported
        show_dip2_cb.setChecked(self.config.get('show_dip2_tx_type', def_dip2))

        def on_dip2_state_changed(x):
            show_dip2 = (x == Qt.Checked)
            self.config.set_key('show_dip2_tx_type', show_dip2, True)
            self.window.history_model.refresh('on_dip2')

        show_dip2_cb.stateChanged.connect(on_dip2_state_changed)
        gui_widgets.append((show_dip2_cb, None))

        show_utxo_time_cb = QCheckBox(_('Show UTXO timestamp/islock time'))
        show_utxo_time_cb.setChecked(self.config.get('show_utxo_time', False))

        def on_show_utxo_time_changed(x):
            show_utxo_time = (x == Qt.Checked)
            self.config.set_key('show_utxo_time', show_utxo_time, True)
            self.window.utxo_list.update()

        show_utxo_time_cb.stateChanged.connect(on_show_utxo_time_changed)
        gui_widgets.append((show_utxo_time_cb, None))

        updatecheck_cb = QCheckBox(
            _("Automatically check for software updates"))
        updatecheck_cb.setChecked(bool(self.config.get('check_updates',
                                                       False)))

        def on_set_updatecheck(v):
            self.config.set_key('check_updates', v == Qt.Checked, save=True)

        updatecheck_cb.stateChanged.connect(on_set_updatecheck)
        gui_widgets.append((updatecheck_cb, None))

        watchonly_w_cb = QCheckBox(_('Show warning for watching only wallets'))
        watchonly_w_cb.setChecked(self.config.get('watch_only_warn', True))

        def on_set_watch_only_warn(v):
            self.config.set_key('watch_only_warn', v == Qt.Checked, save=True)

        watchonly_w_cb.stateChanged.connect(on_set_watch_only_warn)
        gui_widgets.append((watchonly_w_cb, None))

        filelogging_cb = QCheckBox(_("Write logs to file"))
        filelogging_cb.setChecked(bool(self.config.get('log_to_file', False)))

        def on_set_filelogging(v):
            self.config.set_key('log_to_file', v == Qt.Checked, save=True)
            self.need_restart = True

        filelogging_cb.stateChanged.connect(on_set_filelogging)
        filelogging_cb.setToolTip(
            _('Debug logs can be persisted to disk. These are useful for troubleshooting.'
              ))
        gui_widgets.append((filelogging_cb, None))

        preview_cb = QCheckBox(_('Advanced preview'))
        preview_cb.setChecked(bool(self.config.get('advanced_preview', False)))
        preview_cb.setToolTip(
            _("Open advanced transaction preview dialog when 'Pay' is clicked."
              ))

        def on_preview(x):
            self.config.set_key('advanced_preview', x == Qt.Checked)

        preview_cb.stateChanged.connect(on_preview)
        tx_widgets.append((preview_cb, None))

        usechange_cb = QCheckBox(_('Use change addresses'))
        usechange_cb.setChecked(self.window.wallet.use_change)
        if not self.config.is_modifiable('use_change'):
            usechange_cb.setEnabled(False)

        def on_usechange(x):
            usechange_result = x == Qt.Checked
            if self.window.wallet.use_change != usechange_result:
                self.window.wallet.use_change = usechange_result
                self.window.wallet.db.put('use_change',
                                          self.window.wallet.use_change)
                multiple_cb.setEnabled(self.window.wallet.use_change)

        usechange_cb.stateChanged.connect(on_usechange)
        usechange_cb.setToolTip(
            _('Using change addresses makes it more difficult for other people to track your transactions.'
              ))
        tx_widgets.append((usechange_cb, None))

        def on_multiple(x):
            multiple = x == Qt.Checked
            if self.wallet.multiple_change != multiple:
                self.wallet.multiple_change = multiple
                self.wallet.db.put('multiple_change', multiple)

        multiple_change = self.wallet.multiple_change
        multiple_cb = QCheckBox(_('Use multiple change addresses'))
        multiple_cb.setEnabled(self.wallet.use_change)
        multiple_cb.setToolTip('\n'.join([
            _('In some cases, use up to 3 change addresses in order to break '
              'up large coin amounts and obfuscate the recipient address.'),
            _('This may result in higher transactions fees.')
        ]))
        multiple_cb.setChecked(multiple_change)
        multiple_cb.stateChanged.connect(on_multiple)
        tx_widgets.append((multiple_cb, None))

        def fmt_docs(key, klass):
            lines = [ln.lstrip(" ") for ln in klass.__doc__.split("\n")]
            return '\n'.join([key, "", " ".join(lines)])

        choosers = sorted(coinchooser.COIN_CHOOSERS.keys())
        if len(choosers) > 1:
            chooser_name = coinchooser.get_name(self.config)
            msg = _(
                'Choose coin (UTXO) selection method.  The following are available:\n\n'
            )
            msg += '\n\n'.join(
                fmt_docs(*item) for item in coinchooser.COIN_CHOOSERS.items())
            chooser_label = HelpLabel(_('Coin selection') + ':', msg)
            chooser_combo = QComboBox()
            chooser_combo.addItems(choosers)
            i = choosers.index(chooser_name) if chooser_name in choosers else 0
            chooser_combo.setCurrentIndex(i)

            def on_chooser(x):
                chooser_name = choosers[chooser_combo.currentIndex()]
                self.config.set_key('coin_chooser', chooser_name)

            chooser_combo.currentIndexChanged.connect(on_chooser)
            tx_widgets.append((chooser_label, chooser_combo))

        def on_unconf(x):
            self.config.set_key('confirmed_only', bool(x))

        conf_only = bool(self.config.get('confirmed_only', False))
        unconf_cb = QCheckBox(_('Spend only confirmed coins'))
        unconf_cb.setToolTip(_('Spend only confirmed inputs.'))
        unconf_cb.setChecked(conf_only)
        unconf_cb.stateChanged.connect(on_unconf)
        tx_widgets.append((unconf_cb, None))

        def on_outrounding(x):
            self.config.set_key('coin_chooser_output_rounding', bool(x))

        enable_outrounding = bool(
            self.config.get('coin_chooser_output_rounding', True))
        outrounding_cb = QCheckBox(_('Enable output value rounding'))
        outrounding_cb.setToolTip(
            _('Set the value of the change output so that it has similar precision to the other outputs.'
              ) + '\n' + _('This might improve your privacy somewhat.') +
            '\n' +
            _('If enabled, at most 100 sats might be lost due to this, per transaction.'
              ))
        outrounding_cb.setChecked(enable_outrounding)
        outrounding_cb.stateChanged.connect(on_outrounding)
        tx_widgets.append((outrounding_cb, None))

        block_explorers = sorted(util.block_explorer_info().keys())
        BLOCK_EX_CUSTOM_ITEM = _("Custom URL")
        if BLOCK_EX_CUSTOM_ITEM in block_explorers:  # malicious translation?
            block_explorers.remove(BLOCK_EX_CUSTOM_ITEM)
        block_explorers.append(BLOCK_EX_CUSTOM_ITEM)
        msg = _(
            'Choose which online block explorer to use for functions that open a web browser'
        )
        block_ex_label = HelpLabel(_('Online Block Explorer') + ':', msg)
        block_ex_combo = QComboBox()
        block_ex_custom_e = QLineEdit(
            self.config.get('block_explorer_custom') or '')
        block_ex_combo.addItems(block_explorers)
        block_ex_combo.setCurrentIndex(
            block_ex_combo.findText(
                util.block_explorer(self.config) or BLOCK_EX_CUSTOM_ITEM))

        def showhide_block_ex_custom_e():
            block_ex_custom_e.setVisible(
                block_ex_combo.currentText() == BLOCK_EX_CUSTOM_ITEM)

        showhide_block_ex_custom_e()

        def on_be_combo(x):
            if block_ex_combo.currentText() == BLOCK_EX_CUSTOM_ITEM:
                on_be_edit()
            else:
                be_result = block_explorers[block_ex_combo.currentIndex()]
                self.config.set_key('block_explorer_custom', None, False)
                self.config.set_key('block_explorer', be_result, True)
            showhide_block_ex_custom_e()

        block_ex_combo.currentIndexChanged.connect(on_be_combo)

        def on_be_edit():
            val = block_ex_custom_e.text()
            try:
                val = ast.literal_eval(val)  # to also accept tuples
            except:
                pass
            self.config.set_key('block_explorer_custom', val)

        block_ex_custom_e.editingFinished.connect(on_be_edit)
        block_ex_hbox = QHBoxLayout()
        block_ex_hbox.setContentsMargins(0, 0, 0, 0)
        block_ex_hbox.setSpacing(0)
        block_ex_hbox.addWidget(block_ex_combo)
        block_ex_hbox.addWidget(block_ex_custom_e)
        block_ex_hbox_w = QWidget()
        block_ex_hbox_w.setLayout(block_ex_hbox)
        tx_widgets.append((block_ex_label, block_ex_hbox_w))

        # Fiat Currency
        hist_checkbox = QCheckBox()
        hist_capgains_checkbox = QCheckBox()
        fiat_address_checkbox = QCheckBox()
        ccy_combo = QComboBox()
        ex_combo = QComboBox()

        def update_currencies():
            if not self.window.fx: return
            currencies = sorted(
                self.fx.get_currencies(self.fx.get_history_config()))
            ccy_combo.clear()
            ccy_combo.addItems([_('None')] + currencies)
            if self.fx.is_enabled():
                ccy_combo.setCurrentIndex(
                    ccy_combo.findText(self.fx.get_currency()))

        def update_history_cb():
            if not self.fx: return
            hist_checkbox.setChecked(self.fx.get_history_config())
            hist_checkbox.setEnabled(self.fx.is_enabled())

        def update_fiat_address_cb():
            if not self.fx: return
            fiat_address_checkbox.setChecked(self.fx.get_fiat_address_config())

        def update_history_capgains_cb():
            if not self.fx: return
            hist_capgains_checkbox.setChecked(
                self.fx.get_history_capital_gains_config())
            hist_capgains_checkbox.setEnabled(hist_checkbox.isChecked())

        def update_exchanges():
            if not self.fx: return
            b = self.fx.is_enabled()
            ex_combo.setEnabled(b)
            if b:
                h = self.fx.get_history_config()
                c = self.fx.get_currency()
                exchanges = self.fx.get_exchanges_by_ccy(c, h)
            else:
                exchanges = self.fx.get_exchanges_by_ccy('USD', False)
            ex_combo.blockSignals(True)
            ex_combo.clear()
            ex_combo.addItems(sorted(exchanges))
            ex_combo.setCurrentIndex(
                ex_combo.findText(self.fx.config_exchange()))
            ex_combo.blockSignals(False)

        def on_currency(hh):
            if not self.fx: return
            b = bool(ccy_combo.currentIndex())
            ccy = str(ccy_combo.currentText()) if b else None
            self.fx.set_enabled(b)
            if b and ccy != self.fx.ccy:
                self.fx.set_currency(ccy)
            update_history_cb()
            update_exchanges()
            self.window.update_fiat()

        def on_exchange(idx):
            exchange = str(ex_combo.currentText())
            if self.fx and self.fx.is_enabled(
            ) and exchange and exchange != self.fx.exchange.name():
                self.fx.set_exchange(exchange)

        def on_history(checked):
            if not self.fx: return
            self.fx.set_history_config(checked)
            update_exchanges()
            self.window.history_model.refresh('on_history')
            if self.fx.is_enabled() and checked:
                self.fx.trigger_update()
            update_history_capgains_cb()

        def on_history_capgains(checked):
            if not self.fx: return
            self.fx.set_history_capital_gains_config(checked)
            self.window.history_model.refresh('on_history_capgains')

        def on_fiat_address(checked):
            if not self.fx: return
            self.fx.set_fiat_address_config(checked)
            self.window.address_list.refresh_headers()
            self.window.address_list.update()

        update_currencies()
        update_history_cb()
        update_history_capgains_cb()
        update_fiat_address_cb()
        update_exchanges()
        ccy_combo.currentIndexChanged.connect(on_currency)
        hist_checkbox.stateChanged.connect(on_history)
        hist_capgains_checkbox.stateChanged.connect(on_history_capgains)
        fiat_address_checkbox.stateChanged.connect(on_fiat_address)
        ex_combo.currentIndexChanged.connect(on_exchange)

        fiat_widgets = []
        fiat_widgets.append((QLabel(_('Fiat currency')), ccy_combo))
        fiat_widgets.append((QLabel(_('Source')), ex_combo))
        fiat_widgets.append((QLabel(_('Show history rates')), hist_checkbox))
        fiat_widgets.append((QLabel(_('Show capital gains in history')),
                             hist_capgains_checkbox))
        fiat_widgets.append((QLabel(_('Show Fiat balance for addresses')),
                             fiat_address_checkbox))

        tabs_info = [
            (gui_widgets, _('General')),
            (tx_widgets, _('Transactions')),
            (fiat_widgets, _('Fiat')),
            (oa_widgets, _('OpenAlias')),
        ]
        for widgets, name in tabs_info:
            tab = QWidget()
            tab.setObjectName(name)
            tab_vbox = QVBoxLayout(tab)
            grid = QGridLayout()
            for a, b in widgets:
                i = grid.rowCount()
                if b:
                    if a:
                        grid.addWidget(a, i, 0)
                    grid.addWidget(b, i, 1)
                else:
                    grid.addWidget(a, i, 0, 1, 2)
            tab_vbox.addLayout(grid)
            tab_vbox.addStretch(1)
            tabs.addTab(tab, name)

        vbox.addWidget(tabs)
        vbox.addStretch(1)
        vbox.addLayout(Buttons(CloseButton(self)))
        self.setLayout(vbox)

        if go_tab is not None:
            go_tab_w = tabs.findChild(QWidget, go_tab)
            if go_tab_w:
                tabs.setCurrentWidget(go_tab_w)
Beispiel #23
0
class DialogoOferta(QDialog):
    def __init__(self, parent=None, oferta=None, lotes=[], empresas=[], conjunto_ofertas=ConjuntoOfertas()):
        super().__init__(parent)
        self.array_lotes = lotes
        self.array_empresas = empresas
        self.conjunto_ofertas = conjunto_ofertas
        self.oferta = oferta
        self.dibujar_IU()
        if self.oferta != None:
            self.cargar_oferta()
            self.setWindowTitle("Modificación de Oferta")
        self.marcar_empresa_erronea()
    
    def dibujar_IU(self):
        self.setWindowTitle("Ingreso de Oferta")
        self.setWindowModality(Qt.ApplicationModal)
        self.setSizeGripEnabled(False)
        self.setContentsMargins(10, 10, 10, 10)

        self.empresa = QComboBox()
        self.lote = QComboBox()
        self.valor = QLineEdit()
        self.empresa_error = QLabel("*")
        self.lote_error = QLabel("*")
        self.valor_error = QLabel("*")
        self.espaciador = QLabel(" ")

        self.cargar_lista_lotes()
        self.cargar_lista_empresas()

        self.empresa.setFixedWidth(250)
        self.empresa.currentTextChanged.connect(self.marcar_empresa_erronea)
        self.lote.currentTextChanged.connect(self.marcar_lote_erroneo)
        self.lote.setFixedWidth(250)
        self.valor.setValidator(QDoubleValidator(0, 999999999, 3))
        self.valor.textChanged.connect(self.marcar_valor_erroneo)
        self.valor.setAlignment(Qt.AlignRight)
        self.valor.setMaximumWidth(150)
        self.valor.setToolTip("Valor que oferta la Empresa por el Lote")
        self.empresa_error.setStyleSheet("QLabel {color : red}")
        self.empresa_error.setVisible(False)
        self.empresa_error.setFixedWidth(10)
        self.lote_error.setStyleSheet("QLabel {color : red}")
        self.lote_error.setVisible(False)
        self.lote_error.setFixedWidth(10)
        self.valor_error.setStyleSheet("QLabel {color : red}")
        self.valor_error.setVisible(False)
        self.valor_error.setFixedWidth(10)
        self.espaciador.setFixedWidth(10)
        self.espaciador.setFixedHeight(0)

        boton_confirmar = QPushButton(QIcon("iconos/check.png"), "")
        boton_confirmar.clicked.connect(self.accept)
        boton_confirmar.setDefault(True)
        boton_confirmar.setMinimumSize(50, 10)
        boton_cancelar = QPushButton(QIcon("iconos/cancel.png"), "")
        boton_cancelar.clicked.connect(self.reject)
        boton_cancelar.setMinimumSize(50, 10)
        caja_horizontal = QHBoxLayout()
        caja_horizontal.addStretch(1)
        caja_horizontal.addWidget(boton_cancelar)
        caja_horizontal.addStretch(5)
        caja_horizontal.addWidget(boton_confirmar)
        caja_horizontal.addStretch(1)
        caja_horizontal.setContentsMargins(10, 10, 10, 0)

        grilla = QGridLayout()
        grilla.setColumnMinimumWidth(1, 20)
        grilla.addWidget(QLabel("Empresa"), 0, 0)
        grilla.addWidget(self.empresa_error, 0, 1, Qt.AlignTop | Qt.AlignRight)
        grilla.addWidget(self.empresa, 0, 2)
        grilla.addWidget(QLabel("Lote"), 1, 0)
        grilla.addWidget(self.lote_error, 1, 1, Qt.AlignTop | Qt.AlignRight)
        grilla.addWidget(self.lote, 1, 2)
        grilla.addWidget(QLabel("valor"), 2, 0)
        grilla.addWidget(self.valor_error, 2, 1, Qt.AlignTop | Qt.AlignRight)
        grilla.addWidget(self.valor, 2, 2)
        grilla.addWidget(self.espaciador, 3, 1)
        marco = QGroupBox("Oferta")
        marco.setLayout(grilla)

        formulario = QFormLayout(self)
        formulario.addRow(marco)
        formulario.addRow(caja_horizontal)
        self.setMinimumSize(self.sizeHint())
        self.setMaximumSize(self.sizeHint())
    
    def accept(self):
        self.marcar_campos_erroneos()
        if self.oferta_existente():
            self.lote.setFocus()
            self.lote.showPopup()
        elif len(self.valor.text()) == 0:
            self.valor.setFocus()
        else:
            super().accept()
    
    def marcar_campos_erroneos(self):
        self.marcar_lote_erroneo()
        self.marcar_valor_erroneo()
    
    def marcar_empresa_erronea(self):
        if self.oferta_existente():
            self.empresa_error.setVisible(True)
            self.lote_error.setVisible(True)
        else:
            self.empresa_error.setVisible(False)
            self.lote_error.setVisible(False)
    
    def marcar_lote_erroneo(self):
        if self.oferta_existente():
            self.empresa_error.setVisible(True)
            self.lote_error.setVisible(True)
        else:
            self.empresa_error.setVisible(False)
            self.lote_error.setVisible(False)
    
    def marcar_valor_erroneo(self):
        if len(self.valor.text()) == 0:
            self.valor_error.setVisible(True)
        else:
            self.valor_error.setVisible(False)
    
    def cargar_oferta(self):
        self.empresa.setCurrentIndex(self.empresa.findData(self.oferta.empresa))
        self.lote.setCurrentIndex(self.lote.findData(self.oferta.lote))
        self.valor.setText(str(self.oferta.valor))
        self.marcar_campos_erroneos()

    def cargar_lista_lotes(self):
        for lote in self.array_lotes:
            if lote.descripcion != "":
                self.lote.addItem("{0}".format(lote.descripcion.strip()), lote)
            else:
                self.lote.addItem("Lote {0}".format(lote.id), lote)

    def cargar_lista_empresas(self):
        for empresa in self.array_empresas:
            self.empresa.addItem(empresa.nombre.strip(), empresa)

    def obtener_oferta(self):
        return Oferta(self.empresa.currentData(), self.lote.currentData(), float(self.valor.text()))
    
    def oferta_existente(self):
        if self.oferta != None and self.oferta.es_equivalente(Oferta(self.empresa.currentData(), self.lote.currentData(), 0.00, efectiva=False)):
            return False
        else:
            return self.conjunto_ofertas.oferta_existente(Oferta(self.empresa.currentData(), self.lote.currentData(), 0.00, efectiva=False))
Beispiel #24
0
class QualityRateControlTab(QWidget):
    optionchanged = pyqtSignal()

    def __init__(self, encoder, *args, **kwargs):
        super(QualityRateControlTab, self).__init__(*args, **kwargs)
        self.encoder = encoder

        layout = QVBoxLayout()
        layout.setSpacing(4)
        self.setLayout(layout)

        self.rateControlSelection = QComboBox(self)
        self.rateControlSelection.addItem("Not set")
        self.rateControlSelection.insertSeparator(1)
        self.rateControlSelection.addItem("Bitrate", 0)
        self.rateControlSelection.addItem("CRF", 1)
        self.rateControlSelection.addItem("QP", 2)
        self.rateControlSelection.addItem("Lossless", 3)

        self.bitrateSpinBox = QSpinBox(self)
        self.bitrateSpinBox.setMinimum(200)
        self.bitrateSpinBox.setValue(8000)
        self.bitrateSpinBox.setSuffix("kbps")
        self.bitrateSpinBox.setMaximum(60000)
        self.bitrateSpinBox.setSingleStep(100)
        self.bitrateSpinBox.setHidden(True)

        self.crfSpinBox = QDoubleSpinBox(self)
        self.crfSpinBox.setMinimum(0)
        self.crfSpinBox.setValue(28)
        self.crfSpinBox.setMaximum(51)
        self.crfSpinBox.setSingleStep(0.1)
        self.crfSpinBox.setDecimals(2)
        self.crfSpinBox.setHidden(True)

        self.qpSpinBox = QSpinBox(self)
        self.qpSpinBox.setMinimum(0)
        self.qpSpinBox.setValue(22)
        self.qpSpinBox.setMaximum(54)
        self.qpSpinBox.setSingleStep(1)
        self.qpSpinBox.setHidden(True)

        self.rcSpacer = QWidget(self)
        self.rcSpacer.setHidden(True)

        self.bitrateSpinBox.setFixedWidth(96)
        self.crfSpinBox.setFixedWidth(96)
        self.qpSpinBox.setFixedWidth(96)
        self.rcSpacer.setFixedWidth(96)

        if encoder.bitrate is not None:
            idx = self.rateControlSelection.findData(0)
            self.rateControlSelection.setCurrentIndex(idx)
            self.bitrateSpinBox.setValue(encoder.bitrate)
            self.bitrateSpinBox.setVisible(True)

        elif encoder.crf is not None:
            idx = self.rateControlSelection.findData(1)
            self.rateControlSelection.setCurrentIndex(idx)
            self.crfSpinBox.setValue(encoder.crf)
            self.crfSpinBox.setVisible(True)

        elif encoder.qp is not None:
            idx = self.rateControlSelection.findData(2)
            self.rateControlSelection.setCurrentIndex(idx)
            self.qpSpinBox.setValue(encoder.qp)
            self.qpSpinBox.setVisible(True)

        elif encoder.lossless:
            idx = self.rateControlSelection.findData(3)
            self.rateControlSelection.setCurrentIndex(idx)

        else:
            self.rateControlSelection.setCurrentIndex(0)
            self.rcSpacer.setVisible(True)

        self.rateControlSelection.currentIndexChanged.connect(
            self.onRateControlModeChange)
        self.bitrateSpinBox.valueChanged.connect(self.setBitrate)
        self.crfSpinBox.valueChanged.connect(self.setCRF)
        self.qpSpinBox.valueChanged.connect(self.setQP)

        hlayout = QHBoxLayout()
        hlayout.addWidget(QLabel("Rate Control"))
        hlayout.addStretch()
        hlayout.addWidget(self.rateControlSelection)
        hlayout.addWidget(self.bitrateSpinBox)
        hlayout.addWidget(self.crfSpinBox)
        hlayout.addWidget(self.qpSpinBox)
        hlayout.addWidget(self.rcSpacer)
        layout.addLayout(hlayout)

        self.aqmotion = BoolOption(encoder, "AQ Motion", "aq-motion", self)
        self.aqmotion.stateChanged.connect(self.optionchanged.emit)
        self.aqmotion.setToolTip("""
            <b>--aq-motion, --no-aq-motion</b>
            <p>Adjust the AQ offsets based on the relative motion of each
            block with respect to the motion of the frame. The more the
            relative motion of the block, the more quantization is used.</p>
            <p>Default disabled. <b>Experimental Feature.</b></p>
            """)
        layout.addWidget(self.aqmotion)

        self.aqmode = Choices(
            encoder, "AQ Mode", "aq-mode",
            [("Disabled", 0), ("AQ Enabled", 1),
             ("AQ enabled with auto-variance", 2),
             ("AQ enabled with auto-variance and bias to dark scenes", 3),
             ("AQ enabled with auto-variance and edge information", 4)], self)
        self.aqmode.selection.currentIndexChanged.connect(
            self.optionchanged.emit)
        self.aqmode.setToolTip("""
            <b>--aq-mode &lt;0|1|2|3|4&gt;</b>
            <p>Adaptive Quantization operating mode. Raise or lower per-block
            quantization based on complexity analysis of the source image. The
            more complex the block, the more quantization is used. These
            offsets the tendency of the encoder to spend too many bits on
            complex areas and not enough in flat areas.</p>
            """)
        layout.addWidget(self.aqmode)

        self.aqstrength = FloatOption(encoder, "AQ Strength", "aq-strength", 0,
                                      3, 1, 2, self)
        self.aqstrength.spinbox.valueChanged.connect(self.optionchanged.emit)
        self.aqstrength.setToolTip("""
            <b>--aq-strength &lt;float&gt;</b>
            <p>Adjust the strength of the adaptive quantization offsets.
            Setting <span class="pre">--aq-strength</span> to 0 disables AQ.
            At aq-modes 2 and 3, high aq-strengths will lead to high QP
            offsets resulting in a large difference in achieved bitrates.</p>
            <p>Default 1.0. Range of values: 0.0 to 3.0</p>
            """)
        layout.addWidget(self.aqstrength)

        self.qgsize = Choices(encoder, "QG Size", "qg-size", [("8", 8),
                                                              ("16", 16),
                                                              ("32", 32),
                                                              ("64", 64)],
                              self)
        self.qgsize.selection.currentIndexChanged.connect(
            self.optionchanged.emit)
        self.qgsize.setToolTip("""
            <b>--qg-size &lt;64|32|16|8&gt;</b>
            <p>Enable adaptive quantization for sub-CTUs. This parameter
            specifies the minimum CU size at which QP can be adjusted, ie.
            Quantization Group size. Allowed range of values are 64, 32, 16, 8
            provided this falls within the inclusive range [maxCUSize,
            minCUSize].</p>
            <p>Default: same as maxCUSize</p>
            """)
        layout.addWidget(self.qgsize)
        """
        TODO:
        --vbv-bufsize
        --vbv-maxrate
        --vbv-init
        --vbv-end
        --vbv-end-fr-adj

        --crf-max
        --crf-min

        --qpmin
        --qpmax

        --lossless

        --aq-motion
        --hevc-aq
        --qp-adaptation-range
        #--qg-size
        --cutree
        --slow-firstpass
        --multi-pass-opt-analysis
        --multi-pass-opt-distortion
        --strict-cbr
        --cbqpoffs
        --crqpoffs
        --ipratio
        --pbratio
        --qcomp
        --qpstep
        --rc-grain
        --const-vbv
        --qblur
        --cplxblur
        --scenecut-aware-qp
        --scenecut-window
        --max-qp-delta
        """

        layout.addStretch()

    def onRateControlModeChange(self, index):
        data = self.rateControlSelection.currentData()

        if data == 0:
            self.encoder.bitrate = self.bitrateSpinBox.value()
            self.encoder.crf = None
            self.encoder.qp = None
            self.encoder.lossless = None

            self.crfSpinBox.setHidden(True)
            self.qpSpinBox.setHidden(True)
            self.rcSpacer.setHidden(True)
            self.bitrateSpinBox.setHidden(False)

        elif data == 1:
            self.encoder.bitrate = None
            self.encoder.crf = self.crfSpinBox.value()
            self.encoder.qp = None
            self.encoder.lossless = None

            self.bitrateSpinBox.setHidden(True)
            self.qpSpinBox.setHidden(True)
            self.rcSpacer.setHidden(True)
            self.crfSpinBox.setHidden(False)

        elif data == 2:
            self.encoder.bitrate = None
            self.encoder.crf = None
            self.encoder.qp = self.qpSpinBox.value()
            self.encoder.lossless = None

            self.bitrateSpinBox.setHidden(True)
            self.crfSpinBox.setHidden(True)
            self.rcSpacer.setHidden(True)
            self.qpSpinBox.setHidden(False)

        elif data == 3:
            self.encoder.bitrate = None
            self.encoder.crf = None
            self.encoder.qp = None
            self.encoder.lossless = True

            self.bitrateSpinBox.setHidden(True)
            self.crfSpinBox.setHidden(True)
            self.qpSpinBox.setHidden(True)
            self.rcSpacer.setHidden(False)

        else:
            self.encoder.bitrate = None
            self.encoder.crf = None
            self.encoder.qp = None
            self.encoder.lossless = None

            self.crfSpinBox.setHidden(True)
            self.qpSpinBox.setHidden(True)
            self.bitrateSpinBox.setHidden(True)
            self.rcSpacer.setHidden(False)

        self.optionchanged.emit()

    def setBitrate(self, value):
        self.encoder.bitrate = value
        self.optionchanged.emit()

    def setCRF(self, value):
        self.encoder.crf = value
        self.optionchanged.emit()

    def setQP(self, value):
        self.encoder.qp = value
        self.optionchanged.emit()
Beispiel #25
0
class GuiPreferencesEditor(QWidget):
    def __init__(self, theParent):
        QWidget.__init__(self, theParent)

        self.mainConf = novelwriter.CONFIG
        self.theParent = theParent
        self.theTheme = theParent.theTheme

        # The Form
        self.mainForm = QConfigLayout()
        self.mainForm.setHelpTextStyle(self.theTheme.helpText)
        self.setLayout(self.mainForm)

        mW = self.mainConf.pxInt(250)

        # Spell Checking
        # ==============
        self.mainForm.addGroupLabel(self.tr("Spell Checking"))

        # Spell Check Provider and Language
        self.spellLanguage = QComboBox(self)
        self.spellLanguage.setMaximumWidth(mW)

        langAvail = self.theParent.docEditor.spEnchant.listDictionaries()
        if self.mainConf.hasEnchant:
            if langAvail:
                for spTag, spProv in langAvail:
                    qLocal = QLocale(spTag)
                    spLang = qLocal.nativeLanguageName().title()
                    self.spellLanguage.addItem("%s [%s]" % (spLang, spProv),
                                               spTag)
            else:
                self.spellLanguage.addItem(self.tr("None"), "")
                self.spellLanguage.setEnabled(False)
        else:
            self.spellLanguage.addItem(self.tr("Not installed"), "")
            self.spellLanguage.setEnabled(False)

        spellIdx = self.spellLanguage.findData(self.mainConf.spellLanguage)
        if spellIdx != -1:
            self.spellLanguage.setCurrentIndex(spellIdx)

        self.mainForm.addRow(
            self.tr("Spell check language"), self.spellLanguage,
            self.tr("Available languages are determined by your system."))

        # Big Document Size Limit
        self.bigDocLimit = QSpinBox(self)
        self.bigDocLimit.setMinimum(10)
        self.bigDocLimit.setMaximum(10000)
        self.bigDocLimit.setSingleStep(10)
        self.bigDocLimit.setValue(self.mainConf.bigDocLimit)
        self.mainForm.addRow(
            self.tr("Big document limit"),
            self.bigDocLimit,
            self.tr("Full spell checking is disabled above this limit."),
            theUnit=self.tr("kB"))

        # Word Count
        # ==========
        self.mainForm.addGroupLabel(self.tr("Word Count"))

        # Word Count Timer
        self.wordCountTimer = QDoubleSpinBox(self)
        self.wordCountTimer.setDecimals(1)
        self.wordCountTimer.setMinimum(2.0)
        self.wordCountTimer.setMaximum(600.0)
        self.wordCountTimer.setSingleStep(0.1)
        self.wordCountTimer.setValue(self.mainConf.wordCountTimer)
        self.mainForm.addRow(self.tr("Word count interval"),
                             self.wordCountTimer,
                             theUnit=self.tr("seconds"))

        # Include Notes in Word Count
        self.incNotesWCount = QSwitch()
        self.incNotesWCount.setChecked(self.mainConf.incNotesWCount)
        self.mainForm.addRow(
            self.tr("Include project notes in status bar word count"),
            self.incNotesWCount)

        # Writing Guides
        # ==============
        self.mainForm.addGroupLabel(self.tr("Writing Guides"))

        # Show Tabs and Spaces
        self.showTabsNSpaces = QSwitch()
        self.showTabsNSpaces.setChecked(self.mainConf.showTabsNSpaces)
        self.mainForm.addRow(self.tr("Show tabs and spaces"),
                             self.showTabsNSpaces)

        # Show Line Endings
        self.showLineEndings = QSwitch()
        self.showLineEndings.setChecked(self.mainConf.showLineEndings)
        self.mainForm.addRow(self.tr("Show line endings"),
                             self.showLineEndings)

        # Scroll Behaviour
        # ================
        self.mainForm.addGroupLabel(self.tr("Scroll Behaviour"))

        # Scroll Past End
        self.scrollPastEnd = QSpinBox(self)
        self.scrollPastEnd.setMinimum(0)
        self.scrollPastEnd.setMaximum(100)
        self.scrollPastEnd.setSingleStep(1)
        self.scrollPastEnd.setValue(int(self.mainConf.scrollPastEnd))
        self.mainForm.addRow(self.tr("Scroll past end of the document"),
                             self.scrollPastEnd,
                             self.tr("Set to 0 to disable this feature."),
                             theUnit=self.tr("lines"))

        # Typewriter Scrolling
        self.autoScroll = QSwitch()
        self.autoScroll.setChecked(self.mainConf.autoScroll)
        self.mainForm.addRow(
            self.tr("Typewriter style scrolling when you type"),
            self.autoScroll,
            self.tr("Keeps the cursor at a fixed vertical position."))

        # Typewriter Position
        self.autoScrollPos = QSpinBox(self)
        self.autoScrollPos.setMinimum(10)
        self.autoScrollPos.setMaximum(90)
        self.autoScrollPos.setSingleStep(1)
        self.autoScrollPos.setValue(int(self.mainConf.autoScrollPos))
        self.mainForm.addRow(
            self.tr("Minimum position for Typewriter scrolling"),
            self.autoScrollPos,
            self.tr("Percentage of the editor height from the top."),
            theUnit="%")

        return

    def saveValues(self):
        """Save the values set for this tab.
        """
        # Spell Checking
        self.mainConf.spellLanguage = self.spellLanguage.currentData()
        self.mainConf.bigDocLimit = self.bigDocLimit.value()

        # Word Count
        self.mainConf.wordCountTimer = self.wordCountTimer.value()
        self.mainConf.incNotesWCount = self.incNotesWCount.isChecked()

        # Writing Guides
        self.mainConf.showTabsNSpaces = self.showTabsNSpaces.isChecked()
        self.mainConf.showLineEndings = self.showLineEndings.isChecked()

        # Scroll Behaviour
        self.mainConf.scrollPastEnd = self.scrollPastEnd.value()
        self.mainConf.autoScroll = self.autoScroll.isChecked()
        self.mainConf.autoScrollPos = self.autoScrollPos.value()

        self.mainConf.confChanged = True

        return
Beispiel #26
0
class SchemeSelector(QWidget):

    currentChanged = pyqtSignal()
    changed = pyqtSignal()

    def __init__(self, parent=None):
        super(SchemeSelector, self).__init__(parent)
        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        self.setLayout(layout)

        self.label = QLabel()
        self.scheme = QComboBox()
        self.menuButton = QPushButton(flat=True)
        menu = QMenu(self.menuButton)
        self.menuButton.setMenu(menu)
        layout.addWidget(self.label)
        layout.addWidget(self.scheme)
        layout.addWidget(self.menuButton)
        layout.addStretch(1)

        # action generator
        def act(slot, icon=None):
            a = QAction(self, triggered=slot)
            self.addAction(a)
            icon and a.setIcon(icons.get(icon))
            return a

        # add action
        a = self.addAction_ = act(self.slotAdd, 'list-add')
        menu.addAction(a)

        # remove action
        a = self.removeAction = act(self.slotRemove, 'list-remove')
        menu.addAction(a)

        # rename action
        a = self.renameAction = act(self.slotRename, 'document-edit')
        menu.addAction(a)

        menu.addSeparator()

        # import action
        a = self.importAction = act(self.slotImport, 'document-open')
        menu.addAction(a)

        # export action
        a = self.exportAction = act(self.slotExport, 'document-save-as')
        menu.addAction(a)

        self.scheme.currentIndexChanged.connect(self.slotSchemeChanged)
        app.translateUI(self)

    def translateUI(self):
        self.label.setText(_("Scheme:"))
        self.menuButton.setText(_("&Menu"))
        self.addAction_.setText(_("&Add..."))
        self.removeAction.setText(_("&Remove"))
        self.renameAction.setText(_("Re&name..."))
        self.importAction.setText(_("&Import..."))
        self.exportAction.setText(_("&Export..."))

    def slotSchemeChanged(self, index):
        """Called when the Scheme combobox is changed by the user."""
        self.disableDefault(self.scheme.itemData(index) == 'default')
        self.currentChanged.emit()
        self.changed.emit()

    def disableDefault(self, val):
        self.removeAction.setDisabled(val)
        self.renameAction.setDisabled(val)

    def schemes(self):
        """Returns the list with internal names of currently available schemes."""
        return [self.scheme.itemData(i) for i in range(self.scheme.count())]

    def currentScheme(self):
        """Returns the internal name of the currently selected scheme"""
        return self.scheme.itemData(self.scheme.currentIndex())

    def insertSchemeItem(self, name, scheme):
        for i in range(1, self.scheme.count()):
            n = self.scheme.itemText(i)
            if n.lower() > name.lower():
                self.scheme.insertItem(i, name, scheme)
                break
        else:
            self.scheme.addItem(name, scheme)

    def addScheme(self, name):
        num, key = 1, 'user1'
        while key in self.schemes() or key in self._schemesToRemove:
            num += 1
            key = 'user{0}'.format(num)
        self.insertSchemeItem(name, key)
        self.scheme.setCurrentIndex(self.scheme.findData(key))
        return key

    def slotAdd(self):
        name, ok = QInputDialog.getText(
            self, app.caption(_("Add Scheme")),
            _("Please enter a name for the new scheme:"))
        if ok:
            self.addScheme(name)

    def slotRemove(self):
        index = self.scheme.currentIndex()
        scheme = self.scheme.itemData(index)
        if scheme == 'default':
            return  # default can not be removed

        self._schemesToRemove.add(scheme)
        self.scheme.removeItem(index)

    def slotRename(self):
        index = self.scheme.currentIndex()
        name = self.scheme.itemText(index)
        scheme = self.scheme.itemData(index)
        newName, ok = QInputDialog.getText(self,
                                           _("Rename"),
                                           _("New name:"),
                                           text=name)
        if ok:
            self.scheme.blockSignals(True)
            self.scheme.removeItem(index)
            self.insertSchemeItem(newName, scheme)
            self.scheme.setCurrentIndex(self.scheme.findData(scheme))
            self.scheme.blockSignals(False)
            self.changed.emit()

    def slotImport(self):
        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"),
                                                  _("All Files"))
        caption = app.caption(_("dialog title", "Import color theme"))
        filename = QFileDialog.getOpenFileName(self, caption, QDir.homePath(),
                                               filetypes)[0]
        if filename:
            self.parent().import_(filename)

    def slotExport(self):
        name = self.scheme.currentText()
        filetypes = "{0} (*.xml);;{1} (*)".format(_("XML Files"),
                                                  _("All Files"))
        caption = app.caption(
            _("dialog title", "Export {name}").format(name=name))
        path = os.path.join(QDir.homePath(), name + '.xml')
        filename = QFileDialog.getSaveFileName(self, caption, path,
                                               filetypes)[0]
        if filename:
            if os.path.splitext(filename)[1] != '.xml':
                filename += '.xml'
            self.parent().export(name, filename)

    def loadSettings(self, currentKey, namesGroup):
        # don't mark schemes for removal anymore
        self._schemesToRemove = set()

        s = QSettings()
        cur = s.value(currentKey, "default", str)

        # load the names for the shortcut schemes
        s.beginGroup(namesGroup)
        block = self.scheme.blockSignals(True)
        self.scheme.clear()
        self.scheme.addItem(_("Default"), "default")
        lst = [(s.value(key, key, str), key) for key in s.childKeys()]
        for name, key in sorted(lst, key=lambda f: f[0].lower()):
            self.scheme.addItem(name, key)

        # find out index
        index = self.scheme.findData(cur)
        self.disableDefault(cur == 'default')
        self.scheme.setCurrentIndex(index)
        self.scheme.blockSignals(block)
        self.currentChanged.emit()

    def saveSettings(self, currentKey, namesGroup, removePrefix=None):
        # first save new scheme names
        s = QSettings()
        s.beginGroup(namesGroup)
        for i in range(self.scheme.count()):
            if self.scheme.itemData(i) != 'default':
                s.setValue(self.scheme.itemData(i), self.scheme.itemText(i))

        for scheme in self._schemesToRemove:
            s.remove(scheme)
        s.endGroup()
        if removePrefix:
            for scheme in self._schemesToRemove:
                s.remove("{0}/{1}".format(removePrefix, scheme))
        # then save current
        scheme = self.currentScheme()
        s.setValue(currentKey, scheme)
        # clean up
        self._schemesToRemove = set()
Beispiel #27
0
class EmulatorPanel(QWidget):
    def __init__(self, app, *__args):
        super().__init__(*__args)

        self.app = app
        self.emulator = self.app.dwarf.emulator
        self.until_address = 0

        self._uc_user_arch = None
        self._uc_user_mode = None
        self._cs_user_arch = None
        self._cs_user_mode = None

        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)

        self._toolbar_container = QHBoxLayout()
        self._toolbar = QToolBar()
        self._toolbar.addAction('Start', self.handle_start)
        self._toolbar.addAction('Step', self.handle_step)
        self._toolbar.addAction('Step next call', self.handle_step_next_call)
        self._toolbar.addAction('Step next jump', self.handle_step_next_jump)
        self._toolbar.addAction('Stop', self.handle_stop)
        self._toolbar.addAction('Clear', self.handle_clear)
        self._toolbar.addAction('Options', self.handle_options)
        self._toolbar_container.addWidget(self._toolbar)

        selection_layout = QHBoxLayout()
        selection_layout.setAlignment(Qt.AlignRight)
        self.cpu_selection = QComboBox(self)
        for v in unicorn_const.__dict__:
            if 'UC_ARCH_' in v:
                self.cpu_selection.addItem('_'.join(v.split('_')[2:]).lower(),
                                           unicorn_const.__dict__[v])
        self.cpu_selection.activated[str].connect(self._on_cpu_selection)
        self.mode_selection = QComboBox(self)
        for v in unicorn_const.__dict__:
            if 'UC_MODE_' in v:
                self.mode_selection.addItem('_'.join(v.split('_')[2:]).lower(),
                                            unicorn_const.__dict__[v])
        self.mode_selection.activated[str].connect(self._on_mode_selection)
        selection_layout.addWidget(self.cpu_selection)
        selection_layout.addWidget(self.mode_selection)
        self._toolbar_container.addLayout(selection_layout)

        layout.addLayout(self._toolbar_container)

        self.tabs = QTabWidget()
        self.assembly = DisassemblyView(self.app)
        self.assembly.display_jumps = False
        self.assembly.follow_jumps = False
        self.memory_table = MemoryPanel(self.app)
        self.memory_table._read_only = True
        self.tabs.addTab(self.assembly, 'Code')
        self.tabs.addTab(self.memory_table, 'Memory')

        layout.addWidget(self.tabs)

        self.ranges_list = DwarfListView(self.app)
        self.ranges_list.doubleClicked.connect(self.ranges_item_double_clicked)
        self._ranges_model = QStandardItemModel(0, 2)
        self._ranges_model.setHeaderData(0, Qt.Horizontal, 'Memory')
        self._ranges_model.setHeaderData(0, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._ranges_model.setHeaderData(1, Qt.Horizontal, 'Size')
        self._ranges_model.setHeaderData(1, Qt.Horizontal, Qt.AlignLeft,
                                         Qt.TextAlignmentRole)
        self.ranges_list.setModel(self._ranges_model)
        self.tabs.addTab(self.ranges_list, 'Ranges')

        self._access_list = DwarfListView(self.app)
        self._access_list.doubleClicked.connect(
            self.access_item_double_clicked)
        self._access_model = QStandardItemModel(0, 3)
        self._access_model.setHeaderData(0, Qt.Horizontal, 'Address')
        self._access_model.setHeaderData(0, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._access_model.setHeaderData(1, Qt.Horizontal, 'Access')
        self._access_model.setHeaderData(1, Qt.Horizontal, Qt.AlignCenter,
                                         Qt.TextAlignmentRole)
        self._access_model.setHeaderData(2, Qt.Horizontal, 'Value')
        self._access_list.setModel(self._access_model)
        self.tabs.addTab(self._access_list, 'Access')

        layout.setSpacing(0)
        self.setLayout(layout)

        self.console = self.app.console.get_emu_console()

        self.emulator.onEmulatorSetup.connect(self.on_emulator_setup)
        self.emulator.onEmulatorStart.connect(self.on_emulator_start)
        self.emulator.onEmulatorStop.connect(self.on_emulator_stop)
        # self.emulator.onEmulatorStep.connect(self.on_emulator_step)
        self.emulator.onEmulatorHook.connect(self.on_emulator_hook)
        self.emulator.onEmulatorMemoryHook.connect(
            self.on_emulator_memory_hook)
        self.emulator.onEmulatorMemoryRangeMapped.connect(
            self.on_emulator_memory_range_mapped)
        self.emulator.onEmulatorLog.connect(self.on_emulator_log)

        self._require_register_result = None
        self._last_instruction_address = 0

    def _on_cpu_selection(self, cpu):
        self._uc_user_arch = unicorn_const.__dict__['UC_ARCH_' + cpu.upper()]
        self._cs_user_arch = capstone.__dict__['CS_ARCH_' + cpu.upper()]
        self._uc_user_mode = unicorn_const.__dict__[
            'UC_MODE_' + self.mode_selection.itemText(
                self.mode_selection.currentIndex()).upper()]
        self._cs_user_mode = capstone.__dict__[
            'CS_MODE_' + self.mode_selection.itemText(
                self.mode_selection.currentIndex()).upper()]

    def _on_mode_selection(self, mode):
        self._uc_user_mode = unicorn_const.__dict__['UC_MODE_' + mode.upper()]
        self._cs_user_mode = capstone.__dict__['CS_MODE_' + mode.upper()]
        self._uc_user_arch = unicorn_const.__dict__[
            'UC_ARCH_' + self.cpu_selection.itemText(
                self.cpu_selection.currentIndex()).upper()]
        self._cs_user_arch = capstone.__dict__[
            'CS_ARCH_' + self.cpu_selection.itemText(
                self.cpu_selection.currentIndex()).upper()]

    def resizeEvent(self, event):
        self.ranges_list.setFixedHeight((self.height() / 100) * 25)
        self.ranges_list.setFixedWidth((self.width() / 100) * 30)
        self._access_list.setFixedHeight((self.height() / 100) * 25)
        return super().resizeEvent(event)

    def handle_clear(self):
        self.ranges_list.clear()
        self._access_list.clear()
        self.assembly._lines.clear()
        self.assembly.viewport().update()
        # self.memory_table.setRowCount(0)
        self.console.clear()
        self.emulator.clean()

    def handle_options(self):
        EmulatorConfigsDialog.show_dialog(self.app.dwarf)

    def handle_start(self):
        ph = ''
        if self.until_address > 0:
            ph = hex(self.until_address)
        address, inp = InputDialog.input_pointer(
            self.app, input_content=ph, hint='pointer to last instruction')
        if address > 0:
            self.until_address = address
            self.app.console_panel.show_console_tab('emulator')
            self.emulator.emulate(self.until_address,
                                  user_arch=self._uc_user_arch,
                                  user_mode=self._uc_user_mode,
                                  cs_arch=self._cs_user_arch,
                                  cs_mode=self._cs_user_mode)
            # if err > 0:
            #    self.until_address = 0
            #    self.console.log('cannot start emulator. err: %d' % err)
            #    return

    def handle_step(self):
        self.app.console_panel.show_console_tab('emulator')

        try:
            self.emulator.emulate(step_mode=STEP_MODE_SINGLE,
                                  user_arch=self._uc_user_arch,
                                  user_mode=self._uc_user_mode,
                                  cs_arch=self._cs_user_arch,
                                  cs_mode=self._cs_user_mode)
        except self.emulator.EmulatorAlreadyRunningError:
            self.console.log('Emulator already running')
        except self.emulator.EmulatorSetupFailedError as error:
            self.until_address = 0
            self.console.log(error)

    def handle_step_next_call(self):
        self.app.console_panel.show_console_tab('emulator')

        try:
            self.emulator.emulate(step_mode=STEP_MODE_FUNCTION,
                                  user_arch=self._uc_user_arch,
                                  user_mode=self._uc_user_mode,
                                  cs_arch=self._cs_user_arch,
                                  cs_mode=self._cs_user_mode)
        except self.emulator.EmulatorAlreadyRunningError:
            self.console.log('Emulator already running')
        except self.emulator.EmulatorSetupFailedError as error:
            self.until_address = 0
            self.console.log(error)

    def handle_step_next_jump(self):
        self.app.console_panel.show_console_tab('emulator')

        try:
            self.emulator.emulate(step_mode=STEP_MODE_JUMP,
                                  user_arch=self._uc_user_arch,
                                  user_mode=self._uc_user_mode,
                                  cs_arch=self._cs_user_arch,
                                  cs_mode=self._cs_user_mode)
        except self.emulator.EmulatorAlreadyRunningError:
            self.console.log('Emulator already running')
        except self.emulator.EmulatorSetupFailedError as error:
            self.until_address = 0
            self.console.log(error)

    def handle_stop(self):
        self.emulator.stop()

    def on_emulator_hook(self, instruction):
        # @PinkiePonkie why setting context here (which is triggered each instruction) and later, set it again
        # in emulator_stop? step = double hit in set_context, running emulation on more than 1 instruction
        # doesn't need spam of set_context
        # self.app.context_panel.set_context(0, 2, self.emulator.current_context)

        # check if the previous hook is waiting for a register result
        if self._require_register_result is not None:
            row = 1
            if len(self._require_register_result) == 1:
                res = 'jump = %s' % (hex(self._require_register_result[0]))
            else:
                res = '%s = %s' % (self._require_register_result[1],
                                   hex(
                                       self.emulator.uc.reg_read(
                                           self._require_register_result[0])))
            if len(self.assembly._lines) > 1:
                if self.assembly._lines[len(self.assembly._lines) -
                                        row] is None:
                    row = 2

                telescope = self.get_telescope(
                    self.emulator.uc.reg_read(
                        self._require_register_result[0]))
                if telescope is not None and telescope != 'None':
                    res += ' (' + telescope + ')'

                self.assembly._lines[len(self.assembly._lines) -
                                     row].string = res
                # invalidate
                self._require_register_result = None

        # check if the code jumped
        self._last_instruction_address = instruction.address

        self.assembly.add_instruction(instruction)

        # add empty line if jump
        if instruction.is_jump or instruction.is_call:
            self.assembly.add_instruction(None)
            self._require_register_result = [instruction.jump_address]
        else:
            # implicit regs read are notified later through mem access
            if len(instruction.regs_read) == 0:
                if len(instruction.operands) > 0:
                    for i in instruction.operands:
                        if i.type == CS_OP_REG:
                            self._require_register_result = [
                                i.value.reg,
                                instruction.reg_name(i.value.reg)
                            ]
                            break
        self.assembly.verticalScrollBar().setValue(len(self.assembly._lines))
        self.assembly.viewport().update()

        if instruction.is_call:
            range_ = Range(Range.SOURCE_TARGET, self.app.dwarf)
            if range_.init_with_address(instruction.address,
                                        require_data=False) > 0:
                if range_.base > instruction.call_address > range_.tail:
                    if self.emulator.step_mode == STEP_MODE_NONE:
                        self.emulator.stop()
                    action = JumpOutsideTheBoxDialog.show_dialog(
                        self.app.dwarf)
                    if action == 0:
                        # step to jump
                        if self.emulator.step_mode != STEP_MODE_NONE:
                            self.handle_step()
                        else:
                            self.emulator.emulate(self.until_address,
                                                  user_arch=self._uc_user_arch,
                                                  user_mode=self._uc_user_mode,
                                                  cs_arch=self._cs_user_arch,
                                                  cs_mode=self._cs_user_mode)
                    if action == 1:
                        # step to next jump
                        if self.emulator.step_mode != STEP_MODE_NONE:
                            self.handle_step_next_jump()
                        else:
                            self.emulator.emulate(self.until_address,
                                                  user_arch=self._uc_user_arch,
                                                  user_mode=self._uc_user_mode,
                                                  cs_arch=self._cs_user_arch,
                                                  cs_mode=self._cs_user_mode)
                    elif action == 2:
                        # hook lr
                        hook_addr = instruction.address + instruction.size
                        if instruction.thumb:
                            hook_addr += 1
                        self.app.dwarf.hook_native(input_=hex(hook_addr))

    def on_emulator_log(self, log):
        self.app.console_panel.show_console_tab('emulator')
        self.console.log(log)

    def on_emulator_memory_hook(self, data):
        uc, access, address, value = data
        _address = QStandardItem()
        if self.ranges_list.uppercase_hex:
            if self.app.dwarf.pointer_size > 4:
                str_frmt = '0x{0:016X}'.format(address)
            else:
                str_frmt = '0x{0:08X}'.format(address)
        else:
            if self.app.dwarf.pointer_size > 4:
                str_frmt = '0x{0:016x}'.format(address)
            else:
                str_frmt = '0x{0:08x}'.format(address)
        _address.setText(str_frmt)
        _address.setTextAlignment(Qt.AlignCenter)

        _access = QStandardItem()
        if access == UC_MEM_READ:
            _access.setText('READ')
        elif access == UC_MEM_WRITE:
            _access.setText('WRITE')
        elif access == UC_MEM_FETCH:
            _access.setText('FETCH')
        _access.setTextAlignment(Qt.AlignCenter)

        _value = QStandardItem()
        _value.setText(str(value))

        self._access_model.appendRow([_address, _access, _value])

        res = None
        row = 1
        if len(self.assembly._lines) > 1:
            if self.assembly._lines[len(self.assembly._lines) - row] is None:
                row = 2
            if access == UC_MEM_READ:
                if self._require_register_result is not None:
                    if len(self._require_register_result) > 1:
                        res = '%s = %s' % (self._require_register_result[1],
                                           hex(value))
            else:
                if self.assembly._lines[len(self.assembly._lines) -
                                        row].string:
                    res = '%s, %s = %s' % (self.assembly._lines[
                        len(self.assembly._lines) - row].string, hex(address),
                                           hex(value))
                else:
                    res = '%s = %s' % (hex(address), hex(value))
            if res is not None:
                telescope = self.get_telescope(value)
                if telescope is not None and telescope != 'None':
                    res += ' (' + telescope + ')'
                # invalidate
                self._require_register_result = None
                self.assembly._lines[len(self.assembly._lines) -
                                     row].string = res

    def get_telescope(self, address):
        try:
            size = self.app.dwarf.pointer_size
            telescope = self.emulator.uc.mem_read(address, size)
            try:
                for i in range(len(telescope)):
                    if int(telescope[i]) == 0x0 and i != 0:
                        st = telescope.decode('utf8')
                        return st
                st = telescope.decode('utf8')
                if len(st) != size:
                    return '0x%s' % telescope.hex()
                while True:
                    telescope = self.emulator.uc.mem_read(address + size, 1)
                    if int(telescope) == 0x0:
                        break
                    st += telescope.decode('utf8')
                    size += 1
                return st
            except:
                return '0x%s' % telescope.hex()
        except UcError as e:
            # read from js
            telescope = self.app.dwarf.dwarf_api('getAddressTs', address)
            if telescope is None:
                return None
            telescope = str(telescope[1]).replace('\n', ' ')
            if len(telescope) > 50:
                telescope = telescope[:50] + '...'
            return telescope

    def on_emulator_memory_range_mapped(self, data):
        address, size = data
        _address = QStandardItem()
        if self.ranges_list.uppercase_hex:
            if self.app.dwarf.pointer_size > 4:
                str_frmt = '0x{0:016X}'.format(address)
            else:
                str_frmt = '0x{0:08X}'.format(address)
        else:
            if self.app.dwarf.pointer_size > 4:
                str_frmt = '0x{0:016x}'.format(address)
            else:
                str_frmt = '0x{0:08x}'.format(address)
        _address.setText(str_frmt)
        _address.setTextAlignment(Qt.AlignCenter)
        _size = QStandardItem()
        _size.setText("{0:,d}".format(int(size)))
        self._ranges_model.appendRow([_address, _size])

    def on_emulator_setup(self, data):
        user_arch = data[0]
        user_mode = data[1]
        if user_arch is not None and user_mode is not None:
            index = self.cpu_selection.findData(user_arch)
            self.cpu_selection.setCurrentIndex(index)
            index = self.mode_selection.findData(user_mode)
            self.mode_selection.setCurrentIndex(index)

    def on_emulator_start(self):
        pass

    def on_emulator_stop(self):
        self.app.context_panel.set_context(0, 2, self.emulator.current_context)
        # check if the previous hook is waiting for a register result
        if self._require_register_result is not None:
            row = 1
            if len(self._require_register_result) == 1:
                res = 'jump = %s' % (hex(self._require_register_result[0]))
            else:
                res = '%s = %s' % (self._require_register_result[1],
                                   hex(
                                       self.emulator.uc.reg_read(
                                           self._require_register_result[0])))
                telescope = self.get_telescope(
                    self.emulator.uc.reg_read(
                        self._require_register_result[0]))
                if telescope is not None and telescope != 'None':
                    res += ' (' + telescope + ')'

            if len(self.assembly._lines) > 1:
                if self.assembly._lines[len(self.assembly._lines) -
                                        row] is None:
                    row = 2
                self.assembly._lines[len(self.assembly._lines) -
                                     row].string = res
                # invalidate
                self._require_register_result = None

    def ranges_item_double_clicked(self, model_index):
        row = self._ranges_model.itemFromIndex(model_index).row()
        if row != -1:
            item = self._ranges_model.item(row, 0).text()
            self.memory_table.read_memory(item)
            self.tabs.setCurrentIndex(1)

    def access_item_double_clicked(self, model_index):
        row = self._access_model.itemFromIndex(model_index).row()
        if row != -1:
            item = self._access_model.item(row, 0).text()
            self.memory_table.read_memory(item)
            self.tabs.setCurrentIndex(1)
Beispiel #28
0
class fullScreenEditor(QWidget):
    def __init__(self, index, parent=None):
        QWidget.__init__(self, parent)
        self._background = None
        self._index = index
        self._theme = findThemePath(settings.fullScreenTheme)
        self._themeDatas = loadThemeDatas(self._theme)
        self.setMouseTracking(True)
        self._geometries = {}

        # Text editor
        self.editor = textEditView(self,
                                   index=index,
                                   spellcheck=settings.spellcheck,
                                   highlighting=True,
                                   dict=settings.dict)
        self.editor.setFrameStyle(QFrame.NoFrame)
        self.editor.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.editor.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.editor.installEventFilter(self)
        self.editor.setMouseTracking(True)
        self.editor.setVerticalScrollBar(myScrollBar())
        self.scrollBar = self.editor.verticalScrollBar()
        self.scrollBar.setParent(self)

        # Top Panel
        self.topPanel = myPanel(parent=self)
        # self.topPanel.layout().addStretch(1)

        # Spell checking
        if enchant:
            self.btnSpellCheck = QPushButton(self)
            self.btnSpellCheck.setFlat(True)
            self.btnSpellCheck.setIcon(QIcon.fromTheme("tools-check-spelling"))
            self.btnSpellCheck.setCheckable(True)
            self.btnSpellCheck.setChecked(self.editor.spellcheck)
            self.btnSpellCheck.toggled.connect(self.editor.toggleSpellcheck)
            self.topPanel.layout().addWidget(self.btnSpellCheck)

        self.topPanel.layout().addStretch(1)

        # Formatting
        self.textFormat = textFormat(self)
        self.topPanel.layout().addWidget(self.textFormat)
        self.topPanel.layout().addStretch(1)

        self.btnClose = QPushButton(self)
        self.btnClose.setIcon(qApp.style().standardIcon(
            QStyle.SP_DialogCloseButton))
        self.btnClose.clicked.connect(self.close)
        self.btnClose.setFlat(True)
        self.topPanel.layout().addWidget(self.btnClose)

        # Left Panel
        self._locked = False
        self.leftPanel = myPanel(vertical=True, parent=self)
        self.locker = locker(self)
        self.locker.lockChanged.connect(self.setLocked)
        self.leftPanel.layout().addWidget(self.locker)

        # Bottom Panel
        self.bottomPanel = myPanel(parent=self)

        self.bottomPanel.layout().addSpacing(24)
        self.lstThemes = QComboBox(self)
        self.lstThemes.setAttribute(Qt.WA_TranslucentBackground)
        paths = allPaths("resources/themes")
        for p in paths:
            lst = [
                i for i in os.listdir(p) if os.path.splitext(i)[1] == ".theme"
            ]
            for t in lst:
                themeIni = os.path.join(p, t)
                name = loadThemeDatas(themeIni)["Name"]
                # self.lstThemes.addItem(os.path.splitext(t)[0])
                self.lstThemes.addItem(name)
                self.lstThemes.setItemData(self.lstThemes.count() - 1,
                                           os.path.splitext(t)[0])

        self.lstThemes.setCurrentIndex(
            self.lstThemes.findData(settings.fullScreenTheme))
        # self.lstThemes.setCurrentText(settings.fullScreenTheme)
        self.lstThemes.currentTextChanged.connect(self.setTheme)
        self.lstThemes.setMaximumSize(
            QSize(300,
                  QFontMetrics(qApp.font()).height()))
        self.bottomPanel.layout().addWidget(QLabel(self.tr("Theme:"), self))
        self.bottomPanel.layout().addWidget(self.lstThemes)
        self.bottomPanel.layout().addStretch(1)

        self.lblProgress = QLabel(self)
        self.lblProgress.setMaximumSize(QSize(200, 14))
        self.lblProgress.setMinimumSize(QSize(100, 14))
        self.lblWC = QLabel(self)
        self.bottomPanel.layout().addWidget(self.lblWC)
        self.bottomPanel.layout().addWidget(self.lblProgress)
        self.updateStatusBar()

        self.bottomPanel.layout().addSpacing(24)

        # Connection
        self._index.model().dataChanged.connect(self.dataChanged)

        # self.updateTheme()
        self.showFullScreen()
        # self.showMaximized()
        # self.show()

    def setLocked(self, val):
        self._locked = val
        self.btnClose.setVisible(not val)

    def setTheme(self, themeName):
        themeName = self.lstThemes.currentData()
        settings.fullScreenTheme = themeName
        self._theme = findThemePath(themeName)
        self._themeDatas = loadThemeDatas(self._theme)
        self.updateTheme()

    def updateTheme(self):
        # Reinit stored geometries for hiding widgets
        self._geometries = {}
        rect = self.geometry()
        self._background = generateTheme(self._themeDatas, rect)

        setThemeEditorDatas(self.editor, self._themeDatas, self._background,
                            rect)

        # Colors
        if self._themeDatas["Foreground/Color"] == self._themeDatas["Background/Color"] or \
                        self._themeDatas["Foreground/Opacity"] < 5:
            self._bgcolor = QColor(self._themeDatas["Text/Color"])
            self._fgcolor = QColor(self._themeDatas["Background/Color"])
        else:
            self._bgcolor = QColor(self._themeDatas["Foreground/Color"])
            self._bgcolor.setAlpha(self._themeDatas["Foreground/Opacity"] *
                                   255 / 100)
            self._fgcolor = QColor(self._themeDatas["Text/Color"])
            if self._themeDatas["Text/Color"] == self._themeDatas[
                    "Foreground/Color"]:
                self._fgcolor = QColor(self._themeDatas["Background/Color"])

        # ScrollBar
        r = self.editor.geometry()
        w = qApp.style().pixelMetric(QStyle.PM_ScrollBarExtent)
        r.setWidth(w)
        r.moveRight(rect.right() - rect.left())
        self.scrollBar.setGeometry(r)
        # self.scrollBar.setVisible(False)
        self.hideWidget(self.scrollBar)
        p = self.scrollBar.palette()
        b = QBrush(self._background.copy(self.scrollBar.geometry()))
        p.setBrush(QPalette.Base, b)
        self.scrollBar.setPalette(p)

        self.scrollBar.setColor(self._bgcolor)

        # Left Panel
        r = self.locker.geometry()
        r.moveTopLeft(QPoint(0, self.geometry().height() / 2 - r.height() / 2))
        self.leftPanel.setGeometry(r)
        self.hideWidget(self.leftPanel)
        self.leftPanel.setColor(self._bgcolor)

        # Top / Bottom Panels
        r = QRect(0, 0, 0, 24)
        r.setWidth(rect.width())
        # r.moveLeft(rect.center().x() - r.width() / 2)
        self.topPanel.setGeometry(r)
        # self.topPanel.setVisible(False)
        self.hideWidget(self.topPanel)
        r.moveBottom(rect.bottom() - rect.top())
        self.bottomPanel.setGeometry(r)
        # self.bottomPanel.setVisible(False)
        self.hideWidget(self.bottomPanel)
        self.topPanel.setColor(self._bgcolor)
        self.bottomPanel.setColor(self._bgcolor)

        # Lst theme
        # p = self.lstThemes.palette()
        p = self.palette()
        p.setBrush(QPalette.Button, self._bgcolor)
        p.setBrush(QPalette.ButtonText, self._fgcolor)
        p.setBrush(QPalette.WindowText, self._fgcolor)

        for panel in (self.bottomPanel, self.topPanel):
            for i in range(panel.layout().count()):
                item = panel.layout().itemAt(i)
                if item.widget():
                    item.widget().setPalette(p)
        # self.lstThemes.setPalette(p)
        # self.lblWC.setPalette(p)

        self.update()

    def paintEvent(self, event):
        if self._background:
            painter = QPainter(self)
            painter.setClipRegion(event.region())
            painter.drawPixmap(event.rect(), self._background, event.rect())
            painter.end()

    def resizeEvent(self, event):
        self.updateTheme()

    def keyPressEvent(self, event):
        if event.key() in [Qt.Key_Escape, Qt.Key_F11] and \
                not self._locked:
            self.close()
        else:
            QWidget.keyPressEvent(self, event)

    def mouseMoveEvent(self, event):
        r = self.geometry()

        for w in [
                self.scrollBar, self.topPanel, self.bottomPanel, self.leftPanel
        ]:
            # w.setVisible(w.geometry().contains(event.pos()))
            if self._geometries[w].contains(event.pos()):
                self.showWidget(w)
            else:
                self.hideWidget(w)

    def hideWidget(self, widget):
        if widget not in self._geometries:
            self._geometries[widget] = widget.geometry()
        widget.move(self.geometry().bottomRight())

    def showWidget(self, widget):
        if widget in self._geometries:
            widget.move(self._geometries[widget].topLeft())

    def eventFilter(self, obj, event):
        if obj == self.editor and event.type() == QEvent.Enter:
            for w in [
                    self.scrollBar, self.topPanel, self.bottomPanel,
                    self.leftPanel
            ]:
                # w.setVisible(False)
                self.hideWidget(w)
        return QWidget.eventFilter(self, obj, event)

    def dataChanged(self, topLeft, bottomRight):
        if not self._index:
            return
        if topLeft.row() <= self._index.row() <= bottomRight.row():
            self.updateStatusBar()

    def updateStatusBar(self):
        if self._index:
            item = self._index.internalPointer()

        wc = item.data(Outline.wordCount.value)
        goal = item.data(Outline.goal.value)
        pg = item.data(Outline.goalPercentage.value)

        if goal:
            rect = self.lblProgress.geometry()
            rect = QRect(QPoint(0, 0), rect.size())
            self.px = QPixmap(rect.size())
            self.px.fill(Qt.transparent)
            p = QPainter(self.px)
            drawProgress(p, rect, pg, 2)
            p.end()
            self.lblProgress.setPixmap(self.px)
            self.lblWC.setText(self.tr("{} words / {}").format(wc, goal))
        else:
            self.lblProgress.hide()
            self.lblWC.setText(self.tr("{} words").format(wc))

        self.locker.setWordCount(wc)
        # If there's a goal, then we update the locker target's number of word accordingly
        # (also if there is a word count, we deduce it.
        if goal and not self.locker.isLocked():
            if wc and goal - wc > 0:
                self.locker.spnWordTarget.setValue(goal - wc)
            elif not wc:
                self.locker.spnWordTarget.setValue(goal)
Beispiel #29
0
class GuiPreferencesGeneral(QWidget):
    def __init__(self, theParent):
        QWidget.__init__(self, theParent)

        self.mainConf = nw.CONFIG
        self.theParent = theParent
        self.theTheme = theParent.theTheme

        # The Form
        self.mainForm = QConfigLayout()
        self.mainForm.setHelpTextStyle(self.theTheme.helpText)
        self.setLayout(self.mainForm)

        # Look and Feel
        # =============
        self.mainForm.addGroupLabel("Look and Feel")

        ## Select Theme
        self.guiTheme = QComboBox()
        self.guiTheme.setMinimumWidth(self.mainConf.pxInt(200))
        self.theThemes = self.theTheme.listThemes()
        for themeDir, themeName in self.theThemes:
            self.guiTheme.addItem(themeName, themeDir)
        themeIdx = self.guiTheme.findData(self.mainConf.guiTheme)
        if themeIdx != -1:
            self.guiTheme.setCurrentIndex(themeIdx)

        self.mainForm.addRow("Main GUI theme", self.guiTheme,
                             "Changing this requires restarting novelWriter.")

        ## Select Icon Theme
        self.guiIcons = QComboBox()
        self.guiIcons.setMinimumWidth(self.mainConf.pxInt(200))
        self.theIcons = self.theTheme.theIcons.listThemes()
        for iconDir, iconName in self.theIcons:
            self.guiIcons.addItem(iconName, iconDir)
        iconIdx = self.guiIcons.findData(self.mainConf.guiIcons)
        if iconIdx != -1:
            self.guiIcons.setCurrentIndex(iconIdx)

        self.mainForm.addRow("Main icon theme", self.guiIcons,
                             "Changing this requires restarting novelWriter.")

        ## Dark Icons
        self.guiDark = QSwitch()
        self.guiDark.setChecked(self.mainConf.guiDark)
        self.mainForm.addRow("Prefer icons for dark backgrounds", self.guiDark,
                             "May improve the look of icons on dark themes.")

        ## Font Family
        self.guiFont = QLineEdit()
        self.guiFont.setReadOnly(True)
        self.guiFont.setFixedWidth(self.mainConf.pxInt(162))
        self.guiFont.setText(self.mainConf.guiFont)
        self.fontButton = QPushButton("...")
        self.fontButton.setMaximumWidth(
            int(2.5 * self.theTheme.getTextWidth("...")))
        self.fontButton.clicked.connect(self._selectFont)
        self.mainForm.addRow("Font family",
                             self.guiFont,
                             "Changing this requires restarting novelWriter.",
                             theButton=self.fontButton)

        ## Font Size
        self.guiFontSize = QSpinBox(self)
        self.guiFontSize.setMinimum(8)
        self.guiFontSize.setMaximum(60)
        self.guiFontSize.setSingleStep(1)
        self.guiFontSize.setValue(self.mainConf.guiFontSize)
        self.mainForm.addRow("Font size",
                             self.guiFontSize,
                             "Changing this requires restarting novelWriter.",
                             theUnit="pt")

        # GUI Settings
        # ============
        self.mainForm.addGroupLabel("GUI Settings")

        self.showFullPath = QSwitch()
        self.showFullPath.setChecked(self.mainConf.showFullPath)
        self.mainForm.addRow("Show full path in document header",
                             self.showFullPath,
                             "Add the parent folder names to the header.")

        self.hideVScroll = QSwitch()
        self.hideVScroll.setChecked(self.mainConf.hideVScroll)
        self.mainForm.addRow(
            "Hide vertical scroll bars in main windows", self.hideVScroll,
            "Scrolling available with mouse wheel and keys only.")

        self.hideHScroll = QSwitch()
        self.hideHScroll.setChecked(self.mainConf.hideHScroll)
        self.mainForm.addRow(
            "Hide horizontal scroll bars in main windows", self.hideHScroll,
            "Scrolling available with mouse wheel and keys only.")

        return

    def saveValues(self):
        """Save the values set for this tab.
        """
        guiTheme = self.guiTheme.currentData()
        guiIcons = self.guiIcons.currentData()
        guiDark = self.guiDark.isChecked()
        guiFont = self.guiFont.text()
        guiFontSize = self.guiFontSize.value()

        # Check if restart is needed
        needsRestart = False
        needsRestart |= self.mainConf.guiTheme != guiTheme
        needsRestart |= self.mainConf.guiIcons != guiIcons
        needsRestart |= self.mainConf.guiDark != guiDark
        needsRestart |= self.mainConf.guiFont != guiFont
        needsRestart |= self.mainConf.guiFontSize != guiFontSize

        self.mainConf.guiTheme = guiTheme
        self.mainConf.guiIcons = guiIcons
        self.mainConf.guiDark = guiDark
        self.mainConf.guiFont = guiFont
        self.mainConf.guiFontSize = guiFontSize
        self.mainConf.showFullPath = self.showFullPath.isChecked()
        self.mainConf.hideVScroll = self.hideVScroll.isChecked()
        self.mainConf.hideHScroll = self.hideHScroll.isChecked()

        self.mainConf.confChanged = True

        return needsRestart

    ##
    #  Slots
    ##

    def _selectFont(self):
        """Open the QFontDialog and set a font for the font style.
        """
        currFont = QFont()
        currFont.setFamily(self.mainConf.guiFont)
        currFont.setPointSize(self.mainConf.guiFontSize)
        theFont, theStatus = QFontDialog.getFont(currFont, self)
        if theStatus:
            self.guiFont.setText(theFont.family())
            self.guiFontSize.setValue(theFont.pointSize())
        return
Beispiel #30
0
class ObserverFilterDialog(QDialog):

	newFilterSettings = pyqtSignal(dict) # pyQt5 will interpret as QVariantMap (so keys musr be strings)

	#__________________________________________________________________
	def __init__(self, observation, session, logger, correspondents, topics, hiddenCorrespondents, hiddenTopics):

		super(ObserverFilterDialog, self).__init__()

		self._observation = observation
		self._session = session
		self._logger = logger
		self._correspondents = correspondents
		self._topics = topics
		self._hiddenCorrespondents = hiddenCorrespondents
		self._hiddenTopics = hiddenTopics

		self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint)
		self.setWindowTitle(self.tr("Observer filter"))
		self.setWindowIcon(QIcon(':/magnifier-black.svg'))

		self._nameInput = QLineEdit(self._observation)
		self._filter1Input = QLineEdit()
		self._filter2Input = QLineEdit()
		self._filter3Input = QLineEdit()
		self._filter4Input = QLineEdit()
		self._displayFlatButton = QRadioButton(self.tr("Show topic unformatted"))
		self._displayEmphasizedCorrespondentButton = QRadioButton(self.tr("Emphasize correspondent in topic"))
		self._displayCorrespondentOnlyBytton = QRadioButton(self.tr("Show correspondent instead of topic"))
		self._displayNoTopicButton = QRadioButton(self.tr("Don't show topic"))
		self._bufferSizeComboBox = QComboBox()
		self._correspondentsCheckboxes = []
		self._topicsCheckboxes = []

		self._tabWidget = QTabWidget()
		self._allCorrespondentsCheckbox = QCheckBox(self.tr("All"))
		self._allTopicsCheckbox = QCheckBox(self.tr("All"))

		self.buildUi()

		reg = QSettings()
		width = reg.value("filter width", 400)
		self.resize(width, self.size().height())

		self._bufferSizeComboBox.addItem(self.tr("50 kB"), 50000)
		self._bufferSizeComboBox.addItem(self.tr("100 kB"), 100000)
		self._bufferSizeComboBox.addItem(self.tr("200 kB"), 200000)
		self._bufferSizeComboBox.addItem(self.tr("500 kB"), 500000)
		self._bufferSizeComboBox.addItem(self.tr("1000 kB"), 1000000)

		reg.beginGroup(self._session)
		reg.beginGroup(self._observation)
		self._filter1Input.setText(reg.value("filter 1", ''))
		self._filter2Input.setText(reg.value("filter 2", ''))
		self._filter3Input.setText(reg.value("filter 3", ''))
		self._filter4Input.setText(reg.value("filter 4", ''))
		if reg.value("display", int(TopicDisplay.EMPHASIZEDCORRESPONDENT), type=int) == int(TopicDisplay.FLAT):
			self._displayFlatButton.setChecked(True)
		elif reg.value("display", int(TopicDisplay.EMPHASIZEDCORRESPONDENT), type=int) == int(TopicDisplay.EMPHASIZEDCORRESPONDENT):
			self._displayEmphasizedCorrespondentButton.setChecked(True)
		elif reg.value("display", int(TopicDisplay.EMPHASIZEDCORRESPONDENT), type=int) == int(TopicDisplay.CORRESPONDENTONLY):
			self._displayCorrespondentOnlyBytton.setChecked(True)
		else:
			self._displayNoTopicButton.setChecked(True)
		self._bufferSizeComboBox.setCurrentIndex(self._bufferSizeComboBox.findData(reg.value("buffer size", 50000, type=int)))
		reg.endGroup()
		reg.endGroup()

	#__________________________________________________________________
	@pyqtSlot()
	def accept(self):

		self.close()

	#__________________________________________________________________
	@pyqtSlot()
	def apply(self):

		filter = {}
		filter["observation"] = self._observation
		filter["name"] = self._nameInput.text()
		if self._displayFlatButton.isChecked():
			filter["display"] = int(TopicDisplay.FLAT)
		elif self._displayEmphasizedCorrespondentButton.isChecked():
			filter["display"] = int(TopicDisplay.EMPHASIZEDCORRESPONDENT)
		elif self._displayCorrespondentOnlyBytton.isChecked():
			filter["display"] = int(TopicDisplay.CORRESPONDENTONLY)
		else:
			filter["display"] = int(TopicDisplay.NOTOPIC)
		filter["filter 1"] = self._filter1Input.text().replace('/', '')
		filter["filter 2"] = self._filter2Input.text().replace('/', '')
		filter["filter 3"] = self._filter3Input.text().replace('/', '')
		filter["filter 4"] = self._filter4Input.text().replace('/', '')
		filter["buffer size"] = int(self._bufferSizeComboBox.currentData())

		filter["hidden correspondents"] = []
		for cb in self._correspondentsCheckboxes:
			if not cb.isChecked():
				filter["hidden correspondents"].append(cb.text())

		filter["hidden topics"] = []		
		for cb in self._topicsCheckboxes:
			if not cb.isChecked():
				filter["hidden topics"].append(cb.text())

		self.newFilterSettings.emit(filter)

		reg = QSettings()
		reg.beginGroup(self._session)
		reg.setValue("filter width", self.size().width())
		reg.sync()

		self.accept()

	#__________________________________________________________________
	def buildUi(self):	
	
		main_layout = QVBoxLayout()
		main_layout.addWidget(self._tabWidget)

		general_layout = QVBoxLayout()
		general_layout.setSpacing(12)
		general_widget = QWidget()
		general_widget.setLayout(general_layout)
		self._tabWidget.addTab(general_widget, self.tr("General"))

		general_layout.addSpacing(8)

		name_layout = QHBoxLayout()
		name_layout.addWidget(QLabel(self.tr("Observation name :")))
		name_layout.addWidget(self._nameInput)
		general_layout.addLayout(name_layout)

		mqtt_box = QGroupBox(self.tr("Message content filters"))
		mqtt_box_layout = QGridLayout(mqtt_box)
		general_layout.addWidget(mqtt_box)

		mqtt_box_layout.addWidget(QLabel(self.tr("Filter out messages starting with :")), 0, 0)
		mqtt_box_layout.addWidget(self._filter1Input, 0, 1)

		mqtt_box_layout.addWidget(QLabel(self.tr("Filter out messages starting with :")), 1, 0)
		mqtt_box_layout.addWidget(self._filter2Input, 1, 1)

		mqtt_box_layout.addWidget(QLabel(self.tr("Filter out messages starting with :")), 2, 0)
		mqtt_box_layout.addWidget(self._filter3Input, 2, 1)

		mqtt_box_layout.addWidget(QLabel(self.tr("Filter out messages starting with :")), 3, 0)
		mqtt_box_layout.addWidget(self._filter4Input, 3, 1)

		correspondents_layout = QVBoxLayout()
		correspondents_layout.setSpacing(0)
		correspondents_widget = QWidget()
		correspondents_widget.setStyleSheet("font-family:monospace,courier new,courier; white-space:pre; color:blue;")
		correspondents_widget.setLayout(correspondents_layout)
		correspondents_scrollArea = QScrollArea()
		correspondents_scrollArea.setWidgetResizable(True)
		correspondents_scrollArea.setBackgroundRole(QPalette.Base)
		correspondents_scrollArea.setFrameShape(QFrame.NoFrame)
		correspondents_scrollArea.setWidget(correspondents_widget)
		self._tabWidget.addTab(correspondents_scrollArea, self.tr("Correspondents"))

		if self._correspondents:
			correspondents_layout.addWidget(self._allCorrespondentsCheckbox)

			all = True
			none = True

			for correspondent in self._correspondents:
				check_box = QCheckBox(correspondent)
				check_box.setChecked(correspondent not in self._hiddenCorrespondents)
				check_box.clicked.connect(self.checkSomeCorrespondent)
				correspondents_layout.addWidget(check_box)
				self._correspondentsCheckboxes.append(check_box)
				if correspondent in self._hiddenCorrespondents:
					all = False
				else:
					none = False

			if all:
				self._allCorrespondentsCheckbox.setCheckState(Qt.Checked)
			elif none:
				self._allCorrespondentsCheckbox.setCheckState(Qt.Unchecked)
			else:
				self._allCorrespondentsCheckbox.setCheckState(Qt.PartiallyChecked)
		else:
			correspondents_layout.addWidget(QLabel(self.tr("No correspondent defined")))

		correspondents_layout.addStretch()

		topics_layout = QVBoxLayout()
		topics_layout.setSpacing(0)
		topics_widget = QWidget()
		topics_widget.setStyleSheet("font-family:monospace,courier new,courier; white-space:pre; color:blue;")
		topics_widget.setLayout(topics_layout)
		topics_scrollArea = QScrollArea()
		topics_scrollArea.setWidgetResizable(True)
		topics_scrollArea.setBackgroundRole(QPalette.Base)
		topics_scrollArea.setFrameShape(QFrame.NoFrame)
		topics_scrollArea.setWidget(topics_widget)
		self._tabWidget.addTab(topics_scrollArea, self.tr("Topics"))

		if self._topics:
			topics_layout.addWidget(self._allTopicsCheckbox)

			all = True
			none = True

			for topic in self._topics:
				check_box = QCheckBox(topic)
				check_box.setChecked(topic not in self._hiddenTopics)
				check_box.clicked.connect(self.checkSomeTopic)
				topics_layout.addWidget(check_box)
				self._topicsCheckboxes.append(check_box)
				if topic in self._hiddenTopics:
					all = False
				else:
					none = False

			if all:
				self._allTopicsCheckbox.setCheckState(Qt.Checked)
			elif none:
				self._allTopicsCheckbox.setCheckState(Qt.Unchecked)
			else:
				self._allTopicsCheckbox.setCheckState(Qt.PartiallyChecked)

			topics_layout.addStretch()
		else:
			topics_layout.addWidget(QLabel(self.tr("No topic defined")))
			topics_layout.addStretch()

		display_box = QGroupBox(self.tr("Display format"))
		display_box_layout = QVBoxLayout(display_box)
		general_layout.addWidget(display_box)

		display_box_layout.addWidget(self._displayFlatButton)
		display_box_layout.addWidget(self._displayEmphasizedCorrespondentButton)
		display_box_layout.addWidget(self._displayCorrespondentOnlyBytton)
		display_box_layout.addWidget(self._displayNoTopicButton)

		buffer_box = QGroupBox(self.tr("Display buffer"))
		buffer_box_layout = QGridLayout(buffer_box)
		general_layout.addWidget(buffer_box)

		label = QLabel(self.tr("Buffer maximum size :"))
		label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)
		buffer_box_layout.addWidget(label, 0, 0)
		buffer_box_layout.addWidget(self._bufferSizeComboBox, 0, 1)

		general_layout.addSpacing(8)

		apply_button = QPushButton(self.tr("Apply"))
		ignore_button = QPushButton(self.tr("Ignore"))

		button_layout = QHBoxLayout()
		button_layout.addStretch(1)
		button_layout.addWidget(apply_button)
		button_layout.addWidget(ignore_button)
		general_layout.addLayout(button_layout)
		
		self.setLayout(main_layout)

		apply_button.pressed.connect(self.apply)
		ignore_button.pressed.connect(self.accept)
		self._allCorrespondentsCheckbox.clicked.connect(self.checkAllCorrespondents)
		self._allTopicsCheckbox.clicked.connect(self.checkAllTopics)

		ignore_button.setFocus()

	#__________________________________________________________________
	@pyqtSlot(bool)
	def checkAllCorrespondents(self, checked):
		
		if not checked:
			for cb in self._correspondentsCheckboxes:
				cb.setChecked(False)
		else:
			self._allCorrespondentsCheckbox.setCheckState(Qt.Checked) # tristate oblige
			for cb in self._correspondentsCheckboxes:
				cb.setChecked(True)

	#__________________________________________________________________
	@pyqtSlot(bool)
	def checkAllTopics(self, checked):

		if not checked:
			for cb in self._topicsCheckboxes:
				cb.setChecked(False)
		else:
			self._allTopicsCheckbox.setCheckState(Qt.Checked) # tristate oblige
			for cb in self._topicsCheckboxes:
				cb.setChecked(True)

	#__________________________________________________________________
	@pyqtSlot(bool)
	def checkSomeCorrespondent(self, checked):
		
		all = checked
		none = not checked

		for cb in self._correspondentsCheckboxes:
			if checked and not cb.isChecked():
				self._allCorrespondentsCheckbox.setCheckState(Qt.PartiallyChecked)
			elif not checked and cb.isChecked():
				self._allCorrespondentsCheckbox.setCheckState(Qt.PartiallyChecked)
			if cb.isChecked():
				none = False
			else:
				all = False

		if all:
			self._allCorrespondentsCheckbox.setCheckState(Qt.Checked)
		elif none:
			self._allCorrespondentsCheckbox.setCheckState(Qt.Unchecked)

	#__________________________________________________________________
	@pyqtSlot(bool)
	def checkSomeTopic(self, checked):

		all = checked
		none = not checked

		for cb in self._topicsCheckboxes:
			if checked and not cb.isChecked():
				self._allTopicsCheckbox.setCheckState(Qt.PartiallyChecked)
			elif not checked and cb.isChecked():
				self._allTopicsCheckbox.setCheckState(Qt.PartiallyChecked)
			if cb.isChecked():
				none = False
			else:
				all = False

		if all:
			self._allTopicsCheckbox.setCheckState(Qt.Checked)
		elif none:
			self._allTopicsCheckbox.setCheckState(Qt.Unchecked)

	#__________________________________________________________________
	def closeEvent(self, event):

		if self._tabWidget.currentIndex() == 0:
			reg = QSettings()
			reg.setValue("filter width", self.size().width())
			reg.sync()
		else:
			self._tabWidget.setCurrentIndex(0)
			event.ignore()
Beispiel #31
0
class GuiPreferencesSyntax(QWidget):
    def __init__(self, theParent):
        QWidget.__init__(self, theParent)

        self.mainConf = nw.CONFIG
        self.theParent = theParent
        self.theTheme = theParent.theTheme

        # The Form
        self.mainForm = QConfigLayout()
        self.mainForm.setHelpTextStyle(self.theTheme.helpText)
        self.setLayout(self.mainForm)

        # Highlighting Theme
        # ==================
        self.mainForm.addGroupLabel("Highlighting Theme")

        self.guiSyntax = QComboBox()
        self.guiSyntax.setMinimumWidth(self.mainConf.pxInt(200))
        self.theSyntaxes = self.theTheme.listSyntax()
        for syntaxFile, syntaxName in self.theSyntaxes:
            self.guiSyntax.addItem(syntaxName, syntaxFile)
        syntaxIdx = self.guiSyntax.findData(self.mainConf.guiSyntax)
        if syntaxIdx != -1:
            self.guiSyntax.setCurrentIndex(syntaxIdx)

        self.mainForm.addRow(
            "Highlighting theme", self.guiSyntax,
            "Colour theme to apply to the editor and viewer.")

        # Quotes & Dialogue
        # =================
        self.mainForm.addGroupLabel("Quotes & Dialogue")

        self.highlightQuotes = QSwitch()
        self.highlightQuotes.setChecked(self.mainConf.highlightQuotes)
        self.highlightQuotes.toggled.connect(self._toggleHighlightQuotes)
        self.mainForm.addRow("Highlight text wrapped in quotes",
                             self.highlightQuotes,
                             "Applies to single, double and straight quotes.")

        self.allowOpenSQuote = QSwitch()
        self.allowOpenSQuote.setChecked(self.mainConf.allowOpenSQuote)
        self.mainForm.addRow(
            "Allow open-ended single quotes", self.allowOpenSQuote,
            "Highlight single-quoted line with no closing quote.")

        self.allowOpenDQuote = QSwitch()
        self.allowOpenDQuote.setChecked(self.mainConf.allowOpenDQuote)
        self.mainForm.addRow(
            "Allow open-ended double quotes", self.allowOpenDQuote,
            "Highlight double-quoted line with no closing quote.")

        # Text Emphasis
        # =============
        self.mainForm.addGroupLabel("Text Emphasis")

        self.highlightEmph = QSwitch()
        self.highlightEmph.setChecked(self.mainConf.highlightEmph)
        self.mainForm.addRow(
            "Add highlight colour to emphasised text", self.highlightEmph,
            "Applies to emphasis (italic) and strong (bold).")

        return

    def saveValues(self):
        """Save the values set for this tab.
        """
        # Highlighting Theme
        self.mainConf.guiSyntax = self.guiSyntax.currentData()

        # Quotes & Dialogue
        self.mainConf.highlightQuotes = self.highlightQuotes.isChecked()
        self.mainConf.allowOpenSQuote = self.allowOpenSQuote.isChecked()
        self.mainConf.allowOpenDQuote = self.allowOpenDQuote.isChecked()

        # Text Emphasis
        self.mainConf.highlightEmph = self.highlightEmph.isChecked()

        self.mainConf.confChanged = True

        return

    ##
    #  Slots
    ##

    def _toggleHighlightQuotes(self, theState):
        """Enables or disables switches controlled by the highlight
        quotes switch.
        """
        self.allowOpenSQuote.setEnabled(theState)
        self.allowOpenDQuote.setEnabled(theState)
        return
class GuiConfigEditGeneral(QWidget):

    def __init__(self, theParent):
        QWidget.__init__(self, theParent)

        self.mainConf  = nw.CONFIG
        self.theParent = theParent
        self.theTheme  = theParent.theTheme
        self.outerBox  = QGridLayout()

        # User Interface
        self.guiLook     = QGroupBox("User Interface", self)
        self.guiLookForm = QGridLayout(self)
        self.guiLook.setLayout(self.guiLookForm)

        self.guiLookTheme = QComboBox()
        self.guiLookTheme.setMinimumWidth(200)
        self.theThemes = self.theTheme.listThemes()
        for themeDir, themeName in self.theThemes:
            self.guiLookTheme.addItem(themeName, themeDir)
        themeIdx = self.guiLookTheme.findData(self.mainConf.guiTheme)
        if themeIdx != -1:
            self.guiLookTheme.setCurrentIndex(themeIdx)

        self.guiLookSyntax = QComboBox()
        self.guiLookSyntax.setMinimumWidth(200)
        self.theSyntaxes = self.theTheme.listSyntax()
        for syntaxFile, syntaxName in self.theSyntaxes:
            self.guiLookSyntax.addItem(syntaxName, syntaxFile)
        syntaxIdx = self.guiLookSyntax.findData(self.mainConf.guiSyntax)
        if syntaxIdx != -1:
            self.guiLookSyntax.setCurrentIndex(syntaxIdx)

        self.guiDarkIcons = QCheckBox("Prefer icons for dark backgrounds", self)
        self.guiDarkIcons.setToolTip("This may improve the look of icons if the system theme is dark.")
        self.guiDarkIcons.setChecked(self.mainConf.guiDark)

        self.guiLookForm.addWidget(QLabel("Theme"),    0, 0)
        self.guiLookForm.addWidget(self.guiLookTheme,  0, 1)
        self.guiLookForm.addWidget(QLabel("Syntax"),   1, 0)
        self.guiLookForm.addWidget(self.guiLookSyntax, 1, 1)
        self.guiLookForm.addWidget(self.guiDarkIcons,  2, 0, 1, 2)
        self.guiLookForm.setColumnStretch(3, 1)

        # Spell Checking
        self.spellLang     = QGroupBox("Spell Checker", self)
        self.spellLangForm = QGridLayout(self)
        self.spellLang.setLayout(self.spellLangForm)

        self.spellLangList = QComboBox(self)
        self.spellToolList = QComboBox(self)
        self.spellToolList.addItem("Internal (difflib)",        NWSpellCheck.SP_INTERNAL)
        self.spellToolList.addItem("Spell Enchant (pyenchant)", NWSpellCheck.SP_ENCHANT)
        self.spellToolList.addItem("SymSpell (symspellpy)",     NWSpellCheck.SP_SYMSPELL)

        theModel   = self.spellToolList.model()
        idEnchant  = self.spellToolList.findData(NWSpellCheck.SP_ENCHANT)
        idSymSpell = self.spellToolList.findData(NWSpellCheck.SP_SYMSPELL)
        theModel.item(idEnchant).setEnabled(self.mainConf.hasEnchant)
        theModel.item(idSymSpell).setEnabled(self.mainConf.hasSymSpell)

        self.spellToolList.currentIndexChanged.connect(self._doUpdateSpellTool)
        toolIdx = self.spellToolList.findData(self.mainConf.spellTool)
        if toolIdx != -1:
            self.spellToolList.setCurrentIndex(toolIdx)
        self._doUpdateSpellTool(0)

        self.spellBigDoc = QSpinBox(self)
        self.spellBigDoc.setMinimum(10)
        self.spellBigDoc.setMaximum(10000)
        self.spellBigDoc.setSingleStep(10)
        self.spellBigDoc.setToolTip((
            "Disable spell checking when loading large documents. "
            "Spell checking will only run on paragraphs you edit."
        ))
        self.spellBigDoc.setValue(self.mainConf.bigDocLimit)

        self.spellLangForm.addWidget(QLabel("Provider"),   0, 0)
        self.spellLangForm.addWidget(self.spellToolList,   0, 1, 1, 3)
        self.spellLangForm.addWidget(QLabel("Language"),   1, 0)
        self.spellLangForm.addWidget(self.spellLangList,   1, 1, 1, 3)
        self.spellLangForm.addWidget(QLabel("Size limit"), 2, 0)
        self.spellLangForm.addWidget(self.spellBigDoc,     2, 1)
        self.spellLangForm.addWidget(QLabel("kb"),         2, 2)
        self.spellLangForm.setColumnStretch(4, 1)

        # AutoSave
        self.autoSave     = QGroupBox("Automatic Save", self)
        self.autoSaveForm = QGridLayout(self)
        self.autoSave.setLayout(self.autoSaveForm)

        self.autoSaveDoc = QSpinBox(self)
        self.autoSaveDoc.setMinimum(5)
        self.autoSaveDoc.setMaximum(600)
        self.autoSaveDoc.setSingleStep(1)
        self.autoSaveDoc.setValue(self.mainConf.autoSaveDoc)

        self.autoSaveProj = QSpinBox(self)
        self.autoSaveProj.setMinimum(5)
        self.autoSaveProj.setMaximum(600)
        self.autoSaveProj.setSingleStep(1)
        self.autoSaveProj.setValue(self.mainConf.autoSaveProj)

        self.autoSaveForm.addWidget(QLabel("Document"), 0, 0)
        self.autoSaveForm.addWidget(self.autoSaveDoc,   0, 1)
        self.autoSaveForm.addWidget(QLabel("seconds"),  0, 2)
        self.autoSaveForm.addWidget(QLabel("Project"),  1, 0)
        self.autoSaveForm.addWidget(self.autoSaveProj,  1, 1)
        self.autoSaveForm.addWidget(QLabel("seconds"),  1, 2)
        self.autoSaveForm.setColumnStretch(3, 1)

        # Backup
        self.projBackup     = QGroupBox("Backup Folder", self)
        self.projBackupForm = QGridLayout(self)
        self.projBackup.setLayout(self.projBackupForm)

        self.projBackupPath = QLineEdit()
        if path.isdir(self.mainConf.backupPath):
            self.projBackupPath.setText(self.mainConf.backupPath)

        self.projBackupGetPath = QPushButton(self.theTheme.getIcon("folder"),"")
        self.projBackupGetPath.clicked.connect(self._backupFolder)

        self.projBackupClose = QCheckBox("Run on close",self)
        self.projBackupClose.setToolTip("Backup automatically on project close.")
        self.projBackupClose.setChecked(self.mainConf.backupOnClose)

        self.projBackupAsk = QCheckBox("Ask before backup",self)
        self.projBackupAsk.setToolTip("Ask before backup.")
        self.projBackupAsk.setChecked(self.mainConf.askBeforeBackup)

        self.projBackupForm.addWidget(self.projBackupPath,    0, 0, 1, 2)
        self.projBackupForm.addWidget(self.projBackupGetPath, 0, 2)
        self.projBackupForm.addWidget(self.projBackupClose,   1, 0)
        self.projBackupForm.addWidget(self.projBackupAsk,     1, 1, 1, 2)
        self.projBackupForm.setColumnStretch(1, 1)

        # Assemble
        self.outerBox.addWidget(self.guiLook,    0, 0)
        self.outerBox.addWidget(self.spellLang,  1, 0)
        self.outerBox.addWidget(self.autoSave,   2, 0)
        self.outerBox.addWidget(self.projBackup, 3, 0, 1, 2)
        self.outerBox.setColumnStretch(1, 1)
        self.outerBox.setRowStretch(4, 1)
        self.setLayout(self.outerBox)

        return

    def saveValues(self):

        validEntries = True
        needsRestart = False

        guiTheme        = self.guiLookTheme.currentData()
        guiSyntax       = self.guiLookSyntax.currentData()
        guiDark         = self.guiDarkIcons.isChecked()
        spellTool       = self.spellToolList.currentData()
        spellLanguage   = self.spellLangList.currentData()
        bigDocLimit     = self.spellBigDoc.value()
        autoSaveDoc     = self.autoSaveDoc.value()
        autoSaveProj    = self.autoSaveProj.value()
        backupPath      = self.projBackupPath.text()
        backupOnClose   = self.projBackupClose.isChecked()
        askBeforeBackup = self.projBackupAsk.isChecked()

        # Check if restart is needed
        needsRestart |= self.mainConf.guiTheme != guiTheme

        self.mainConf.guiTheme        = guiTheme
        self.mainConf.guiSyntax       = guiSyntax
        self.mainConf.guiDark         = guiDark
        self.mainConf.spellTool       = spellTool
        self.mainConf.spellLanguage   = spellLanguage
        self.mainConf.bigDocLimit     = bigDocLimit
        self.mainConf.autoSaveDoc     = autoSaveDoc
        self.mainConf.autoSaveProj    = autoSaveProj
        self.mainConf.backupPath      = backupPath
        self.mainConf.backupOnClose   = backupOnClose
        self.mainConf.askBeforeBackup = askBeforeBackup

        self.mainConf.confChanged = True

        return validEntries, needsRestart

    ##
    #  Internal Functions
    ##

    def _backupFolder(self):

        currDir = self.projBackupPath.text()
        if not path.isdir(currDir):
            currDir = ""

        dlgOpt  = QFileDialog.Options()
        dlgOpt |= QFileDialog.ShowDirsOnly
        dlgOpt |= QFileDialog.DontUseNativeDialog
        newDir = QFileDialog.getExistingDirectory(
            self,"Backup Directory",currDir,options=dlgOpt
        )
        if newDir:
            self.projBackupPath.setText(newDir)
            return True

        return False

    def _disableComboItem(self, theList, theValue):
        theIdx = theList.findData(theValue)
        theModel = theList.model()
        anItem = theModel.item(1)
        anItem.setFlags(anItem.flags() ^ Qt.ItemIsEnabled)
        return theModel

    def _doUpdateSpellTool(self, currIdx):
        spellTool = self.spellToolList.currentData()
        self._updateLanguageList(spellTool)
        return

    def _updateLanguageList(self, spellTool):
        """Updates the list of available spell checking dictionaries
        available for the selected spell check tool. It will try to
        preserve the language choice, if the language exists in the
        updated list.
        """
        if spellTool == NWSpellCheck.SP_ENCHANT:
            theDict = NWSpellEnchant()
        else:
            theDict = NWSpellSimple()

        self.spellLangList.clear()
        for spTag, spName in theDict.listDictionaries():
            self.spellLangList.addItem(spName, spTag)

        spellIdx = self.spellLangList.findData(self.mainConf.spellLanguage)
        if spellIdx != -1:
            self.spellLangList.setCurrentIndex(spellIdx)

        return
Beispiel #33
0
class NewGameDialog(DialogBase):
    def __init__(self,
                 settings,
                 client,
                 share_dir,
                 show_exit=False,
                 open_file=(None, None),
                 parent=None):
        DialogBase.__init__(self, parent)
        self.settings = settings
        self.client = client
        self.share_dir = share_dir
        self.show_exit = show_exit
        self.preset_file, self.preset_mask = open_file

        self.setWindowTitle(_("Start new game"))

        widget = QWidget()
        self.form_layout = layout = QFormLayout()

        self.game_type = QComboBox(self)
        self.game_type.addItem(_("Start a game against computer"),
                               START_AI_GAME)
        self.game_type.addItem(_("Start a game against human"),
                               START_HUMAN_GAME)
        self.game_type.addItem(_("Join a game against human"), JOIN_HUMAN_GAME)
        layout.addRow(_("Action"), self.game_type)

        self.rules = QComboBox()
        for name, title in supported_rules:
            self.rules.addItem(title, name)
        rules = settings.value("rules")
        if rules is not None:
            idx = self.rules.findData(rules)
            self.rules.setCurrentIndex(idx)
        layout.addRow(_("Rules"), self.rules)

        self.user_name = MandatoryField(_("User name"), QLineEdit(self))
        self.user_name.widget.setText(getpass.getuser())
        self.user_name_validator = NameValidator(self)
        self.user_name.widget.setValidator(self.user_name_validator)
        self.user_name.add_to_form(layout)

        self.user_side = QComboBox(self)
        self.user_side.addItem(_("White"), FIRST)
        self.user_side.addItem(_("Black"), SECOND)
        layout.addRow(_("User plays"), self.user_side)
        user_side = settings.value("user_side", type=int)
        if user_side is not None:
            self.user_side.setCurrentIndex(user_side)

        self.board_type = QComboBox(self)
        self.board_type.addItem(_("Use default initial position"),
                                DEFAULT_BOARD)  # 0
        self.board_type.addItem(_("Manual initial position setup"),
                                MANUAL_BOARD)  # 1
        self.board_type.addItem(_("Use initial board of previous game"),
                                PREVIOUS_BOARD)  # 2
        self.board_type.addItem(_("Load initial board from FEN file"),
                                LOAD_FEN)  # 3
        self.board_type.addItem(_("Load initial board from PDN file"),
                                LOAD_PDN)  # 4
        layout.addRow(_("Initial board type"), self.board_type)

        self.file_path = FileSelectWidget(self)
        self.file_path.setVisible(False)
        #self.file_path.selected.connect(self._on_file_selected)
        layout.addRow(_("Select file"), self.file_path)
        layout.labelForField(self.file_path).setVisible(False)

        widget.setLayout(layout)

        vbox = QVBoxLayout()
        vbox.addWidget(widget)

        self.message_label = QLabel(self)
        self.message_label.hide()

        buttons = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self)
        buttons.accepted.connect(self._on_accept)
        buttons.rejected.connect(self.reject)
        self.ok_button = buttons.button(QDialogButtonBox.Ok)
        self.ok_button.setToolTip(_("Start a new game"))
        cancel_button = buttons.button(QDialogButtonBox.Cancel)
        cancel_button.setToolTip(
            _("Close this dialog without starting a new game"))

        self.ai = QComboBox(self)
        self._fill_ais(settings)
        layout.addRow(_("AI"), self.ai)

        lobby_hbox = QHBoxLayout()

        self.lobby = LobbyWidget(client=client, parent=self)
        self.lobby.selected.connect(self._on_game_selected)
        self.lobby.hide()
        lobby_hbox.addWidget(self.lobby)

        self.refresh_button = refresh = QPushButton(_("Refresh"), self)
        refresh.setIcon(QIcon.fromTheme("view-refresh"))
        refresh.clicked.connect(self.lobby.fill)
        refresh.hide()
        lobby_hbox.addWidget(refresh)

        self.lobby.fill()

        vbox.addLayout(lobby_hbox)
        self.setLayout(vbox)

        self.game_type.currentIndexChanged.connect(self._on_action_changed)
        self.board_type.currentIndexChanged.connect(
            self._on_board_type_changed)

        vbox.addWidget(self.message_label)

        settings_btn = QPushButton(self)
        settings_btn.setIcon(QIcon.fromTheme("preferences-system"))
        settings_btn.setToolTip(_("Open settings dialog"))
        settings_btn.clicked.connect(self._on_settings)

        buttons_box = QHBoxLayout()
        buttons_box.addWidget(settings_btn)
        buttons_box.addStretch()
        buttons_box.addWidget(buttons)

        if self.show_exit:
            exit = QPushButton(_("E&xit"), self)
            exit.setIcon(QIcon.fromTheme("application-exit"))
            exit.setToolTip(_("Close this dialog and exit the program"))
            exit.clicked.connect(self._on_exit)
            buttons_box.addWidget(exit)

        vbox.addLayout(buttons_box)

        if self.preset_file:
            if self.preset_mask == PDN_MASK:
                self.board_type.setCurrentIndex(4)
            elif self.preset_mask == FEN_MASK:
                self.board_type.setCurrentIndex(3)
            self.file_path.mask = self.preset_mask
            self.file_path.setPath(self.preset_file)

    def _fill_ais(self, settings):
        self.ai.clear()
        self.ais = AI.list_from_settings(settings)
        for idx, ai in enumerate(self.ais):
            self.ai.addItem(ai.title, idx)
        ai = settings.value("ai")
        if ai is not None:
            idx = self.ai.findText(ai)
            self.ai.setCurrentIndex(idx)

    def message(self, text):
        self.message_label.setVisible(bool(text))
        if text:
            self.message_label.setText(text)

    def get_ok_button(self):
        return self.ok_button

    def get_form_layout(self):
        return self.form_layout

    def _on_exit(self):
        self.done(EXIT)

    def _on_settings(self):
        dialog = SettingsDialog(self.settings, self.share_dir, self)
        result = dialog.exec_()
        if result == QDialog.Accepted:
            self.settings.sync()
            self._fill_ais(self.settings)
            self.client.base_url = self.settings.value("server_url",
                                                       DEFAULT_SERVER_URL)
            level = self.settings.value("log_level", logging.INFO, type=int)
            logging.getLogger().setLevel(level)
            logging.info(_("Settings have been updated."))

    def _on_action_changed(self, idx):
        action = self.game_type.itemData(idx)
        board_type = self.board_type.itemData(idx)

        show_ai = action == START_AI_GAME
        show_lobby = action == JOIN_HUMAN_GAME or board_type == PREVIOUS_BOARD
        show_side = action != JOIN_HUMAN_GAME
        show_board_setup = action != JOIN_HUMAN_GAME

        self.ai.setVisible(show_ai)
        self.form_layout.labelForField(self.ai).setVisible(show_ai)
        self.lobby.setVisible(show_lobby)
        self.refresh_button.setVisible(show_lobby)

        self.user_side.setVisible(show_side)
        self.form_layout.labelForField(self.user_side).setVisible(show_side)
        self.board_type.setVisible(show_board_setup)
        self.form_layout.labelForField(
            self.board_type).setVisible(show_board_setup)

    def _on_board_type_changed(self, idx):
        action = self.game_type.currentData()
        board_type = self.board_type.itemData(idx)
        show_file = board_type == LOAD_FEN or board_type == LOAD_PDN
        self.file_path.setVisible(show_file)
        self.form_layout.labelForField(self.file_path).setVisible(show_file)

        show_rules = board_type != LOAD_PDN
        self.rules.setVisible(show_rules)

        show_lobby = action == JOIN_HUMAN_GAME or board_type == PREVIOUS_BOARD
        self.lobby.setVisible(show_lobby)
        self.lobby.set_selectable(board_type != PREVIOUS_BOARD)

        if board_type == LOAD_FEN:
            self.file_path.mask = FEN_MASK
        elif board_type == LOAD_PDN:
            self.file_path.mask = PDN_MASK

    def _on_game_selected(self, game):
        action = self.game_type.currentData()
        if action == JOIN_HUMAN_GAME:
            used_name = game.get_used_name()
            if used_name is None:
                return
            self.user_name_validator.used_name = used_name
            name = self.user_name.widget.text()
            self.user_name.widget.setText(self.user_name_validator.fixup(name))

    def _on_accept(self):
        self.settings.setValue("ai", self.ai.currentText())
        self.settings.setValue("rules", self.rules.currentData())
        self.settings.setValue("user_side", self.user_side.currentIndex())
        self.accept()

    def get_settings(self):
        game = GameSettings()
        game.user_name = self.user_name.widget.text()
        game.rules = self.rules.currentData()
        action = self.game_type.currentData()
        game.run_now = action != START_HUMAN_GAME
        side = self.user_side.currentData()
        board_type = self.board_type.currentData()
        game.user_turn_first = side == FIRST
        if self.client.get_invert_colors(game.rules):
            game.user_turn_first = not game.user_turn_first
        game.action = action
        game.board_type = board_type

        game.board_setup = board_type == MANUAL_BOARD
        if board_type == LOAD_FEN:
            game.fen_path = self.file_path.path()
        elif board_type == LOAD_PDN:
            game.pdn_path = self.file_path.path()
        elif board_type == PREVIOUS_BOARD:
            game.previous_board_game = self.lobby.get_game_id()

        game.ai = self.ais[self.ai.currentIndex()]

        return game
Beispiel #34
0
class HeapPluginForm(PluginForm):
    def __init__(self):
        super(HeapPluginForm, self).__init__()
        self.parent = None
        self.config_path = None
        self.config = None
        self.tracer = None
        self.heap = None
        self.cur_arena = None  # default: main_arena
        self.ptr_size = get_arch_ptrsize()

    def OnCreate(self, form):
        self.parent = self.FormToPyQtWidget(form)
        self.setup_gui()
        self.init_heap()
        self.populate_gui()

    def setup_gui(self):
        self.chunk_widget = ChunkWidget(self)
        self.tracer_tab = TracerWidget(self)
        self.arena_widget = ArenaWidget(self)
        self.bins_widget = BinsWidget(self)
        self.tcache_widget = TcacheWidget(self)
        self.magic_widget = MagicWidget(self)
        self.config_widget = ConfigWidget(self)

        self.tabs = QTabWidget()
        self.tabs.addTab(self.tracer_tab, "Tracer")
        self.tabs.addTab(self.arena_widget, "Arena")
        self.tabs.addTab(self.bins_widget, "Bins")
        self.tabs.addTab(self.tcache_widget, "Tcache")
        self.tabs.addTab(self.magic_widget, "Magic")
        self.tabs.addTab(self.config_widget, "Config")

        self.btn_reload = QPushButton("Reload info")
        icon = QtGui.QIcon(os.path.normpath(ICONS_DIR + '/refresh.png'))
        self.btn_reload.setIcon(icon)
        self.btn_reload.setFixedWidth(120)
        self.btn_reload.setEnabled(False)
        self.btn_reload.clicked.connect(self.reload_gui_info)

        self.cb_arenas = QComboBox()
        self.cb_arenas.setFixedWidth(150)
        self.cb_arenas.currentIndexChanged[int].connect(self.cb_arenas_changed)

        hbox_arenas = QHBoxLayout()
        hbox_arenas.addWidget(QLabel('Switch arena: '))
        hbox_arenas.addWidget(self.cb_arenas)
        hbox_arenas.setContentsMargins(0, 0, 0, 0)

        self.arenas_widget = QWidget()
        self.arenas_widget.setLayout(hbox_arenas)
        self.arenas_widget.setVisible(False)

        self.txt_warning = QLabel()
        self.txt_warning.setStyleSheet("font-weight: bold; color: red")
        self.txt_warning.setVisible(False)

        hbox_top = QHBoxLayout()
        hbox_top.addWidget(self.btn_reload)
        hbox_top.addWidget(self.arenas_widget)
        hbox_top.addWidget(self.txt_warning)
        hbox_top.setContentsMargins(0, 0, 0, 0)
        hbox_top.addStretch(1)

        vbox_left_panel = QVBoxLayout()
        vbox_left_panel.addLayout(hbox_top)
        vbox_left_panel.addWidget(self.tabs)
        vbox_left_panel.setContentsMargins(0, 0, 0, 0)

        left_panel = QWidget()
        left_panel.setLayout(vbox_left_panel)

        self.splitter = QSplitter(QtCore.Qt.Horizontal)
        self.splitter.addWidget(left_panel)
        self.splitter.addWidget(self.chunk_widget)
        self.splitter.setStretchFactor(0, 1)

        main_layout = QVBoxLayout()
        main_layout.addWidget(self.splitter)
        self.parent.setLayout(main_layout)

    def populate_gui(self):
        self.magic_widget.populate_libc_offsets()
        self.reload_gui_info()

    def reload_gui_info(self, from_arena_cb=False):
        if not self.heap:
            return

        if not self.heap.get_heap_base():
            self.show_warning('Heap not initialized')
            return

        self.hide_warning()
        self.arenas_widget.setVisible(True)

        if not from_arena_cb:
            self.populate_arenas()

        self.arena_widget.populate_table()
        self.tcache_widget.populate_table()
        self.bins_widget.populate_tables()

    def init_heap(self):
        try:
            self.config_path = CONFIG_PATH
            self.config = HeapConfig(self.config_path)
            self.config_widget.load_config()

        except Exception as e:
            self.config = None
            self.show_warning('Please, update the config file')
            warning(str(e))
            return

        if not self.config.offsets:
            arch_bits = self.ptr_size * 8
            self.show_warning('Config: Libc offsets for %d bits not found.' %
                              arch_bits)
            return

        try:
            current_libc_version = get_libc_version()
            if self.config.libc_version == current_libc_version or current_libc_version == None:
                self.heap = Heap(self.config)
                self.btn_reload.setEnabled(True)
                self.tabs.setTabEnabled(3, self.heap.tcache_enabled)
            else:
                self.show_warning('Config: glibc version mismatched: %s != %s' % \
                    (str(self.config.libc_version), str(current_libc_version)))

        except AttributeError:
            self.show_warning('Invalid config file content')

    def populate_arenas(self):
        old_arena = self.cur_arena
        self.cb_arenas.clear()

        for addr, arena in self.heap.arenas():
            if addr == self.heap.main_arena_addr:
                self.cb_arenas.addItem("main_arena", None)
            else:
                self.cb_arenas.addItem("0x%x" % addr, addr)

        idx = self.cb_arenas.findData(old_arena)
        if idx != -1:
            self.cb_arenas.setCurrentIndex(idx)

    def show_warning(self, txt):
        self.txt_warning.setText(txt)
        self.txt_warning.setVisible(True)

    def hide_warning(self):
        self.txt_warning.setVisible(False)

    def cb_arenas_changed(self, idx):
        self.cur_arena = self.cb_arenas.itemData(idx)
        self.reload_gui_info(True)

    def show_chunk_info(self, address):
        self.chunk_widget.show_chunk(address)

    def Show(self):
        return PluginForm.Show(self,
                               PLUGNAME,
                               options=(PluginForm.FORM_TAB
                                        | PluginForm.FORM_CLOSE_LATER))

    def OnClose(self, form):
        if self.tracer:
            self.tracer.unhook()
            log("Tracer disabled")
        log("Form closed")
Beispiel #35
0
    def addParam(self,
                 *,
                 layout=None,
                 label='some param name',
                 box=None,
                 paramName='n/a',
                 rowIndex=1,
                 decimals=2,
                 minimum=0,
                 maximum=10000,
                 unit='',
                 float=True,
                 listport=False,
                 listmonitor=False,
                 boolean=False):
        self.settings += [paramName]
        layout.addWidget(QLabel(label, box), rowIndex, 1, 1, 1)
        if listmonitor:
            valc = QComboBox()
            i = 0
            for monitor in QApplication.screens():
                valc.insertItem(i, str(i) + ': ' + monitor.name(), i)
                i += 1
            sindex = valc.findData(AppData.configParams[paramName][-1][0])
            if sindex == -1:
                sindex = 0
            valc.setCurrentIndex(sindex)
        elif listport:
            valc = QComboBox()
            i = 0
            for port in self.comports:
                valc.insertItem(i, 'RTS on ' + port[0] + ': ' + port[1],
                                (port[0], 'RTS'))
                valc.insertItem(i, 'BREAK on ' + port[0] + ': ' + port[1],
                                (port[0], 'BREAK'))
                i += 1
            sindex = valc.findData(AppData.configParams[paramName][-1][0][1])
            if sindex == -1:
                valc.insertItem(
                    i, AppData.configParams[paramName][-1][0][1] + ' on ' +
                    AppData.configParams[paramName][-1][0][0] +
                    ': no device detected',
                    AppData.configParams[paramName][-1][0])
            else:
                valc.setCurrentIndex(sindex)
        elif boolean:
            valc = QComboBox()
            valc.insertItem(0, 'True', True)
            valc.insertItem(1, 'False', False)
            sindex = valc.findData(AppData.configParams[paramName][-1][0])
            valc.setCurrentIndex(sindex)
        else:
            if float:
                valc = QDoubleSpinBox()
                valc.setDecimals(decimals)
            else:
                valc = QSpinBox()
            valc.lineEdit().setCursor(AppData.cursors['text'])
            valc.setMinimum(minimum)
            valc.setMaximum(maximum)
            valc.setValue(AppData.configParams[paramName][-1][0])
        setattr(self, paramName, valc)
        dowc = QLabel()
        checkc = QPushButton()
        filec = QLabel()
        setattr(self, paramName + 'Default', checkc)

        def resetParam():
            setattr(self, paramName + 'Override', False)
            if AppData.configParams[paramName][-1][1] == self.filename:
                dowc.setText('')
                checkc.setText('')
                filec.setText(unit + ' from ' +
                              AppData.configParams[paramName][-2][1])
                dowc.setEnabled(False)
                checkc.setEnabled(False)
                filec.setEnabled(False)
                valc.blockSignals(True)
                if listmonitor or listport or boolean:
                    sindex = valc.findData(
                        AppData.configParams[paramName][-2][0])
                    valc.setCurrentIndex(sindex)
                else:
                    valc.setValue(AppData.configParams[paramName][-2][0])
                valc.blockSignals(False)
            else:
                dowc.setText('')
                checkc.setText('')
                filec.setText(unit + ' from ' +
                              AppData.configParams[paramName][-1][1])
                dowc.setEnabled(False)
                checkc.setEnabled(False)
                filec.setEnabled(False)
                valc.blockSignals(True)
                if listmonitor or listport or boolean:
                    sindex = valc.findData(
                        AppData.configParams[paramName][-1][0])
                    valc.setCurrentIndex(sindex)
                else:
                    valc.setValue(AppData.configParams[paramName][-1][0])
                valc.blockSignals(False)

        def trumpParam():
            setattr(self, paramName + 'Override', True)
            if AppData.configParams[paramName][-1][1] == self.filename:
                dowc.setText(unit + ' overriding')
                checkc.setText(
                    str(AppData.configParams[paramName][-2][0]) + ' ' + unit)
                filec.setText('specified in ' +
                              AppData.configParams[paramName][-2][1])
                dowc.setEnabled(True)
                checkc.setEnabled(True)
                filec.setEnabled(True)
            else:
                dowc.setText(unit + ' overriding')
                checkc.setText(
                    str(AppData.configParams[paramName][-1][0]) + ' ' + unit)
                filec.setText('\tspecified in ' +
                              AppData.configParams[paramName][-1][1])
                dowc.setEnabled(True)
                checkc.setEnabled(True)
                filec.setEnabled(True)

        checkc.pressed.connect(resetParam)
        if listport or listmonitor or boolean:
            valc.currentIndexChanged.connect(trumpParam)
        else:
            valc.valueChanged.connect(trumpParam)
        overridesParam = AppData.configParams[paramName][-1][
            1] == self.filename
        setattr(self, paramName + 'Override', overridesParam)
        if overridesParam:
            trumpParam()
        else:
            resetParam()
        layout.addWidget(valc, rowIndex, 2, 1, 1)
        layout.addWidget(dowc, rowIndex, 3, 1, 1)
        layout.addWidget(checkc, rowIndex, 4, 1, 1)
        layout.addWidget(filec, rowIndex, 5, 1, 1)
Beispiel #36
0
class AmbientLightV3(COMCUPluginBase):
    def __init__(self, *args):
        super().__init__(BrickletAmbientLightV3, *args)

        self.al = self.device

        self.cbe_illuminance = CallbackEmulator(self.al.get_illuminance,
                                                None,
                                                self.cb_illuminance,
                                                self.increase_error_count)

        self.alf = AmbientLightFrame()
        self.out_of_range_label = QLabel('Illuminance is out-of-range')
        self.saturated_label = QLabel('Sensor is saturated')

        self.out_of_range_label.hide()
        self.out_of_range_label.setStyleSheet('QLabel { color: red }')
        self.saturated_label.hide()
        self.saturated_label.setStyleSheet('QLabel { color: magenta }')

        self.current_illuminance = CurveValueWrapper() # float, lx

        plots = [('Illuminance', Qt.red, self.current_illuminance, '{:.2f} lx (Lux)'.format)]
        self.plot_widget = PlotWidget('Illuminance [lx]', plots,
                                      extra_key_widgets=[self.out_of_range_label, self.saturated_label, self.alf],
                                      y_resolution=0.001)

        self.range_label = QLabel('Illuminance Range:')
        self.range_combo = QComboBox()

        self.range_combo.addItem("Unlimited", BrickletAmbientLightV3.ILLUMINANCE_RANGE_UNLIMITED)
        self.range_combo.addItem("0 - 64000 lx", BrickletAmbientLightV3.ILLUMINANCE_RANGE_64000LUX)
        self.range_combo.addItem("0 - 32000 lx", BrickletAmbientLightV3.ILLUMINANCE_RANGE_32000LUX)
        self.range_combo.addItem("0 - 16000 lx", BrickletAmbientLightV3.ILLUMINANCE_RANGE_16000LUX)
        self.range_combo.addItem("0 - 8000 lx", BrickletAmbientLightV3.ILLUMINANCE_RANGE_8000LUX)
        self.range_combo.addItem("0 - 1300 lx", BrickletAmbientLightV3.ILLUMINANCE_RANGE_1300LUX)
        self.range_combo.addItem("0 - 600 lx", BrickletAmbientLightV3.ILLUMINANCE_RANGE_600LUX)
        self.range_combo.currentIndexChanged.connect(self.new_config)

        self.time_label = QLabel('Integration Time:')
        self.time_combo = QComboBox()
        self.time_combo.addItem("50 ms", BrickletAmbientLightV3.INTEGRATION_TIME_50MS)
        self.time_combo.addItem("100 ms", BrickletAmbientLightV3.INTEGRATION_TIME_100MS)
        self.time_combo.addItem("150 ms", BrickletAmbientLightV3.INTEGRATION_TIME_150MS)
        self.time_combo.addItem("200 ms", BrickletAmbientLightV3.INTEGRATION_TIME_200MS)
        self.time_combo.addItem("250 ms", BrickletAmbientLightV3.INTEGRATION_TIME_250MS)
        self.time_combo.addItem("300 ms", BrickletAmbientLightV3.INTEGRATION_TIME_300MS)
        self.time_combo.addItem("350 ms", BrickletAmbientLightV3.INTEGRATION_TIME_350MS)
        self.time_combo.addItem("400 ms", BrickletAmbientLightV3.INTEGRATION_TIME_400MS)
        self.time_combo.currentIndexChanged.connect(self.new_config)

        hlayout = QHBoxLayout()
        hlayout.addWidget(self.range_label)
        hlayout.addWidget(self.range_combo)
        hlayout.addStretch()
        hlayout.addWidget(self.time_label)
        hlayout.addWidget(self.time_combo)

        line = QFrame()
        line.setObjectName("line")
        line.setFrameShape(QFrame.HLine)
        line.setFrameShadow(QFrame.Sunken)

        layout = QVBoxLayout(self)
        layout.addWidget(self.plot_widget)
        layout.addWidget(line)
        layout.addLayout(hlayout)

    def start(self):
        async_call(self.al.get_configuration, None, self.get_configucation_async, self.increase_error_count)

        self.cbe_illuminance.set_period(100)

        self.plot_widget.stop = False

    def stop(self):
        self.cbe_illuminance.set_period(0)

        self.plot_widget.stop = True

    def destroy(self):
        pass

    @staticmethod
    def has_device_identifier(device_identifier):
        return device_identifier == BrickletAmbientLightV3.DEVICE_IDENTIFIER

    def get_configucation_async(self, conf):
        self.range_combo.setCurrentIndex(self.range_combo.findData(conf.illuminance_range))
        self.time_combo.setCurrentIndex(self.time_combo.findData(conf.integration_time))

    def new_config(self, _value):
        try:
            self.al.set_configuration(self.range_combo.itemData(self.range_combo.currentIndex()),
                                      self.time_combo.itemData(self.time_combo.currentIndex()))
        except ip_connection.Error:
            pass

    def cb_illuminance(self, illuminance):
        self.current_illuminance.value = illuminance / 100.0

        max_illuminance = 12000000 # Approximation for unlimited range
        current_range = self.range_combo.itemData(self.range_combo.currentIndex())

        if current_range == BrickletAmbientLightV3.ILLUMINANCE_RANGE_64000LUX:
            max_illuminance = 6400001
        elif current_range == BrickletAmbientLightV3.ILLUMINANCE_RANGE_32000LUX:
            max_illuminance = 3200001
        elif current_range == BrickletAmbientLightV3.ILLUMINANCE_RANGE_16000LUX:
            max_illuminance = 1600001
        elif current_range == BrickletAmbientLightV3.ILLUMINANCE_RANGE_8000LUX:
            max_illuminance = 800001
        elif current_range == BrickletAmbientLightV3.ILLUMINANCE_RANGE_1300LUX:
            max_illuminance = 130001
        elif current_range == BrickletAmbientLightV3.ILLUMINANCE_RANGE_600LUX:
            max_illuminance = 60001

        if illuminance == 0:
            self.plot_widget.get_key_item(0).setStyleSheet('QLabel { color: magenta }')
            self.out_of_range_label.hide()
            self.saturated_label.show()
        elif illuminance >= max_illuminance:
            self.plot_widget.get_key_item(0).setStyleSheet('QLabel { color: red }')
            self.out_of_range_label.show()
            self.saturated_label.hide()
        else:
            self.plot_widget.get_key_item(0).setStyleSheet('')
            self.out_of_range_label.hide()
            self.saturated_label.hide()

        value = min(max(illuminance * 255 // max_illuminance, 0), 255)
        self.alf.set_color(value, value, value)
Beispiel #37
0
class OWCSVFileImport(widget.OWWidget):
    name = "CSV File Import"
    description = "Import a data table from a CSV formatted file."
    icon = "icons/CSVFile.svg"
    priority = 11
    category = "Data"
    keywords = ["file", "load", "read", "open", "csv"]

    outputs = [
        widget.OutputSignal(
            name="Data",
            type=Orange.data.Table,
            doc="Loaded data set."),
        widget.OutputSignal(
            name="Data Frame",
            type=pd.DataFrame,
            doc=""
        )
    ]

    class Error(widget.OWWidget.Error):
        error = widget.Msg(
            "Unexpected error"
        )
        encoding_error = widget.Msg(
            "Encoding error\n"
            "The file might be encoded in an unsupported encoding or it "
            "might be binary"
        )

    #: Paths and options of files accessed in a 'session'
    _session_items = settings.Setting(
        [], schema_only=True)  # type: List[Tuple[str, dict]]

    #: Saved dialog state (last directory and selected filter)
    dialog_state = settings.Setting({
        "directory": "",
        "filter": ""
    })  # type: Dict[str, str]
    MaxHistorySize = 50

    want_main_area = False
    buttons_area_orientation = None

    def __init__(self, *args, **kwargs):
        super().__init__(self, *args, **kwargs)

        self.__committimer = QTimer(self, singleShot=True)
        self.__committimer.timeout.connect(self.commit)

        self.__executor = qconcurrent.ThreadExecutor()
        self.__watcher = None  # type: Optional[qconcurrent.FutureWatcher]

        self.controlArea.layout().setSpacing(-1)  # reset spacing
        grid = QGridLayout()
        grid.addWidget(QLabel("File:", self), 0, 0, 1, 1)

        self.import_items_model = QStandardItemModel(self)
        self.recent_combo = QComboBox(
            self, objectName="recent-combo", toolTip="Recent files.",
            sizeAdjustPolicy=QComboBox.AdjustToMinimumContentsLengthWithIcon,
            minimumContentsLength=16,
        )
        self.recent_combo.setModel(self.import_items_model)
        self.recent_combo.activated.connect(self.activate_recent)
        self.recent_combo.setSizePolicy(
            QSizePolicy.MinimumExpanding, QSizePolicy.Fixed)
        self.browse_button = QPushButton(
            "…", icon=self.style().standardIcon(QStyle.SP_DirOpenIcon),
            toolTip="Browse filesystem", autoDefault=False,
        )
        self.browse_button.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        self.browse_button.clicked.connect(self.browse)
        grid.addWidget(self.recent_combo, 0, 1, 1, 1)
        grid.addWidget(self.browse_button, 0, 2, 1, 1)
        self.controlArea.layout().addLayout(grid)

        ###########
        # Info text
        ###########
        box = gui.widgetBox(self.controlArea, "Info", addSpace=False)
        self.summary_text = QTextBrowser(
            verticalScrollBarPolicy=Qt.ScrollBarAsNeeded,
            readOnly=True,
        )
        self.summary_text.viewport().setBackgroundRole(QPalette.NoRole)
        self.summary_text.setFrameStyle(QTextBrowser.NoFrame)
        self.summary_text.setMinimumHeight(self.fontMetrics().ascent() * 2 + 4)
        self.summary_text.viewport().setAutoFillBackground(False)
        box.layout().addWidget(self.summary_text)

        button_box = QDialogButtonBox(
            orientation=Qt.Horizontal,
            standardButtons=QDialogButtonBox.Cancel | QDialogButtonBox.Retry
        )
        self.load_button = b = button_box.button(QDialogButtonBox.Retry)
        b.setText("Load")
        b.clicked.connect(self.__committimer.start)
        b.setEnabled(False)
        b.setDefault(True)

        self.cancel_button = b = button_box.button(QDialogButtonBox.Cancel)
        b.clicked.connect(self.cancel)
        b.setEnabled(False)
        b.setAutoDefault(False)

        self.import_options_button = QPushButton(
            "Import Options…", enabled=False, autoDefault=False,
            clicked=self._activate_import_dialog
        )

        def update_buttons(cbindex):
            self.import_options_button.setEnabled(cbindex != -1)
            self.load_button.setEnabled(cbindex != -1)
        self.recent_combo.currentIndexChanged.connect(update_buttons)

        button_box.addButton(
            self.import_options_button, QDialogButtonBox.ActionRole
        )
        button_box.setStyleSheet(
            "button-layout: {:d};".format(QDialogButtonBox.MacLayout)
        )
        self.controlArea.layout().addWidget(button_box)

        self._restoreState()
        if self.current_item() is not None:
            self._invalidate()
        self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)

    @Slot(int)
    def activate_recent(self, index):
        """
        Activate an item from the recent list.
        """
        if 0 <= index < self.import_items_model.rowCount():
            item = self.import_items_model.item(index)
            assert item is not None
            path = item.data(ImportItem.PathRole)
            opts = item.data(ImportItem.OptionsRole)
            if not isinstance(opts, Options):
                opts = None
            self.set_selected_file(path, opts)
        else:
            self.recent_combo.setCurrentIndex(-1)

    @Slot()
    def browse(self):
        """
        Open a file dialog and select a user specified file.
        """
        formats = [
            "Text - comma separated (*.csv, *)",
            "Text - tab separated (*.tsv, *)",
            "Text - all files (*)"
        ]

        dlg = QFileDialog(
            self, windowTitle="Open Data File",
            acceptMode=QFileDialog.AcceptOpen,
            fileMode=QFileDialog.ExistingFile
        )
        dlg.setNameFilters(formats)
        state = self.dialog_state
        lastdir = state.get("directory", "")
        lastfilter = state.get("filter", "")

        if lastdir and os.path.isdir(lastdir):
            dlg.setDirectory(lastdir)
        if lastfilter:
            dlg.selectNameFilter(lastfilter)

        status = dlg.exec_()
        dlg.deleteLater()
        if status == QFileDialog.Accepted:
            self.dialog_state["directory"] = dlg.directory().absolutePath()
            self.dialog_state["filter"] = dlg.selectedNameFilter()

            selected_filter = dlg.selectedNameFilter()
            path = dlg.selectedFiles()[0]
            # pre-flight check; try to determine the nature of the file
            mtype = _mime_type_for_path(path)
            if not mtype.inherits("text/plain"):
                mb = QMessageBox(
                    parent=self,
                    windowTitle="",
                    icon=QMessageBox.Question,
                    text="The '{basename}' may be a binary file.\n"
                         "Are you sure you want to continue?".format(
                             basename=os.path.basename(path)),
                    standardButtons=QMessageBox.Cancel | QMessageBox.Yes
                )
                mb.setWindowModality(Qt.WindowModal)
                if mb.exec() == QMessageBox.Cancel:
                    return

            # initialize dialect based on selected extension
            if selected_filter in formats[:-1]:
                filter_idx = formats.index(selected_filter)
                if filter_idx == 0:
                    dialect = csv.excel()
                elif filter_idx == 1:
                    dialect = csv.excel_tab()
                else:
                    dialect = csv.excel_tab()
                header = True
            else:
                try:
                    dialect, header = sniff_csv_with_path(path)
                except Exception:  # pylint: disable=broad-except
                    dialect, header = csv.excel(), True

            options = None
            # Search for path in history.
            # If found use the stored params to initialize the import dialog
            items = self.itemsFromSettings()
            idx = index_where(items, lambda t: samepath(t[0], path))
            if idx is not None:
                _, options_ = items[idx]
                if options_ is not None:
                    options = options_

            if options is None:
                if not header:
                    rowspec = []
                else:
                    rowspec = [(range(0, 1), RowSpec.Header)]
                options = Options(
                    encoding="utf-8", dialect=dialect, rowspec=rowspec)

            dlg = CSVImportDialog(
                self, windowTitle="Import Options", sizeGripEnabled=True)
            dlg.setWindowModality(Qt.WindowModal)
            dlg.setPath(path)
            dlg.setOptions(options)
            status = dlg.exec_()
            dlg.deleteLater()
            if status == QDialog.Accepted:
                self.set_selected_file(path, dlg.options())

    def current_item(self):
        # type: () -> Optional[ImportItem]
        """
        Return the current selected item (file) or None if there is no
        current item.
        """
        idx = self.recent_combo.currentIndex()
        if idx == -1:
            return None

        item = self.recent_combo.model().item(idx)  # type: QStandardItem
        if isinstance(item, ImportItem):
            return item
        else:
            return None

    def _activate_import_dialog(self):
        """Activate the Import Options dialog for the  current item."""
        item = self.current_item()
        assert item is not None
        dlg = CSVImportDialog(
            self, windowTitle="Import Options", sizeGripEnabled=True,
        )
        dlg.setWindowModality(Qt.WindowModal)
        dlg.setAttribute(Qt.WA_DeleteOnClose)
        settings = QSettings()
        qualname = qname(type(self))
        settings.beginGroup(qualname)
        size = settings.value("size", QSize(), type=QSize)  # type: QSize
        if size.isValid():
            dlg.resize(size)

        path = item.data(ImportItem.PathRole)
        options = item.data(ImportItem.OptionsRole)
        dlg.setPath(path)  # Set path before options so column types can
        if isinstance(options, Options):
            dlg.setOptions(options)

        def update():
            newoptions = dlg.options()
            item.setData(newoptions, ImportItem.OptionsRole)
            # update the stored item
            self._add_recent(path, newoptions)
            if newoptions != options:
                self._invalidate()
        dlg.accepted.connect(update)

        def store_size():
            settings.setValue("size", dlg.size())
        dlg.finished.connect(store_size)
        dlg.show()

    def set_selected_file(self, filename, options=None):
        """
        Set the current selected filename path.
        """
        self._add_recent(filename, options)
        self._invalidate()

    #: Saved options for a filename
    SCHEMA = {
        "path": str,  # Local filesystem path
        "options": str,  # json encoded 'Options'
    }

    @classmethod
    def _local_settings(cls):
        # type: () -> QSettings
        """Return a QSettings instance with local persistent settings."""
        filename = "{}.ini".format(qname(cls))
        fname = os.path.join(settings.widget_settings_dir(), filename)
        return QSettings(fname, QSettings.IniFormat)

    def _add_recent(self, filename, options=None):
        # type: (str, Optional[Options]) -> None
        """
        Add filename to the list of recent files.
        """
        model = self.import_items_model
        index = index_where(
            (model.index(i, 0).data(ImportItem.PathRole)
             for i in range(model.rowCount())),
            lambda path: isinstance(path, str) and samepath(path, filename)
        )
        if index is not None:
            item, *_ = model.takeRow(index)
        else:
            item = ImportItem.fromPath(filename)

        model.insertRow(0, item)

        if options is not None:
            item.setOptions(options)

        self.recent_combo.setCurrentIndex(0)
        # store items to local persistent settings
        s = self._local_settings()
        arr = QSettings_readArray(s, "recent", OWCSVFileImport.SCHEMA)
        item = {"path": filename}
        if options is not None:
            item["options"] = json.dumps(options.as_dict())

        arr = [item for item in arr if item.get("path") != filename]
        arr.append(item)
        QSettings_writeArray(s, "recent", arr)

        # update workflow session items
        items = self._session_items[:]
        idx = index_where(items, lambda t: samepath(t[0], filename))
        if idx is not None:
            del items[idx]
        items.insert(0, (filename, options.as_dict()))
        self._session_items = items[:OWCSVFileImport.MaxHistorySize]

    def _invalidate(self):
        # Invalidate the current output and schedule a new commit call.
        # (NOTE: The widget enters a blocking state)
        self.__committimer.start()
        if self.__watcher is not None:
            self.__cancel_task()
        self.setBlocking(True)

    def commit(self):
        """
        Commit the current state and submit the load task for execution.

        Note
        ----
        Any existing pending task is canceled.
        """
        self.__committimer.stop()
        if self.__watcher is not None:
            self.__cancel_task()
        self.error()

        item = self.current_item()
        if item is None:
            return
        path = item.data(ImportItem.PathRole)
        opts = item.data(ImportItem.OptionsRole)
        if not isinstance(opts, Options):
            return

        task = state = TaskState()
        state.future = ...
        state.watcher = qconcurrent.FutureWatcher()
        state.progressChanged.connect(self.__set_read_progress,
                                      Qt.QueuedConnection)

        def progress_(i, j):
            task.emitProgressChangedOrCancel(i, j)

        task.future = self.__executor.submit(
            clear_stack_on_cancel(load_csv),
            path, opts, progress_,
        )
        task.watcher.setFuture(task.future)
        w = task.watcher
        w.done.connect(self.__handle_result)
        w.progress = state
        self.__watcher = w
        self.__set_running_state()

    @Slot('qint64', 'qint64')
    def __set_read_progress(self, read, count):
        if count > 0:
            self.progressBarSet(100 * read / count)

    def __cancel_task(self):
        # Cancel and dispose of the current task
        assert self.__watcher is not None
        w = self.__watcher
        w.future().cancel()
        w.progress.cancel = True
        w.done.disconnect(self.__handle_result)
        w.progress.progressChanged.disconnect(self.__set_read_progress)
        w.progress.deleteLater()
        # wait until completion
        futures.wait([w.future()])
        self.__watcher = None

    def cancel(self):
        """
        Cancel current pending or executing task.
        """
        if self.__watcher is not None:
            self.__cancel_task()
            self.__clear_running_state()
            self.setStatusMessage("Cancelled")
            self.summary_text.setText(
                "<div>Cancelled<br/><small>Press 'Reload' to try again</small></div>"
            )

    def __set_running_state(self):
        self.progressBarInit()
        self.setBlocking(True)
        self.setStatusMessage("Running")
        self.cancel_button.setEnabled(True)
        self.load_button.setText("Restart")
        path = self.current_item().path()
        self.Error.clear()
        self.summary_text.setText(
            "<div>Loading: <i>{}</i><br/>".format(prettyfypath(path))
        )

    def __clear_running_state(self, ):
        self.progressBarFinished()
        self.setStatusMessage("")
        self.setBlocking(False)
        self.cancel_button.setEnabled(False)
        self.load_button.setText("Reload")

    def __set_error_state(self, err):
        self.Error.clear()
        if isinstance(err, UnicodeDecodeError):
            self.Error.encoding_error(exc_info=err)
        else:
            self.Error.error(exc_info=err)

        path = self.current_item().path()
        basename = os.path.basename(path)
        if isinstance(err, UnicodeDecodeError):
            text = (
                "<div><i>{basename}</i> was not loaded due to a text encoding "
                "error. The file might be saved in an unknown or invalid "
                "encoding, or it might be a binary file.</div>"
            ).format(
                basename=escape(basename)
            )
        else:
            text = (
                "<div><i>{basename}</i> was not loaded due to an error:"
                "<p style='white-space: pre;'>{err}</p>"
            ).format(
                basename=escape(basename),
                err="".join(traceback.format_exception_only(type(err), err))
            )
        self.summary_text.setText(text)

    def __clear_error_state(self):
        self.Error.error.clear()
        self.summary_text.setText("")

    def onDeleteWidget(self):
        """Reimplemented."""
        if self.__watcher is not None:
            self.__cancel_task()
            self.__executor.shutdown()
        super().onDeleteWidget()

    @Slot(object)
    def __handle_result(self, f):
        # type: (qconcurrent.Future[pd.DataFrame]) -> None
        assert f.done()
        assert f is self.__watcher.future()
        self.__watcher = None
        self.__clear_running_state()

        try:
            df = f.result()
            assert isinstance(df, pd.DataFrame)
        except pandas.errors.EmptyDataError:
            df = pd.DataFrame({})
        except Exception as e:  # pylint: disable=broad-except
            self.__set_error_state(e)
            df = None
        else:
            self.__clear_error_state()

        if df is not None:
            table = pandas_to_table(df)
        else:
            table = None
        self.send("Data Frame", df)
        self.send('Data', table)
        self._update_status_messages(table)

    def _update_status_messages(self, data):
        if data is None:
            return

        def pluralize(seq):
            return "s" if len(seq) != 1 else ""

        summary = ("{n_instances} row{plural_1}, "
                   "{n_features} feature{plural_2}, "
                   "{n_meta} meta{plural_3}").format(
                       n_instances=len(data), plural_1=pluralize(data),
                       n_features=len(data.domain.attributes),
                       plural_2=pluralize(data.domain.attributes),
                       n_meta=len(data.domain.metas),
                       plural_3=pluralize(data.domain.metas))
        self.summary_text.setText(summary)

    def itemsFromSettings(self):
        # type: () -> List[Tuple[str, Options]]
        """
        Return items from local history.
        """
        s = self._local_settings()
        items_ = QSettings_readArray(s, "recent", OWCSVFileImport.SCHEMA)
        items = []  # type: List[Tuple[str, Options]]
        for item in items_:
            path = item.get("path", "")
            if not path:
                continue
            opts_json = item.get("options", "")
            try:
                opts = Options.from_dict(json.loads(opts_json))
            except (csv.Error, LookupError, TypeError, json.JSONDecodeError):
                _log.error("Could not reconstruct options for '%s'", path,
                           exc_info=True)
            else:
                items.append((path, opts))
        return items[::-1]

    def _restoreState(self):
        # Restore the state. Merge session (workflow) items with the
        # local history.
        model = self.import_items_model
        # local history
        items = self.itemsFromSettings()
        # stored session items
        sitems = []
        for p, m in self._session_items:
            try:
                item_ = (p, Options.from_dict(m))
            except (csv.Error, LookupError):
                # Is it better to fail then to lose a item slot?
                _log.error("Failed to restore '%s'", p, exc_info=True)
            else:
                sitems.append(item_)

        items = sitems + items
        items = unique(items, key=lambda t: pathnormalize(t[0]))

        curr = self.recent_combo.currentIndex()
        if curr != -1:
            currentpath = self.recent_combo.currentData(ImportItem.PathRole)
        else:
            currentpath = None
        for path, options in items:
            item = ImportItem.fromPath(path)
            item.setOptions(options)
            model.appendRow(item)

        if currentpath is not None:
            idx = self.recent_combo.findData(currentpath, ImportItem.PathRole)
            if idx != -1:
                self.recent_combo.setCurrentIndex(idx)
Beispiel #38
0
class GuiDocSplit(QDialog):
    def __init__(self, theParent, theProject):
        QDialog.__init__(self, theParent)

        logger.debug("Initialising GuiDocSplit ...")
        self.setObjectName("GuiDocSplit")

        self.mainConf = nw.CONFIG
        self.theParent = theParent
        self.theProject = theProject
        self.optState = self.theProject.optState
        self.sourceItem = None

        self.outerBox = QVBoxLayout()
        self.setWindowTitle(self.tr("Split Document"))

        self.headLabel = QLabel("<b>%s</b>" % self.tr("Document Headers"))
        self.helpLabel = QHelpLabel(
            self.tr("Select the maximum level to split into files."),
            self.theParent.theTheme.helpText)

        self.listBox = QListWidget()
        self.listBox.setDragDropMode(QAbstractItemView.NoDragDrop)
        self.listBox.setMinimumWidth(self.mainConf.pxInt(400))
        self.listBox.setMinimumHeight(self.mainConf.pxInt(180))

        self.splitLevel = QComboBox(self)
        self.splitLevel.addItem(self.tr("Split on Header Level 1 (Title)"), 1)
        self.splitLevel.addItem(
            self.tr("Split up to Header Level 2 (Chapter)"), 2)
        self.splitLevel.addItem(self.tr("Split up to Header Level 3 (Scene)"),
                                3)
        self.splitLevel.addItem(
            self.tr("Split up to Header Level 4 (Section)"), 4)
        spIndex = self.splitLevel.findData(
            self.optState.getInt("GuiDocSplit", "spLevel", 3))
        if spIndex != -1:
            self.splitLevel.setCurrentIndex(spIndex)
        self.splitLevel.currentIndexChanged.connect(self._populateList)

        self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok
                                          | QDialogButtonBox.Cancel)
        self.buttonBox.accepted.connect(self._doSplit)
        self.buttonBox.rejected.connect(self._doClose)

        self.outerBox.setSpacing(0)
        self.outerBox.addWidget(self.headLabel)
        self.outerBox.addWidget(self.helpLabel)
        self.outerBox.addSpacing(self.mainConf.pxInt(8))
        self.outerBox.addWidget(self.listBox)
        self.outerBox.addWidget(self.splitLevel)
        self.outerBox.addSpacing(self.mainConf.pxInt(12))
        self.outerBox.addWidget(self.buttonBox)
        self.setLayout(self.outerBox)

        self.rejected.connect(self._doClose)

        self._populateList()

        logger.debug("GuiDocSplit initialisation complete")

        return

    ##
    #  Buttons
    ##

    def _doSplit(self):
        """Perform the split of the file, create a new folder in the
        same parent folder, and multiple files depending on split level
        settings. The old file is not removed in the split process, and
        must be deleted manually.
        """
        logger.verbose("GuiDocSplit split button clicked")

        if self.sourceItem is None:
            self.theParent.makeAlert(
                self.tr("No source document selected. Nothing to do."),
                nwAlert.ERROR)
            return

        srcItem = self.theProject.projTree[self.sourceItem]
        if srcItem is None:
            self.theParent.makeAlert(
                self.tr("Could not parse source document."), nwAlert.ERROR)
            return

        theDoc = NWDoc(self.theProject, self.theParent)
        theText = theDoc.openDocument(self.sourceItem, False)
        theLines = theText.splitlines()
        nLines = len(theLines)
        theLines.insert(0, "%Split Doc")
        logger.debug("Splitting document %s with %d lines" %
                     (self.sourceItem, nLines))

        finalOrder = []
        for i in range(self.listBox.count()):
            listItem = self.listBox.item(i)
            wTitle = listItem.text()
            lineNo = listItem.data(Qt.UserRole)
            finalOrder.append([wTitle, lineNo, nLines])
            if i > 0:
                finalOrder[i - 1][2] = lineNo

        nFiles = len(finalOrder)
        if nFiles == 0:
            self.theParent.makeAlert(
                self.tr("No headers found. Nothing to do."), nwAlert.ERROR)
            return

        # Check that another folder can be created
        parTree = self.theProject.projTree.getItemPath(srcItem.itemParent)
        if len(parTree) >= nwConst.MAX_DEPTH - 1:
            self.theParent.makeAlert(
                self.
                tr("Cannot add new folder for the document split. "
                   "Maximum folder depth has been reached. "
                   "Please move the file to another level in the project tree."
                   ), nwAlert.ERROR)
            return

        msgYes = self.theParent.askQuestion(
            self.tr("Split Document"),
            "%s<br><br>%s" % (self.tr(
                "The document will be split into {0} file(s) in a new folder. "
                "The original document will remain intact.").format(nFiles),
                              self.tr("Continue with the splitting process?")))
        if not msgYes:
            return

        # Create the folder
        fHandle = self.theProject.newFolder(srcItem.itemName,
                                            srcItem.itemClass,
                                            srcItem.itemParent)
        self.theParent.treeView.revealNewTreeItem(fHandle)
        logger.verbose("Creating folder %s" % fHandle)

        # Loop through, and create the files
        for wTitle, iStart, iEnd in finalOrder:

            itemLayout = nwItemLayout.NOTE
            if srcItem.itemClass == nwItemClass.NOVEL:
                if wTitle.startswith("# "):
                    itemLayout = nwItemLayout.PARTITION
                elif wTitle.startswith("## "):
                    itemLayout = nwItemLayout.CHAPTER
                elif wTitle.startswith("### "):
                    itemLayout = nwItemLayout.SCENE
                elif wTitle.startswith("#### "):
                    itemLayout = nwItemLayout.SCENE

            wTitle = wTitle.lstrip("#")
            wTitle = wTitle.strip()

            nHandle = self.theProject.newFile(wTitle, srcItem.itemClass,
                                              fHandle)
            newItem = self.theProject.projTree[nHandle]
            newItem.setLayout(itemLayout)
            newItem.setStatus(srcItem.itemStatus)
            logger.verbose(
                "Creating new document %s with text from line %d to %d" %
                (nHandle, iStart, iEnd - 1))

            theText = "\n".join(theLines[iStart:iEnd])
            theText = theText.rstrip("\n") + "\n\n"
            theDoc.openDocument(nHandle, False)
            theDoc.saveDocument(theText)
            theDoc.clearDocument()
            self.theParent.treeView.revealNewTreeItem(nHandle)

        self._doClose()

        return

    def _doClose(self):
        """Close the dialog window without doing anything.
        """
        self.optState.saveSettings()
        self.close()
        return

    ##
    #  Internal Functions
    ##

    def _populateList(self):
        """Get the item selected in the tree, check that it is a folder,
        and try to find all files associated with it. The valid files
        are then added to the list view in order. The list itself can be
        reordered by the user.
        """
        if self.sourceItem is None:
            self.sourceItem = self.theParent.treeView.getSelectedHandle()

        if self.sourceItem is None:
            return

        nwItem = self.theProject.projTree[self.sourceItem]
        if nwItem is None:
            return
        if nwItem.itemType is not nwItemType.FILE:
            self.theParent.makeAlert(
                self.tr(
                    "Element selected in the project tree must be a file."),
                nwAlert.ERROR)
            return

        self.listBox.clear()
        theDoc = NWDoc(self.theProject, self.theParent)
        theText = theDoc.openDocument(self.sourceItem, False)

        spLevel = self.splitLevel.currentData()
        self.optState.setValue("GuiDocSplit", "spLevel", spLevel)
        logger.debug("Scanning document %s for headings level <= %d" %
                     (self.sourceItem, spLevel))

        lineNo = 0
        for aLine in theText.splitlines():

            lineNo += 1
            onLine = 0

            if aLine.startswith("# ") and spLevel >= 1:
                onLine = lineNo
            elif aLine.startswith("## ") and spLevel >= 2:
                onLine = lineNo
            elif aLine.startswith("### ") and spLevel >= 3:
                onLine = lineNo
            elif aLine.startswith("#### ") and spLevel >= 4:
                onLine = lineNo

            if onLine > 0:
                newItem = QListWidgetItem()
                newItem.setText(aLine.strip())
                newItem.setData(Qt.UserRole, onLine)
                self.listBox.addItem(newItem)

        return
Beispiel #39
0
    def __init__(self, parent: 'ElectrumWindow', config: 'SimpleConfig'):
        WindowModalDialog.__init__(self, parent, _('Preferences'))
        self.config = config
        self.window = parent
        self.need_restart = False
        self.fx = self.window.fx
        self.wallet = self.window.wallet
        
        vbox = QVBoxLayout()
        tabs = QTabWidget()
        gui_widgets = []
        tx_widgets = []
        oa_widgets = []

        # language
        lang_help = _('Select which language is used in the GUI (after restart).')
        lang_label = HelpLabel(_('Language') + ':', lang_help)
        lang_combo = QComboBox()
        lang_combo.addItems(list(languages.values()))
        lang_keys = list(languages.keys())
        lang_cur_setting = self.config.get("language", '')
        try:
            index = lang_keys.index(lang_cur_setting)
        except ValueError:  # not in list
            index = 0
        lang_combo.setCurrentIndex(index)
        if not self.config.is_modifiable('language'):
            for w in [lang_combo, lang_label]: w.setEnabled(False)
        def on_lang(x):
            lang_request = list(languages.keys())[lang_combo.currentIndex()]
            if lang_request != self.config.get('language'):
                self.config.set_key("language", lang_request, True)
                self.need_restart = True
        lang_combo.currentIndexChanged.connect(on_lang)
        gui_widgets.append((lang_label, lang_combo))

        nz_help = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
        nz_label = HelpLabel(_('Zeros after decimal point') + ':', nz_help)
        nz = QSpinBox()
        nz.setMinimum(0)
        nz.setMaximum(self.config.decimal_point)
        nz.setValue(self.config.num_zeros)
        if not self.config.is_modifiable('num_zeros'):
            for w in [nz, nz_label]: w.setEnabled(False)
        def on_nz():
            value = nz.value()
            if self.config.num_zeros != value:
                self.config.num_zeros = value
                self.config.set_key('num_zeros', value, True)
                self.window.need_update.set()
        nz.valueChanged.connect(on_nz)
        gui_widgets.append((nz_label, nz))

        use_rbf = bool(self.config.get('use_rbf', True))
        use_rbf_cb = QCheckBox(_('Use Replace-By-Fee'))
        use_rbf_cb.setChecked(use_rbf)
        use_rbf_cb.setToolTip(
            _('If you check this box, your transactions will be marked as non-final,') + '\n' + \
            _('and you will have the possibility, while they are unconfirmed, to replace them with transactions that pay higher fees.') + '\n' + \
            _('Note that some merchants do not accept non-final transactions until they are confirmed.'))
        def on_use_rbf(x):
            self.config.set_key('use_rbf', bool(x))
            batch_rbf_cb.setEnabled(bool(x))
        use_rbf_cb.stateChanged.connect(on_use_rbf)
        tx_widgets.append((use_rbf_cb, None))

        batch_rbf_cb = QCheckBox(_('Batch RBF transactions'))
        batch_rbf_cb.setChecked(bool(self.config.get('batch_rbf', False)))
        batch_rbf_cb.setEnabled(use_rbf)
        batch_rbf_cb.setToolTip(
            _('If you check this box, your unconfirmed transactions will be consolidated into a single transaction.') + '\n' + \
            _('This will save fees.'))
        def on_batch_rbf(x):
            self.config.set_key('batch_rbf', bool(x))
        batch_rbf_cb.stateChanged.connect(on_batch_rbf)
        tx_widgets.append((batch_rbf_cb, None))

        # lightning
        lightning_widgets = []

        if self.wallet.lnworker and self.wallet.lnworker.has_deterministic_node_id():
            help_recov = _(messages.MSG_RECOVERABLE_CHANNELS)
            recov_cb = QCheckBox(_("Create recoverable channels"))
            recov_cb.setToolTip(messages.to_rtf(help_recov))
            recov_cb.setChecked(bool(self.config.get('use_recoverable_channels', True)))
            def on_recov_checked(x):
                self.config.set_key('use_recoverable_channels', bool(x))
            recov_cb.stateChanged.connect(on_recov_checked)
            recov_cb.setEnabled(not bool(self.config.get('lightning_listen')))
            lightning_widgets.append((recov_cb, None))

        help_trampoline = _(messages.MSG_HELP_TRAMPOLINE)
        trampoline_cb = QCheckBox(_("Use trampoline routing (disable gossip)"))
        trampoline_cb.setToolTip(messages.to_rtf(help_trampoline))
        trampoline_cb.setChecked(not bool(self.config.get('use_gossip', False)))
        def on_trampoline_checked(use_trampoline):
            use_gossip = not bool(use_trampoline)
            self.config.set_key('use_gossip', use_gossip)
            if use_gossip:
                self.window.network.start_gossip()
            else:
                self.window.network.run_from_another_thread(
                    self.window.network.stop_gossip())
            util.trigger_callback('ln_gossip_sync_progress')
            # FIXME: update all wallet windows
            util.trigger_callback('channels_updated', self.wallet)
        trampoline_cb.stateChanged.connect(on_trampoline_checked)
        lightning_widgets.append((trampoline_cb, None))

        help_remote_wt = ' '.join([
            _("A watchtower is a daemon that watches your channels and prevents the other party from stealing funds by broadcasting an old state."),
            _("If you have private a watchtower, enter its URL here."),
            _("Check our online documentation if you want to configure Electrum as a watchtower."),
        ])
        remote_wt_cb = QCheckBox(_("Use a remote watchtower"))
        remote_wt_cb.setToolTip('<p>'+help_remote_wt+'</p>')
        remote_wt_cb.setChecked(bool(self.config.get('use_watchtower', False)))
        def on_remote_wt_checked(x):
            self.config.set_key('use_watchtower', bool(x))
            self.watchtower_url_e.setEnabled(bool(x))
        remote_wt_cb.stateChanged.connect(on_remote_wt_checked)
        watchtower_url = self.config.get('watchtower_url')
        self.watchtower_url_e = QLineEdit(watchtower_url)
        self.watchtower_url_e.setEnabled(self.config.get('use_watchtower', False))
        def on_wt_url():
            url = self.watchtower_url_e.text() or None
            watchtower_url = self.config.set_key('watchtower_url', url)
        self.watchtower_url_e.editingFinished.connect(on_wt_url)
        lightning_widgets.append((remote_wt_cb, self.watchtower_url_e))

        msg = _('OpenAlias record, used to receive coins and to sign payment requests.') + '\n\n'\
              + _('The following alias providers are available:') + '\n'\
              + '\n'.join(['https://cryptoname.co/', 'http://xmr.link']) + '\n\n'\
              + 'For more information, see https://openalias.org'
        alias_label = HelpLabel(_('OpenAlias') + ':', msg)
        alias = self.config.get('alias','')
        self.alias_e = QLineEdit(alias)
        self.set_alias_color()
        self.alias_e.editingFinished.connect(self.on_alias_edit)
        oa_widgets.append((alias_label, self.alias_e))

        msat_cb = QCheckBox(_("Show amounts with msat precision"))
        msat_cb.setChecked(bool(self.config.get('amt_precision_post_satoshi', False)))
        def on_msat_checked(v):
            prec = 3 if v == Qt.Checked else 0
            if self.config.amt_precision_post_satoshi != prec:
                self.config.amt_precision_post_satoshi = prec
                self.config.set_key('amt_precision_post_satoshi', prec)
                self.window.need_update.set()
        msat_cb.stateChanged.connect(on_msat_checked)
        lightning_widgets.append((msat_cb, None))

        # units
        units = base_units_list
        msg = (_('Base unit of your wallet.')
               + '\n1 BTC = 1000 mBTC. 1 mBTC = 1000 bits. 1 bit = 100 sat.\n'
               + _('This setting affects the Send tab, and all balance related fields.'))
        unit_label = HelpLabel(_('Base unit') + ':', msg)
        unit_combo = QComboBox()
        unit_combo.addItems(units)
        unit_combo.setCurrentIndex(units.index(self.window.base_unit()))
        def on_unit(x, nz):
            unit_result = units[unit_combo.currentIndex()]
            if self.window.base_unit() == unit_result:
                return
            edits = self.window.amount_e, self.window.receive_amount_e
            amounts = [edit.get_amount() for edit in edits]
            self.config.set_base_unit(unit_result)
            nz.setMaximum(self.config.decimal_point)
            self.window.update_tabs()
            for edit, amount in zip(edits, amounts):
                edit.setAmount(amount)
            self.window.update_status()
        unit_combo.currentIndexChanged.connect(lambda x: on_unit(x, nz))
        gui_widgets.append((unit_label, unit_combo))

        thousandsep_cb = QCheckBox(_("Add thousand separators to bitcoin amounts"))
        thousandsep_cb.setChecked(bool(self.config.get('amt_add_thousands_sep', False)))
        def on_set_thousandsep(v):
            checked = v == Qt.Checked
            if self.config.amt_add_thousands_sep != checked:
                self.config.amt_add_thousands_sep = checked
                self.config.set_key('amt_add_thousands_sep', checked)
                self.window.need_update.set()
        thousandsep_cb.stateChanged.connect(on_set_thousandsep)
        gui_widgets.append((thousandsep_cb, None))

        qr_combo = QComboBox()
        qr_combo.addItem("Default", "default")
        msg = (_("For scanning QR codes.") + "\n"
               + _("Install the zbar package to enable this."))
        qr_label = HelpLabel(_('Video Device') + ':', msg)
        from .qrreader import find_system_cameras
        system_cameras = find_system_cameras()
        for cam_desc, cam_path in system_cameras.items():
            qr_combo.addItem(cam_desc, cam_path)
        index = qr_combo.findData(self.config.get("video_device"))
        qr_combo.setCurrentIndex(index)
        on_video_device = lambda x: self.config.set_key("video_device", qr_combo.itemData(x), True)
        qr_combo.currentIndexChanged.connect(on_video_device)
        gui_widgets.append((qr_label, qr_combo))

        colortheme_combo = QComboBox()
        colortheme_combo.addItem(_('Light'), 'default')
        colortheme_combo.addItem(_('Dark'), 'dark')
        index = colortheme_combo.findData(self.config.get('qt_gui_color_theme', 'default'))
        colortheme_combo.setCurrentIndex(index)
        colortheme_label = QLabel(_('Color theme') + ':')
        def on_colortheme(x):
            self.config.set_key('qt_gui_color_theme', colortheme_combo.itemData(x), True)
            self.need_restart = True
        colortheme_combo.currentIndexChanged.connect(on_colortheme)
        gui_widgets.append((colortheme_label, colortheme_combo))

        updatecheck_cb = QCheckBox(_("Automatically check for software updates"))
        updatecheck_cb.setChecked(bool(self.config.get('check_updates', False)))
        def on_set_updatecheck(v):
            self.config.set_key('check_updates', v == Qt.Checked, save=True)
        updatecheck_cb.stateChanged.connect(on_set_updatecheck)
        gui_widgets.append((updatecheck_cb, None))

        filelogging_cb = QCheckBox(_("Write logs to file"))
        filelogging_cb.setChecked(bool(self.config.get('log_to_file', False)))
        def on_set_filelogging(v):
            self.config.set_key('log_to_file', v == Qt.Checked, save=True)
            self.need_restart = True
        filelogging_cb.stateChanged.connect(on_set_filelogging)
        filelogging_cb.setToolTip(_('Debug logs can be persisted to disk. These are useful for troubleshooting.'))
        gui_widgets.append((filelogging_cb, None))

        preview_cb = QCheckBox(_('Advanced preview'))
        preview_cb.setChecked(bool(self.config.get('advanced_preview', False)))
        preview_cb.setToolTip(_("Open advanced transaction preview dialog when 'Pay' is clicked."))
        def on_preview(x):
            self.config.set_key('advanced_preview', x == Qt.Checked)
        preview_cb.stateChanged.connect(on_preview)
        tx_widgets.append((preview_cb, None))

        usechange_cb = QCheckBox(_('Use change addresses'))
        usechange_cb.setChecked(self.window.wallet.use_change)
        if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
        def on_usechange(x):
            usechange_result = x == Qt.Checked
            if self.window.wallet.use_change != usechange_result:
                self.window.wallet.use_change = usechange_result
                self.window.wallet.db.put('use_change', self.window.wallet.use_change)
                multiple_cb.setEnabled(self.window.wallet.use_change)
        usechange_cb.stateChanged.connect(on_usechange)
        usechange_cb.setToolTip(_('Using change addresses makes it more difficult for other people to track your transactions.'))
        tx_widgets.append((usechange_cb, None))

        def on_multiple(x):
            multiple = x == Qt.Checked
            if self.wallet.multiple_change != multiple:
                self.wallet.multiple_change = multiple
                self.wallet.db.put('multiple_change', multiple)
        multiple_change = self.wallet.multiple_change
        multiple_cb = QCheckBox(_('Use multiple change addresses'))
        multiple_cb.setEnabled(self.wallet.use_change)
        multiple_cb.setToolTip('\n'.join([
            _('In some cases, use up to 3 change addresses in order to break '
              'up large coin amounts and obfuscate the recipient address.'),
            _('This may result in higher transactions fees.')
        ]))
        multiple_cb.setChecked(multiple_change)
        multiple_cb.stateChanged.connect(on_multiple)
        tx_widgets.append((multiple_cb, None))

        def fmt_docs(key, klass):
            lines = [ln.lstrip(" ") for ln in klass.__doc__.split("\n")]
            return '\n'.join([key, "", " ".join(lines)])

        choosers = sorted(coinchooser.COIN_CHOOSERS.keys())
        if len(choosers) > 1:
            chooser_name = coinchooser.get_name(self.config)
            msg = _('Choose coin (UTXO) selection method.  The following are available:\n\n')
            msg += '\n\n'.join(fmt_docs(*item) for item in coinchooser.COIN_CHOOSERS.items())
            chooser_label = HelpLabel(_('Coin selection') + ':', msg)
            chooser_combo = QComboBox()
            chooser_combo.addItems(choosers)
            i = choosers.index(chooser_name) if chooser_name in choosers else 0
            chooser_combo.setCurrentIndex(i)
            def on_chooser(x):
                chooser_name = choosers[chooser_combo.currentIndex()]
                self.config.set_key('coin_chooser', chooser_name)
            chooser_combo.currentIndexChanged.connect(on_chooser)
            tx_widgets.append((chooser_label, chooser_combo))

        def on_unconf(x):
            self.config.set_key('confirmed_only', bool(x))
        conf_only = bool(self.config.get('confirmed_only', False))
        unconf_cb = QCheckBox(_('Spend only confirmed coins'))
        unconf_cb.setToolTip(_('Spend only confirmed inputs.'))
        unconf_cb.setChecked(conf_only)
        unconf_cb.stateChanged.connect(on_unconf)
        tx_widgets.append((unconf_cb, None))

        def on_outrounding(x):
            self.config.set_key('coin_chooser_output_rounding', bool(x))
        enable_outrounding = bool(self.config.get('coin_chooser_output_rounding', True))
        outrounding_cb = QCheckBox(_('Enable output value rounding'))
        outrounding_cb.setToolTip(
            _('Set the value of the change output so that it has similar precision to the other outputs.') + '\n' +
            _('This might improve your privacy somewhat.') + '\n' +
            _('If enabled, at most 100 satoshis might be lost due to this, per transaction.'))
        outrounding_cb.setChecked(enable_outrounding)
        outrounding_cb.stateChanged.connect(on_outrounding)
        tx_widgets.append((outrounding_cb, None))

        block_explorers = sorted(util.block_explorer_info().keys())
        BLOCK_EX_CUSTOM_ITEM = _("Custom URL")
        if BLOCK_EX_CUSTOM_ITEM in block_explorers:  # malicious translation?
            block_explorers.remove(BLOCK_EX_CUSTOM_ITEM)
        block_explorers.append(BLOCK_EX_CUSTOM_ITEM)
        msg = _('Choose which online block explorer to use for functions that open a web browser')
        block_ex_label = HelpLabel(_('Online Block Explorer') + ':', msg)
        block_ex_combo = QComboBox()
        block_ex_custom_e = QLineEdit(str(self.config.get('block_explorer_custom') or ''))
        block_ex_combo.addItems(block_explorers)
        block_ex_combo.setCurrentIndex(
            block_ex_combo.findText(util.block_explorer(self.config) or BLOCK_EX_CUSTOM_ITEM))
        def showhide_block_ex_custom_e():
            block_ex_custom_e.setVisible(block_ex_combo.currentText() == BLOCK_EX_CUSTOM_ITEM)
        showhide_block_ex_custom_e()
        def on_be_combo(x):
            if block_ex_combo.currentText() == BLOCK_EX_CUSTOM_ITEM:
                on_be_edit()
            else:
                be_result = block_explorers[block_ex_combo.currentIndex()]
                self.config.set_key('block_explorer_custom', None, False)
                self.config.set_key('block_explorer', be_result, True)
            showhide_block_ex_custom_e()
        block_ex_combo.currentIndexChanged.connect(on_be_combo)
        def on_be_edit():
            val = block_ex_custom_e.text()
            try:
                val = ast.literal_eval(val)  # to also accept tuples
            except:
                pass
            self.config.set_key('block_explorer_custom', val)
        block_ex_custom_e.editingFinished.connect(on_be_edit)
        block_ex_hbox = QHBoxLayout()
        block_ex_hbox.setContentsMargins(0, 0, 0, 0)
        block_ex_hbox.setSpacing(0)
        block_ex_hbox.addWidget(block_ex_combo)
        block_ex_hbox.addWidget(block_ex_custom_e)
        block_ex_hbox_w = QWidget()
        block_ex_hbox_w.setLayout(block_ex_hbox)
        tx_widgets.append((block_ex_label, block_ex_hbox_w))

        # Fiat Currency
        hist_checkbox = QCheckBox()
        hist_capgains_checkbox = QCheckBox()
        fiat_address_checkbox = QCheckBox()
        ccy_combo = QComboBox()
        ex_combo = QComboBox()

        def update_currencies():
            if not self.window.fx: return
            currencies = sorted(self.fx.get_currencies(self.fx.get_history_config()))
            ccy_combo.clear()
            ccy_combo.addItems([_('None')] + currencies)
            if self.fx.is_enabled():
                ccy_combo.setCurrentIndex(ccy_combo.findText(self.fx.get_currency()))

        def update_history_cb():
            if not self.fx: return
            hist_checkbox.setChecked(self.fx.get_history_config())
            hist_checkbox.setEnabled(self.fx.is_enabled())

        def update_fiat_address_cb():
            if not self.fx: return
            fiat_address_checkbox.setChecked(self.fx.get_fiat_address_config())

        def update_history_capgains_cb():
            if not self.fx: return
            hist_capgains_checkbox.setChecked(self.fx.get_history_capital_gains_config())
            hist_capgains_checkbox.setEnabled(hist_checkbox.isChecked())

        def update_exchanges():
            if not self.fx: return
            b = self.fx.is_enabled()
            ex_combo.setEnabled(b)
            if b:
                h = self.fx.get_history_config()
                c = self.fx.get_currency()
                exchanges = self.fx.get_exchanges_by_ccy(c, h)
            else:
                exchanges = self.fx.get_exchanges_by_ccy('USD', False)
            ex_combo.blockSignals(True)
            ex_combo.clear()
            ex_combo.addItems(sorted(exchanges))
            ex_combo.setCurrentIndex(ex_combo.findText(self.fx.config_exchange()))
            ex_combo.blockSignals(False)

        def on_currency(hh):
            if not self.fx: return
            b = bool(ccy_combo.currentIndex())
            ccy = str(ccy_combo.currentText()) if b else None
            self.fx.set_enabled(b)
            if b and ccy != self.fx.ccy:
                self.fx.set_currency(ccy)
            update_history_cb()
            update_exchanges()
            self.window.update_fiat()

        def on_exchange(idx):
            exchange = str(ex_combo.currentText())
            if self.fx and self.fx.is_enabled() and exchange and exchange != self.fx.exchange.name():
                self.fx.set_exchange(exchange)

        def on_history(checked):
            if not self.fx: return
            self.fx.set_history_config(checked)
            update_exchanges()
            self.window.history_model.refresh('on_history')
            if self.fx.is_enabled() and checked:
                self.fx.trigger_update()
            update_history_capgains_cb()

        def on_history_capgains(checked):
            if not self.fx: return
            self.fx.set_history_capital_gains_config(checked)
            self.window.history_model.refresh('on_history_capgains')

        def on_fiat_address(checked):
            if not self.fx: return
            self.fx.set_fiat_address_config(checked)
            self.window.address_list.refresh_headers()
            self.window.address_list.update()

        update_currencies()
        update_history_cb()
        update_history_capgains_cb()
        update_fiat_address_cb()
        update_exchanges()
        ccy_combo.currentIndexChanged.connect(on_currency)
        hist_checkbox.stateChanged.connect(on_history)
        hist_capgains_checkbox.stateChanged.connect(on_history_capgains)
        fiat_address_checkbox.stateChanged.connect(on_fiat_address)
        ex_combo.currentIndexChanged.connect(on_exchange)

        fiat_widgets = []
        fiat_widgets.append((QLabel(_('Fiat currency')), ccy_combo))
        fiat_widgets.append((QLabel(_('Source')), ex_combo))
        fiat_widgets.append((QLabel(_('Show history rates')), hist_checkbox))
        fiat_widgets.append((QLabel(_('Show capital gains in history')), hist_capgains_checkbox))
        fiat_widgets.append((QLabel(_('Show Fiat balance for addresses')), fiat_address_checkbox))

        tabs_info = [
            (gui_widgets, _('General')),
            (tx_widgets, _('Transactions')),
            (lightning_widgets, _('Lightning')),
            (fiat_widgets, _('Fiat')),
            (oa_widgets, _('OpenAlias')),
        ]
        for widgets, name in tabs_info:
            tab = QWidget()
            tab_vbox = QVBoxLayout(tab)
            grid = QGridLayout()
            for a,b in widgets:
                i = grid.rowCount()
                if b:
                    if a:
                        grid.addWidget(a, i, 0)
                    grid.addWidget(b, i, 1)
                else:
                    grid.addWidget(a, i, 0, 1, 2)
            tab_vbox.addLayout(grid)
            tab_vbox.addStretch(1)
            tabs.addTab(tab, name)

        vbox.addWidget(tabs)
        vbox.addStretch(1)
        vbox.addLayout(Buttons(CloseButton(self)))
        self.setLayout(vbox)
Beispiel #40
0
class DialogoAdicional(QDialog):
    def __init__(self, parent=None, adicional=None, ofertas=[], lotes=[], empresas=[], adicionales=[]):
        super().__init__(parent)
        self.adicional = adicional
        self.array_ofertas = ofertas
        self.array_lotes = sorted(lotes, key=attrgetter("id"))
        self.array_empresas = sorted(empresas, key=attrgetter("id"))
        self.array_adicionales = adicionales
        self.dibujar_IU()
        if self.adicional != None:
            self.cargar_adicional()
    
    def dibujar_IU(self):
        self.setWindowTitle("Ingreso Descuento")
        self.setWindowModality(Qt.ApplicationModal)
        self.setSizeGripEnabled(False)
        self.setContentsMargins(10, 10, 10, 10)

        self.empresa = QComboBox()
        self.ofertas = QGridLayout()
        self.porcentaje = QLineEdit()
        self.empresa_error = QLabel("*")
        self.ofertas_error = QLabel("*")
        self.porcentaje_error = QLabel("*")
        self.espaciador = QLabel(" ")

        self.cargar_lista_empresas()
        self.completar_grilla_con_lotes()

        self.empresa.setFixedWidth(250)
        self.empresa.currentTextChanged.connect(self.cargar_lista_ofertas)
        self.empresa.currentTextChanged.connect(self.marcar_adicional_erroneo)
        self.porcentaje.setValidator(QDoubleValidator(1, 100, 3))
        self.porcentaje.textChanged.connect(self.marcar_porcentaje_erroneo)
        self.porcentaje.setAlignment(Qt.AlignRight)
        self.porcentaje.setMaximumWidth(150)
        self.porcentaje.setMinimumWidth(150)
        self.porcentaje.setToolTip("Porcentaje de descuento que la Empresa ofrece por el conjunto de Ofertas")
        self.empresa_error.setStyleSheet("QLabel {color : red}")
        self.empresa_error.setVisible(False)
        self.empresa_error.setFixedWidth(10)
        self.ofertas_error.setStyleSheet("QLabel {color : red}")
        self.ofertas_error.setVisible(False)
        self.ofertas_error.setFixedWidth(10)
        self.porcentaje_error.setStyleSheet("QLabel {color : red}")
        self.porcentaje_error.setVisible(False)
        self.porcentaje_error.setFixedWidth(10)
        self.espaciador.setFixedWidth(10)
        self.espaciador.setFixedHeight(0)

        boton_confirmar = QPushButton(QIcon("iconos/check.png"), "")
        boton_confirmar.clicked.connect(self.accept)
        boton_confirmar.setDefault(True)
        boton_confirmar.setMinimumSize(50, 10)
        boton_cancelar = QPushButton(QIcon("iconos/cancel.png"), "")
        boton_cancelar.clicked.connect(self.reject)
        boton_cancelar.setMinimumSize(50, 10)
        caja_horizontal = QHBoxLayout()
        caja_horizontal.addStretch(1)
        caja_horizontal.addWidget(boton_cancelar)
        caja_horizontal.addStretch(5)
        caja_horizontal.addWidget(boton_confirmar)
        caja_horizontal.addStretch(1)
        caja_horizontal.setContentsMargins(10, 10, 10, 0)

        self.grilla = QGridLayout()
        self.grilla.setColumnMinimumWidth(1, 20)
        self.grilla.addWidget(QLabel("Empresa"), 0, 0)
        self.grilla.addWidget(self.empresa_error, 0, 1, Qt.AlignTop | Qt.AlignRight)
        self.grilla.addWidget(self.empresa, 0, 2)
        self.grilla.addWidget(QLabel("Porcentaje"), 1, 0)
        self.grilla.addWidget(self.porcentaje_error, 1, 1, Qt.AlignTop | Qt.AlignRight)
        self.grilla.addWidget(self.porcentaje, 1, 2)
        self.grilla.addWidget(QLabel("Lotes"), 2, 0, Qt.AlignTop)
        self.grilla.addWidget(self.ofertas_error, 2, 1, Qt.AlignTop | Qt.AlignRight)
        self.grilla.addLayout(self.ofertas, 2, 2)
        self.grilla.addWidget(self.espaciador, 3, 1)
        marco = QGroupBox("Descuento")
        marco.setLayout(self.grilla)

        formulario = QFormLayout(self)
        formulario.addRow(marco)
        formulario.addRow(caja_horizontal)
        #self.setMinimumSize(self.sizeHint())
        #self.setMaximumSize(self.sizeHint())
    
    def accept(self):
        self.marcar_campos_erroneos()
        if not self.oferta_seleccionada():
            pass
        elif self.adicional_existente():
            pass
        elif len(self.porcentaje.text()) == 0:
            self.porcentaje.setFocus()
        else:
            super().accept()
    
    def marcar_campos_erroneos(self):
        self.marcar_adicional_erroneo()
        self.marcar_porcentaje_erroneo()
    
    def marcar_adicional_erroneo(self):
        if not self.oferta_seleccionada():
            self.ofertas_error.setVisible(True)
        else:
            self.ofertas_error.setVisible(False)
            if self.adicional_existente():
                self.empresa_error.setVisible(True)
                self.ofertas_error.setVisible(True)
            else:
                self.empresa_error.setVisible(False)
                self.ofertas_error.setVisible(False)
    
    def marcar_porcentaje_erroneo(self):
        if len(self.porcentaje.text()) == 0:
            self.porcentaje_error.setVisible(True)
        else:
            self.porcentaje_error.setVisible(False)
        
    def oferta_seleccionada(self):
        return any(self.ofertas.itemAt(i).widget().isChecked() for i in range(self.ofertas.count()))
    
    def cargar_adicional(self):
        self.empresa.setCurrentIndex(self.empresa.findData(self.adicional.empresa))
        self.porcentaje.setText(str(self.adicional.porcentaje * -1))
        #self.completar_grilla_con_ofertas()
        for i in range(self.ofertas.count()):
            item = self.ofertas.itemAt(i).widget()
            if self.adicional.conjunto_ofertas.oferta_contenida(item.oferta):
                item.setCheckState(Qt.Checked)

    def cargar_lista_empresas(self):
        for empresa in self.array_empresas:
            self.empresa.addItem(empresa.nombre.strip(), empresa)
    
    def cargar_lista_ofertas(self):
        for i in range(self.ofertas.count()):
            item_lote = self.ofertas.itemAt(i).widget()
            item_lote.setear_oferta(None)
            item_lote.deshabilitar()
            for oferta in self.array_ofertas:
                if oferta.lote == item_lote.lote and oferta.empresa == self.empresa.currentData():
                    item_lote.setear_oferta(oferta)
                    item_lote.habilitar()
                    break

    def obtener_adicional(self):
        return Adicional(self.empresa.currentData(), self.conjunto_ofertas(), float(self.porcentaje.text()) * -1)
    
    def adicional_existente(self):
        return any(self.adicional_temporal().es_equivalente(adicional) for adicional in self.array_adicionales if (self.adicional == None) or (self.adicional != None and not adicional.es_equivalente(self.adicional)))
    
    def adicional_temporal(self):
        return Adicional(self.empresa.currentData(), self.conjunto_ofertas(), 0.00, efectiva=False)
    
    def conjunto_ofertas(self):
        conjunto_ofertas = ConjuntoOfertas()
        for i in range(0, self.ofertas.count()):
            item_lote = self.ofertas.itemAt(i).widget()
            if item_lote.isChecked():
                conjunto_ofertas.agregar_oferta(item_lote.oferta)
        return conjunto_ofertas
    
    def cambio_seleccion(self, asdf):
        self.marcar_adicional_erroneo()
    
    def completar_grilla_con_lotes(self):
        for i, lote in enumerate(self.array_lotes):
            item_lote = QCheckBoxLote(lote)
            item_lote.stateChanged.connect(self.cambio_seleccion)
            if len(self.array_lotes) <= 10:
                if (i >= len(self.array_lotes) / 2):
                    self.ofertas.addWidget(item_lote, i - len(self.array_lotes) / 2, 1)
                else:
                    self.ofertas.addWidget(item_lote, i, 0)
            else:
                if (i >= len(self.array_lotes) / 3 * 2):
                    self.ofertas.addWidget(item_lote, i - len(self.array_lotes) / 3 * 2, 2)
                elif (i >= len(self.array_lotes) / 3):
                    self.ofertas.addWidget(item_lote, i - len(self.array_lotes) / 3, 1)
                else:
                    self.ofertas.addWidget(item_lote, i, 0)
        self.cargar_lista_ofertas()
Beispiel #41
0
class fullScreenEditor(QWidget):
    def __init__(self, index, parent=None):
        QWidget.__init__(self, parent)
        self._background = None
        self._index = index
        self._theme = findThemePath(settings.fullScreenTheme)
        self._themeDatas = loadThemeDatas(self._theme)
        self.setMouseTracking(True)
        self._geometries = {}

        # Text editor
        self.editor = MDEditView(self,
                                 index=index,
                                 spellcheck=settings.spellcheck,
                                 highlighting=True,
                                 dict=settings.dict)
        self.editor.setFrameStyle(QFrame.NoFrame)
        self.editor.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.editor.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.editor.installEventFilter(self)
        self.editor.setMouseTracking(True)
        self.editor.setVerticalScrollBar(myScrollBar())
        self.scrollBar = self.editor.verticalScrollBar()
        self.scrollBar.setParent(self)

        # Top Panel
        self.topPanel = myPanel(parent=self)
        # self.topPanel.layout().addStretch(1)

        # Spell checking
        if enchant:
            self.btnSpellCheck = QPushButton(self)
            self.btnSpellCheck.setFlat(True)
            self.btnSpellCheck.setIcon(QIcon.fromTheme("tools-check-spelling"))
            self.btnSpellCheck.setCheckable(True)
            self.btnSpellCheck.setChecked(self.editor.spellcheck)
            self.btnSpellCheck.toggled.connect(self.editor.toggleSpellcheck)
        else:
            self.btnSpellCheck = None

        # Navigation Buttons
        self.btnPrevious = QPushButton(self)
        self.btnPrevious.setFlat(True)
        self.btnPrevious.setIcon(QIcon.fromTheme("arrow-left"))
        self.btnPrevious.clicked.connect(self.switchPreviousItem)
        self.btnNext = QPushButton(self)
        self.btnNext.setFlat(True)
        self.btnNext.setIcon(QIcon.fromTheme("arrow-right"))
        self.btnNext.clicked.connect(self.switchNextItem)
        self.btnNew = QPushButton(self)
        self.btnNew.setFlat(True)
        self.btnNew.setIcon(QIcon.fromTheme("document-new"))
        self.btnNew.clicked.connect(self.createNewText)

        # Path and New Text Buttons
        self.wPath = myPath(self)

        # Close
        self.btnClose = QPushButton(self)
        self.btnClose.setIcon(qApp.style().standardIcon(
            QStyle.SP_DialogCloseButton))
        self.btnClose.clicked.connect(self.close)
        self.btnClose.setFlat(True)

        # Top panel Layout
        if self.btnSpellCheck:
            self.topPanel.layout().addWidget(self.btnSpellCheck)
        self.topPanel.layout().addSpacing(15)
        self.topPanel.layout().addWidget(self.btnPrevious)
        self.topPanel.layout().addWidget(self.btnNext)
        self.topPanel.layout().addWidget(self.btnNew)

        self.topPanel.layout().addStretch(1)
        self.topPanel.layout().addWidget(self.wPath)
        self.topPanel.layout().addStretch(1)

        self.topPanel.layout().addWidget(self.btnClose)
        self.updateTopBar()

        # Left Panel
        self._locked = False
        self.leftPanel = myPanel(vertical=True, parent=self)
        self.locker = locker(self)
        self.locker.lockChanged.connect(self.setLocked)
        self.leftPanel.layout().addWidget(self.locker)

        # Bottom Panel
        self.bottomPanel = myPanel(parent=self)

        self.bottomPanel.layout().addSpacing(24)
        self.lstThemes = QComboBox(self)
        self.lstThemes.setAttribute(Qt.WA_TranslucentBackground)
        paths = allPaths("resources/themes")
        for p in paths:
            lst = [
                i for i in os.listdir(p) if os.path.splitext(i)[1] == ".theme"
            ]
            for t in lst:
                themeIni = os.path.join(p, t)
                name = loadThemeDatas(themeIni)["Name"]
                # self.lstThemes.addItem(os.path.splitext(t)[0])
                self.lstThemes.addItem(name)
                self.lstThemes.setItemData(self.lstThemes.count() - 1,
                                           os.path.splitext(t)[0])

        self.lstThemes.setCurrentIndex(
            self.lstThemes.findData(settings.fullScreenTheme))
        # self.lstThemes.setCurrentText(settings.fullScreenTheme)
        self.lstThemes.currentTextChanged.connect(self.setTheme)
        self.lstThemes.setMaximumSize(
            QSize(300,
                  QFontMetrics(qApp.font()).height()))
        themeLabel = QLabel(self.tr("Theme:"), self)
        self.bottomPanel.layout().addWidget(themeLabel)
        self.bottomPanel.layout().addWidget(self.lstThemes)
        self.bottomPanel.layout().addStretch(1)

        self.lblProgress = QLabel(self)
        self.lblProgress.setMaximumSize(QSize(200, 14))
        self.lblProgress.setMinimumSize(QSize(100, 14))
        self.lblWC = QLabel(self)
        self.lblClock = myClockLabel(self)
        self.bottomPanel.layout().addWidget(self.lblWC)
        self.bottomPanel.layout().addWidget(self.lblProgress)
        self.bottomPanel.layout().addSpacing(15)
        self.bottomPanel.layout().addWidget(self.lblClock)
        self.updateStatusBar()

        self.bottomPanel.layout().addSpacing(24)

        # Add Widget Settings
        if self.btnSpellCheck:
            self.topPanel.addWidgetSetting(self.tr("Spellcheck"),
                                           'top-spellcheck',
                                           (self.btnSpellCheck, ))
        self.topPanel.addWidgetSetting(self.tr("Navigation"), 'top-navigation',
                                       (self.btnPrevious, self.btnNext))
        self.topPanel.addWidgetSetting(self.tr("New Text"), 'top-new-doc',
                                       (self.btnNew, ))
        self.topPanel.addWidgetSetting(self.tr("Title"), 'top-title',
                                       (self.wPath, ))
        self.topPanel.addSetting(self.tr("Title: Show Full Path"),
                                 'title-show-full-path', True)
        self.topPanel.setSettingCallback('title-show-full-path',
                                         lambda var, val: self.updateTopBar())
        self.bottomPanel.addWidgetSetting(self.tr("Theme selector"),
                                          'bottom-theme',
                                          (self.lstThemes, themeLabel))
        self.bottomPanel.addWidgetSetting(self.tr("Word count"), 'bottom-wc',
                                          (self.lblWC, ))
        self.bottomPanel.addWidgetSetting(self.tr("Progress"),
                                          'bottom-progress',
                                          (self.lblProgress, ))
        self.bottomPanel.addSetting(self.tr("Progress: Auto Show/Hide"),
                                    'progress-auto-show', True)
        self.bottomPanel.addWidgetSetting(self.tr("Clock"), 'bottom-clock',
                                          (self.lblClock, ))
        self.bottomPanel.addSetting(self.tr("Clock: Show Seconds"),
                                    'clock-show-seconds', True)
        self.bottomPanel.setAutoHideVariable('autohide-bottom')
        self.topPanel.setAutoHideVariable('autohide-top')
        self.leftPanel.setAutoHideVariable('autohide-left')

        # Connection
        self._index.model().dataChanged.connect(self.dataChanged)

        # self.updateTheme()
        self.showFullScreen()
        # self.showMaximized()
        # self.show()

    def __del__(self):
        # print("Leaving fullScreenEditor via Destructor event", flush=True)
        self.showNormal()
        self.close()

    def setLocked(self, val):
        self._locked = val
        self.btnClose.setVisible(not val)

    def setTheme(self, themeName):
        themeName = self.lstThemes.currentData()
        settings.fullScreenTheme = themeName
        self._theme = findThemePath(themeName)
        self._themeDatas = loadThemeDatas(self._theme)
        self.updateTheme()

    def updateTheme(self):
        # Reinit stored geometries for hiding widgets
        self._geometries = {}
        rect = self.geometry()
        self._background = generateTheme(self._themeDatas, rect)

        setThemeEditorDatas(self.editor, self._themeDatas, self._background,
                            rect)

        # Colors
        if self._themeDatas["Foreground/Color"] == self._themeDatas["Background/Color"] or \
                        self._themeDatas["Foreground/Opacity"] < 5:
            self._fgcolor = QColor(self._themeDatas["Text/Color"])
            self._bgcolor = QColor(self._themeDatas["Background/Color"])
        else:
            self._bgcolor = QColor(self._themeDatas["Foreground/Color"])
            self._bgcolor.setAlpha(self._themeDatas["Foreground/Opacity"] *
                                   255 / 100)
            self._fgcolor = QColor(self._themeDatas["Text/Color"])
            if self._themeDatas["Text/Color"] == self._themeDatas[
                    "Foreground/Color"]:
                self._fgcolor = QColor(self._themeDatas["Background/Color"])

        # ScrollBar
        r = self.editor.geometry()
        w = qApp.style().pixelMetric(QStyle.PM_ScrollBarExtent)
        r.setWidth(w)
        r.moveRight(rect.right() - rect.left())
        self.scrollBar.setGeometry(r)
        # self.scrollBar.setVisible(False)
        self.hideWidget(self.scrollBar)
        p = self.scrollBar.palette()
        b = QBrush(self._background.copy(self.scrollBar.geometry()))
        p.setBrush(QPalette.Base, b)
        self.scrollBar.setPalette(p)

        self.scrollBar.setColor(self._bgcolor)

        # Left Panel
        r = self.locker.geometry()
        r.moveTopLeft(QPoint(0, self.geometry().height() / 2 - r.height() / 2))
        self.leftPanel.setGeometry(r)
        self.hideWidget(self.leftPanel)
        self.leftPanel.setColor(self._bgcolor)

        # Top / Bottom Panels
        r = QRect(0, 0, 0, 24)
        r.setWidth(rect.width())
        # r.moveLeft(rect.center().x() - r.width() / 2)
        self.topPanel.setGeometry(r)
        # self.topPanel.setVisible(False)
        self.hideWidget(self.topPanel)
        r.moveBottom(rect.bottom() - rect.top())
        self.bottomPanel.setGeometry(r)
        # self.bottomPanel.setVisible(False)
        self.hideWidget(self.bottomPanel)
        self.topPanel.setColor(self._bgcolor)
        self.bottomPanel.setColor(self._bgcolor)

        # Lst theme
        # p = self.lstThemes.palette()
        p = self.palette()
        p.setBrush(QPalette.Button, self._bgcolor)
        p.setBrush(QPalette.ButtonText, self._fgcolor)
        p.setBrush(QPalette.WindowText, self._fgcolor)

        for panel in (self.bottomPanel, self.topPanel, self.leftPanel):
            for i in range(panel.layout().count()):
                item = panel.layout().itemAt(i)
                if item.widget():
                    item.widget().setPalette(p)
        # self.lstThemes.setPalette(p)
        # self.lblWC.setPalette(p)

        self.update()
        self.editor.centerCursor()

    def paintEvent(self, event):
        if self._background:
            painter = QPainter(self)
            painter.setClipRegion(event.region())
            painter.drawPixmap(event.rect(), self._background, event.rect())
            painter.end()

    def resizeEvent(self, event):
        self.updateTheme()

    def keyPressEvent(self, event):
        if event.key() in [Qt.Key_Escape, Qt.Key_F11] and \
                not self._locked:
            # print("Leaving fullScreenEditor via keyPressEvent", flush=True)
            self.showNormal()
            self.close()
        elif (event.modifiers() & Qt.AltModifier) and \
                event.key() in [Qt.Key_PageUp, Qt.Key_PageDown, Qt.Key_Left, Qt.Key_Right]:
            if event.key() in [Qt.Key_PageUp, Qt.Key_Left]:
                success = self.switchPreviousItem()
            if event.key() in [Qt.Key_PageDown, Qt.Key_Right]:
                success = self.switchNextItem()
            if not success:
                QWidget.keyPressEvent(self, event)
        else:
            QWidget.keyPressEvent(self, event)

    def mouseMoveEvent(self, event):
        r = self.geometry()

        for w in [
                self.scrollBar, self.topPanel, self.bottomPanel, self.leftPanel
        ]:
            # w.setVisible(w.geometry().contains(event.pos()))
            if self._geometries[w].contains(event.pos()):
                self.showWidget(w)
            else:
                self.hideWidget(w)

    def hideWidget(self, widget):
        if widget not in self._geometries:
            self._geometries[widget] = widget.geometry()

        if hasattr(widget, "_autoHide") and not widget._autoHide:
            return

        # Hides widget in the bottom right corner
        widget.move(self.geometry().bottomRight() + QPoint(1, 1))

    def showWidget(self, widget):
        if widget in self._geometries:
            widget.move(self._geometries[widget].topLeft())

    def eventFilter(self, obj, event):
        if obj == self.editor and event.type() == QEvent.Enter:
            for w in [
                    self.scrollBar, self.topPanel, self.bottomPanel,
                    self.leftPanel
            ]:
                # w.setVisible(False)
                self.hideWidget(w)
        return QWidget.eventFilter(self, obj, event)

    def dataChanged(self, topLeft, bottomRight):
        # This is called sometimes after self has been destroyed. Don't know why.
        if not self or not self._index:
            return
        if topLeft.row() <= self._index.row() <= bottomRight.row():
            self.updateStatusBar()

    def updateTopBar(self):
        item = self._index.internalPointer()
        previousItem = self.previousTextItem(item)
        nextItem = self.nextTextItem(item)
        self.btnPrevious.setEnabled(previousItem is not None)
        self.btnNext.setEnabled(nextItem is not None)
        self.wPath.setItem(item)

    def updateStatusBar(self):
        if self._index:
            item = self._index.internalPointer()

        wc = item.data(Outline.wordCount)
        goal = item.data(Outline.goal)
        pg = item.data(Outline.goalPercentage)

        if goal:
            if settings.fullscreenSettings.get("progress-auto-show", True):
                self.lblProgress.show()
            self.lblWC.setText(self.tr("{} words / {}").format(wc, goal))
        else:
            if settings.fullscreenSettings.get("progress-auto-show", True):
                self.lblProgress.hide()
            self.lblWC.setText(self.tr("{} words").format(wc))
            pg = 0
        rect = self.lblProgress.geometry()
        rect = QRect(QPoint(0, 0), rect.size())
        self.px = QPixmap(rect.size())
        self.px.fill(Qt.transparent)
        p = QPainter(self.px)
        drawProgress(p, rect, pg, 2)
        p.end()
        self.lblProgress.setPixmap(self.px)

        self.locker.setWordCount(wc)
        # If there's a goal, then we update the locker target's number of word accordingly
        # (also if there is a word count, we deduce it.
        if goal and not self.locker.isLocked():
            if wc and goal - wc > 0:
                self.locker.spnWordTarget.setValue(goal - wc)
            elif not wc:
                self.locker.spnWordTarget.setValue(goal)

    def setCurrentModelIndex(self, index):
        self._index = index
        self.editor.setCurrentModelIndex(index)
        self.updateTopBar()
        self.updateStatusBar()

    def switchPreviousItem(self):
        item = self._index.internalPointer()
        previousItem = self.previousTextItem(item)
        if previousItem:
            self.setCurrentModelIndex(previousItem.index())
            return True
        return False

    def switchNextItem(self):
        item = self._index.internalPointer()
        nextItem = self.nextTextItem(item)
        if nextItem:
            self.setCurrentModelIndex(nextItem.index())
            return True
        return False

    def switchToItem(self, item):
        item = self.firstTextItem(item)
        if item:
            self.setCurrentModelIndex(item.index())

    def createNewText(self):
        item = self._index.internalPointer()
        newItem = outlineItem(title=qApp.translate("outlineBasics", "New"),
                              _type=settings.defaultTextType)
        self._index.model().insertItem(newItem,
                                       item.row() + 1,
                                       item.parent().index())
        self.setCurrentModelIndex(newItem.index())

    def previousModelItem(self, item):
        parent = item.parent()
        if not parent:
            # Root has no sibling
            return None

        row = parent.childItems.index(item)
        if row > 0:
            return parent.child(row - 1)
        return self.previousModelItem(parent)

    def nextModelItem(self, item):
        parent = item.parent()
        if not parent:
            # Root has no sibling
            return None

        row = parent.childItems.index(item)
        if row + 1 < parent.childCount():
            return parent.child(row + 1)
        return self.nextModelItem(parent)

    def previousTextItem(self, item):
        previous = self.previousModelItem(item)

        while previous:
            last = self.lastTextItem(previous)
            if last:
                return last
            previous = self.previousModelItem(previous)
        return None

    def nextTextItem(self, item):
        if item.isFolder() and item.childCount() > 0:
            next = item.child(0)
        else:
            next = self.nextModelItem(item)

        while next:
            first = self.firstTextItem(next)
            if first:
                return first
            next = self.nextModelItem(next)
        return None

    def firstTextItem(self, item):
        if item.isText():
            return item
        for child in item.children():
            first = self.firstTextItem(child)
            if first:
                return first
        return None

    def lastTextItem(self, item):
        if item.isText():
            return item
        for child in reversed(item.children()):
            last = self.lastTextItem(child)
            if last:
                return last
        return None
Beispiel #42
0
class SettingsDialog(QDialog):
    worker = None
    config = None
    configfile = None

    saved = QtCore.pyqtSignal()

    def __init__(self, parent, worker, config, configfile):
        QDialog.__init__(self, parent)

        self.worker = worker
        self.config = config
        self.configfile = configfile
        self.setStyleSheet("QGroupBox { font-weight: bold; } ")
        self.setWindowTitle('Settings')
        layout = QGridLayout()

        # Categories
        self.category_list = QListWidget()
        category_media = QListWidgetItem(getIcon('media-playback-start'),
                                         'Media', self.category_list)
        category_sync = QListWidgetItem(getIcon('view-refresh'), 'Sync',
                                        self.category_list)
        category_ui = QListWidgetItem(getIcon('window-new'), 'User Interface',
                                      self.category_list)
        category_theme = QListWidgetItem(getIcon('applications-graphics'),
                                         'Theme', self.category_list)
        self.category_list.setSelectionMode(QAbstractItemView.SingleSelection)
        self.category_list.setCurrentRow(0)
        self.category_list.setMaximumWidth(
            self.category_list.sizeHintForColumn(0) + 15)
        self.category_list.setFocus()
        self.category_list.currentItemChanged.connect(self.s_switch_page)

        # Media tab
        page_media = QWidget()
        page_media_layout = QVBoxLayout()
        page_media_layout.setAlignment(QtCore.Qt.AlignTop)

        # Group: Media settings
        g_media = QGroupBox('Media settings')
        g_media.setFlat(True)
        g_media_layout = QFormLayout()
        self.tracker_enabled = QCheckBox()
        self.tracker_enabled.toggled.connect(self.tracker_type_change)
        self.tracker_type_local = QRadioButton('Local')
        self.tracker_type_local.toggled.connect(self.tracker_type_change)
        self.tracker_type_plex = QRadioButton('Plex media server')
        self.tracker_type_plex.toggled.connect(self.tracker_type_change)
        self.tracker_interval = QSpinBox()
        self.tracker_interval.setRange(5, 1000)
        self.tracker_interval.setMaximumWidth(60)
        self.tracker_process = QLineEdit()
        self.tracker_update_wait = QSpinBox()
        self.tracker_update_wait.setRange(0, 1000)
        self.tracker_update_wait.setMaximumWidth(60)
        self.tracker_update_close = QCheckBox()
        self.tracker_update_prompt = QCheckBox()
        self.tracker_not_found_prompt = QCheckBox()

        g_media_layout.addRow('Enable tracker', self.tracker_enabled)
        g_media_layout.addRow(self.tracker_type_local)
        g_media_layout.addRow(self.tracker_type_plex)
        g_media_layout.addRow('Tracker interval (seconds)',
                              self.tracker_interval)
        g_media_layout.addRow('Process name (regex)', self.tracker_process)
        g_media_layout.addRow('Wait before updating (seconds)',
                              self.tracker_update_wait)
        g_media_layout.addRow('Wait until the player is closed',
                              self.tracker_update_close)
        g_media_layout.addRow('Ask before updating',
                              self.tracker_update_prompt)
        g_media_layout.addRow('Ask to add new shows',
                              self.tracker_not_found_prompt)

        g_media.setLayout(g_media_layout)

        # Group: Plex settings
        g_plex = QGroupBox('Plex Media Server')
        g_plex.setFlat(True)
        self.plex_host = QLineEdit()
        self.plex_port = QLineEdit()
        self.plex_user = QLineEdit()
        self.plex_passw = QLineEdit()
        self.plex_passw.setEchoMode(QLineEdit.Password)
        self.plex_obey_wait = QCheckBox()

        g_plex_layout = QGridLayout()
        g_plex_layout.addWidget(QLabel('Host and Port'), 0, 0, 1, 1)
        g_plex_layout.addWidget(self.plex_host, 0, 1, 1, 1)
        g_plex_layout.addWidget(self.plex_port, 0, 2, 1, 2)
        g_plex_layout.addWidget(QLabel('Use "wait before updating" time'), 1,
                                0, 1, 1)
        g_plex_layout.addWidget(self.plex_obey_wait, 1, 2, 1, 1)
        g_plex_layout.addWidget(QLabel('myPlex login (claimed server)'), 2, 0,
                                1, 1)
        g_plex_layout.addWidget(self.plex_user, 2, 1, 1, 1)
        g_plex_layout.addWidget(self.plex_passw, 2, 2, 1, 2)

        g_plex.setLayout(g_plex_layout)

        # Group: Library
        g_playnext = QGroupBox('Library')
        g_playnext.setFlat(True)
        self.player = QLineEdit()
        self.player_browse = QPushButton('Browse...')
        self.player_browse.clicked.connect(self.s_player_browse)
        lbl_searchdirs = QLabel('Media directories')
        lbl_searchdirs.setAlignment(QtCore.Qt.AlignTop)
        self.searchdirs = QListWidget()
        self.searchdirs_add = QPushButton('Add...')
        self.searchdirs_add.clicked.connect(self.s_searchdirs_add)
        self.searchdirs_remove = QPushButton('Remove')
        self.searchdirs_remove.clicked.connect(self.s_searchdirs_remove)
        self.searchdirs_buttons = QVBoxLayout()
        self.searchdirs_buttons.setAlignment(QtCore.Qt.AlignTop)
        self.searchdirs_buttons.addWidget(self.searchdirs_add)
        self.searchdirs_buttons.addWidget(self.searchdirs_remove)
        self.searchdirs_buttons.addWidget(QSplitter())
        self.library_autoscan = QCheckBox()
        self.scan_whole_list = QCheckBox()
        self.library_full_path = QCheckBox()

        g_playnext_layout = QGridLayout()
        g_playnext_layout.addWidget(QLabel('Player'), 0, 0, 1, 1)
        g_playnext_layout.addWidget(self.player, 0, 1, 1, 1)
        g_playnext_layout.addWidget(self.player_browse, 0, 2, 1, 1)
        g_playnext_layout.addWidget(lbl_searchdirs, 1, 0, 1, 1)
        g_playnext_layout.addWidget(self.searchdirs, 1, 1, 1, 1)
        g_playnext_layout.addLayout(self.searchdirs_buttons, 1, 2, 1, 1)
        g_playnext_layout.addWidget(QLabel('Rescan Library at startup'), 2, 0,
                                    1, 2)
        g_playnext_layout.addWidget(self.library_autoscan, 2, 2, 1, 1)
        g_playnext_layout.addWidget(QLabel('Scan through whole list'), 3, 0, 1,
                                    2)
        g_playnext_layout.addWidget(self.scan_whole_list, 3, 2, 1, 1)
        g_playnext_layout.addWidget(
            QLabel('Take subdirectory name into account'), 4, 0, 1, 2)
        g_playnext_layout.addWidget(self.library_full_path, 4, 2, 1, 1)

        g_playnext.setLayout(g_playnext_layout)

        # Media form
        page_media_layout.addWidget(g_media)
        page_media_layout.addWidget(g_plex)
        page_media_layout.addWidget(g_playnext)
        page_media.setLayout(page_media_layout)

        # Sync tab
        page_sync = QWidget()
        page_sync_layout = QVBoxLayout()
        page_sync_layout.setAlignment(QtCore.Qt.AlignTop)

        # Group: Autoretrieve
        g_autoretrieve = QGroupBox('Autoretrieve')
        g_autoretrieve.setFlat(True)
        self.autoretrieve_off = QRadioButton('Disabled')
        self.autoretrieve_always = QRadioButton('Always at start')
        self.autoretrieve_days = QRadioButton('After n days')
        self.autoretrieve_days.toggled.connect(self.s_autoretrieve_days)
        self.autoretrieve_days_n = QSpinBox()
        self.autoretrieve_days_n.setRange(1, 100)
        g_autoretrieve_layout = QGridLayout()
        g_autoretrieve_layout.setColumnStretch(0, 1)
        g_autoretrieve_layout.addWidget(self.autoretrieve_off, 0, 0, 1, 1)
        g_autoretrieve_layout.addWidget(self.autoretrieve_always, 1, 0, 1, 1)
        g_autoretrieve_layout.addWidget(self.autoretrieve_days, 2, 0, 1, 1)
        g_autoretrieve_layout.addWidget(self.autoretrieve_days_n, 2, 1, 1, 1)
        g_autoretrieve.setLayout(g_autoretrieve_layout)

        # Group: Autosend
        g_autosend = QGroupBox('Autosend')
        g_autosend.setFlat(True)
        self.autosend_off = QRadioButton('Disabled')
        self.autosend_always = QRadioButton('Immediately after every change')
        self.autosend_minutes = QRadioButton('After n minutes')
        self.autosend_minutes.toggled.connect(self.s_autosend_minutes)
        self.autosend_minutes_n = QSpinBox()
        self.autosend_minutes_n.setRange(1, 1000)
        self.autosend_size = QRadioButton('After the queue reaches n items')
        self.autosend_size.toggled.connect(self.s_autosend_size)
        self.autosend_size_n = QSpinBox()
        self.autosend_size_n.setRange(2, 20)
        self.autosend_at_exit = QCheckBox('At exit')
        g_autosend_layout = QGridLayout()
        g_autosend_layout.setColumnStretch(0, 1)
        g_autosend_layout.addWidget(self.autosend_off, 0, 0, 1, 1)
        g_autosend_layout.addWidget(self.autosend_always, 1, 0, 1, 1)
        g_autosend_layout.addWidget(self.autosend_minutes, 2, 0, 1, 1)
        g_autosend_layout.addWidget(self.autosend_minutes_n, 2, 1, 1, 1)
        g_autosend_layout.addWidget(self.autosend_size, 3, 0, 1, 1)
        g_autosend_layout.addWidget(self.autosend_size_n, 3, 1, 1, 1)
        g_autosend_layout.addWidget(self.autosend_at_exit, 4, 0, 1, 1)
        g_autosend.setLayout(g_autosend_layout)

        # Group: Extra
        g_extra = QGroupBox('Additional options')
        g_extra.setFlat(True)
        self.auto_status_change = QCheckBox('Change status automatically')
        self.auto_status_change.toggled.connect(self.s_auto_status_change)
        self.auto_status_change_if_scored = QCheckBox(
            'Change status automatically only if scored')
        self.auto_date_change = QCheckBox(
            'Change start and finish dates automatically')
        g_extra_layout = QVBoxLayout()
        g_extra_layout.addWidget(self.auto_status_change)
        g_extra_layout.addWidget(self.auto_status_change_if_scored)
        g_extra_layout.addWidget(self.auto_date_change)
        g_extra.setLayout(g_extra_layout)

        # Sync layout
        page_sync_layout.addWidget(g_autoretrieve)
        page_sync_layout.addWidget(g_autosend)
        page_sync_layout.addWidget(g_extra)
        page_sync.setLayout(page_sync_layout)

        # UI tab
        page_ui = QWidget()
        page_ui_layout = QFormLayout()
        page_ui_layout.setAlignment(QtCore.Qt.AlignTop)

        # Group: Icon
        g_icon = QGroupBox('Notification Icon')
        g_icon.setFlat(True)
        self.tray_icon = QCheckBox('Show tray icon')
        self.tray_icon.toggled.connect(self.s_tray_icon)
        self.close_to_tray = QCheckBox('Close to tray')
        self.start_in_tray = QCheckBox('Start minimized to tray')
        self.tray_api_icon = QCheckBox('Use API icon as tray icon')
        self.notifications = QCheckBox(
            'Show notification when tracker detects new media')
        g_icon_layout = QVBoxLayout()
        g_icon_layout.addWidget(self.tray_icon)
        g_icon_layout.addWidget(self.close_to_tray)
        g_icon_layout.addWidget(self.start_in_tray)
        g_icon_layout.addWidget(self.tray_api_icon)
        g_icon_layout.addWidget(self.notifications)
        g_icon.setLayout(g_icon_layout)

        # Group: Window
        g_window = QGroupBox('Window')
        g_window.setFlat(True)
        self.remember_geometry = QCheckBox('Remember window size and position')
        self.remember_columns = QCheckBox('Remember column layouts and widths')
        self.columns_per_api = QCheckBox(
            'Use different visible columns per API')
        g_window_layout = QVBoxLayout()
        g_window_layout.addWidget(self.remember_geometry)
        g_window_layout.addWidget(self.remember_columns)
        g_window_layout.addWidget(self.columns_per_api)
        g_window.setLayout(g_window_layout)

        # Group: Lists
        g_lists = QGroupBox('Lists')
        g_lists.setFlat(True)
        self.filter_bar_position = QComboBox()
        filter_bar_positions = [(FilterBar.PositionHidden, 'Hidden'),
                                (FilterBar.PositionAboveLists, 'Above lists'),
                                (FilterBar.PositionBelowLists, 'Below lists')]
        for (n, label) in filter_bar_positions:
            self.filter_bar_position.addItem(label, n)
        self.inline_edit = QCheckBox('Enable in-line editing')
        g_lists_layout = QFormLayout()
        g_lists_layout.addRow('Filter bar position:', self.filter_bar_position)
        g_lists_layout.addRow(self.inline_edit)
        g_lists.setLayout(g_lists_layout)

        # UI layout
        page_ui_layout.addWidget(g_icon)
        page_ui_layout.addWidget(g_window)
        page_ui_layout.addWidget(g_lists)
        page_ui.setLayout(page_ui_layout)

        # Theming tab
        page_theme = QWidget()
        page_theme_layout = QFormLayout()
        page_theme_layout.setAlignment(QtCore.Qt.AlignTop)

        # Group: Episode Bar
        g_ep_bar = QGroupBox('Episode Bar')
        g_ep_bar.setFlat(True)
        self.ep_bar_style = QComboBox()
        ep_bar_styles = [(ShowsTableDelegate.BarStyleBasic, 'Basic'),
                         (ShowsTableDelegate.BarStyle04, 'Trackma'),
                         (ShowsTableDelegate.BarStyleHybrid, 'Hybrid')]
        for (n, label) in ep_bar_styles:
            self.ep_bar_style.addItem(label, n)
        self.ep_bar_style.currentIndexChanged.connect(self.s_ep_bar_style)
        self.ep_bar_text = QCheckBox('Show text label')
        g_ep_bar_layout = QFormLayout()
        g_ep_bar_layout.addRow('Style:', self.ep_bar_style)
        g_ep_bar_layout.addRow(self.ep_bar_text)
        g_ep_bar.setLayout(g_ep_bar_layout)

        # Group: Colour scheme
        g_scheme = QGroupBox('Color Scheme')
        g_scheme.setFlat(True)
        col_tabs = [('rows', '&Row highlights'),
                    ('progress', '&Progress widget')]
        self.colors = {}
        self.colors['rows'] = [('is_playing', 'Playing'),
                               ('is_queued', 'Queued'),
                               ('new_episode', 'New Episode'),
                               ('is_airing', 'Airing'),
                               ('not_aired', 'Unaired')]
        self.colors['progress'] = [('progress_bg', 'Background'),
                                   ('progress_fg', 'Watched bar'),
                                   ('progress_sub_bg', 'Aired episodes'),
                                   ('progress_sub_fg', 'Stored episodes'),
                                   ('progress_complete', 'Complete')]
        self.color_buttons = []
        self.syscolor_buttons = []
        g_scheme_layout = QGridLayout()
        tw_scheme = QTabWidget()
        for (key, tab_title) in col_tabs:
            page = QFrame()
            page_layout = QGridLayout()
            col = 0
            # Generate widgets from the keys and values
            for (key, label) in self.colors[key]:
                self.color_buttons.append(QPushButton())
                # self.color_buttons[-1].setStyleSheet('background-color: ' + getColor(self.config['colors'][key]).name())
                self.color_buttons[-1].setFocusPolicy(QtCore.Qt.NoFocus)
                self.color_buttons[-1].clicked.connect(
                    self.s_color_picker(key, False))
                self.syscolor_buttons.append(QPushButton('System Colors'))
                self.syscolor_buttons[-1].clicked.connect(
                    self.s_color_picker(key, True))
                page_layout.addWidget(QLabel(label), col, 0, 1, 1)
                page_layout.addWidget(self.color_buttons[-1], col, 1, 1, 1)
                page_layout.addWidget(self.syscolor_buttons[-1], col, 2, 1, 1)
                col += 1
            page.setLayout(page_layout)
            tw_scheme.addTab(page, tab_title)
        g_scheme_layout.addWidget(tw_scheme)
        g_scheme.setLayout(g_scheme_layout)

        # UI layout
        page_theme_layout.addWidget(g_ep_bar)
        page_theme_layout.addWidget(g_scheme)
        page_theme.setLayout(page_theme_layout)

        # Content
        self.contents = QStackedWidget()
        self.contents.addWidget(page_media)
        self.contents.addWidget(page_sync)
        self.contents.addWidget(page_ui)
        self.contents.addWidget(page_theme)
        if pyqt_version is not 5:
            self.contents.layout().setMargin(0)

        # Bottom buttons
        bottombox = QDialogButtonBox(QDialogButtonBox.Ok
                                     | QDialogButtonBox.Apply
                                     | QDialogButtonBox.Cancel)
        bottombox.accepted.connect(self.s_save)
        bottombox.button(QDialogButtonBox.Apply).clicked.connect(self._save)
        bottombox.rejected.connect(self.reject)

        # Main layout finish
        layout.addWidget(self.category_list, 0, 0, 1, 1)
        layout.addWidget(self.contents, 0, 1, 1, 1)
        layout.addWidget(bottombox, 1, 0, 1, 2)
        layout.setColumnStretch(1, 1)

        self._load()
        self.update_colors()

        self.setLayout(layout)

    def _add_dir(self, path):
        self.searchdirs.addItem(path)

    def _load(self):
        engine = self.worker.engine
        tracker_type = engine.get_config('tracker_type')
        autoretrieve = engine.get_config('autoretrieve')
        autosend = engine.get_config('autosend')

        self.tracker_enabled.setChecked(engine.get_config('tracker_enabled'))
        self.tracker_interval.setValue(engine.get_config('tracker_interval'))
        self.tracker_process.setText(engine.get_config('tracker_process'))
        self.tracker_update_wait.setValue(
            engine.get_config('tracker_update_wait_s'))
        self.tracker_update_close.setChecked(
            engine.get_config('tracker_update_close'))
        self.tracker_update_prompt.setChecked(
            engine.get_config('tracker_update_prompt'))
        self.tracker_not_found_prompt.setChecked(
            engine.get_config('tracker_not_found_prompt'))

        self.player.setText(engine.get_config('player'))
        self.library_autoscan.setChecked(engine.get_config('library_autoscan'))
        self.scan_whole_list.setChecked(engine.get_config('scan_whole_list'))
        self.library_full_path.setChecked(
            engine.get_config('library_full_path'))
        self.plex_host.setText(engine.get_config('plex_host'))
        self.plex_port.setText(engine.get_config('plex_port'))
        self.plex_obey_wait.setChecked(
            engine.get_config('plex_obey_update_wait_s'))
        self.plex_user.setText(engine.get_config('plex_user'))
        self.plex_passw.setText(engine.get_config('plex_passwd'))

        for path in engine.get_config('searchdir'):
            self._add_dir(path)

        if tracker_type == 'local':
            self.tracker_type_local.setChecked(True)
            self.plex_host.setEnabled(False)
            self.plex_port.setEnabled(False)
            self.plex_obey_wait.setEnabled(False)
        elif tracker_type == 'plex':
            self.tracker_type_plex.setChecked(True)
            self.tracker_process.setEnabled(False)

        if autoretrieve == 'always':
            self.autoretrieve_always.setChecked(True)
        elif autoretrieve == 'days':
            self.autoretrieve_days.setChecked(True)
        else:
            self.autoretrieve_off.setChecked(True)

        self.autoretrieve_days_n.setValue(
            engine.get_config('autoretrieve_days'))

        if autosend == 'always':
            self.autosend_always.setChecked(True)
        elif autosend in ('minutes', 'hours'):
            self.autosend_minutes.setChecked(True)
        elif autosend == 'size':
            self.autosend_size.setChecked(True)
        else:
            self.autosend_off.setChecked(True)

        self.autosend_minutes_n.setValue(engine.get_config('autosend_minutes'))
        self.autosend_size_n.setValue(engine.get_config('autosend_size'))

        self.autosend_at_exit.setChecked(engine.get_config('autosend_at_exit'))
        self.auto_status_change.setChecked(
            engine.get_config('auto_status_change'))
        self.auto_status_change_if_scored.setChecked(
            engine.get_config('auto_status_change_if_scored'))
        self.auto_date_change.setChecked(engine.get_config('auto_date_change'))

        self.tray_icon.setChecked(self.config['show_tray'])
        self.close_to_tray.setChecked(self.config['close_to_tray'])
        self.start_in_tray.setChecked(self.config['start_in_tray'])
        self.tray_api_icon.setChecked(self.config['tray_api_icon'])
        self.notifications.setChecked(self.config['notifications'])
        self.remember_geometry.setChecked(self.config['remember_geometry'])
        self.remember_columns.setChecked(self.config['remember_columns'])
        self.columns_per_api.setChecked(self.config['columns_per_api'])
        self.filter_bar_position.setCurrentIndex(
            self.filter_bar_position.findData(
                self.config['filter_bar_position']))
        self.inline_edit.setChecked(self.config['inline_edit'])

        self.ep_bar_style.setCurrentIndex(
            self.ep_bar_style.findData(self.config['episodebar_style']))
        self.ep_bar_text.setChecked(self.config['episodebar_text'])

        self.autoretrieve_days_n.setEnabled(self.autoretrieve_days.isChecked())
        self.autosend_minutes_n.setEnabled(self.autosend_minutes.isChecked())
        self.autosend_size_n.setEnabled(self.autosend_size.isChecked())
        self.close_to_tray.setEnabled(self.tray_icon.isChecked())
        self.start_in_tray.setEnabled(self.tray_icon.isChecked())
        self.notifications.setEnabled(self.tray_icon.isChecked())

        self.color_values = self.config['colors'].copy()

    def _save(self):
        engine = self.worker.engine

        engine.set_config('tracker_enabled', self.tracker_enabled.isChecked())
        engine.set_config('tracker_interval', self.tracker_interval.value())
        engine.set_config('tracker_process', str(self.tracker_process.text()))
        engine.set_config('tracker_update_wait_s',
                          self.tracker_update_wait.value())
        engine.set_config('tracker_update_close',
                          self.tracker_update_close.isChecked())
        engine.set_config('tracker_update_prompt',
                          self.tracker_update_prompt.isChecked())
        engine.set_config('tracker_not_found_prompt',
                          self.tracker_not_found_prompt.isChecked())

        engine.set_config('player', self.player.text())
        engine.set_config('library_autoscan',
                          self.library_autoscan.isChecked())
        engine.set_config('scan_whole_list', self.scan_whole_list.isChecked())
        engine.set_config('library_full_path',
                          self.library_full_path.isChecked())
        engine.set_config('plex_host', self.plex_host.text())
        engine.set_config('plex_port', self.plex_port.text())
        engine.set_config('plex_obey_update_wait_s',
                          self.plex_obey_wait.isChecked())
        engine.set_config('plex_user', self.plex_user.text())
        engine.set_config('plex_passwd', self.plex_passw.text())

        engine.set_config('searchdir', [
            self.searchdirs.item(i).text()
            for i in range(self.searchdirs.count())
        ])

        if self.tracker_type_local.isChecked():
            engine.set_config('tracker_type', 'local')
        elif self.tracker_type_plex.isChecked():
            engine.set_config('tracker_type', 'plex')

        if self.autoretrieve_always.isChecked():
            engine.set_config('autoretrieve', 'always')
        elif self.autoretrieve_days.isChecked():
            engine.set_config('autoretrieve', 'days')
        else:
            engine.set_config('autoretrieve', 'off')

        engine.set_config('autoretrieve_days',
                          self.autoretrieve_days_n.value())

        if self.autosend_always.isChecked():
            engine.set_config('autosend', 'always')
        elif self.autosend_minutes.isChecked():
            engine.set_config('autosend', 'minutes')
        elif self.autosend_size.isChecked():
            engine.set_config('autosend', 'size')
        else:
            engine.set_config('autosend', 'off')

        engine.set_config('autosend_minutes', self.autosend_minutes_n.value())
        engine.set_config('autosend_size', self.autosend_size_n.value())

        engine.set_config('autosend_at_exit',
                          self.autosend_at_exit.isChecked())
        engine.set_config('auto_status_change',
                          self.auto_status_change.isChecked())
        engine.set_config('auto_status_change_if_scored',
                          self.auto_status_change_if_scored.isChecked())
        engine.set_config('auto_date_change',
                          self.auto_date_change.isChecked())

        engine.save_config()

        self.config['show_tray'] = self.tray_icon.isChecked()
        self.config['close_to_tray'] = self.close_to_tray.isChecked()
        self.config['start_in_tray'] = self.start_in_tray.isChecked()
        self.config['tray_api_icon'] = self.tray_api_icon.isChecked()
        self.config['notifications'] = self.notifications.isChecked()
        self.config['remember_geometry'] = self.remember_geometry.isChecked()
        self.config['remember_columns'] = self.remember_columns.isChecked()
        self.config['columns_per_api'] = self.columns_per_api.isChecked()
        self.config['filter_bar_position'] = self.filter_bar_position.itemData(
            self.filter_bar_position.currentIndex())
        self.config['inline_edit'] = self.inline_edit.isChecked()

        self.config['episodebar_style'] = self.ep_bar_style.itemData(
            self.ep_bar_style.currentIndex())
        self.config['episodebar_text'] = self.ep_bar_text.isChecked()

        self.config['colors'] = self.color_values

        utils.save_config(self.config, self.configfile)

        self.saved.emit()

    def s_save(self):
        self._save()
        self.accept()

    def tracker_type_change(self, checked):
        if self.tracker_enabled.isChecked():
            self.tracker_interval.setEnabled(True)
            self.tracker_update_wait.setEnabled(True)
            self.tracker_type_local.setEnabled(True)
            self.tracker_type_plex.setEnabled(True)
            if self.tracker_type_local.isChecked():
                self.tracker_process.setEnabled(True)
                self.plex_host.setEnabled(False)
                self.plex_port.setEnabled(False)
                self.plex_obey_wait.setEnabled(False)
            elif self.tracker_type_plex.isChecked():
                self.plex_host.setEnabled(True)
                self.plex_port.setEnabled(True)
                self.plex_obey_wait.setEnabled(True)
                self.tracker_process.setEnabled(False)
        else:
            self.tracker_type_local.setEnabled(False)
            self.tracker_type_plex.setEnabled(False)
            self.plex_host.setEnabled(False)
            self.plex_port.setEnabled(False)
            self.plex_obey_wait.setEnabled(False)
            self.tracker_process.setEnabled(False)
            self.tracker_interval.setEnabled(False)
            self.tracker_update_wait.setEnabled(False)

    def s_autoretrieve_days(self, checked):
        self.autoretrieve_days_n.setEnabled(checked)

    def s_autosend_minutes(self, checked):
        self.autosend_minutes_n.setEnabled(checked)

    def s_autosend_size(self, checked):
        self.autosend_size_n.setEnabled(checked)

    def s_tray_icon(self, checked):
        self.close_to_tray.setEnabled(checked)
        self.start_in_tray.setEnabled(checked)
        self.tray_api_icon.setEnabled(checked)
        self.notifications.setEnabled(checked)

    def s_ep_bar_style(self, index):
        if self.ep_bar_style.itemData(index) == ShowsTableDelegate.BarStyle04:
            self.ep_bar_text.setEnabled(False)
        else:
            self.ep_bar_text.setEnabled(True)

    def s_auto_status_change(self, checked):
        self.auto_status_change_if_scored.setEnabled(checked)

    def s_player_browse(self):
        if pyqt_version is 5:
            self.player.setText(
                QFileDialog.getOpenFileName(
                    caption='Choose player executable')[0])
        else:
            self.player.setText(
                QFileDialog.getOpenFileName(
                    caption='Choose player executable'))

    def s_searchdirs_add(self):
        self._add_dir(
            QFileDialog.getExistingDirectory(caption='Choose media directory'))

    def s_searchdirs_remove(self):
        row = self.searchdirs.currentRow()
        if row != -1:
            self.searchdirs.takeItem(row)

    def s_switch_page(self, new, old):
        if not new:
            new = old

        self.contents.setCurrentIndex(self.category_list.row(new))

    def s_color_picker(self, key, system):
        return lambda: self.color_picker(key, system)

    def color_picker(self, key, system):
        if system is True:
            current = self.color_values[key]
            result = ThemedColorPicker.do()
            if result is not None and result is not current:
                self.color_values[key] = result
                self.update_colors()
        else:
            current = getColor(self.color_values[key])
            result = QColorDialog.getColor(current)
            if result.isValid() and result is not current:
                self.color_values[key] = str(result.name())
                self.update_colors()

    def update_colors(self):
        for ((key, label),
             color) in zip(self.colors['rows'] + self.colors['progress'],
                           self.color_buttons):
            color.setStyleSheet('background-color: ' +
                                getColor(self.color_values[key]).name())
Beispiel #43
0
class SettingsDialog(QDialog):
    worker = None
    config = None
    configfile = None

    saved = QtCore.pyqtSignal()

    def __init__(self, parent, worker, config, configfile):
        QDialog.__init__(self, parent)

        self.worker = worker
        self.config = config
        self.configfile = configfile
        self.setStyleSheet("QGroupBox { font-weight: bold; } ")
        self.setWindowTitle('Settings')
        layout = QGridLayout()

        # Categories
        self.category_list = QListWidget()
        category_media = QListWidgetItem(getIcon('media-playback-start'), 'Media', self.category_list)
        category_sync = QListWidgetItem(getIcon('view-refresh'), 'Sync', self.category_list)
        category_ui = QListWidgetItem(getIcon('window-new'), 'User Interface', self.category_list)
        category_theme = QListWidgetItem(getIcon('applications-graphics'), 'Theme', self.category_list)
        self.category_list.setSelectionMode(QAbstractItemView.SingleSelection)
        self.category_list.setCurrentRow(0)
        self.category_list.setMaximumWidth(self.category_list.sizeHintForColumn(0) + 15)
        self.category_list.setFocus()
        self.category_list.currentItemChanged.connect(self.s_switch_page)

        # Media tab
        page_media = QWidget()
        page_media_layout = QVBoxLayout()
        page_media_layout.setAlignment(QtCore.Qt.AlignTop)

        # Group: Media settings
        g_media = QGroupBox('Media settings')
        g_media.setFlat(True)
        g_media_layout = QFormLayout()
        self.tracker_enabled = QCheckBox()
        self.tracker_enabled.toggled.connect(self.tracker_type_change)

        self.tracker_type = QComboBox()
        for (n, label) in utils.available_trackers:
            self.tracker_type.addItem(label, n)
        self.tracker_type.currentIndexChanged.connect(self.tracker_type_change)

        self.tracker_interval = QSpinBox()
        self.tracker_interval.setRange(5, 1000)
        self.tracker_interval.setMaximumWidth(60)
        self.tracker_process = QLineEdit()
        self.tracker_update_wait = QSpinBox()
        self.tracker_update_wait.setRange(0, 1000)
        self.tracker_update_wait.setMaximumWidth(60)
        self.tracker_update_close = QCheckBox()
        self.tracker_update_prompt = QCheckBox()
        self.tracker_not_found_prompt = QCheckBox()

        g_media_layout.addRow('Enable tracker', self.tracker_enabled)
        g_media_layout.addRow('Tracker type', self.tracker_type)
        g_media_layout.addRow('Tracker interval (seconds)', self.tracker_interval)
        g_media_layout.addRow('Process name (regex)', self.tracker_process)
        g_media_layout.addRow('Wait before updating (seconds)', self.tracker_update_wait)
        g_media_layout.addRow('Wait until the player is closed', self.tracker_update_close)
        g_media_layout.addRow('Ask before updating', self.tracker_update_prompt)
        g_media_layout.addRow('Ask to add new shows', self.tracker_not_found_prompt)

        g_media.setLayout(g_media_layout)

        # Group: Plex settings
        g_plex = QGroupBox('Plex Media Server')
        g_plex.setFlat(True)
        self.plex_host = QLineEdit()
        self.plex_port = QLineEdit()
        self.plex_user = QLineEdit()
        self.plex_passw = QLineEdit()
        self.plex_passw.setEchoMode(QLineEdit.Password)
        self.plex_obey_wait = QCheckBox()

        g_plex_layout = QGridLayout()
        g_plex_layout.addWidget(QLabel('Host and Port'),                   0, 0, 1, 1)
        g_plex_layout.addWidget(self.plex_host,                            0, 1, 1, 1)
        g_plex_layout.addWidget(self.plex_port,                            0, 2, 1, 2)
        g_plex_layout.addWidget(QLabel('Use "wait before updating" time'), 1, 0, 1, 1)
        g_plex_layout.addWidget(self.plex_obey_wait,                       1, 2, 1, 1)
        g_plex_layout.addWidget(QLabel('myPlex login (claimed server)'),   2, 0, 1, 1)
        g_plex_layout.addWidget(self.plex_user,                            2, 1, 1, 1)
        g_plex_layout.addWidget(self.plex_passw,                           2, 2, 1, 2)

        g_plex.setLayout(g_plex_layout)

        # Group: Library
        g_playnext = QGroupBox('Library')
        g_playnext.setFlat(True)
        self.player = QLineEdit()
        self.player_browse = QPushButton('Browse...')
        self.player_browse.clicked.connect(self.s_player_browse)
        lbl_searchdirs = QLabel('Media directories')
        lbl_searchdirs.setAlignment(QtCore.Qt.AlignTop)
        self.searchdirs = QListWidget()
        self.searchdirs_add = QPushButton('Add...')
        self.searchdirs_add.clicked.connect(self.s_searchdirs_add)
        self.searchdirs_remove = QPushButton('Remove')
        self.searchdirs_remove.clicked.connect(self.s_searchdirs_remove)
        self.searchdirs_buttons = QVBoxLayout()
        self.searchdirs_buttons.setAlignment(QtCore.Qt.AlignTop)
        self.searchdirs_buttons.addWidget(self.searchdirs_add)
        self.searchdirs_buttons.addWidget(self.searchdirs_remove)
        self.searchdirs_buttons.addWidget(QSplitter())
        self.library_autoscan = QCheckBox()
        self.scan_whole_list = QCheckBox()
        self.library_full_path = QCheckBox()


        g_playnext_layout = QGridLayout()
        g_playnext_layout.addWidget(QLabel('Player'),                    0, 0, 1, 1)
        g_playnext_layout.addWidget(self.player,                         0, 1, 1, 1)
        g_playnext_layout.addWidget(self.player_browse,                  0, 2, 1, 1)
        g_playnext_layout.addWidget(lbl_searchdirs,                      1, 0, 1, 1)
        g_playnext_layout.addWidget(self.searchdirs,                     1, 1, 1, 1)
        g_playnext_layout.addLayout(self.searchdirs_buttons,             1, 2, 1, 1)
        g_playnext_layout.addWidget(QLabel('Rescan Library at startup'), 2, 0, 1, 2)
        g_playnext_layout.addWidget(self.library_autoscan,               2, 2, 1, 1)
        g_playnext_layout.addWidget(QLabel('Scan through whole list'),   3, 0, 1, 2)
        g_playnext_layout.addWidget(self.scan_whole_list,                3, 2, 1, 1)
        g_playnext_layout.addWidget(QLabel('Take subdirectory name into account'), 4, 0, 1, 2)
        g_playnext_layout.addWidget(self.library_full_path,              4, 2, 1, 1)

        g_playnext.setLayout(g_playnext_layout)

        # Media form
        page_media_layout.addWidget(g_media)
        page_media_layout.addWidget(g_plex)
        page_media_layout.addWidget(g_playnext)
        page_media.setLayout(page_media_layout)

        # Sync tab
        page_sync = QWidget()
        page_sync_layout = QVBoxLayout()
        page_sync_layout.setAlignment(QtCore.Qt.AlignTop)

        # Group: Autoretrieve
        g_autoretrieve = QGroupBox('Autoretrieve')
        g_autoretrieve.setFlat(True)
        self.autoretrieve_off = QRadioButton('Disabled')
        self.autoretrieve_always = QRadioButton('Always at start')
        self.autoretrieve_days = QRadioButton('After n days')
        self.autoretrieve_days.toggled.connect(self.s_autoretrieve_days)
        self.autoretrieve_days_n = QSpinBox()
        self.autoretrieve_days_n.setRange(1, 100)
        g_autoretrieve_layout = QGridLayout()
        g_autoretrieve_layout.setColumnStretch(0, 1)
        g_autoretrieve_layout.addWidget(self.autoretrieve_off,    0, 0, 1, 1)
        g_autoretrieve_layout.addWidget(self.autoretrieve_always, 1, 0, 1, 1)
        g_autoretrieve_layout.addWidget(self.autoretrieve_days,   2, 0, 1, 1)
        g_autoretrieve_layout.addWidget(self.autoretrieve_days_n, 2, 1, 1, 1)
        g_autoretrieve.setLayout(g_autoretrieve_layout)

        # Group: Autosend
        g_autosend = QGroupBox('Autosend')
        g_autosend.setFlat(True)
        self.autosend_off = QRadioButton('Disabled')
        self.autosend_always = QRadioButton('Immediately after every change')
        self.autosend_minutes = QRadioButton('After n minutes')
        self.autosend_minutes.toggled.connect(self.s_autosend_minutes)
        self.autosend_minutes_n = QSpinBox()
        self.autosend_minutes_n.setRange(1, 1000)
        self.autosend_size = QRadioButton('After the queue reaches n items')
        self.autosend_size.toggled.connect(self.s_autosend_size)
        self.autosend_size_n = QSpinBox()
        self.autosend_size_n.setRange(2, 20)
        self.autosend_at_exit = QCheckBox('At exit')
        g_autosend_layout = QGridLayout()
        g_autosend_layout.setColumnStretch(0, 1)
        g_autosend_layout.addWidget(self.autosend_off,      0, 0, 1, 1)
        g_autosend_layout.addWidget(self.autosend_always,   1, 0, 1, 1)
        g_autosend_layout.addWidget(self.autosend_minutes,    2, 0, 1, 1)
        g_autosend_layout.addWidget(self.autosend_minutes_n,  2, 1, 1, 1)
        g_autosend_layout.addWidget(self.autosend_size,     3, 0, 1, 1)
        g_autosend_layout.addWidget(self.autosend_size_n,   3, 1, 1, 1)
        g_autosend_layout.addWidget(self.autosend_at_exit,  4, 0, 1, 1)
        g_autosend.setLayout(g_autosend_layout)

        # Group: Extra
        g_extra = QGroupBox('Additional options')
        g_extra.setFlat(True)
        self.auto_status_change = QCheckBox('Change status automatically')
        self.auto_status_change.toggled.connect(self.s_auto_status_change)
        self.auto_status_change_if_scored = QCheckBox('Change status automatically only if scored')
        self.auto_date_change = QCheckBox('Change start and finish dates automatically')
        g_extra_layout = QVBoxLayout()
        g_extra_layout.addWidget(self.auto_status_change)
        g_extra_layout.addWidget(self.auto_status_change_if_scored)
        g_extra_layout.addWidget(self.auto_date_change)
        g_extra.setLayout(g_extra_layout)

        # Sync layout
        page_sync_layout.addWidget(g_autoretrieve)
        page_sync_layout.addWidget(g_autosend)
        page_sync_layout.addWidget(g_extra)
        page_sync.setLayout(page_sync_layout)

        # UI tab
        page_ui = QWidget()
        page_ui_layout = QFormLayout()
        page_ui_layout.setAlignment(QtCore.Qt.AlignTop)

        # Group: Icon
        g_icon = QGroupBox('Notification Icon')
        g_icon.setFlat(True)
        self.tray_icon = QCheckBox('Show tray icon')
        self.tray_icon.toggled.connect(self.s_tray_icon)
        self.close_to_tray = QCheckBox('Close to tray')
        self.start_in_tray = QCheckBox('Start minimized to tray')
        self.tray_api_icon = QCheckBox('Use API icon as tray icon')
        self.notifications = QCheckBox('Show notification when tracker detects new media')
        g_icon_layout = QVBoxLayout()
        g_icon_layout.addWidget(self.tray_icon)
        g_icon_layout.addWidget(self.close_to_tray)
        g_icon_layout.addWidget(self.start_in_tray)
        g_icon_layout.addWidget(self.tray_api_icon)
        g_icon_layout.addWidget(self.notifications)
        g_icon.setLayout(g_icon_layout)

        # Group: Window
        g_window = QGroupBox('Window')
        g_window.setFlat(True)
        self.remember_geometry = QCheckBox('Remember window size and position')
        self.remember_columns = QCheckBox('Remember column layouts and widths')
        self.columns_per_api = QCheckBox('Use different visible columns per API')
        g_window_layout = QVBoxLayout()
        g_window_layout.addWidget(self.remember_geometry)
        g_window_layout.addWidget(self.remember_columns)
        g_window_layout.addWidget(self.columns_per_api)
        g_window.setLayout(g_window_layout)

        # Group: Lists
        g_lists = QGroupBox('Lists')
        g_lists.setFlat(True)
        self.filter_bar_position = QComboBox()
        filter_bar_positions = [(FilterBar.PositionHidden,     'Hidden'),
                                (FilterBar.PositionAboveLists, 'Above lists'),
                                (FilterBar.PositionBelowLists, 'Below lists')]
        for (n, label) in filter_bar_positions:
            self.filter_bar_position.addItem(label, n)
        self.inline_edit = QCheckBox('Enable in-line editing')
        g_lists_layout = QFormLayout()
        g_lists_layout.addRow('Filter bar position:', self.filter_bar_position)
        g_lists_layout.addRow(self.inline_edit)
        g_lists.setLayout(g_lists_layout)

        # UI layout
        page_ui_layout.addWidget(g_icon)
        page_ui_layout.addWidget(g_window)
        page_ui_layout.addWidget(g_lists)
        page_ui.setLayout(page_ui_layout)

        # Theming tab
        page_theme = QWidget()
        page_theme_layout = QFormLayout()
        page_theme_layout.setAlignment(QtCore.Qt.AlignTop)

        # Group: Episode Bar
        g_ep_bar = QGroupBox('Episode Bar')
        g_ep_bar.setFlat(True)
        self.ep_bar_style = QComboBox()
        ep_bar_styles = [(ShowsTableDelegate.BarStyleBasic,  'Basic'),
                         (ShowsTableDelegate.BarStyle04,     'Trackma'),
                         (ShowsTableDelegate.BarStyleHybrid, 'Hybrid')]
        for (n, label) in ep_bar_styles:
            self.ep_bar_style.addItem(label, n)
        self.ep_bar_style.currentIndexChanged.connect(self.s_ep_bar_style)
        self.ep_bar_text = QCheckBox('Show text label')
        g_ep_bar_layout = QFormLayout()
        g_ep_bar_layout.addRow('Style:', self.ep_bar_style)
        g_ep_bar_layout.addRow(self.ep_bar_text)
        g_ep_bar.setLayout(g_ep_bar_layout)

        # Group: Colour scheme
        g_scheme = QGroupBox('Color Scheme')
        g_scheme.setFlat(True)
        col_tabs = [('rows',     '&Row highlights'),
                    ('progress', '&Progress widget')]
        self.colors = {}
        self.colors['rows'] = [('is_playing',  'Playing'),
                               ('is_queued',   'Queued'),
                               ('new_episode', 'New Episode'),
                               ('is_airing',   'Airing'),
                               ('not_aired',   'Unaired')]
        self.colors['progress'] = [('progress_bg',       'Background'),
                                   ('progress_fg',       'Watched bar'),
                                   ('progress_sub_bg',   'Aired episodes'),
                                   ('progress_sub_fg',   'Stored episodes'),
                                   ('progress_complete', 'Complete')]
        self.color_buttons = []
        self.syscolor_buttons = []
        g_scheme_layout = QGridLayout()
        tw_scheme = QTabWidget()
        for (key, tab_title) in col_tabs:
            page = QFrame()
            page_layout = QGridLayout()
            col = 0
            # Generate widgets from the keys and values
            for (key, label) in self.colors[key]:
                self.color_buttons.append(QPushButton())
                # self.color_buttons[-1].setStyleSheet('background-color: ' + getColor(self.config['colors'][key]).name())
                self.color_buttons[-1].setFocusPolicy(QtCore.Qt.NoFocus)
                self.color_buttons[-1].clicked.connect(self.s_color_picker(key, False))
                self.syscolor_buttons.append(QPushButton('System Colors'))
                self.syscolor_buttons[-1].clicked.connect(self.s_color_picker(key, True))
                page_layout.addWidget(QLabel(label),             col, 0, 1, 1)
                page_layout.addWidget(self.color_buttons[-1],    col, 1, 1, 1)
                page_layout.addWidget(self.syscolor_buttons[-1], col, 2, 1, 1)
                col += 1
            page.setLayout(page_layout)
            tw_scheme.addTab(page, tab_title)
        g_scheme_layout.addWidget(tw_scheme)
        g_scheme.setLayout(g_scheme_layout)

        # UI layout
        page_theme_layout.addWidget(g_ep_bar)
        page_theme_layout.addWidget(g_scheme)
        page_theme.setLayout(page_theme_layout)

        # Content
        self.contents = QStackedWidget()
        self.contents.addWidget(page_media)
        self.contents.addWidget(page_sync)
        self.contents.addWidget(page_ui)
        self.contents.addWidget(page_theme)
        if pyqt_version is not 5:
            self.contents.layout().setMargin(0)

        # Bottom buttons
        bottombox = QDialogButtonBox(
            QDialogButtonBox.Ok
            | QDialogButtonBox.Apply
            | QDialogButtonBox.Cancel
        )
        bottombox.accepted.connect(self.s_save)
        bottombox.button(QDialogButtonBox.Apply).clicked.connect(self._save)
        bottombox.rejected.connect(self.reject)

        # Main layout finish
        layout.addWidget(self.category_list,  0, 0, 1, 1)
        layout.addWidget(self.contents,       0, 1, 1, 1)
        layout.addWidget(bottombox,           1, 0, 1, 2)
        layout.setColumnStretch(1, 1)

        self._load()
        self.update_colors()

        self.setLayout(layout)

    def _add_dir(self, path):
        self.searchdirs.addItem(path)

    def _load(self):
        engine = self.worker.engine
        tracker_type = self.tracker_type.findData(engine.get_config('tracker_type'))
        autoretrieve = engine.get_config('autoretrieve')
        autosend = engine.get_config('autosend')

        self.tracker_enabled.setChecked(engine.get_config('tracker_enabled'))
        self.tracker_type.setCurrentIndex(max(0, tracker_type))
        self.tracker_interval.setValue(engine.get_config('tracker_interval'))
        self.tracker_process.setText(engine.get_config('tracker_process'))
        self.tracker_update_wait.setValue(engine.get_config('tracker_update_wait_s'))
        self.tracker_update_close.setChecked(engine.get_config('tracker_update_close'))
        self.tracker_update_prompt.setChecked(engine.get_config('tracker_update_prompt'))
        self.tracker_not_found_prompt.setChecked(engine.get_config('tracker_not_found_prompt'))

        self.player.setText(engine.get_config('player'))
        self.library_autoscan.setChecked(engine.get_config('library_autoscan'))
        self.scan_whole_list.setChecked(engine.get_config('scan_whole_list'))
        self.library_full_path.setChecked(engine.get_config('library_full_path'))
        self.plex_host.setText(engine.get_config('plex_host'))
        self.plex_port.setText(engine.get_config('plex_port'))
        self.plex_obey_wait.setChecked(engine.get_config('plex_obey_update_wait_s'))
        self.plex_user.setText(engine.get_config('plex_user'))
        self.plex_passw.setText(engine.get_config('plex_passwd'))

        for path in engine.get_config('searchdir'):
            self._add_dir(path)

        if autoretrieve == 'always':
            self.autoretrieve_always.setChecked(True)
        elif autoretrieve == 'days':
            self.autoretrieve_days.setChecked(True)
        else:
            self.autoretrieve_off.setChecked(True)

        self.autoretrieve_days_n.setValue(engine.get_config('autoretrieve_days'))

        if autosend == 'always':
            self.autosend_always.setChecked(True)
        elif autosend in ('minutes', 'hours'):
            self.autosend_minutes.setChecked(True)
        elif autosend == 'size':
            self.autosend_size.setChecked(True)
        else:
            self.autosend_off.setChecked(True)

        self.autosend_minutes_n.setValue(engine.get_config('autosend_minutes'))
        self.autosend_size_n.setValue(engine.get_config('autosend_size'))

        self.autosend_at_exit.setChecked(engine.get_config('autosend_at_exit'))
        self.auto_status_change.setChecked(engine.get_config('auto_status_change'))
        self.auto_status_change_if_scored.setChecked(engine.get_config('auto_status_change_if_scored'))
        self.auto_date_change.setChecked(engine.get_config('auto_date_change'))

        self.tray_icon.setChecked(self.config['show_tray'])
        self.close_to_tray.setChecked(self.config['close_to_tray'])
        self.start_in_tray.setChecked(self.config['start_in_tray'])
        self.tray_api_icon.setChecked(self.config['tray_api_icon'])
        self.notifications.setChecked(self.config['notifications'])
        self.remember_geometry.setChecked(self.config['remember_geometry'])
        self.remember_columns.setChecked(self.config['remember_columns'])
        self.columns_per_api.setChecked(self.config['columns_per_api'])
        self.filter_bar_position.setCurrentIndex(self.filter_bar_position.findData(self.config['filter_bar_position']))
        self.inline_edit.setChecked(self.config['inline_edit'])

        self.ep_bar_style.setCurrentIndex(self.ep_bar_style.findData(self.config['episodebar_style']))
        self.ep_bar_text.setChecked(self.config['episodebar_text'])

        self.autoretrieve_days_n.setEnabled(self.autoretrieve_days.isChecked())
        self.autosend_minutes_n.setEnabled(self.autosend_minutes.isChecked())
        self.autosend_size_n.setEnabled(self.autosend_size.isChecked())
        self.close_to_tray.setEnabled(self.tray_icon.isChecked())
        self.start_in_tray.setEnabled(self.tray_icon.isChecked())
        self.notifications.setEnabled(self.tray_icon.isChecked())

        self.color_values = self.config['colors'].copy()

        self.tracker_type_change(None)

    def _save(self):
        engine = self.worker.engine

        engine.set_config('tracker_enabled',       self.tracker_enabled.isChecked())
        engine.set_config('tracker_type',          self.tracker_type.itemData(self.tracker_type.currentIndex()))
        engine.set_config('tracker_interval',      self.tracker_interval.value())
        engine.set_config('tracker_process',       str(self.tracker_process.text()))
        engine.set_config('tracker_update_wait_s', self.tracker_update_wait.value())
        engine.set_config('tracker_update_close',  self.tracker_update_close.isChecked())
        engine.set_config('tracker_update_prompt', self.tracker_update_prompt.isChecked())
        engine.set_config('tracker_not_found_prompt', self.tracker_not_found_prompt.isChecked())

        engine.set_config('player',            self.player.text())
        engine.set_config('library_autoscan',  self.library_autoscan.isChecked())
        engine.set_config('scan_whole_list', self.scan_whole_list.isChecked())
        engine.set_config('library_full_path', self.library_full_path.isChecked())
        engine.set_config('plex_host',         self.plex_host.text())
        engine.set_config('plex_port',         self.plex_port.text())
        engine.set_config('plex_obey_update_wait_s', self.plex_obey_wait.isChecked())
        engine.set_config('plex_user',         self.plex_user.text())
        engine.set_config('plex_passwd',       self.plex_passw.text())

        engine.set_config('searchdir',         [self.searchdirs.item(i).text() for i in range(self.searchdirs.count())])

        if self.autoretrieve_always.isChecked():
            engine.set_config('autoretrieve', 'always')
        elif self.autoretrieve_days.isChecked():
            engine.set_config('autoretrieve', 'days')
        else:
            engine.set_config('autoretrieve', 'off')

        engine.set_config('autoretrieve_days',   self.autoretrieve_days_n.value())

        if self.autosend_always.isChecked():
            engine.set_config('autosend', 'always')
        elif self.autosend_minutes.isChecked():
            engine.set_config('autosend', 'minutes')
        elif self.autosend_size.isChecked():
            engine.set_config('autosend', 'size')
        else:
            engine.set_config('autosend', 'off')

        engine.set_config('autosend_minutes', self.autosend_minutes_n.value())
        engine.set_config('autosend_size',  self.autosend_size_n.value())

        engine.set_config('autosend_at_exit',   self.autosend_at_exit.isChecked())
        engine.set_config('auto_status_change', self.auto_status_change.isChecked())
        engine.set_config('auto_status_change_if_scored', self.auto_status_change_if_scored.isChecked())
        engine.set_config('auto_date_change',   self.auto_date_change.isChecked())

        engine.save_config()

        self.config['show_tray'] = self.tray_icon.isChecked()
        self.config['close_to_tray'] = self.close_to_tray.isChecked()
        self.config['start_in_tray'] = self.start_in_tray.isChecked()
        self.config['tray_api_icon'] = self.tray_api_icon.isChecked()
        self.config['notifications'] = self.notifications.isChecked()
        self.config['remember_geometry'] = self.remember_geometry.isChecked()
        self.config['remember_columns'] = self.remember_columns.isChecked()
        self.config['columns_per_api'] = self.columns_per_api.isChecked()
        self.config['filter_bar_position'] = self.filter_bar_position.itemData(self.filter_bar_position.currentIndex())
        self.config['inline_edit'] = self.inline_edit.isChecked()

        self.config['episodebar_style'] = self.ep_bar_style.itemData(self.ep_bar_style.currentIndex())
        self.config['episodebar_text'] = self.ep_bar_text.isChecked()

        self.config['colors'] = self.color_values

        utils.save_config(self.config, self.configfile)

        self.saved.emit()

    def s_save(self):
        self._save()
        self.accept()

    def tracker_type_change(self, checked):
        if self.tracker_enabled.isChecked():
            self.tracker_interval.setEnabled(True)
            self.tracker_update_wait.setEnabled(True)
            self.tracker_type.setEnabled(True)
            if self.tracker_type.itemData(self.tracker_type.currentIndex()) == 'plex':
                self.plex_host.setEnabled(True)
                self.plex_port.setEnabled(True)
                self.plex_obey_wait.setEnabled(True)
                self.plex_user.setEnabled(True)
                self.plex_passw.setEnabled(True)
                self.tracker_process.setEnabled(False)
            else:
                self.tracker_process.setEnabled(True)
                self.plex_host.setEnabled(False)
                self.plex_port.setEnabled(False)
                self.plex_user.setEnabled(False)
                self.plex_passw.setEnabled(False)
                self.plex_obey_wait.setEnabled(False)
        else:
            self.tracker_type.setEnabled(False)
            self.plex_host.setEnabled(False)
            self.plex_port.setEnabled(False)
            self.plex_user.setEnabled(False)
            self.plex_passw.setEnabled(False)
            self.plex_obey_wait.setEnabled(False)
            self.tracker_process.setEnabled(False)
            self.tracker_interval.setEnabled(False)
            self.tracker_update_wait.setEnabled(False)

    def s_autoretrieve_days(self, checked):
        self.autoretrieve_days_n.setEnabled(checked)

    def s_autosend_minutes(self, checked):
        self.autosend_minutes_n.setEnabled(checked)

    def s_autosend_size(self, checked):
        self.autosend_size_n.setEnabled(checked)

    def s_tray_icon(self, checked):
        self.close_to_tray.setEnabled(checked)
        self.start_in_tray.setEnabled(checked)
        self.tray_api_icon.setEnabled(checked)
        self.notifications.setEnabled(checked)

    def s_ep_bar_style(self, index):
        if self.ep_bar_style.itemData(index) == ShowsTableDelegate.BarStyle04:
            self.ep_bar_text.setEnabled(False)
        else:
            self.ep_bar_text.setEnabled(True)

    def s_auto_status_change(self, checked):
        self.auto_status_change_if_scored.setEnabled(checked)

    def s_player_browse(self):
        if pyqt_version is 5:
            self.player.setText(QFileDialog.getOpenFileName(caption='Choose player executable')[0])
        else:
            self.player.setText(QFileDialog.getOpenFileName(caption='Choose player executable'))

    def s_searchdirs_add(self):
        self._add_dir(QFileDialog.getExistingDirectory(caption='Choose media directory'))

    def s_searchdirs_remove(self):
        row = self.searchdirs.currentRow()
        if row != -1:
            self.searchdirs.takeItem(row)

    def s_switch_page(self, new, old):
        if not new:
            new = old

        self.contents.setCurrentIndex(self.category_list.row(new))

    def s_color_picker(self, key, system):
        return lambda: self.color_picker(key, system)

    def color_picker(self, key, system):
        if system is True:
            current = self.color_values[key]
            result = ThemedColorPicker.do()
            if result is not None and result is not current:
                self.color_values[key] = result
                self.update_colors()
        else:
            current = getColor(self.color_values[key])
            result = QColorDialog.getColor(current)
            if result.isValid() and result is not current:
                self.color_values[key] = str(result.name())
                self.update_colors()

    def update_colors(self):
        for ((key, label), color) in zip(self.colors['rows']+self.colors['progress'], self.color_buttons):
            color.setStyleSheet('background-color: ' + getColor(self.color_values[key]).name())
class fullScreenEditor(QWidget):
    def __init__(self, index, parent=None):
        QWidget.__init__(self, parent)
        self._background = None
        self._index = index
        self._theme = findThemePath(settings.fullScreenTheme)
        self._themeDatas = loadThemeDatas(self._theme)
        self.setMouseTracking(True)
        self._geometries = {}

        # Text editor
        self.editor = textEditView(self,
                                   index=index,
                                   spellcheck=settings.spellcheck,
                                   highlighting=True,
                                   dict=settings.dict)
        self.editor.setFrameStyle(QFrame.NoFrame)
        self.editor.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.editor.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.editor.installEventFilter(self)
        self.editor.setMouseTracking(True)
        self.editor.setVerticalScrollBar(myScrollBar())
        self.scrollBar = self.editor.verticalScrollBar()
        self.scrollBar.setParent(self)

        # Top Panel
        self.topPanel = myPanel(parent=self)
        # self.topPanel.layout().addStretch(1)

        # Spell checking
        if enchant:
            self.btnSpellCheck = QPushButton(self)
            self.btnSpellCheck.setFlat(True)
            self.btnSpellCheck.setIcon(QIcon.fromTheme("tools-check-spelling"))
            self.btnSpellCheck.setCheckable(True)
            self.btnSpellCheck.setChecked(self.editor.spellcheck)
            self.btnSpellCheck.toggled.connect(self.editor.toggleSpellcheck)
            self.topPanel.layout().addWidget(self.btnSpellCheck)

        self.topPanel.layout().addStretch(1)

        # Formatting
        self.textFormat = textFormat(self)
        self.topPanel.layout().addWidget(self.textFormat)
        self.topPanel.layout().addStretch(1)

        self.btnClose = QPushButton(self)
        self.btnClose.setIcon(qApp.style().standardIcon(QStyle.SP_DialogCloseButton))
        self.btnClose.clicked.connect(self.close)
        self.btnClose.setFlat(True)
        self.topPanel.layout().addWidget(self.btnClose)

        # Left Panel
        self._locked = False
        self.leftPanel = myPanel(vertical=True, parent=self)
        self.locker = locker(self)
        self.locker.lockChanged.connect(self.setLocked)
        self.leftPanel.layout().addWidget(self.locker)

        # Bottom Panel
        self.bottomPanel = myPanel(parent=self)

        self.bottomPanel.layout().addSpacing(24)
        self.lstThemes = QComboBox(self)
        self.lstThemes.setAttribute(Qt.WA_TranslucentBackground)
        paths = allPaths("resources/themes")
        for p in paths:
            lst = [i for i in os.listdir(p) if os.path.splitext(i)[1] == ".theme"]
            for t in lst:
                themeIni = os.path.join(p, t)
                name = loadThemeDatas(themeIni)["Name"]
                # self.lstThemes.addItem(os.path.splitext(t)[0])
                self.lstThemes.addItem(name)
                self.lstThemes.setItemData(self.lstThemes.count()-1, os.path.splitext(t)[0])

        self.lstThemes.setCurrentIndex(self.lstThemes.findData(settings.fullScreenTheme))
        # self.lstThemes.setCurrentText(settings.fullScreenTheme)
        self.lstThemes.currentTextChanged.connect(self.setTheme)
        self.lstThemes.setMaximumSize(QSize(300, QFontMetrics(qApp.font()).height()))
        self.bottomPanel.layout().addWidget(QLabel(self.tr("Theme:"), self))
        self.bottomPanel.layout().addWidget(self.lstThemes)
        self.bottomPanel.layout().addStretch(1)

        self.lblProgress = QLabel(self)
        self.lblProgress.setMaximumSize(QSize(200, 14))
        self.lblProgress.setMinimumSize(QSize(100, 14))
        self.lblWC = QLabel(self)
        self.bottomPanel.layout().addWidget(self.lblWC)
        self.bottomPanel.layout().addWidget(self.lblProgress)
        self.updateStatusBar()

        self.bottomPanel.layout().addSpacing(24)

        # Connection
        self._index.model().dataChanged.connect(self.dataChanged)

        # self.updateTheme()
        self.showFullScreen()
        # self.showMaximized()
        # self.show()

    def setLocked(self, val):
        self._locked = val
        self.btnClose.setVisible(not val)

    def setTheme(self, themeName):
        themeName = self.lstThemes.currentData()
        settings.fullScreenTheme = themeName
        self._theme = findThemePath(themeName)
        self._themeDatas = loadThemeDatas(self._theme)
        self.updateTheme()

    def updateTheme(self):
        # Reinit stored geometries for hiding widgets
        self._geometries = {}
        rect = self.geometry()
        self._background = generateTheme(self._themeDatas, rect)

        setThemeEditorDatas(self.editor, self._themeDatas, self._background, rect)

        # Colors
        if self._themeDatas["Foreground/Color"] == self._themeDatas["Background/Color"] or \
                        self._themeDatas["Foreground/Opacity"] < 5:
            self._bgcolor = QColor(self._themeDatas["Text/Color"])
            self._fgcolor = QColor(self._themeDatas["Background/Color"])
        else:
            self._bgcolor = QColor(self._themeDatas["Foreground/Color"])
            self._bgcolor.setAlpha(self._themeDatas["Foreground/Opacity"] * 255 / 100)
            self._fgcolor = QColor(self._themeDatas["Text/Color"])
            if self._themeDatas["Text/Color"] == self._themeDatas["Foreground/Color"]:
                self._fgcolor = QColor(self._themeDatas["Background/Color"])

        # ScrollBar
        r = self.editor.geometry()
        w = qApp.style().pixelMetric(QStyle.PM_ScrollBarExtent)
        r.setWidth(w)
        r.moveRight(rect.right() - rect.left())
        self.scrollBar.setGeometry(r)
        # self.scrollBar.setVisible(False)
        self.hideWidget(self.scrollBar)
        p = self.scrollBar.palette()
        b = QBrush(self._background.copy(self.scrollBar.geometry()))
        p.setBrush(QPalette.Base, b)
        self.scrollBar.setPalette(p)

        self.scrollBar.setColor(self._bgcolor)

        # Left Panel
        r = self.locker.geometry()
        r.moveTopLeft(QPoint(
                0,
                self.geometry().height() / 2 - r.height() / 2
        ))
        self.leftPanel.setGeometry(r)
        self.hideWidget(self.leftPanel)
        self.leftPanel.setColor(self._bgcolor)

        # Top / Bottom Panels
        r = QRect(0, 0, 0, 24)
        r.setWidth(rect.width())
        # r.moveLeft(rect.center().x() - r.width() / 2)
        self.topPanel.setGeometry(r)
        # self.topPanel.setVisible(False)
        self.hideWidget(self.topPanel)
        r.moveBottom(rect.bottom() - rect.top())
        self.bottomPanel.setGeometry(r)
        # self.bottomPanel.setVisible(False)
        self.hideWidget(self.bottomPanel)
        self.topPanel.setColor(self._bgcolor)
        self.bottomPanel.setColor(self._bgcolor)

        # Lst theme
        # p = self.lstThemes.palette()
        p = self.palette()
        p.setBrush(QPalette.Button, self._bgcolor)
        p.setBrush(QPalette.ButtonText, self._fgcolor)
        p.setBrush(QPalette.WindowText, self._fgcolor)

        for panel in (self.bottomPanel, self.topPanel):
            for i in range(panel.layout().count()):
                item = panel.layout().itemAt(i)
                if item.widget():
                    item.widget().setPalette(p)
        # self.lstThemes.setPalette(p)
        # self.lblWC.setPalette(p)

        self.update()

    def paintEvent(self, event):
        if self._background:
            painter = QPainter(self)
            painter.setClipRegion(event.region())
            painter.drawPixmap(event.rect(), self._background, event.rect())
            painter.end()

    def resizeEvent(self, event):
        self.updateTheme()

    def keyPressEvent(self, event):
        if event.key() in [Qt.Key_Escape, Qt.Key_F11] and \
                not self._locked:
            self.close()
        else:
            QWidget.keyPressEvent(self, event)

    def mouseMoveEvent(self, event):
        r = self.geometry()

        for w in [self.scrollBar, self.topPanel,
                  self.bottomPanel, self.leftPanel]:
            # w.setVisible(w.geometry().contains(event.pos()))
            if self._geometries[w].contains(event.pos()):
                self.showWidget(w)
            else:
                self.hideWidget(w)

    def hideWidget(self, widget):
        if widget not in self._geometries:
            self._geometries[widget] = widget.geometry()
        widget.move(self.geometry().bottomRight())

    def showWidget(self, widget):
        if widget in self._geometries:
            widget.move(self._geometries[widget].topLeft())

    def eventFilter(self, obj, event):
        if obj == self.editor and event.type() == QEvent.Enter:
            for w in [self.scrollBar, self.topPanel,
                      self.bottomPanel, self.leftPanel]:
                # w.setVisible(False)
                self.hideWidget(w)
        return QWidget.eventFilter(self, obj, event)

    def dataChanged(self, topLeft, bottomRight):
        if not self._index:
            return
        if topLeft.row() <= self._index.row() <= bottomRight.row():
            self.updateStatusBar()

    def updateStatusBar(self):
        if self._index:
            item = self._index.internalPointer()

        wc = item.data(Outline.wordCount.value)
        goal = item.data(Outline.goal.value)
        pg = item.data(Outline.goalPercentage.value)

        if goal:
            rect = self.lblProgress.geometry()
            rect = QRect(QPoint(0, 0), rect.size())
            self.px = QPixmap(rect.size())
            self.px.fill(Qt.transparent)
            p = QPainter(self.px)
            drawProgress(p, rect, pg, 2)
            p.end()
            self.lblProgress.setPixmap(self.px)
            self.lblWC.setText(self.tr("{} words / {}").format(wc, goal))
        else:
            self.lblProgress.hide()
            self.lblWC.setText(self.tr("{} words").format(wc))

        self.locker.setWordCount(wc)
        # If there's a goal, then we update the locker target's number of word accordingly
        # (also if there is a word count, we deduce it.
        if goal and not self.locker.isLocked():
            if wc and goal - wc > 0:
                self.locker.spnWordTarget.setValue(goal - wc)
            elif not wc:
                self.locker.spnWordTarget.setValue(goal)