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()
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
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 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)
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
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()
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)
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()
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")
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')
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()
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
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))
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()
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()
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)
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)
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())
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)
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))
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 <0|1|2|3|4></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 <float></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 <64|32|16|8></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()
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
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()
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)
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)
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
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()
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
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
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")
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)
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)
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)
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
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)
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()
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
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())
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)