class FindInFilesActions(QWidget): searchRequested = pyqtSignal('QString', bool, bool, bool) def __init__(self, parent=None): super().__init__(parent) self.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Ignored) self._scope = QComboBox() self._scope.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) self.ninjaide = IDE.get_service('ide') self.ninjaide.filesAndProjectsLoaded.connect( self._update_combo_projects) main_layout = QVBoxLayout(self) hbox = QHBoxLayout() hbox.addWidget(QLabel(translations.TR_SEARCH_SCOPE)) hbox.addWidget(self._scope) main_layout.addLayout(hbox) widgets_layout = QGridLayout() widgets_layout.setContentsMargins(0, 0, 0, 0) self._line_search = QLineEdit() self._line_search.setPlaceholderText(translations.TR_SEARCH_FOR) main_layout.addWidget(self._line_search) # TODO: replace self._check_cs = QCheckBox(translations.TR_SEARCH_CASE_SENSITIVE) self._check_cs.setChecked(True) widgets_layout.addWidget(self._check_cs, 2, 0) self._check_wo = QCheckBox(translations.TR_SEARCH_WHOLE_WORDS) widgets_layout.addWidget(self._check_wo, 2, 1) self._check_re = QCheckBox(translations.TR_SEARCH_REGEX) widgets_layout.addWidget(self._check_re, 3, 0) self._check_recursive = QCheckBox('Recursive') widgets_layout.addWidget(self._check_recursive, 3, 1) main_layout.addLayout(widgets_layout) main_layout.addStretch(1) # Connections self._line_search.returnPressed.connect(self.search_requested) def _update_combo_projects(self): projects = self.ninjaide.get_projects() for nproject in projects.values(): self._scope.addItem(nproject.name, nproject.path) @property def current_project_path(self): """Returns NProject.path of current project""" return self._scope.itemData(self._scope.currentIndex()) def search_requested(self): text = self._line_search.text() if not text.strip(): return has_search = self._line_search.text() cs = self._check_cs.isChecked() regex = self._check_re.isChecked() wo = self._check_wo.isChecked() self.searchRequested.emit(has_search, cs, regex, wo)
class PreviewForm(QDialog): def __init__(self, parent): super(PreviewForm, self).__init__(parent) self.encodingComboBox = QComboBox() encodingLabel = QLabel("&Encoding:") encodingLabel.setBuddy(self.encodingComboBox) self.textEdit = QTextEdit() self.textEdit.setLineWrapMode(QTextEdit.NoWrap) self.textEdit.setReadOnly(True) buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.encodingComboBox.activated.connect(self.updateTextEdit) buttonBox.accepted.connect(self.accept) buttonBox.rejected.connect(self.reject) mainLayout = QGridLayout() mainLayout.addWidget(encodingLabel, 0, 0) mainLayout.addWidget(self.encodingComboBox, 0, 1) mainLayout.addWidget(self.textEdit, 1, 0, 1, 2) mainLayout.addWidget(buttonBox, 2, 0, 1, 2) self.setLayout(mainLayout) self.setWindowTitle("Choose Encoding") self.resize(400, 300) def setCodecList(self, codecs): self.encodingComboBox.clear() for codec in codecs: self.encodingComboBox.addItem(codec_name(codec), codec.mibEnum()) def setEncodedData(self, data): self.encodedData = data self.updateTextEdit() def decodedString(self): return self.decodedStr def updateTextEdit(self): mib = self.encodingComboBox.itemData(self.encodingComboBox.currentIndex()) codec = QTextCodec.codecForMib(mib) data = QTextStream(self.encodedData) data.setAutoDetectUnicode(False) data.setCodec(codec) self.decodedStr = data.readAll() self.textEdit.setPlainText(self.decodedStr)
class Settings_Misc(QWidget): def __init__(self, parent: QWidget): super(Settings_Misc, self).__init__(parent) self._layout = QVBoxLayout() self.setLayout(self._layout) # tray icon settings self._l_ti = QHBoxLayout() self._lbl_ti = QLabel(self.tr("System tray icon:"), self) self._cb_ti = QComboBox(self) self._cb_ti.addItem(self.tr("Do not use"), QVariant("none")) self._cb_ti.addItem(self.tr("Show"), QVariant("show")) self._cb_ti.addItem(self.tr("Show, minimize to tray"), QVariant("show_min")) # finalize layout self._l_ti.addWidget(self._lbl_ti) self._l_ti.addWidget(self._cb_ti) self._layout.addLayout(self._l_ti) self._layout.addStretch() # self.hide() def save_to_config(self, cfg: configparser.ConfigParser): # ensure there is a 'net' section if "tray" not in cfg: cfg.add_section("tray") # tray icon settings idx = self._cb_ti.currentIndex() if idx >= 0: usage = str(self._cb_ti.itemData(idx, Qt.UserRole)) cfg["tray"]["icon_usage"] = usage logger.debug("Saved misc config") def load_from_config(self, cfg: configparser.ConfigParser): # defaults usage = "none" if "tray" in cfg: usage = cfg["tray"]["icon_usage"] if usage == "none": self._cb_ti.setCurrentIndex(0) elif usage == "show": self._cb_ti.setCurrentIndex(1) elif usage == "show_min": self._cb_ti.setCurrentIndex(2) else: raise ValueError("Ivalid tray icon usage setting: " + usage)
class MainWindow(QWidget): # constructor def __init__(self): super().__init__() # changing the background color to yellow # self.setStyleSheet("background-color: #ece2e1;color:#000;") # core self.core = Core() # translate function # list of italy regions self.italy_regions = self.core.get_region() # list of european region self.european_countries = self.core.get_european_countries() # initialSetup self.initialSetup() # initialSetup def initialSetup(self): # set title self.setWindowTitle("Italy Covid Report 2021") # set window screen self.setFixedSize(1300, 800) # show content self.contentWidget() # show the window self.show() def contentWidget(self): # select region v box 1 select_region_h_box1 = QHBoxLayout() # selection one notice select_region_h_box1.addSpacing(10) # selection label 1 self.select_region_label = QLabel("Select Region", self) select_region_h_box1.addWidget(self.select_region_label) # drop down button self.select_region_combo = QComboBox(self) self.select_region_combo.addItems(self.italy_regions) select_region_h_box1.addWidget(self.select_region_combo) # push button self.select_region_botton = QPushButton("Check Region", self) self.select_region_botton.pressed.connect(self.italyRegionClicked) select_region_h_box1.addWidget(self.select_region_botton) # select region v box 2 select_region_h_box2 = QHBoxLayout() select_region_h_box2.addSpacing(10) # selection label 1 self.select_region_label = QLabel("Italy against", self) select_region_h_box2.addWidget(self.select_region_label) # drop down button (combo ) self.select_euro_combo = QComboBox(self) self.select_euro_combo.addItems(self.european_countries) select_region_h_box2.addWidget(self.select_euro_combo) # push button self.compare_button = QPushButton("Compare", self) self.compare_button.pressed.connect(self.europeanCountriesClicked) select_region_h_box2.addWidget(self.compare_button) # selection main V box select_region_main_v_box = QVBoxLayout() # An overall language pull down menu is being requested at the very top of the GUI # with the option of English and Italian labelling language_h_box = QHBoxLayout() self.language_lbl = QLabel("Select Language", self) self.language_combobox = QComboBox() # language option options = ([ ('English', 'en'), ('Italian', 'it'), ('Spanish', 'es'), ('Chinese', 'zh-CN'), ]) # add language and change language for i, (text, lang) in enumerate(options): self.language_combobox.addItem(text) self.language_combobox.setItemData(i, lang) language_h_box.addWidget(self.language_lbl) # on index changed self.language_combobox.currentIndexChanged.connect( self.languageChanged) language_h_box.addWidget(self.language_combobox) language_h_box.addStretch() # add language_h_box layout select_region_main_v_box.addLayout(language_h_box) # Italy Region Covid Report self.italy_lbl = QLabel("Italy Region Covid Report", self) self.italy_lbl.setStyleSheet("border: 0.5px solid gray") select_region_main_v_box.addWidget(self.italy_lbl) select_region_main_v_box.addLayout(select_region_h_box1) select_region_main_v_box.setSpacing(15) self.euro_text = QLabel( "Italy Covid report against European countries", self) self.euro_text.setStyleSheet("border: 0.5px solid gray") select_region_main_v_box.addWidget(self.euro_text) select_region_main_v_box.addLayout(select_region_h_box2) select_region_main_v_box.addStretch() # for region map and demographic region_map_box = QVBoxLayout() self.coordinate_title = "This is a title" self.coordinate = coordinate['Campania'] m = folium.Map(tiles="Stamen Terrain", zoom_start=6, location=self.coordinate) # create HTML for pop up def foliumHtml(lo): # get stats if lo != "Italy": stats = self.core.getRegionStats(str(lo)) return f""" <h1 style='color:#7b113a;'> {lo} </h1> <hr/> <p style='color:#7b113a;font-size:20px;'>Region Population: {stats['region_population']}</p> <p style='color:#7b113a;font-size:20px;'>Total Covid Case: {stats['case_number']}</p> <p style='color:#7b113a;font-size:20px;'>Daily Cases: {stats['expectedChanges']}</p> <p style='color:#7b113a;font-size:20px;'>Percentage: {stats['percentage']}%</p> """ else: return f""" <h1> {lo}</h1> <p>European country with a long Mediterranean coastline, has left a powerful mark on Western culture and cuisine.</p> """ # add marker one by one on the map for lo in coordinate: # add pop ups html = foliumHtml(lo) iframe = folium.IFrame(html=html, width=300, height=250) popUp = folium.Popup(iframe, max_width=2650) # Marker starts here folium.Marker(location=coordinate[lo], popup=popUp, icon=folium.DivIcon(html=f""" <div><svg> <circle cx="50" cy="50" r="40" fill="#7b113a" opacity=".4"/> <rect x="35", y="35" width="30" height="30", fill="#fff600", opacity=".3" </svg></div>""")).add_to(m) # save map data to data object data = io.BytesIO() m.save(data, close_file=False) webView = QWebEngineView() webView.setHtml(data.getvalue().decode()) region_map_box.addWidget(webView) # main box top - bottom h_box = QHBoxLayout() h_box.addLayout(select_region_main_v_box) h_box.addLayout(region_map_box) self.setLayout(h_box) # languageChanged clicked @qt.pyqtSlot(int) def languageChanged(self, index): data = self.language_combobox.itemData(index) translator = Translator() # print(translator.translate('Hello.', dest=data).text) # select language self.language_lbl.setText( translator.translate('Select Language.', dest=data).text) # selection headings self.italy_lbl.setText( translator.translate('Italy Region Covid Report', dest=data).text) self.euro_text.setText( translator.translate( 'Italy Covid report against european countries.', dest=data).text) # # # # italy selection region text self.select_region_label.setText( translator.translate('Select Region.', dest=data).text) self.select_region_botton.setText( translator.translate('Check Region', dest=data).text) # compare section self.select_region_label.setText( translator.translate('Italy Against.', dest=data).text) self.compare_button.setText( translator.translate('Compare.', dest=data).text) # this method when clicked get and display region statistics # italyRegionClicked def italyRegionClicked(self): # check if value returned is not None if self.select_region_combo.currentText() is not None: stats = self.core.getRegionStats( self.select_region_combo.currentText()) self.italyRegionStatistics(stats) else: self.errorMessage("Error getting region") # on clicked this method display the statistic between italy and euro countries # europeanCountriesClicked def europeanCountriesClicked(self): if self.select_euro_combo.currentText() is not None: # get italy stats italy_stats = self.core.getItalyStats("Italy") euro_stats = self.core.getEuropeanCountryStats( self.select_euro_combo.currentText()) # display stats dialog self.compareStatisticsDialog(italy_stats, euro_stats) else: self.errorMessage("Error comparing countries") # for handling error message def errorMessage(self, message): QMessageBox().warning(self, "Unexpected Error", message, QMessageBox.Ok, QMessageBox.Ok) # display italy region statistics def italyRegionStatistics(self, stats): # create dialog regionDialog = QDialog(self) regionDialog.setFixedSize(390, 230) # set dialog title regionDialog.setWindowTitle(f"{stats['region']} Report".capitalize()) # show region # dialog title d_title = QLabel(f"{stats['region']}", self) d_title.setFont(QFont('Times', 25, QFont.Light)) d_title.setAlignment(qt.Qt.AlignCenter) # h rows r1_box = QHBoxLayout() r1_box.addStretch() region_label = QLabel('Region :', self) region_label.setFont(QFont('Times', 25, QFont.Light)) region_text = QLabel(f"{stats['region']}", self) region_text.setFont(QFont('Times', 25, QFont.Light)) r1_box.addWidget(region_label) r1_box.addWidget(region_text) r1_box.addStretch() # 2 h rows r2_box = QHBoxLayout() r2_box.addStretch() population_label = QLabel('Population :', self) population_label.setFont(QFont('Times', 25, QFont.Light)) population_text = QLabel(f"{stats['population']}", self) population_text.setFont(QFont('Times', 25, QFont.Light)) r2_box.addWidget(population_label) r2_box.addWidget(population_text) r2_box.addStretch() # 3 h rows r3_box = QHBoxLayout() r3_box.addStretch() region_population_label = QLabel('Region Population :', self) region_population_label.setFont(QFont('Times', 25, QFont.Light)) region_population_text = QLabel(f"{stats['region_population']}", self) region_population_text.setFont(QFont('Times', 25, QFont.Light)) r3_box.addWidget(region_population_label) r3_box.addWidget(region_population_text) r3_box.addStretch() # 4 h rows r4_box = QHBoxLayout() r4_box.addStretch() case_number_label = QLabel('Total Covid Case :', self) case_number_label.setFont(QFont('Times', 25, QFont.Light)) case_number_text = QLabel(f"{stats['case_number']}", self) case_number_text.setFont(QFont('Times', 25, QFont.Light)) r4_box.addWidget(case_number_label) r4_box.addWidget(case_number_text) r4_box.addStretch() # 5 h rows r5_box = QHBoxLayout() r5_box.addStretch() expected_changes_label = QLabel('Daily Cases :', self) expected_changes_label.setFont(QFont('Times', 25, QFont.Light)) expected_changes_text = QLabel(f"{stats['expectedChanges']}", self) expected_changes_text.setFont(QFont('Times', 25, QFont.Light)) r5_box.addWidget(expected_changes_label) r5_box.addWidget(expected_changes_text) r5_box.addStretch() # 6 h rows r6_box = QHBoxLayout() r6_box.addStretch() cal_percentage_label = QLabel('Percentage :', self) cal_percentage_label.setFont(QFont('Times', 25, QFont.Light)) cal_percentage_text = QLabel(f"{stats['percentage']}%", self) cal_percentage_text.setFont(QFont('Times', 25, QFont.Light)) r6_box.addWidget(cal_percentage_label) r6_box.addWidget(cal_percentage_text) r6_box.addStretch() # main v box main_v_box = QVBoxLayout() main_v_box.setSpacing(10) # add row 1 main_v_box.addLayout(r1_box) # add row 2 main_v_box.addLayout(r2_box) # add row 3 main_v_box.addLayout(r3_box) # add row 4 main_v_box.addLayout(r4_box) # add row 5 main_v_box.addLayout(r5_box) # add row 6 main_v_box.addLayout(r6_box) main_v_box.addStretch() regionDialog.setLayout(main_v_box) regionDialog.exec_() # comparing italy and other european country def compareStatisticsDialog(self, italy_stats, euro_stats): # create dialog regionDialog = QDialog(self) regionDialog.setFixedSize(700, 300) # set dialog title regionDialog.setWindowTitle(f"{euro_stats['country']}") # show comparison # left and right v box left_v_box = QVBoxLayout() right_v_box = QVBoxLayout() # horizontal box # Left H box 1 left_h_box_1 = QHBoxLayout() left_h_box_1.addStretch() country_lbl = QLabel("Country: ", self) country_lbl.setFont(QFont('Times', 20, QFont.Light)) left_h_box_1.addWidget(country_lbl) country_text = QLabel(italy_stats['country'], self) country_text.setFont(QFont('Times', 20, QFont.Light)) left_h_box_1.addWidget(country_text) left_h_box_1.addStretch() # add layout left_v_box.addLayout(left_h_box_1) # Right H box 1 right_h_box_1 = QHBoxLayout() right_h_box_1.addStretch() euro_country_lbl = QLabel("Country: ", self) euro_country_lbl.setFont(QFont('Times', 20, QFont.Light)) right_h_box_1.addWidget(euro_country_lbl) euro_country_text = QLabel(euro_stats['country'], self) euro_country_text.setFont(QFont('Times', 20, QFont.Light)) right_h_box_1.addWidget(euro_country_text) right_h_box_1.addStretch() # add layout right_v_box.addLayout(right_h_box_1) # Left H box 2 left_h_box_2 = QHBoxLayout() left_h_box_2.addStretch() population_lbl = QLabel("Total Population: ", self) population_lbl.setFont(QFont('Times', 20, QFont.Light)) left_h_box_2.addWidget(population_lbl) population_text = QLabel(str(italy_stats['population']), self) population_text.setFont(QFont('Times', 20, QFont.Light)) left_h_box_2.addWidget(population_text) left_h_box_2.addStretch() # add layout left_v_box.addLayout(left_h_box_2) # Right H box 2 right_h_box_2 = QHBoxLayout() right_h_box_2.addStretch() euro_population_lbl = QLabel("Total Population: ", self) euro_population_lbl.setFont(QFont('Times', 20, QFont.Light)) right_h_box_2.addWidget(euro_population_lbl) euro_population_text = QLabel(str(euro_stats['population']), self) euro_population_text.setFont(QFont('Times', 20, QFont.Light)) right_h_box_2.addWidget(euro_population_text) right_h_box_2.addStretch() # add layout right_v_box.addLayout(right_h_box_2) # Left H box 3 left_h_box_3 = QHBoxLayout() left_h_box_3.addStretch() case_lbl = QLabel("Total Case: ", self) case_lbl.setFont(QFont('Times', 20, QFont.Light)) left_h_box_3.addWidget(case_lbl) case_text = QLabel(str(italy_stats['case']), self) case_text.setFont(QFont('Times', 20, QFont.Light)) left_h_box_3.addWidget(case_text) left_h_box_3.addStretch() # add layout left_v_box.addLayout(left_h_box_3) # Right H box 3 right_h_box_3 = QHBoxLayout() right_h_box_3.addStretch() euro_case_lbl = QLabel("Total Case: ", self) euro_case_lbl.setFont(QFont('Times', 20, QFont.Light)) right_h_box_3.addWidget(euro_case_lbl) euro_case_text = QLabel(str(euro_stats['case']), self) euro_case_text.setFont(QFont('Times', 20, QFont.Light)) right_h_box_3.addWidget(euro_case_text) right_h_box_3.addStretch() # add layout right_v_box.addLayout(right_h_box_3) # Left H box 4 left_h_box_4 = QHBoxLayout() left_h_box_4.addStretch() rate_lbl = QLabel("Infection Rate: ", self) rate_lbl.setFont(QFont('Times', 20, QFont.Light)) left_h_box_4.addWidget(rate_lbl) rate_text = QLabel(str(italy_stats['rate']), self) rate_text.setFont(QFont('Times', 20, QFont.Light)) left_h_box_4.addWidget(rate_text) left_h_box_4.addStretch() # add layout left_v_box.addLayout(left_h_box_4) # Right H box 4 right_h_box_4 = QHBoxLayout() right_h_box_4.addStretch() euro_rate_lbl = QLabel("Infection Rate: ", self) euro_rate_lbl.setFont(QFont('Times', 20, QFont.Light)) right_h_box_4.addWidget(euro_rate_lbl) euro_rate_text = QLabel(str(euro_stats['rate']), self) euro_rate_text.setFont(QFont('Times', 20, QFont.Light)) right_h_box_4.addWidget(euro_rate_text) right_h_box_4.addStretch() # add layout right_v_box.addLayout(right_h_box_4) # inner h box second_main_h_box = QHBoxLayout() # add right and left v box second_main_h_box.addLayout(left_v_box) second_main_h_box.addLayout(right_v_box) # main v box main_v_box = QVBoxLayout() # set main title main_v_title = QLabel( f"Comparison reports between Italy and {euro_stats['country']}", self) main_v_title.setFont(QFont('Times', 20, QFont.Bold)) main_v_title.setAlignment(qt.Qt.AlignCenter) main_v_box.addWidget(main_v_title) main_v_box.addSpacing(30) # add second_main_h_box main_v_box.addLayout(second_main_h_box) # push everything to the top main_v_box.addStretch() # set main layout regionDialog.setLayout(main_v_box) regionDialog.exec_()
class AmbientLightV3(COMCUPluginBase): def __init__(self, *args): super().__init__(BrickletAmbientLightV3, *args) self.al = self.device self.cbe_illuminance = CallbackEmulator(self, self.al.get_illuminance, None, self.cb_illuminance, self.increase_error_count) self.alf = ColorFrame(25, 25, QColor(128, 128, 128)) self.out_of_range_label = QLabel('Illuminance is out-of-range') self.invalid_label = QLabel('Illuminance is invalid') self.out_of_range_label.hide() self.out_of_range_label.setStyleSheet('QLabel { color: red }') self.invalid_label.hide() self.invalid_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.invalid_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.invalid_label.show() elif illuminance >= max_illuminance: self.plot_widget.get_key_item(0).setStyleSheet('QLabel { color: red }') self.out_of_range_label.show() self.invalid_label.hide() else: self.plot_widget.get_key_item(0).setStyleSheet('') self.out_of_range_label.hide() self.invalid_label.hide() value = min(max(illuminance * 255 // max_illuminance, 0), 255) self.alf.set_color(QColor(value, value, value))
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 addTileDialog(QDialog): output = None ready = False def __init__(self): super().__init__() self.tilename = QComboBox() for tile in tilelist.tilelist: self.tilename.addItem(tile.__name__, tile) self.tilename.currentIndexChanged.connect(self.setTable) okbtn = QPushButton('Ok') okbtn.clicked.connect(self.onOk) clbtn = QPushButton('Cancel') clbtn.clicked.connect(self.onCancel) self.table = QTableWidget(2, 2) self.setTable(self.tilename.currentIndex()) hbox = QHBoxLayout() hbox.addWidget(clbtn) hbox.addWidget(okbtn) vbox = QVBoxLayout() vbox.addWidget(self.tilename) vbox.addWidget(self.table) vbox.addLayout(hbox) self.setLayout(vbox) self.resize(400, 300) self.table.setColumnWidth(1, 250) self.setModal(True) self.show() def setTable(self, index): try: raw = 2 + len(self.tilename.itemData(index).extraText) except TypeError: raw = 2 self.table.setRowCount(raw) self.table.setItem(0, 0, QTableWidgetItem('X position')) self.table.setCellWidget(0, 1, QSpinBox()) self.table.setItem(1, 0, QTableWidgetItem('Y position')) self.table.setCellWidget(1, 1, QSpinBox()) if self.tilename.itemData(index).extraText != None: i = 0 for line in self.tilename.itemData(index).extraText: self.table.setItem(2 + i, 0, QTableWidgetItem(line)) self.table.setCellWidget(2 + i, 1, QLineEdit('')) i += 1 def getParams(self): x = self.table.cellWidget(0, 1).value() y = self.table.cellWidget(1, 1).value() extra = None if self.tilename.currentData().extraTypes != None: i = 0 extra = [] for t in self.tilename.currentData().extraTypes: if t == 'bool': extra.append(bool(self.table.cellWidget(2 + i, 1).text())) elif t == 'int': extra.append(int(self.table.cellWidget(2 + i, 1).text())) elif t == 'float': extra.append(float(self.table.cellWidget(2 + i, 1).text())) elif t == 'str': extra.append(str(self.table.cellWidget(2 + i, 1).text())) else: extra.append(str(self.table.cellWidget(2 + i, 1).text())) i += 1 return (x, y, extra) def getTileParams(self): return self.output def onOk(self): self.output = (self.tilename.currentData(), self.getParams()) self.ready = True self.accept() def onCancel(self): self.output = (self.tilename.currentData(), self.getParams()) self.ready = True self.reject()
class App(QMainWindow): def __init__(self, paths=None): super().__init__() self.stations: List[Station] = [] self.svg: BytesIO = None self.freq = '1H' self.dates = None self.title = None self.setWindowTitle('SHEAR Weather Reporter') self.activateWindow() self.setAcceptDrops(True) self.paths = paths if type(paths) == list else [] plotWidget = QWidget() plotWidgetLayout = QHBoxLayout() plotWidget.setLayout(plotWidgetLayout) self.plotWidget = QtSvg.QSvgWidget() self.plotWidget.setFixedWidth(800) self.plotWidget.setFixedHeight(500) plotWidgetLayout.addWidget(self.plotWidget) self.logosWidget = QWidget() layout = QHBoxLayout() self.logosWidget.setLayout(layout) self.logos = [ os.path.join(os.path.dirname(__file__), 'img', image) for image in [ 'newcastle_university_logo.png', 'uganda_red_cross_society_long_small.png', 'shear_logo.png', 'actogether_uganda_small.png', 'makerere_university_logo_long_small.png', ] ] for image in self.logos: logo = QLabel() logo.setPixmap(QtGui.QPixmap(image)) layout.addWidget(logo) logo.setAlignment(QtCore.Qt.AlignCenter) self.saveButton = QPushButton('Save') self.saveButton.clicked.connect(self.save) self.updateButton = QPushButton('Rename') self.updateButton.clicked.connect(self.rename_locations) self.mainLayout = QVBoxLayout() self.mainWidget = QWidget() self.mainWidget.setLayout(self.mainLayout) self.setCentralWidget(self.mainWidget) self.resampleDropDown = QComboBox() self.dateDropDown = QComboBox() self.durationDropDown = QComboBox() self.durationDropDown.activated.connect(self.set_duration) self.dropWidget = QLabel('Drop a Davis WeatherLink export file here') self.dropWidget.setStyleSheet( "margin:5px; border:1px dashed rgb(0, 0, 0); padding:10px") self.resampleDropDown.activated.connect(self.set_frequency) self.dateDropDown.activated.connect(self.update_plot) self.mainLayout.setAlignment(QtCore.Qt.AlignCenter) self.title_widget = QWidget() title_layout = QHBoxLayout() for widget in [ self.resampleDropDown, QLabel('Weather Report for the'), self.durationDropDown, QLabel('of'), self.dateDropDown ]: title_layout.addWidget(widget) widget.setFont(QtGui.QFont("Times", 15, QtGui.QFont.Bold)) title_layout.setAlignment(QtCore.Qt.AlignCenter) self.title_widget.setLayout(title_layout) self.mainLayout.addWidget(self.dropWidget) self.mainLayout.addWidget(self.title_widget) self.mainLayout.addWidget(plotWidget) buttons = QWidget() buttons_layout = QHBoxLayout() self.linkButton = QPushButton('View Code on GitHub') buttons_layout.addWidget(self.linkButton) self.linkButton.clicked.connect( lambda _: webbrowser.open('github.com/fmcclean/weather-reporter')) buttons_layout.addWidget(self.updateButton) buttons_layout.addWidget(self.saveButton) buttons.setLayout(buttons_layout) self.mainLayout.addWidget(buttons) self.mainLayout.addWidget(self.logosWidget) self.showWidgets(False) if len(self.paths) > 0: self.add_data() def update_location(self): if self.df is not None: self.update_plot() def showWidgets(self, show: bool): self.title_widget.setVisible(show) self.logosWidget.setVisible(show) self.linkButton.setVisible(show) self.updateButton.setVisible(show) self.plotWidget.setVisible(show) self.saveButton.setVisible(show) self.dropWidget.setVisible(not show) def update_plot(self): self.svg = BytesIO() f, axes = plt.subplots(len(self.stations), figsize=(9, 6), sharex=True) date = self.dateDropDown.currentData() end_index = self.dates.index.get_loc(date) + 1 for ax, station in zip(axes.flat if len(self.paths) > 1 else [axes], self.stations): if end_index >= len(self.dates): end_date = station.temp.index[-1] else: end_date = self.dates.index[end_index] try: temp = station.temp.loc[date:end_date] except KeyError: temp = station.temp.iloc[0:0] ax.plot(temp.index.start_time, temp.values, color='firebrick') ax.set_ylabel('Temperature (C)') ymin, ymax = ax.get_ylim() ymax = ymax + ymax - ymin ax.set_ylim(ymin, ymax) twinx = ax.twinx() twinx.invert_yaxis() try: rain = station.rain.loc[date:end_date].iloc[:-1] except KeyError: rain = station.rain.iloc[0:0] width = rain.index.end_time - rain.index.start_time twinx.bar(rain.index.start_time, rain.values, width=width, align='edge') twinx.set_ylabel('Rainfall (mm)') ymax, ymin = twinx.get_ylim() ymax = ymax + ymax - ymin twinx.set_ylim(ymax, ymin) locator = mdates.AutoDateLocator() ax.xaxis.set_major_locator(locator) ax.xaxis.set_major_formatter(mdates.ConciseDateFormatter(locator)) plt.tight_layout() self.title = '{} Weather Report for the {} of {}'.format( self.resampleDropDown.currentText(), self.durationDropDown.currentText(), self.dateDropDown.currentText()) ax.set_title("{}".format(station.location)) ax.patch.set_visible(False) f.patch.set_visible(False) f.savefig(self.svg, format='svg') self.svg.seek(0) plt.close(f) self.plotWidget.load(self.svg.read()) self.svg.seek(0) def create_pdf(self, path): title_style = style['h1'] title_style.alignment = 1 doc = SimpleDocTemplate(path, rightMargin=0, leftMargin=0, topMargin=0, bottomMargin=0, pagesize=landscape(A4)) story = [Paragraph(self.title, style=title_style), svg2rlg(self.svg)] chart_style = TableStyle([('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('VALIGN', (0, 0), (-1, -1), 'CENTER')]) images = [] for logo in self.logos: from reportlab.lib import utils from reportlab.lib.units import cm height = 0.5 * cm img = utils.ImageReader(logo) iw, ih = img.getSize() aspect = iw / ih images.append(Image(logo, width=height * aspect, height=height)) story.append( Table([images], colWidths=[150 for _ in self.logos], rowHeights=[3], style=chart_style)) doc.build(story) def dragEnterEvent(self, event): if event.mimeData().hasText(): event.accept() else: event.ignore() def dropEvent(self, event): url = event.mimeData().urls()[0].toLocalFile() self.paths.append(url) try: self.add_data([url]) except pd.errors.ParserError: self.paths.pop() msg = QMessageBox() msg.setText('Could not load from {}'.format( os.path.basename(self.path))) msg.exec_() return def add_data(self, paths=None): if paths is None: paths = self.paths for path in paths: self.stations.append(Station(path)) duration = self.stations[0].df.index[-1].end_time - self.stations[ 0].df.index[0].start_time if duration > pd.Timedelta(hours=min_length): if self.resampleDropDown.findText('Hourly') == -1: self.resampleDropDown.addItem('Hourly', '1H') if self.durationDropDown.findText('Day') == -1: self.durationDropDown.addItem('Day', 'day') if duration > pd.Timedelta(days=min_length): if self.resampleDropDown.findText('Daily') == -1: self.resampleDropDown.addItem('Daily', '1D') if self.durationDropDown.findText('Week') == -1: self.durationDropDown.addItem('Week', 'week') if self.durationDropDown.findText('Month') == -1: self.durationDropDown.addItem('Month', 'month') if duration > pd.Timedelta(weeks=min_length): if self.resampleDropDown.findText('Weekly') == -1: self.resampleDropDown.addItem('Weekly', '1W') if duration > pd.Timedelta(days=31 * min_length): if self.resampleDropDown.findText('Monthly') == -1: self.resampleDropDown.addItem('Monthly', '1M') if self.durationDropDown.findText('Year') == -1: self.durationDropDown.addItem('Year', 'year') self.set_duration() self.set_frequency() self.update_plot() self.showWidgets(True) def set_duration(self): duration = self.durationDropDown.currentData() record_lengths = [station.record_length for station in self.stations] idx = record_lengths.index(max(record_lengths)) periods = getattr(self.stations[idx].rain.index.to_series().dt, duration) self.dates = periods[periods.diff() != 0] self.dateDropDown.clear() if duration == 'month': date_string = '{:%m/%Y}' elif duration == 'year': date_string = '{:%Y}' else: date_string = '{:%d/%m/%Y}' for date in self.dates.index: self.dateDropDown.addItem(date_string.format(date.start_time), date) self.update_plot() def save(self): dialog = QFileDialog.getSaveFileName(filter="PDF Files (*.pdf)") if dialog[0] != '': self.create_pdf(dialog[0]) def set_frequency(self): freq = self.resampleDropDown.itemData( self.resampleDropDown.currentIndex()) self.freq = freq for station in self.stations: station.resample(self.freq) self.update_plot() def rename_locations(self): dialog = QDialog() layout = QVBoxLayout() dialog.setLayout(layout) b = QPushButton('Update') for station in self.stations: row = QWidget(dialog) row_layout = QHBoxLayout() row_layout.addWidget(QLabel(station.path)) edit = QLineEdit(station.location) row_layout.addWidget(edit) edit.textChanged.connect(station.rename_location) row.setLayout(row_layout) layout.addWidget(row) layout.addWidget(b) b.clicked.connect(dialog.close) dialog.exec_() self.update_plot()
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 RegExpDialog(QDialog): MaxCaptures = 6 def __init__(self, parent=None): super(RegExpDialog, self).__init__(parent) self.patternComboBox = QComboBox() self.patternComboBox.setEditable(True) self.patternComboBox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) patternLabel = QLabel("&Pattern:") patternLabel.setBuddy(self.patternComboBox) self.escapedPatternLineEdit = QLineEdit() self.escapedPatternLineEdit.setReadOnly(True) palette = self.escapedPatternLineEdit.palette() palette.setBrush(QPalette.Base, palette.brush(QPalette.Disabled, QPalette.Base)) self.escapedPatternLineEdit.setPalette(palette) escapedPatternLabel = QLabel("&Escaped Pattern:") escapedPatternLabel.setBuddy(self.escapedPatternLineEdit) self.syntaxComboBox = QComboBox() self.syntaxComboBox.addItem("Regular expression v1", QRegExp.RegExp) self.syntaxComboBox.addItem("Regular expression v2", QRegExp.RegExp2) self.syntaxComboBox.addItem("Wildcard", QRegExp.Wildcard) self.syntaxComboBox.addItem("Fixed string", QRegExp.FixedString) syntaxLabel = QLabel("&Pattern Syntax:") syntaxLabel.setBuddy(self.syntaxComboBox) self.textComboBox = QComboBox() self.textComboBox.setEditable(True) self.textComboBox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) textLabel = QLabel("&Text:") textLabel.setBuddy(self.textComboBox) self.caseSensitiveCheckBox = QCheckBox("Case &Sensitive") self.caseSensitiveCheckBox.setChecked(True) self.minimalCheckBox = QCheckBox("&Minimal") indexLabel = QLabel("Index of Match:") self.indexEdit = QLineEdit() self.indexEdit.setReadOnly(True) matchedLengthLabel = QLabel("Matched Length:") self.matchedLengthEdit = QLineEdit() self.matchedLengthEdit.setReadOnly(True) self.captureLabels = [] self.captureEdits = [] for i in range(self.MaxCaptures): self.captureLabels.append(QLabel("Capture %d:" % i)) self.captureEdits.append(QLineEdit()) self.captureEdits[i].setReadOnly(True) self.captureLabels[0].setText("Match:") checkBoxLayout = QHBoxLayout() checkBoxLayout.addWidget(self.caseSensitiveCheckBox) checkBoxLayout.addWidget(self.minimalCheckBox) checkBoxLayout.addStretch(1) mainLayout = QGridLayout() mainLayout.addWidget(patternLabel, 0, 0) mainLayout.addWidget(self.patternComboBox, 0, 1) mainLayout.addWidget(escapedPatternLabel, 1, 0) mainLayout.addWidget(self.escapedPatternLineEdit, 1, 1) mainLayout.addWidget(syntaxLabel, 2, 0) mainLayout.addWidget(self.syntaxComboBox, 2, 1) mainLayout.addLayout(checkBoxLayout, 3, 0, 1, 2) mainLayout.addWidget(textLabel, 4, 0) mainLayout.addWidget(self.textComboBox, 4, 1) mainLayout.addWidget(indexLabel, 5, 0) mainLayout.addWidget(self.indexEdit, 5, 1) mainLayout.addWidget(matchedLengthLabel, 6, 0) mainLayout.addWidget(self.matchedLengthEdit, 6, 1) for i in range(self.MaxCaptures): mainLayout.addWidget(self.captureLabels[i], 7 + i, 0) mainLayout.addWidget(self.captureEdits[i], 7 + i, 1) self.setLayout(mainLayout) self.patternComboBox.editTextChanged.connect(self.refresh) self.textComboBox.editTextChanged.connect(self.refresh) self.caseSensitiveCheckBox.toggled.connect(self.refresh) self.minimalCheckBox.toggled.connect(self.refresh) self.syntaxComboBox.currentIndexChanged.connect(self.refresh) self.patternComboBox.addItem("[A-Za-z_]+([A-Za-z_0-9]*)") self.textComboBox.addItem("(10 + delta4)* 32") self.setWindowTitle("RegExp") self.setFixedHeight(self.sizeHint().height()) self.refresh() def refresh(self): self.setUpdatesEnabled(False) pattern = self.patternComboBox.currentText() text = self.textComboBox.currentText() escaped = str(pattern) escaped.replace('\\', '\\\\') escaped.replace('"', '\\"') self.escapedPatternLineEdit.setText('"' + escaped + '"') rx = QRegExp(pattern) cs = Qt.CaseSensitive if self.caseSensitiveCheckBox.isChecked() else Qt.CaseInsensitive rx.setCaseSensitivity(cs) rx.setMinimal(self.minimalCheckBox.isChecked()) syntax = self.syntaxComboBox.itemData(self.syntaxComboBox.currentIndex()) rx.setPatternSyntax(syntax) palette = self.patternComboBox.palette() if rx.isValid(): palette.setColor(QPalette.Text, self.textComboBox.palette().color(QPalette.Text)) else: palette.setColor(QPalette.Text, Qt.red) self.patternComboBox.setPalette(palette) self.indexEdit.setText(str(rx.indexIn(text))) self.matchedLengthEdit.setText(str(rx.matchedLength())) for i in range(self.MaxCaptures): self.captureLabels[i].setEnabled(i <= rx.captureCount()) self.captureEdits[i].setEnabled(i <= rx.captureCount()) self.captureEdits[i].setText(rx.cap(i)) self.setUpdatesEnabled(True)
class FileManager(QWidget, _HalWidgetBase): def __init__(self, parent=None): super(FileManager, self).__init__(parent) self.title = 'Qtvcp File System View' self.left = 10 self.top = 10 self.width = 640 self.height = 480 self.media_path = (os.path.join(os.path.expanduser('~'), 'linuxcnc/nc_files')) user = os.path.split(os.path.expanduser('~'))[-1] self.user_path = (os.path.join('/media', user)) self.currentPath = None self.currentFolder = None self.initUI() def initUI(self): self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) pasteBox = QHBoxLayout() self.textLine = QLineEdit() self.textLine.setToolTip('Current Director/selected File') self.pasteButton = QToolButton() self.pasteButton.setEnabled(False) self.pasteButton.setText('Paste') self.pasteButton.setToolTip( 'Copy file from copy path to current directory/file') self.pasteButton.clicked.connect(self.paste) self.pasteButton.hide() pasteBox.addWidget(self.textLine) pasteBox.addWidget(self.pasteButton) self.copyBox = QFrame() hbox = QHBoxLayout() hbox.setContentsMargins(0, 0, 0, 0) self.copyLine = QLineEdit() self.copyLine.setToolTip('File path to copy from, when pasting') self.copyButton = QToolButton() self.copyButton.setText('Copy') self.copyButton.setToolTip('Record current file as copy path') self.copyButton.clicked.connect(self.recordCopyPath) hbox.addWidget(self.copyButton) hbox.addWidget(self.copyLine) self.copyBox.setLayout(hbox) self.copyBox.hide() self.model = QFileSystemModel() self.model.setRootPath(QDir.currentPath()) self.model.setFilter(QDir.AllDirs | QDir.NoDot | QDir.Files) self.model.setNameFilterDisables(False) self.model.rootPathChanged.connect(self.folderChanged) self.list = QListView() self.list.setModel(self.model) self.updateDirectoryView(self.media_path) self.list.resize(640, 480) self.list.clicked[QModelIndex].connect(self.listClicked) self.list.activated.connect(self._getPathActivated) self.list.setAlternatingRowColors(True) self.cb = QComboBox() self.cb.currentIndexChanged.connect(self.filterChanged) self.fillCombobox(INFO.PROGRAM_FILTERS_EXTENSIONS) self.cb.setMinimumHeight(30) self.cb.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) self.button2 = QToolButton() self.button2.setText('Media') self.button2.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) self.button2.setMinimumSize(60, 30) self.button2.setToolTip('Jump to Media directory') self.button2.clicked.connect(self.onJumpClicked) SettingMenu = QMenu(self) self.settingMenu = SettingMenu for i in ('Media', 'User'): axisButton = QAction(QIcon.fromTheme('user-home'), i, self) # weird lambda i=i to work around 'function closure' axisButton.triggered.connect( lambda state, i=i: self.jumpTriggered(i)) SettingMenu.addAction(axisButton) self.button2.setMenu(SettingMenu) self.button3 = QToolButton() self.button3.setText('Add Jump') self.button3.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) self.button3.setMinimumSize(60, 30) self.button3.setToolTip('Add current directory to jump button list') self.button3.clicked.connect(self.onActionClicked) hbox = QHBoxLayout() hbox.addWidget(self.button2) hbox.addWidget(self.button3) hbox.insertStretch(2, stretch=0) hbox.addWidget(self.cb) windowLayout = QVBoxLayout() windowLayout.addLayout(pasteBox) windowLayout.addWidget(self.copyBox) windowLayout.addWidget(self.list) windowLayout.addLayout(hbox) self.setLayout(windowLayout) self.show() def _hal_init(self): if self.PREFS_: last_path = self.PREFS_.getpref('last_loaded_directory', self.media_path, str, 'BOOK_KEEPING') self.updateDirectoryView(last_path) LOG.debug("lAST FILE PATH: {}".format(last_path)) else: LOG.debug("lAST FILE PATH: {}".format(self.media_path)) self.updateDirectoryView(self.media_path) ######################### # callbacks ######################### # add shown text and hidden filter data from the INI def fillCombobox(self, data): for i in data: self.cb.addItem(i[0], i[1]) def folderChanged(self, data): self.currentFolder = data self.textLine.setText(data) def updateDirectoryView(self, path): self.list.setRootIndex(self.model.setRootPath(path)) # retrieve selected filter (it's held as QT.userData) def filterChanged(self, index): userdata = self.cb.itemData(index) self.model.setNameFilters(userdata) def listClicked(self, index): # the signal passes the index of the clicked item dir_path = self.model.filePath(index) if self.model.fileInfo(index).isFile(): self.currentPath = dir_path self.textLine.setText(self.currentPath) return root_index = self.model.setRootPath(dir_path) self.list.setRootIndex(root_index) def onUserClicked(self): self.showUserDir() def onMediaClicked(self): self.showMediaDir() def onJumpClicked(self): data = self.button2.text() if data == 'Media': self.showMediaDir() elif data == 'User': self.showUserDir() else: self.updateDirectoryView(self.button2.text()) def jumpTriggered(self, data): if data == 'Media': self.button2.setText('{}'.format(data)) self.button2.setToolTip('Jump to Media directory') self.showMediaDir() elif data == 'User': self.button2.setText('{}'.format(data)) self.button2.setToolTip('Jump to User directory') self.showUserDir() else: self.button2.setText('{}'.format(data)) self.button2.setToolTip('Jump to directory: {}'.format(data)) self.updateDirectoryView(self.button2.text()) def onActionClicked(self): i = self.currentFolder button = QAction(QIcon.fromTheme('user-home'), i, self) # weird lambda i=i to work around 'function closure' button.triggered.connect(lambda state, i=i: self.jumpTriggered(i)) self.settingMenu.addAction(button) # get current selection and update the path # then if the path is good load it into linuxcnc # record it in the preference file if available def _getPathActivated(self): row = self.list.selectionModel().currentIndex() self.listClicked(row) fname = self.currentPath if fname is None: return if fname: self.load(fname) def recordCopyPath(self): data, isFile = self.getCurrentSelected() if isFile: self.copyLine.setText(os.path.normpath(data)) self.pasteButton.setEnabled(True) else: self.copyLine.setText('') self.pasteButton.setEnabled(False) STATUS.emit('error', OPERATOR_ERROR, 'Can only copy a file, not a folder') def paste(self): res = self.copyFile(self.copyLine.text(), self.textLine.text()) if res: self.copyLine.setText('') self.pasteButton.setEnabled(False) ######################## # helper functions ######################## def showCopyControls(self, state): if state: self.copyBox.show() self.pasteButton.show() else: self.copyBox.hide() self.pasteButton.hide() def showMediaDir(self): self.updateDirectoryView(self.user_path) def showUserDir(self): self.updateDirectoryView(self.media_path) def copyFile(self, s, d): try: shutil.copy(s, d) return True except Exception as e: LOG.error("Copy file error: {}".format(e)) STATUS.emit('error', OPERATOR_ERROR, "Copy file error: {}".format(e)) return False # moves the selection up # used with MPG scrolling def up(self): self.select_row('up') # moves the selection down # used with MPG scrolling def down(self): self.select_row('down') def select_row(self, style='down'): style = style.lower() selectionModel = self.list.selectionModel() row = selectionModel.currentIndex().row() self.rows = self.model.rowCount(self.list.rootIndex()) if style == 'last': row = self.rows elif style == 'up': if row > 0: row -= 1 else: row = 0 elif style == 'down': if row < self.rows: row += 1 else: row = self.rows else: return top = self.model.index(row, 0, self.list.rootIndex()) selectionModel.setCurrentIndex( top, QItemSelectionModel.Select | QItemSelectionModel.Rows) selection = QItemSelection(top, top) selectionModel.clearSelection() selectionModel.select(selection, QItemSelectionModel.Select) # returns the current highlighted (selected) path as well as # whether it's a file or not. def getCurrentSelected(self): selectionModel = self.list.selectionModel() index = selectionModel.currentIndex() dir_path = self.model.filePath(index) if self.model.fileInfo(index).isFile(): return (dir_path, True) else: return (dir_path, False) # This can be class patched to do something else def load(self, fname=None): if fname is None: self._getPathActivated() return self.recordBookKeeping() ACTION.OPEN_PROGRAM(fname) STATUS.emit('update-machine-log', 'Loaded: ' + fname, 'TIME') # This can be class patched to do something else def recordBookKeeping(self): fname = self.currentPath if fname is None: return if self.PREFS_: self.PREFS_.putpref('last_loaded_directory', self.model.rootPath(), str, 'BOOK_KEEPING') self.PREFS_.putpref('RecentPath_0', fname, str, 'BOOK_KEEPING')
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 EditPointsDialog: def __init__(self, model, owner_object): self.model = model self.__owner_object = owner_object self.__changes = {} self.__window = QDialog() layout = QVBoxLayout() self.__window.setLayout(layout) self.__group_widget = QComboBox() self.__group_widget.addItem("- all -", None) for teacher in model.subject.teachers: for group in teacher.taught_groups: self.__group_widget.addItem(group.moodle_name, group) layout.addWidget(self.__group_widget) self.__points_widget = QTableWidget() self.__points_widget.setColumnCount(2) self.__points_widget.setHorizontalHeaderLabels(["Name", "Points"]) layout.addWidget(self.__points_widget) button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) button_box.accepted.connect(self.__window.accept) button_box.rejected.connect(self.__window.reject) layout.addWidget(button_box) self.__refresh_students() self.__group_widget.currentIndexChanged.connect( lambda *args: self.__refresh_students()) self.__points_widget.itemChanged.connect(self.__cell_changed) def __refresh_students(self): self.__highlight_edit = False group = self.__group_widget.itemData( self.__group_widget.currentIndex()) if group is None: students = self.model.subject.students else: students = list(group.get_students()) self.__points_widget.setRowCount(len(students)) for no, student in enumerate(students): name_cell = QTableWidgetItem(f"{student.name} {student.surname}") name_cell.setFlags(name_cell.flags() ^ Qt.ItemIsEditable) self.__points_widget.setItem(no, 0, name_cell) points = student.get_points_for(self.__owner_object) if points is not None: self.__points_widget.setItem( no, 1, PointsTableItem(student, points.points)) else: self.__points_widget.setItem(no, 1, PointsTableItem(student)) self.__highlight_edit = True def __cell_changed(self, cell): if self.__highlight_edit and isinstance(cell, PointsTableItem): cell.setBackground(QBrush(QColor(255, 255, 0))) self.__group_widget.setEnabled(False) self.__changes[cell.student] = cell.points def run(self): if self.__window.exec() == QDialog.Accepted: for student, points in self.__changes.items(): student.set_points_for(self.__owner_object, points) self.model.data_edited()
class Window(QWidget): def __init__(self): super(Window, self).__init__() self.proxyModel = MySortFilterProxyModel(self) self.proxyModel.setDynamicSortFilter(True) self.sourceView = QTreeView() self.sourceView.setRootIsDecorated(False) self.sourceView.setAlternatingRowColors(True) sourceLayout = QHBoxLayout() sourceLayout.addWidget(self.sourceView) sourceGroupBox = QGroupBox("Original Model") sourceGroupBox.setLayout(sourceLayout) self.filterCaseSensitivityCheckBox = QCheckBox("Case sensitive filter") self.filterCaseSensitivityCheckBox.setChecked(True) self.filterPatternLineEdit = QLineEdit() self.filterPatternLineEdit.setText("Grace|Sports") filterPatternLabel = QLabel("&Filter pattern:") filterPatternLabel.setBuddy(self.filterPatternLineEdit) self.filterSyntaxComboBox = QComboBox() self.filterSyntaxComboBox.addItem("Regular expression", QRegExp.RegExp) self.filterSyntaxComboBox.addItem("Wildcard", QRegExp.Wildcard) self.filterSyntaxComboBox.addItem("Fixed string", QRegExp.FixedString) self.fromDateEdit = QDateEdit() self.fromDateEdit.setDate(QDate(2006, 12, 22)) self.fromDateEdit.setCalendarPopup(True) fromLabel = QLabel("F&rom:") fromLabel.setBuddy(self.fromDateEdit) self.toDateEdit = QDateEdit() self.toDateEdit.setDate(QDate(2007, 1, 5)) self.toDateEdit.setCalendarPopup(True) toLabel = QLabel("&To:") toLabel.setBuddy(self.toDateEdit) self.filterPatternLineEdit.textChanged.connect(self.textFilterChanged) self.filterSyntaxComboBox.currentIndexChanged.connect(self.textFilterChanged) self.filterCaseSensitivityCheckBox.toggled.connect(self.textFilterChanged) self.fromDateEdit.dateChanged.connect(self.dateFilterChanged) self.toDateEdit.dateChanged.connect(self.dateFilterChanged) self.proxyView = QTreeView() self.proxyView.setRootIsDecorated(False) self.proxyView.setAlternatingRowColors(True) self.proxyView.setModel(self.proxyModel) self.proxyView.setSortingEnabled(True) self.proxyView.sortByColumn(1, Qt.AscendingOrder) self.textFilterChanged() self.dateFilterChanged() proxyLayout = QGridLayout() proxyLayout.addWidget(self.proxyView, 0, 0, 1, 3) proxyLayout.addWidget(filterPatternLabel, 1, 0) proxyLayout.addWidget(self.filterPatternLineEdit, 1, 1) proxyLayout.addWidget(self.filterSyntaxComboBox, 1, 2) proxyLayout.addWidget(self.filterCaseSensitivityCheckBox, 2, 0, 1, 3) proxyLayout.addWidget(fromLabel, 3, 0) proxyLayout.addWidget(self.fromDateEdit, 3, 1, 1, 2) proxyLayout.addWidget(toLabel, 4, 0) proxyLayout.addWidget(self.toDateEdit, 4, 1, 1, 2) proxyGroupBox = QGroupBox("Sorted/Filtered Model") proxyGroupBox.setLayout(proxyLayout) mainLayout = QVBoxLayout() mainLayout.addWidget(sourceGroupBox) mainLayout.addWidget(proxyGroupBox) self.setLayout(mainLayout) self.setWindowTitle("Custom Sort/Filter Model") self.resize(500, 450) def setSourceModel(self, model): self.proxyModel.setSourceModel(model) self.sourceView.setModel(model) def textFilterChanged(self): syntax = QRegExp.PatternSyntax( self.filterSyntaxComboBox.itemData( self.filterSyntaxComboBox.currentIndex())) caseSensitivity = ( self.filterCaseSensitivityCheckBox.isChecked() and Qt.CaseSensitive or Qt.CaseInsensitive) regExp = QRegExp(self.filterPatternLineEdit.text(), caseSensitivity, syntax) self.proxyModel.setFilterRegExp(regExp) def dateFilterChanged(self): self.proxyModel.setFilterMinimumDate(self.fromDateEdit.date()) self.proxyModel.setFilterMaximumDate(self.toDateEdit.date())
class StretchLayout(QFormLayout): """ Layout that contains the actual stretch information """ def __init__(self, parent, stretch, gdaldataset=None): QFormLayout.__init__(self) # the mode self.modeCombo = QComboBox(parent) for text, code in MODE_DATA: self.modeCombo.addItem(text, code) # callback so we can set the state of other items when changed self.modeCombo.currentIndexChanged.connect(self.modeChanged) self.rampCombo = QComboBox(parent) # make sure the pseudocolor has the extra ramps loaded try: pseudocolor.loadExtraRamps() except Exception as e: QMessageBox.critical(parent, MESSAGE_TITLE, str(e)) # populate combo - sort by type for (name, display) in pseudocolor.getRampsForDisplay(): self.rampCombo.addItem(display, name) self.modeLayout = QHBoxLayout() self.modeLayout.addWidget(self.modeCombo) self.modeLayout.addWidget(self.rampCombo) self.addRow("Mode", self.modeLayout) if gdaldataset is None: # we don't have a dateset - is a rule # create spin boxes for the bands self.createSpinBands(parent) else: # we have a dataset. create combo # boxes with the band names self.createComboBands(gdaldataset, parent) self.addRow("Bands", self.bandLayout) # create the combo for the type of stretch self.stretchLayout = QHBoxLayout() self.stretchCombo = QComboBox(parent) for text, code in STRETCH_DATA: self.stretchCombo.addItem(text, code) # callback so we can set the state of other items when changed self.stretchCombo.currentIndexChanged.connect(self.stretchChanged) self.stretchLayout.addWidget(self.stretchCombo) # create the spin boxes for the std devs or hist min and max self.stretchParam1 = QDoubleSpinBox(parent) self.stretchParam1.setDecimals(3) self.stretchParam1Stats = QCheckBox(parent) self.stretchParam1Stats.setText("Statistics Min") self.stretchParam1Stats.stateChanged.connect(self.param1StatsChanged) self.stretchParam2 = QDoubleSpinBox(parent) self.stretchParam2.setDecimals(3) self.stretchParam2Stats = QCheckBox(parent) self.stretchParam2Stats.setText("Statistics Max") self.stretchParam2Stats.stateChanged.connect(self.param2StatsChanged) self.stretchLayout.addWidget(self.stretchParam1) self.stretchLayout.addWidget(self.stretchParam1Stats) self.stretchLayout.addWidget(self.stretchParam2) self.stretchLayout.addWidget(self.stretchParam2Stats) self.addRow("Stretch", self.stretchLayout) # now for no data, background and NaN self.fixedColorLayout = QHBoxLayout() self.nodataLabel = QLabel(parent) self.nodataLabel.setText("No Data") self.fixedColorLayout.addWidget(self.nodataLabel) self.fixedColorLayout.setAlignment(self.nodataLabel, Qt.AlignRight) self.nodataButton = ColorButton(parent) self.fixedColorLayout.addWidget(self.nodataButton) self.backgroundLabel = QLabel(parent) self.backgroundLabel.setText("Background") self.fixedColorLayout.addWidget(self.backgroundLabel) self.fixedColorLayout.setAlignment(self.backgroundLabel, Qt.AlignRight) self.backgroundButton = ColorButton(parent) self.fixedColorLayout.addWidget(self.backgroundButton) self.NaNLabel = QLabel(parent) self.NaNLabel.setText("NaN") self.fixedColorLayout.addWidget(self.NaNLabel) self.fixedColorLayout.setAlignment(self.NaNLabel, Qt.AlignRight) self.NaNButton = ColorButton(parent) self.fixedColorLayout.addWidget(self.NaNButton) self.addRow("Fixed Colors", self.fixedColorLayout) # set state of GUI for this stretch self.updateStretch(stretch) def param1StatsChanged(self, state): """ Called when the 'Statistics Min' box is checked """ self.stretchParam1.setEnabled(state != Qt.Checked) def param2StatsChanged(self, state): """ Called when the 'Statistics Max' box is checked """ self.stretchParam2.setEnabled(state != Qt.Checked) def updateStretch(self, stretch): """ Change the state of the GUI to match the given stretch """ # the mode idx = self.modeCombo.findData(stretch.mode) if idx != -1: self.modeCombo.setCurrentIndex(idx) # ramp if stretch.rampName is not None: idx = self.rampCombo.findData(stretch.rampName) if idx != -1: self.rampCombo.setCurrentIndex(idx) # set ramp state depending on if we are pseudo color or not state = stretch.mode == viewerstretch.VIEWER_MODE_PSEUDOCOLOR self.rampCombo.setEnabled(state) # set the bands depending on if we are RGB or not if stretch.mode == viewerstretch.VIEWER_MODE_RGB: self.redWidget.setToolTip("Red") self.greenWidget.setToolTip("Green") self.blueWidget.setToolTip("Blue") (r, g, b) = stretch.bands if isinstance(self.redWidget, QSpinBox): self.redWidget.setValue(r) self.greenWidget.setValue(g) self.blueWidget.setValue(b) else: self.redWidget.setCurrentIndex(r - 1) self.greenWidget.setCurrentIndex(g - 1) self.blueWidget.setCurrentIndex(b - 1) else: self.redWidget.setToolTip("Displayed Band") self.greenWidget.setEnabled(False) self.blueWidget.setEnabled(False) if isinstance(self.redWidget, QSpinBox): self.redWidget.setValue(stretch.bands[0]) else: self.redWidget.setCurrentIndex(stretch.bands[0] - 1) # stretch mode idx = self.stretchCombo.findData(stretch.stretchmode) if idx != -1: self.stretchCombo.setCurrentIndex(idx) state = stretch.mode != viewerstretch.VIEWER_MODE_COLORTABLE self.stretchCombo.setEnabled(state) if stretch.stretchmode == viewerstretch.VIEWER_STRETCHMODE_STDDEV: self.stretchParam2.setEnabled(False) self.stretchParam1Stats.setEnabled(False) self.stretchParam2Stats.setEnabled(False) self.stretchParam1Stats.setCheckState(Qt.Unchecked) self.stretchParam2Stats.setCheckState(Qt.Unchecked) self.stretchParam1.setRange(0, 10) self.stretchParam1.setSingleStep(0.1) self.stretchParam1.setValue(stretch.stretchparam[0]) self.stretchParam1.setToolTip("Number of Standard Deviations") elif stretch.stretchmode == viewerstretch.VIEWER_STRETCHMODE_HIST: self.stretchParam1Stats.setEnabled(False) self.stretchParam2Stats.setEnabled(False) self.stretchParam1Stats.setCheckState(Qt.Unchecked) self.stretchParam2Stats.setCheckState(Qt.Unchecked) self.stretchParam1.setRange(0, 1) self.stretchParam1.setSingleStep(0.005) self.stretchParam1.setValue(stretch.stretchparam[0]) self.stretchParam1.setToolTip("Minimum Proportion of Histogram") self.stretchParam2.setRange(0, 1) self.stretchParam2.setSingleStep(0.005) self.stretchParam2.setValue(stretch.stretchparam[1]) self.stretchParam2.setToolTip("Maximum Proportion of Histogram") elif stretch.stretchmode == viewerstretch.VIEWER_STRETCHMODE_LINEAR: self.stretchParam1Stats.setEnabled(True) self.stretchParam2Stats.setEnabled(True) self.stretchParam1.setRange(-2**32, 2**32) self.stretchParam1.setSingleStep(1) if stretch.stretchparam[0] is None: self.stretchParam1Stats.setCheckState(Qt.Checked) else: self.stretchParam1.setValue(stretch.stretchparam[0]) self.stretchParam1Stats.setCheckState(Qt.Unchecked) self.stretchParam1.setToolTip("Minimum Value") self.stretchParam2.setRange(-2**32, 2**32) self.stretchParam2.setSingleStep(1) if stretch.stretchparam[1] is None: self.stretchParam2Stats.setCheckState(Qt.Checked) else: self.stretchParam2.setValue(stretch.stretchparam[1]) self.stretchParam2Stats.setCheckState(Qt.Unchecked) self.stretchParam2.setToolTip("Maximum Value") else: # no stretch self.stretchParam1.setEnabled(False) self.stretchParam2.setEnabled(False) self.stretchParam1Stats.setCheckState(Qt.Unchecked) self.stretchParam2Stats.setCheckState(Qt.Unchecked) self.stretchParam1Stats.setEnabled(False) self.stretchParam2Stats.setEnabled(False) self.stretchParam1.setToolTip("") self.stretchParam2.setToolTip("") # nodata etc self.nodataButton.setColorAsRGBATuple(stretch.nodata_rgba) self.backgroundButton.setColorAsRGBATuple(stretch.background_rgba) self.NaNButton.setColorAsRGBATuple(stretch.nan_rgba) def createSpinBands(self, parent): """ For the case where we are creating a rule we have no band names so create spin boxes """ # create the 3 band spin boxes self.bandLayout = QHBoxLayout() self.redWidget = QSpinBox(parent) self.redWidget.setRange(1, MAX_BAND_NUMBER) self.bandLayout.addWidget(self.redWidget) self.greenWidget = QSpinBox(parent) self.greenWidget.setRange(1, MAX_BAND_NUMBER) self.bandLayout.addWidget(self.greenWidget) self.blueWidget = QSpinBox(parent) self.blueWidget.setRange(1, MAX_BAND_NUMBER) self.bandLayout.addWidget(self.blueWidget) def createComboBands(self, gdaldataset, parent): """ We have a dataset - create combo boxes with the band names """ self.bandLayout = QHBoxLayout() self.redWidget = QComboBox(parent) self.bandLayout.addWidget(self.redWidget) self.greenWidget = QComboBox(parent) self.bandLayout.addWidget(self.greenWidget) self.blueWidget = QComboBox(parent) self.bandLayout.addWidget(self.blueWidget) self.populateComboFromDataset(self.redWidget, gdaldataset) self.populateComboFromDataset(self.greenWidget, gdaldataset) self.populateComboFromDataset(self.blueWidget, gdaldataset) def populateComboFromDataset(self, combo, gdaldataset): """ Go through all the bands in the dataset and add a combo item for each one. Set the current index to the currentBand """ for count in range(gdaldataset.RasterCount): bandnum = count + 1 gdalband = gdaldataset.GetRasterBand(bandnum) name = gdalband.GetDescription() if name == '': # make up a name so the user can still choose name = 'Band %d' % bandnum combo.addItem(name, bandnum) @staticmethod def getBandValue(widget): """ Depending on whether widget it a spinbox or a combo box extract the current value for it. """ if isinstance(widget, QSpinBox): value = widget.value() else: index = widget.currentIndex() var = widget.itemData(index) value = var return value def getStretch(self): """ Return a ViewerStretch object that reflects the current state of the GUI """ obj = viewerstretch.ViewerStretch() index = self.modeCombo.currentIndex() obj.mode = self.modeCombo.itemData(index) bands = [] value = self.getBandValue(self.redWidget) bands.append(value) if obj.mode == viewerstretch.VIEWER_MODE_RGB: value = self.getBandValue(self.greenWidget) bands.append(value) value = self.getBandValue(self.blueWidget) bands.append(value) obj.setBands(tuple(bands)) if obj.mode == viewerstretch.VIEWER_MODE_PSEUDOCOLOR: idx = self.rampCombo.currentIndex() rampName = self.rampCombo.itemData(idx) obj.setPseudoColor(str(rampName)) index = self.stretchCombo.currentIndex() obj.stretchmode = self.stretchCombo.itemData(index) if obj.stretchmode == viewerstretch.VIEWER_STRETCHMODE_STDDEV: value = self.stretchParam1.value() obj.setStdDevStretch(value) elif obj.stretchmode == viewerstretch.VIEWER_STRETCHMODE_HIST: histmin = self.stretchParam1.value() histmax = self.stretchParam2.value() obj.setHistStretch(histmin, histmax) elif obj.stretchmode == viewerstretch.VIEWER_STRETCHMODE_LINEAR: # need to do something cleverer here with the special value minVal = self.stretchParam1.value() if self.stretchParam1Stats.checkState() == Qt.Checked: minVal = None maxVal = self.stretchParam2.value() if self.stretchParam2Stats.checkState() == Qt.Checked: maxVal = None obj.setLinearStretch(minVal, maxVal) obj.setNoDataRGBA(self.nodataButton.getColorAsRGBATuple()) obj.setBackgroundRGBA(self.backgroundButton.getColorAsRGBATuple()) obj.setNaNRGBA(self.NaNButton.getColorAsRGBATuple()) return obj def modeChanged(self, index): """ Called when user changed the mode. Updates other GUI elements as needed """ mode = self.modeCombo.itemData(index) greenredEnabled = (mode == viewerstretch.VIEWER_MODE_RGB) self.greenWidget.setEnabled(greenredEnabled) self.blueWidget.setEnabled(greenredEnabled) if greenredEnabled: self.redWidget.setToolTip("Red") self.greenWidget.setToolTip("Green") self.blueWidget.setToolTip("Blue") else: self.redWidget.setToolTip("Displayed Band") self.greenWidget.setToolTip("") self.blueWidget.setToolTip("") if mode == viewerstretch.VIEWER_MODE_COLORTABLE: # need to set stretch to none self.stretchCombo.setCurrentIndex(0) state = mode != viewerstretch.VIEWER_MODE_COLORTABLE self.stretchCombo.setEnabled(state) state = mode == viewerstretch.VIEWER_MODE_PSEUDOCOLOR self.rampCombo.setEnabled(state) def stretchChanged(self, index): """ Called when user changed the stretch. Updates other GUI elements as needed """ stretchmode = self.stretchCombo.itemData(index) if stretchmode == viewerstretch.VIEWER_STRETCHMODE_STDDEV: self.stretchParam1.setEnabled(True) self.stretchParam2.setEnabled(False) self.stretchParam1Stats.setCheckState(Qt.Unchecked) self.stretchParam2Stats.setCheckState(Qt.Unchecked) self.stretchParam1Stats.setEnabled(False) self.stretchParam2Stats.setEnabled(False) self.stretchParam1.setRange(0, 10) self.stretchParam1.setSingleStep(0.1) # always set back to this default self.stretchParam1.setValue(viewerstretch.VIEWER_DEFAULT_STDDEV) self.stretchParam1.setToolTip("Number of Standard Deviations") self.stretchParam2.setToolTip("") elif stretchmode == viewerstretch.VIEWER_STRETCHMODE_HIST: self.stretchParam1.setEnabled(True) self.stretchParam2.setEnabled(True) self.stretchParam1Stats.setCheckState(Qt.Unchecked) self.stretchParam2Stats.setCheckState(Qt.Unchecked) self.stretchParam1Stats.setEnabled(False) self.stretchParam2Stats.setEnabled(False) self.stretchParam1.setRange(0, 1) self.stretchParam1.setSingleStep(0.005) self.stretchParam1.setToolTip("Minimum Proportion of Histogram") self.stretchParam2.setRange(0, 1) self.stretchParam2.setSingleStep(0.005) self.stretchParam2.setToolTip("Maximum Proportion of Histogram") # set back to these defaults self.stretchParam1.setValue(viewerstretch.VIEWER_DEFAULT_HISTMIN) self.stretchParam2.setValue(viewerstretch.VIEWER_DEFAULT_HISTMAX) elif stretchmode == viewerstretch.VIEWER_STRETCHMODE_LINEAR: self.stretchParam1.setEnabled(True) self.stretchParam2.setEnabled(True) self.stretchParam1Stats.setEnabled(True) self.stretchParam2Stats.setEnabled(True) self.stretchParam1.setRange(-2**32, 2**32) self.stretchParam1.setSingleStep(1) self.stretchParam1.setToolTip("Minimum Value") self.stretchParam2.setRange(-2**32, 2**32) self.stretchParam2.setSingleStep(1) self.stretchParam2.setToolTip("Maximum Value") self.stretchParam1.setValue(0) # set back to these defaults self.stretchParam2.setValue(0) self.stretchParam1Stats.setCheckState(Qt.Checked) self.stretchParam2Stats.setCheckState(Qt.Checked) else: self.stretchParam1.setEnabled(False) self.stretchParam2.setEnabled(False) self.stretchParam1Stats.setCheckState(Qt.Unchecked) self.stretchParam2Stats.setCheckState(Qt.Unchecked) self.stretchParam1Stats.setEnabled(False) self.stretchParam2Stats.setEnabled(False) self.stretchParam1.setToolTip("") self.stretchParam2.setToolTip("")
class FileManager(QWidget, _HalWidgetBase): def __init__(self, parent=None): super(FileManager, self).__init__(parent) self.title = 'PyQt5 file system view - pythonspot.com' self.left = 10 self.top = 10 self.width = 640 self.height = 480 self.default_path = (os.path.join(os.path.expanduser('~'), 'linuxcnc/nc_files/examples')) self.user_path = (os.path.join('/media')) self.currentPath = None self.initUI() # add shown text and hidden filter data from the INI def fillCombobox(self, data): for i in data: self.cb.addItem(i[0], i[1]) def initUI(self): self.setWindowTitle(self.title) self.setGeometry(self.left, self.top, self.width, self.height) self.model = QFileSystemModel() self.model.setRootPath(QDir.currentPath()) self.model.setFilter(QDir.AllDirs | QDir.NoDot | QDir.Files) self.model.setNameFilterDisables(False) self.list = QListView() self.list.setModel(self.model) self.updateDirectoryView(self.default_path) self.list.setWindowTitle("Dir View") self.list.resize(640, 480) self.list.clicked[QModelIndex].connect(self.clicked) self.list.activated.connect(self._getPathActivated) #self.list.currentChanged = self.currentChanged self.list.setAlternatingRowColors(True) self.cb = QComboBox() self.cb.currentIndexChanged.connect(self.filterChanged) self.fillCombobox(INFO.PROGRAM_FILTERS_EXTENSIONS) #self.cb.setSizePolicy(QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) self.button = QPushButton() self.button.setText('Media') self.button.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) self.button.setToolTip('Jump to Media directory') self.button.clicked.connect(self.onMediaClicked) self.button2 = QPushButton() self.button2.setText('User') self.button2.setSizePolicy( QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)) self.button2.setToolTip('Jump to linuxcnc directory') self.button2.clicked.connect(self.onUserClicked) hbox = QHBoxLayout() hbox.addWidget(self.button) hbox.addWidget(self.button2) hbox.addWidget(self.cb) windowLayout = QVBoxLayout() windowLayout.addWidget(self.list) windowLayout.addLayout(hbox) self.setLayout(windowLayout) self.show() # this could return the current/previous selected as it's selected. # need to uncomment monkey patch of self.list.currentChanged above # so far this is not needed def currentChanged(self, c, p): dir_path = self.model.filePath(c) def updateDirectoryView(self, path): self.list.setRootIndex(self.model.setRootPath(path)) # retrieve selected filter (it's held as QT.userData) def filterChanged(self, index): userdata = self.cb.itemData(index) self.model.setNameFilters(userdata) def clicked(self, index): # the signal passes the index of the clicked item dir_path = self.model.filePath(index) if self.model.fileInfo(index).isFile(): self.currentPath = dir_path return root_index = self.model.setRootPath(dir_path) self.list.setRootIndex(root_index) def onMediaClicked(self): self.updateDirectoryView(self.user_path) def onUserClicked(self): self.updateDirectoryView(self.default_path) def select_row(self, style): style = style.lower() selectionModel = self.list.selectionModel() row = selectionModel.currentIndex().row() self.rows = self.model.rowCount(self.list.rootIndex()) if style == 'last': row = self.rows elif style == 'up': if row > 0: row -= 1 else: row = 0 elif style == 'down': if row < self.rows: row += 1 else: row = self.rows else: return top = self.model.index(row, 0, self.list.rootIndex()) selectionModel.setCurrentIndex( top, QItemSelectionModel.Select | QItemSelectionModel.Rows) selection = QItemSelection(top, top) selectionModel.clearSelection() selectionModel.select(selection, QItemSelectionModel.Select) # returns the current highlighted (selected) path as well as # whether it's a file or not. def getCurrentSelected(self): selectionModel = self.list.selectionModel() index = selectionModel.currentIndex() dir_path = self.model.filePath(index) if self.model.fileInfo(index).isFile(): return (dir_path, True) else: return (dir_path, False) def _hal_init(self): if self.PREFS_: last_path = self.PREFS_.getpref('last_loaded_directory', self.default_path, str, 'BOOK_KEEPING') self.updateDirectoryView(last_path) LOG.debug("lAST FILE PATH: {}".format(last_path)) else: LOG.debug("lAST FILE PATH: {}".format(self.default_path)) self.updateDirectoryView(self.default_path) # get current selection and update the path # then if the path is good load it into linuxcnc # record it in the preference file if available def _getPathActivated(self): row = self.list.selectionModel().currentIndex() self.clicked(row) fname = self.currentPath if fname is None: return if fname: self.load(fname) # this can be class patched to do something else def load(self, fname=None): if fname is None: self._getPathActivated() return self.recordBookKeeping() ACTION.OPEN_PROGRAM(fname) STATUS.emit('update-machine-log', 'Loaded: ' + fname, 'TIME') # this can be class patched to do something else def recordBookKeeping(self): fname = self.currentPath if fname is None: return if self.PREFS_: self.PREFS_.putpref('last_loaded_directory', self.model.rootPath(), str, 'BOOK_KEEPING') self.PREFS_.putpref('RecentPath_0', fname, str, 'BOOK_KEEPING') # moves the selection up # used with MPG scrolling def up(self): self.select_row('up') # moves the selection down # used with MPG scrolling def down(self): self.select_row('down')
class ProjectTreeColumn(QDialog): # Signalsnproject = dockWidget = pyqtSignal('PyQt_PyObject') undockWidget = pyqtSignal() changeTitle = pyqtSignal('PyQt_PyObject', 'QString') updateLocator = pyqtSignal() activeProjectChanged = pyqtSignal() def __init__(self, parent=None): super(ProjectTreeColumn, self).__init__(parent) vbox = QVBoxLayout(self) vbox.setSizeConstraint(QVBoxLayout.SetDefaultConstraint) vbox.setContentsMargins(0, 0, 0, 0) vbox.setSpacing(0) self._buttons = [] frame = QFrame() frame.setObjectName("actionbar") box = QVBoxLayout(frame) box.setContentsMargins(1, 1, 1, 1) box.setSpacing(0) self._combo_project = QComboBox() self._combo_project.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Fixed) self._combo_project.setSizeAdjustPolicy( QComboBox.AdjustToMinimumContentsLengthWithIcon) self._combo_project.setObjectName("combo_projects") box.addWidget(self._combo_project) vbox.addWidget(frame) self._combo_project.setContextMenuPolicy(Qt.CustomContextMenu) self._projects_area = QStackedLayout() logger.debug("This is the projects area") vbox.addLayout(self._projects_area) # Empty widget self._empty_proj = QLabel(translations.TR_NO_PROJECTS) self._empty_proj.setAlignment(Qt.AlignCenter) self._empty_proj.setAutoFillBackground(True) self._empty_proj.setBackgroundRole(QPalette.Base) self._projects_area.addWidget(self._empty_proj) self._projects_area.setCurrentWidget(self._empty_proj) self.projects = [] self._combo_project.activated.connect( self._change_current_project) self._combo_project.customContextMenuRequested[ 'const QPoint&'].connect(self.context_menu_for_root) connections = ( { "target": "main_container", "signal_name": "addToProject", "slot": self._add_file_to_project }, { "target": "main_container", "signal_name": "showFileInExplorer", "slot": self._show_file_in_explorer }, ) IDE.register_service('projects_explorer', self) IDE.register_signals('projects_explorer', connections) ExplorerContainer.register_tab(translations.TR_TAB_PROJECTS, self) # FIXME: Should have a ninja settings object that stores tree state # FIXME: Or bettter, application data object # TODO: check this: # self.connect(ide, SIGNAL("goingDown()"), # self.tree_projects.shutdown) # def close_project_signal(): # self.emit(SIGNAL("updateLocator()")) def install_tab(self): ide = IDE.get_service('ide') ui_tools.install_shortcuts(self, actions.PROJECTS_TREE_ACTIONS, ide) ide.goingDown.connect(self._on_ide_going_down) def _on_ide_going_down(self): """Save some settings before close""" if self.current_tree is None: return ds = IDE.data_settings() show_filesize = not bool(self.current_tree.isColumnHidden(1)) ds.setValue("projectsExplorer/showFileSize", show_filesize) def load_session_projects(self, projects): for project in projects: if os.path.exists(project): self._open_project_folder(project) def open_project_folder(self, folderName=None): if settings.WORKSPACE: directory = settings.WORKSPACE else: directory = os.path.expanduser("~") if folderName is None: folderName = QFileDialog.getExistingDirectory( self, translations.TR_OPEN_PROJECT_DIRECTORY, directory) logger.debug("Choosing Foldername") if folderName: if not file_manager.folder_exists(folderName): QMessageBox.information( self, translations.TR_PROJECT_NONEXIST_TITLE, translations.TR_PROJECT_NONEXIST % folderName) return logger.debug("Opening %s" % folderName) for p in self.projects: if p.project.path == folderName: QMessageBox.information( self, translations.TR_PROJECT_PATH_ALREADY_EXIST_TITLE, translations.TR_PROJECT_PATH_ALREADY_EXIST % folderName) return self._open_project_folder(folderName) def _open_project_folder(self, folderName): ninjaide = IDE.get_service("ide") # TODO: handle exception when .nja file is empty project = NProject(folderName) qfsm = ninjaide.filesystem.open_project(project) if qfsm: self.add_project(project) self.save_recent_projects(folderName) # FIXME: show editor area? # main_container = IDE.get_service('main_container') # if main_container: # main_container.show_editor_area() if len(self.projects) > 1: title = "%s (%s)" % ( translations.TR_TAB_PROJECTS, len(self.projects)) else: title = translations.TR_TAB_PROJECTS self.changeTitle.emit(self, title) def _add_file_to_project(self, path): """Add the file for 'path' in the project the user choose here.""" if self._projects_area.count() > 0: path_project = [self.current_project] _add_to_project = add_to_project.AddToProject(path_project, self) _add_to_project.exec_() if not _add_to_project.path_selected: return main_container = IDE.get_service('main_container') if not main_container: return editorWidget = main_container.get_current_editor() if not editorWidget.file_path: name = QInputDialog.getText( None, translations.TR_ADD_FILE_TO_PROJECT, translations.TR_FILENAME + ": ")[0] if not name: QMessageBox.information( self, translations.TR_INVALID_FILENAME, translations.TR_INVALID_FILENAME_ENTER_A_FILENAME) return else: name = file_manager.get_basename(editorWidget.file_path) new_path = file_manager.create_path( _add_to_project.path_selected, name) ide_srv = IDE.get_service("ide") old_file = ide_srv.get_or_create_nfile(path) new_file = old_file.save(editorWidget.text(), new_path) # FIXME: Make this file replace the original in the open tab else: pass # Message about no project def _show_file_in_explorer(self, path): '''Iterate through the list of available projects and show the current file in the explorer view for the first project that contains it (i.e. if the same file is included in multiple open projects, the path will be expanded for the first project only). Note: This slot is connected to the main container's "showFileInExplorer(QString)" signal.''' central = IDE.get_service('central_container') if central and not central.is_lateral_panel_visible(): return for project in self.projects: index = project.model().index(path) if index.isValid(): # This highlights the index in the tree for us project.scrollTo(index, QAbstractItemView.EnsureVisible) project.setCurrentIndex(index) break def add_project(self, project): if project not in self.projects: self._combo_project.addItem(project.name) tooltip = utils.path_with_tilde_homepath(project.path) self._combo_project.setToolTip(tooltip) index = self._combo_project.count() - 1 self._combo_project.setItemData(index, project) ptree = TreeProjectsWidget(project) self._projects_area.addWidget(ptree) ptree.closeProject['PyQt_PyObject'].connect(self._close_project) pmodel = project.model ptree.setModel(pmodel) pindex = pmodel.index(pmodel.rootPath()) ptree.setRootIndex(pindex) self.projects.append(ptree) self._projects_area.setCurrentWidget(ptree) # Can be empty widget self._combo_project.setCurrentIndex(index) # FIXME: improve? # if len(self.projects) == 1: # self._combo_project.currentIndexChanged[int].connect( # self._change_current_project) def _close_project(self, widget): """Close the project related to the tree widget.""" index = self._combo_project.currentIndex() self.projects.remove(widget) # index + 1 is necessary because the widget # with index 0 is the empty widget self._projects_area.takeAt(index + 1) self._combo_project.removeItem(index) index = self._combo_project.currentIndex() self._projects_area.setCurrentIndex(index + 1) ninjaide = IDE.get_service('ide') ninjaide.filesystem.close_project(widget.project.path) widget.deleteLater() if len(self.projects) > 1: title = "%s (%s)" % ( translations.TR_TAB_PROJECTS, len(self.projects)) else: title = translations.TR_TAB_PROJECTS self.changeTitle.emit(self, title) self.updateLocator.emit() def _change_current_project(self, index): nproject = self._combo_project.itemData(index) ninjaide = IDE.get_service("ide") projects = ninjaide.get_projects() for project in projects.values(): if project == nproject: nproject.is_current = True else: project.is_current = False self._projects_area.setCurrentIndex(index + 1) self.activeProjectChanged.emit() def close_opened_projects(self): for project in reversed(self.projects): self._close_project(project) def save_project(self): """Save all the opened files that belongs to the actual project.""" if self.current_project is not None: path = self.current_project.path main_container = IDE.get_service('main_container') if path and main_container: main_container.save_project(path) def create_new_project(self): wizard = new_project_manager.NewProjectManager(self) wizard.show() @property def current_project(self): project = None if self._projects_area.count() > 0 and self.current_tree is not None: project = self.current_tree.project return project @property def current_tree(self): tree = None widget = self._projects_area.currentWidget() if isinstance(widget, TreeProjectsWidget): tree = widget return tree def set_current_item(self, path): if self.current_project is not None: self.current_tree.set_current_item(path) def save_recent_projects(self, folder): settings = IDE.data_settings() recent_project_list = settings.value('recentProjects', {}) # if already exist on the list update the date time projectProperties = json_manager.read_ninja_project(folder) name = projectProperties.get('name', '') description = projectProperties.get('description', '') if not name: name = file_manager.get_basename(folder) if not description: description = translations.TR_NO_DESCRIPTION if folder in recent_project_list: properties = recent_project_list[folder] properties["lastopen"] = QDateTime.currentDateTime() properties["name"] = name properties["description"] = description recent_project_list[folder] = properties else: recent_project_list[folder] = { "name": name, "description": description, "isFavorite": False, "lastopen": QDateTime.currentDateTime()} # if the length of the project list it's high that 10 then delete # the most old # TODO: add the length of available projects to setting if len(recent_project_list) > MAX_RECENT_PROJECTS: del recent_project_list[self.find_most_old_open( recent_project_list)] settings.setValue('recentProjects', recent_project_list) def find_most_old_open(self, recent_project_list): listFounder = [] for recent_project_path, content in list(recent_project_list.items()): listFounder.append((recent_project_path, int( content["lastopen"].toString("yyyyMMddHHmmzzz")))) listFounder = sorted(listFounder, key=lambda date: listFounder[1], reverse=True) # sort by date last used return listFounder[0][0] def reject(self): if self.parent() is None: self.dockWidget.emit(self) def closeEvent(self, event): self.dockWidget.emit(self) event.ignore() def context_menu_for_root(self): menu = QMenu(self) if self.current_tree is None: # No projects return path = self.current_tree.project.path # Reset index self.current_tree.setCurrentIndex(QModelIndex()) action_add_file = menu.addAction(QIcon(":img/new"), translations.TR_ADD_NEW_FILE) action_add_folder = menu.addAction(QIcon( ":img/openProj"), translations.TR_ADD_NEW_FOLDER) action_create_init = menu.addAction(translations.TR_CREATE_INIT) menu.addSeparator() action_run_project = menu.addAction(translations.TR_RUN_PROJECT) action_properties = menu.addAction(translations.TR_PROJECT_PROPERTIES) action_show_file_size = menu.addAction(translations.TR_SHOW_FILESIZE) menu.addSeparator() action_close = menu.addAction(translations.TR_CLOSE_PROJECT) # Connections action_add_file.triggered.connect( lambda: self.current_tree._add_new_file(path)) action_add_folder.triggered.connect( lambda: self.current_tree._add_new_folder(path)) action_create_init.triggered.connect(self.current_tree._create_init) action_run_project.triggered.connect( self.current_tree._execute_project) action_properties.triggered.connect( self.current_tree.open_project_properties) action_close.triggered.connect(self.current_tree._close_project) action_show_file_size.triggered.connect( self.current_tree.show_filesize_info) # menu for the project for m in self.current_tree.extra_menus_by_scope['project']: if isinstance(m, QMenu): menu.addSeparator() menu.addMenu(m) # show the menu! menu.exec_(QCursor.pos())
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 = [] fee_widgets = [] tx_widgets = [] oa_widgets = [] services_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.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)) msg = '\n'.join([ _('Time based: fee rate is based on average confirmation time estimates' ), _('Mempool based: fee rate is targeting a depth in the memory pool' ) ]) fee_type_label = HelpLabel(_('Fee estimation') + ':', msg) fee_type_combo = QComboBox() fee_type_combo.addItems([_('Static'), _('ETA'), _('Mempool')]) fee_type_combo.setCurrentIndex((2 if self.config.use_mempool_fees( ) else 1) if self.config.is_dynfee() else 0) def on_fee_type(x): self.config.set_key('mempool_fees', x == 2) self.config.set_key('dynamic_fees', x > 0) fee_type_combo.currentIndexChanged.connect(on_fee_type) fee_widgets.append((fee_type_label, fee_type_combo)) 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) fee_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) fee_widgets.append((batch_rbf_cb, None)) # lightning lightning_widgets = [] backup_help = _( """If you configure a backup directory, a backup of your wallet file will be saved everytime you create a new channel.\n\nA backup file cannot be used as a wallet; it can only be used to retrieve the funds locked in your channels, by requesting your channels to be force closed (using data loss protection).\n\nIf the remote node is online, they will force-close your channels when you open the backup file. Note that a backup is not strictly necessary for that; if the remote party is online but they cannot reach you because you lost your wallet file, they should eventually close your channels, and your funds should be sent to an address recoverable from your seed (using static_remotekey).\n\nIf the remote node is not online, you can use the backup file to force close your channels, but only at the risk of losing all your funds in the channel, because you will be broadcasting an old state.""" ) backup_dir = self.config.get('backup_dir') backup_dir_label = HelpLabel(_('Backup directory') + ':', backup_help) self.backup_dir_e = QPushButton(backup_dir) self.backup_dir_e.clicked.connect(self.select_backup_dir) lightning_widgets.append((backup_dir_label, self.backup_dir_e)) help_persist = _( """If this option is checked, Electrum will persist as a daemon after you close all your wallet windows. Your local watchtower will keep running, and it will protect your channels even if your wallet is not open. For this to work, your computer needs to be online regularly.""") persist_cb = QCheckBox(_("Run as daemon after the GUI is 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)) # Services ssl_cert = self.config.get('ssl_certfile') ssl_cert_label = HelpLabel( _('SSL cert file') + ':', 'certificate file, with intermediate certificates if needed') self.ssl_cert_e = QPushButton(ssl_cert) self.ssl_cert_e.clicked.connect(self.select_ssl_certfile) services_widgets.append((ssl_cert_label, self.ssl_cert_e)) ssl_privkey = self.config.get('ssl_keyfile') ssl_privkey_label = HelpLabel(_('SSL key file') + ':', '') self.ssl_privkey_e = QPushButton(ssl_privkey) self.ssl_privkey_e.clicked.connect(self.select_ssl_privkey) services_widgets.append((ssl_privkey_label, self.ssl_privkey_e)) ssl_domain_label = HelpLabel(_('SSL domain') + ':', '') self.ssl_domain_e = QLineEdit('') self.ssl_domain_e.setReadOnly(True) services_widgets.append((ssl_domain_label, self.ssl_domain_e)) self.check_ssl_config() hostname = self.config.get('services_hostname', 'localhost') hostname_label = HelpLabel( _('Hostname') + ':', 'must match your SSL domain') self.hostname_e = QLineEdit(hostname) self.hostname_e.editingFinished.connect(self.on_hostname) services_widgets.append((hostname_label, self.hostname_e)) payserver_cb = QCheckBox(_("Run PayServer")) payserver_cb.setToolTip("Configure a port") payserver_cb.setChecked(bool(self.config.get('run_payserver', False))) def on_payserver_checked(x): self.config.set_key('run_payserver', bool(x)) self.payserver_port_e.setEnabled(bool(x)) payserver_cb.stateChanged.connect(on_payserver_checked) payserver_port = self.config.get('payserver_port', 8002) self.payserver_port_e = QLineEdit(str(payserver_port)) self.payserver_port_e.editingFinished.connect(self.on_payserver_port) self.payserver_port_e.setEnabled( self.config.get('run_payserver', False)) services_widgets.append((payserver_cb, self.payserver_port_e)) help_local_wt = _( """To run a watchtower, you must run Electrum on a machine that is always connected to the internet. Configure a port if you want it to be public.""" ) local_wt_cb = QCheckBox(_("Run Watchtower")) local_wt_cb.setToolTip(help_local_wt) local_wt_cb.setChecked(bool(self.config.get('run_watchtower', False))) def on_local_wt_checked(x): self.config.set_key('run_watchtower', bool(x)) self.local_wt_port_e.setEnabled(bool(x)) local_wt_cb.stateChanged.connect(on_local_wt_checked) watchtower_port = self.config.get('watchtower_port', '') self.local_wt_port_e = QLineEdit(str(watchtower_port)) self.local_wt_port_e.setEnabled( self.config.get('run_watchtower', False)) self.local_wt_port_e.editingFinished.connect(self.on_watchtower_port) services_widgets.append((local_wt_cb, self.local_wt_port_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.window.decimal_point = base_unit_name_to_decimal_point( unit_result) 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)) 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', False)) 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()) 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')), (fee_widgets, _('Fees')), (tx_widgets, _('Transactions')), (lightning_widgets, _('Lightning')), (fiat_widgets, _('Fiat')), (services_widgets, _('Services')), (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 MainGlyphWindow(QMainWindow): def __init__(self, glyph, parent=None): super().__init__(parent) menuBar = self.menuBar() fileMenu = QMenu("&File", self) fileMenu.addAction("E&xit", self.close, QKeySequence.Quit) menuBar.addMenu(fileMenu) editMenu = QMenu("&Edit", self) self._undoAction = editMenu.addAction( "&Undo", self.undo, QKeySequence.Undo) self._redoAction = editMenu.addAction( "&Redo", self.redo, QKeySequence.Redo) editMenu.addSeparator() # XXX action = editMenu.addAction("C&ut", self.cutOutlines, QKeySequence.Cut) action.setEnabled(False) self._copyAction = editMenu.addAction( "&Copy", self.copyOutlines, QKeySequence.Copy) editMenu.addAction("&Paste", self.pasteOutlines, QKeySequence.Paste) editMenu.addAction( "Select &All", self.selectAll, QKeySequence.SelectAll) editMenu.addAction("&Deselect", self.deselect, "Ctrl+D") menuBar.addMenu(editMenu) glyphMenu = QMenu("&Glyph", self) glyphMenu.addAction("&Next Glyph", lambda: self.glyphOffset(1), "End") glyphMenu.addAction( "&Previous Glyph", lambda: self.glyphOffset(-1), "Home") glyphMenu.addAction("&Go To…", self.changeGlyph, "G") glyphMenu.addSeparator() self._layerAction = glyphMenu.addAction( "&Layer Actions…", self.layerActions, "L") menuBar.addMenu(glyphMenu) # create tools and buttons toolBars self._tools = [] self._toolsToolBar = QToolBar("Tools", self) self._toolsToolBar.setMovable(False) self._buttons = [] self._buttonsToolBar = QToolBar("Buttons", self) self._buttonsToolBar.setMovable(False) self.addToolBar(self._toolsToolBar) self.addToolBar(self._buttonsToolBar) # http://www.setnode.com/blog/right-aligning-a-button-in-a-qtoolbar/ self._layersToolBar = QToolBar("Layers", self) spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._currentLayerBox = QComboBox(self) self._currentLayerBox.currentIndexChanged.connect( self._layerChanged) self._layersToolBar.addWidget(spacer) self._layersToolBar.addWidget(self._currentLayerBox) self._layersToolBar.setContentsMargins(0, 0, 2, 0) self._layersToolBar.setMovable(False) self.addToolBar(self._layersToolBar) viewMenu = self.createPopupMenu() viewMenu.setTitle("View") viewMenu.addSeparator() action = viewMenu.addAction("Lock Toolbars", self.lockToolBars) action.setCheckable(True) action.setChecked(True) menuBar.addMenu(viewMenu) self.view = GlyphView(self) self.setGlyph(glyph) selectionTool = self.installTool(SelectionTool) selectionTool.trigger() self.installTool(PenTool) self.installTool(RulerTool) self.installTool(KnifeTool) self.installButton(RemoveOverlapButton) self.setCentralWidget(self.view.scrollArea()) self.resize(900, 700) self.view.setFocus(True) # ---------- # Menu items # ---------- def glyphOffset(self, offset): currentGlyph = self.view.glyph() font = currentGlyph.font glyphOrder = font.glyphOrder # should be enforced in fontView already if not (glyphOrder and len(glyphOrder)): return index = glyphOrder.index(currentGlyph.name) newIndex = (index + offset) % len(glyphOrder) glyph = font[glyphOrder[newIndex]] self.setGlyph(glyph) def changeGlyph(self): glyph = self.view.glyph() newGlyph, ok = GotoDialog.getNewGlyph(self, glyph) if ok and newGlyph is not None: self.setGlyph(newGlyph) def layerActions(self): glyph = self.view.glyph() newLayer, action, ok = LayerActionsDialog.getLayerAndAction( self, glyph) if ok and newLayer is not None: # TODO: whole glyph for now, but consider selection too if not glyph.name in newLayer: newLayer.newGlyph(glyph.name) otherGlyph = newLayer[glyph.name] otherGlyph.disableNotifications() if action == "Swap": tempGlyph = TGlyph() otherGlyph.drawPoints(tempGlyph.getPointPen()) tempGlyph.width = otherGlyph.width otherGlyph.clearContours() glyph.drawPoints(otherGlyph.getPointPen()) otherGlyph.width = glyph.width if action != "Copy": glyph.disableNotifications() glyph.clearContours() if action == "Swap": tempGlyph.drawPoints(glyph.getPointPen()) glyph.width = tempGlyph.width glyph.enableNotifications() otherGlyph.enableNotifications() def undo(self): glyph = self.view.glyph() glyph.undo() def redo(self): glyph = self.view.glyph() glyph.redo() def cutOutlines(self): pass def copyOutlines(self): glyph = self.view.glyph() clipboard = QApplication.clipboard() mimeData = QMimeData() copyGlyph = glyph.getRepresentation("defconQt.FilterSelection") mimeData.setData("application/x-defconQt-glyph-data", pickle.dumps([copyGlyph.serialize( blacklist=("name", "unicode") )])) clipboard.setMimeData(mimeData) def pasteOutlines(self): glyph = self.view.glyph() clipboard = QApplication.clipboard() mimeData = clipboard.mimeData() if mimeData.hasFormat("application/x-defconQt-glyph-data"): data = pickle.loads(mimeData.data( "application/x-defconQt-glyph-data")) if len(data) == 1: pen = glyph.getPointPen() pasteGlyph = TGlyph() pasteGlyph.deserialize(data[0]) # TODO: if we serialize selected state, we don't need to do # this pasteGlyph.selected = True if len(pasteGlyph) or len(pasteGlyph.components) or \ len(pasteGlyph.anchors): glyph.prepareUndo() pasteGlyph.drawPoints(pen) def selectAll(self): glyph = self.view.glyph() glyph.selected = True if not len(glyph): for component in glyph.components: component.selected = True def deselect(self): glyph = self.view.glyph() for anchor in glyph.anchors: anchor.selected = False for component in glyph.components: component.selected = False glyph.selected = False def lockToolBars(self): action = self.sender() movable = not action.isChecked() for toolBar in ( self._toolsToolBar, self._buttonsToolBar, self._layersToolBar): toolBar.setMovable(movable) # -------------------------- # Tools & buttons management # -------------------------- def installTool(self, tool): action = self._toolsToolBar.addAction( QIcon(tool.iconPath), tool.name, self._setViewTool) action.setCheckable(True) num = len(self._tools) action.setData(num) action.setShortcut(QKeySequence(str(num + 1))) self._tools.append(tool(parent=self.view)) return action def uninstallTool(self, tool): pass # XXX def _setViewTool(self): action = self.sender() index = action.data() newTool = self._tools[index] if newTool == self.view.currentTool(): action.setChecked(True) return ok = self.view.setCurrentTool(newTool) # if view did change tool, disable them all and enable the one we want # otherwise, just disable the tool that was clicked. # previously we used QActionGroup to have exclusive buttons, but doing # it manually allows us to NAK a button change. if ok: for act in self._toolsToolBar.actions(): act.setChecked(False) action.setChecked(ok) def installButton(self, button): action = self._buttonsToolBar.addAction( QIcon(button.iconPath), button.name, self._buttonAction) action.setData(len(self._buttons)) self._buttons.append(button(parent=self.view)) return action def uninstallButton(self, button): pass # XXX def _buttonAction(self): action = self.sender() index = action.data() button = self._buttons[index] button.clicked() # -------------------- # Notification support # -------------------- # glyph def _subscribeToGlyph(self, glyph): if glyph is not None: glyph.addObserver(self, "_glyphChanged", "Glyph.Changed") glyph.addObserver(self, "_glyphNameChanged", "Glyph.NameChanged") glyph.addObserver( self, "_glyphSelectionChanged", "Glyph.SelectionChanged") undoManager = glyph.undoManager undoManager.canUndoChanged.connect(self._undoAction.setEnabled) undoManager.canRedoChanged.connect(self._redoAction.setEnabled) self._subscribeToFontAndLayerSet(glyph.font) def _unsubscribeFromGlyph(self, glyph): if glyph is not None: glyph.removeObserver(self, "Glyph.Changed") glyph.removeObserver(self, "Glyph.NameChanged") glyph.removeObserver(self, "Glyph.SelectionChanged") undoManager = glyph.undoManager undoManager.canUndoChanged.disconnect(self._undoAction.setEnabled) undoManager.canRedoChanged.disconnect(self._redoAction.setEnabled) self._unsubscribeFromFontAndLayerSet(glyph.font) def _glyphChanged(self, notification): self.view.glyphChanged() def _glyphNameChanged(self, notification): glyph = self.view.glyph() self.setWindowTitle(glyph.name, glyph.font) def _glyphSelectionChanged(self, notification): self._updateSelection() self.view.glyphChanged() def _fontInfoChanged(self, notification): self.view.fontInfoChanged() glyph = self.view.glyph() self.setWindowTitle(glyph.name, glyph.font) # layers & font def _subscribeToFontAndLayerSet(self, font): """Note: called by _subscribeToGlyph.""" if font is None: return font.info.addObserver(self, "_fontInfoChanged", "Info.Changed") layerSet = font.layers if layerSet is None: return layerSet.addObserver(self, '_layerSetLayerDeleted', 'LayerSet.LayerDeleted') for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged', 'LayerSet.LayerOrderChanged'): layerSet.addObserver(self, '_layerSetEvents', event) def _unsubscribeFromFontAndLayerSet(self, font): """Note: called by _unsubscribeFromGlyph.""" if font is None: return font.info.removeObserver(self, "Info.Changed") layerSet = font.layers if layerSet is None: return for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged', 'LayerSet.LayerOrderChanged', 'LayerSet.LayerDeleted'): layerSet.removeObserver(self, event) def _layerSetEvents(self, notification): self._updateLayerControls() def _layerSetLayerDeleted(self, notification): self._layerSetEvents(notification) self._currentLayerBox.setCurrentIndex(0) # other updaters def _updateUndoRedo(self): glyph = self.view.glyph() self._undoAction.setEnabled(glyph.canUndo()) self._redoAction.setEnabled(glyph.canRedo()) def _updateSelection(self): def hasSelection(): glyph = self.view.glyph() for contour in glyph: if len(contour.selection): return True for anchor in glyph.anchors: if anchor.selected: return True for component in glyph.components: if component.selected: return True return False self._copyAction.setEnabled(hasSelection()) # -------------- # Public Methods # -------------- def setGlyph(self, glyph): currentGlyph = self.view.glyph() self._unsubscribeFromGlyph(currentGlyph) self._subscribeToGlyph(glyph) self.view.setGlyph(glyph) self._updateLayerControls() self._updateUndoRedo() self._updateSelection() self.setWindowTitle(glyph.name, glyph.font) def setDrawingAttribute(self, attr, value, layerName=None): self.view.setDrawingAttribute(attr, value, layerName) def drawingAttribute(self, attr, layerName=None): return self.view.drawingAttribute(attr, layerName) # ----------------- # Layers management # ----------------- def _layerChanged(self, newLayerIndex): glyph = self.view.glyph() layer = self._currentLayerBox.itemData(newLayerIndex) if layer is None: layer = self._makeLayer() if layer is None: # restore comboBox to active index layerSet = glyph.layerSet index = layerSet.layerOrder.index(glyph.layer.name) self._setLayerBoxIndex(index) return if glyph.name in layer: newGlyph = layer[glyph.name] else: # TODO: make sure we mimic defcon ufo3 APIs for that newGlyph = self._makeLayerGlyph(layer, glyph) self.setGlyph(newGlyph) # setting the layer-glyph here app = QApplication.instance() app.setCurrentGlyph(newGlyph) def _makeLayer(self): # TODO: what with duplicate names? glyph = self.view.glyph() newLayerName, ok = AddLayerDialog.getNewLayerName(self) if ok: layerSet = glyph.layerSet # TODO: this should return the layer layerSet.newLayer(newLayerName) return layerSet[newLayerName] else: return None def _makeLayerGlyph(self, layer, currentGlyph): glyph = layer.newGlyph(currentGlyph.name) glyph.width = currentGlyph.width glyph.template = True return glyph def _updateLayerControls(self): comboBox = self._currentLayerBox glyph = self.view.glyph() comboBox.blockSignals(True) comboBox.clear() for layer in glyph.layerSet: comboBox.addItem(layer.name, layer) comboBox.setCurrentText(glyph.layer.name) comboBox.addItem("New layer…", None) comboBox.blockSignals(False) self._layerAction.setEnabled(len(glyph.layerSet) > 1) def _setLayerBoxIndex(self, index): comboBox = self._currentLayerBox comboBox.blockSignals(True) comboBox.setCurrentIndex(index) comboBox.blockSignals(False) # --------------------- # QMainWindow functions # --------------------- def event(self, event): if event.type() == QEvent.WindowActivate: app = QApplication.instance() app.setCurrentGlyph(self.view.glyph()) return super().event(event) def closeEvent(self, event): glyph = self.view.glyph() self._unsubscribeFromGlyph(glyph) event.accept() def setWindowTitle(self, title, font=None): if font is not None: title = "%s – %s %s" % ( title, font.info.familyName, font.info.styleName) super().setWindowTitle(title)
class AudioTest(QMainWindow): PUSH_MODE_LABEL = "Enable push mode" PULL_MODE_LABEL = "Enable pull mode" SUSPEND_LABEL = "Suspend playback" RESUME_LABEL = "Resume playback" DurationSeconds = 1 ToneSampleRateHz = 600 DataSampleRateHz = 44100 def __init__(self): super(AudioTest, self).__init__() self.m_device = QAudioDeviceInfo.defaultOutputDevice() self.m_output = None self.initializeWindow() self.initializeAudio() def initializeWindow(self): layout = QVBoxLayout() self.m_deviceBox = QComboBox(activated=self.deviceChanged) for deviceInfo in QAudioDeviceInfo.availableDevices(QAudio.AudioOutput): self.m_deviceBox.addItem(deviceInfo.deviceName(), deviceInfo) layout.addWidget(self.m_deviceBox) self.m_modeButton = QPushButton(clicked=self.toggleMode) self.m_modeButton.setText(self.PUSH_MODE_LABEL) layout.addWidget(self.m_modeButton) self.m_suspendResumeButton = QPushButton( clicked=self.toggleSuspendResume) self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) layout.addWidget(self.m_suspendResumeButton) volumeBox = QHBoxLayout() volumeLabel = QLabel("Volume:") self.m_volumeSlider = QSlider(Qt.Horizontal, minimum=0, maximum=100, singleStep=10, valueChanged=self.volumeChanged) volumeBox.addWidget(volumeLabel) volumeBox.addWidget(self.m_volumeSlider) layout.addLayout(volumeBox) window = QWidget() window.setLayout(layout) self.setCentralWidget(window) def initializeAudio(self): self.m_pullTimer = QTimer(self, timeout=self.pullTimerExpired) self.m_pullMode = True self.m_format = QAudioFormat() self.m_format.setSampleRate(self.DataSampleRateHz) self.m_format.setChannelCount(1) self.m_format.setSampleSize(16) self.m_format.setCodec('audio/pcm') self.m_format.setByteOrder(QAudioFormat.LittleEndian) self.m_format.setSampleType(QAudioFormat.SignedInt) info = QAudioDeviceInfo(QAudioDeviceInfo.defaultOutputDevice()) if not info.isFormatSupported(self.m_format): qWarning("Default format not supported - trying to use nearest") self.m_format = info.nearestFormat(self.m_format) self.m_generator = Generator(self.m_format, self.DurationSeconds * 1000000, self.ToneSampleRateHz, self) self.createAudioOutput() def createAudioOutput(self): self.m_audioOutput = QAudioOutput(self.m_device, self.m_format) self.m_audioOutput.notify.connect(self.notified) self.m_audioOutput.stateChanged.connect(self.handleStateChanged) self.m_generator.start() self.m_audioOutput.start(self.m_generator) self.m_volumeSlider.setValue(self.m_audioOutput.volume() * 100) def deviceChanged(self, index): self.m_pullTimer.stop() self.m_generator.stop() self.m_audioOutput.stop() self.m_device = self.m_deviceBox.itemData(index) self.createAudioOutput() def volumeChanged(self, value): if self.m_audioOutput is not None: self.m_audioOutput.setVolume(value / 100.0) def notified(self): qWarning("bytesFree = %d, elapsedUSecs = %d, processedUSecs = %d" % ( self.m_audioOutput.bytesFree(), self.m_audioOutput.elapsedUSecs(), self.m_audioOutput.processedUSecs())) def pullTimerExpired(self): if self.m_audioOutput is not None and self.m_audioOutput.state() != QAudio.StoppedState: chunks = self.m_audioOutput.bytesFree() // self.m_audioOutput.periodSize() for _ in range(chunks): data = self.m_generator.read(self.m_audioOutput.periodSize()) if data is None or len(data) != self.m_audioOutput.periodSize(): break self.m_output.write(data) def toggleMode(self): self.m_pullTimer.stop() self.m_audioOutput.stop() if self.m_pullMode: self.m_modeButton.setText(self.PULL_MODE_LABEL) self.m_output = self.m_audioOutput.start() self.m_pullMode = False self.m_pullTimer.start(20) else: self.m_modeButton.setText(self.PUSH_MODE_LABEL) self.m_pullMode = True self.m_audioOutput.start(self.m_generator) self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) def toggleSuspendResume(self): if self.m_audioOutput.state() == QAudio.SuspendedState: qWarning("status: Suspended, resume()") self.m_audioOutput.resume() self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) elif self.m_audioOutput.state() == QAudio.ActiveState: qWarning("status: Active, suspend()") self.m_audioOutput.suspend() self.m_suspendResumeButton.setText(self.RESUME_LABEL) elif self.m_audioOutput.state() == QAudio.StoppedState: qWarning("status: Stopped, resume()") self.m_audioOutput.resume() self.m_suspendResumeButton.setText(self.SUSPEND_LABEL) elif self.m_audioOutput.state() == QAudio.IdleState: qWarning("status: IdleState") stateMap = { QAudio.ActiveState: "ActiveState", QAudio.SuspendedState: "SuspendedState", QAudio.StoppedState: "StoppedState", QAudio.IdleState: "IdleState"} def handleStateChanged(self, state): qWarning("state = " + self.stateMap.get(state, "Unknown"))
class Window(QWidget): def __init__(self): super(Window, self).__init__() self.renderArea = RenderArea() self.shapeComboBox = QComboBox() self.shapeComboBox.addItem("Polygon", RenderArea.Polygon) self.shapeComboBox.addItem("Rectangle", RenderArea.Rect) self.shapeComboBox.addItem("Rounded Rectangle", RenderArea.RoundedRect) self.shapeComboBox.addItem("Ellipse", RenderArea.Ellipse) self.shapeComboBox.addItem("Pie", RenderArea.Pie) self.shapeComboBox.addItem("Chord", RenderArea.Chord) self.shapeComboBox.addItem("Path", RenderArea.Path) self.shapeComboBox.addItem("Line", RenderArea.Line) self.shapeComboBox.addItem("Polyline", RenderArea.Polyline) self.shapeComboBox.addItem("Arc", RenderArea.Arc) self.shapeComboBox.addItem("Points", RenderArea.Points) self.shapeComboBox.addItem("Text", RenderArea.Text) self.shapeComboBox.addItem("Pixmap", RenderArea.Pixmap) shapeLabel = QLabel("&Shape:") shapeLabel.setBuddy(self.shapeComboBox) self.penWidthSpinBox = QSpinBox() self.penWidthSpinBox.setRange(0, 20) self.penWidthSpinBox.setSpecialValueText("0 (cosmetic pen)") penWidthLabel = QLabel("Pen &Width:") penWidthLabel.setBuddy(self.penWidthSpinBox) self.penStyleComboBox = QComboBox() self.penStyleComboBox.addItem("Solid", Qt.SolidLine) self.penStyleComboBox.addItem("Dash", Qt.DashLine) self.penStyleComboBox.addItem("Dot", Qt.DotLine) self.penStyleComboBox.addItem("Dash Dot", Qt.DashDotLine) self.penStyleComboBox.addItem("Dash Dot Dot", Qt.DashDotDotLine) self.penStyleComboBox.addItem("None", Qt.NoPen) penStyleLabel = QLabel("&Pen Style:") penStyleLabel.setBuddy(self.penStyleComboBox) self.penCapComboBox = QComboBox() self.penCapComboBox.addItem("Flat", Qt.FlatCap) self.penCapComboBox.addItem("Square", Qt.SquareCap) self.penCapComboBox.addItem("Round", Qt.RoundCap) penCapLabel = QLabel("Pen &Cap:") penCapLabel.setBuddy(self.penCapComboBox) self.penJoinComboBox = QComboBox() self.penJoinComboBox.addItem("Miter", Qt.MiterJoin) self.penJoinComboBox.addItem("Bevel", Qt.BevelJoin) self.penJoinComboBox.addItem("Round", Qt.RoundJoin) penJoinLabel = QLabel("Pen &Join:") penJoinLabel.setBuddy(self.penJoinComboBox) self.brushStyleComboBox = QComboBox() self.brushStyleComboBox.addItem("Linear Gradient", Qt.LinearGradientPattern) self.brushStyleComboBox.addItem("Radial Gradient", Qt.RadialGradientPattern) self.brushStyleComboBox.addItem("Conical Gradient", Qt.ConicalGradientPattern) self.brushStyleComboBox.addItem("Texture", Qt.TexturePattern) self.brushStyleComboBox.addItem("Solid", Qt.SolidPattern) self.brushStyleComboBox.addItem("Horizontal", Qt.HorPattern) self.brushStyleComboBox.addItem("Vertical", Qt.VerPattern) self.brushStyleComboBox.addItem("Cross", Qt.CrossPattern) self.brushStyleComboBox.addItem("Backward Diagonal", Qt.BDiagPattern) self.brushStyleComboBox.addItem("Forward Diagonal", Qt.FDiagPattern) self.brushStyleComboBox.addItem("Diagonal Cross", Qt.DiagCrossPattern) self.brushStyleComboBox.addItem("Dense 1", Qt.Dense1Pattern) self.brushStyleComboBox.addItem("Dense 2", Qt.Dense2Pattern) self.brushStyleComboBox.addItem("Dense 3", Qt.Dense3Pattern) self.brushStyleComboBox.addItem("Dense 4", Qt.Dense4Pattern) self.brushStyleComboBox.addItem("Dense 5", Qt.Dense5Pattern) self.brushStyleComboBox.addItem("Dense 6", Qt.Dense6Pattern) self.brushStyleComboBox.addItem("Dense 7", Qt.Dense7Pattern) self.brushStyleComboBox.addItem("None", Qt.NoBrush) brushStyleLabel = QLabel("&Brush Style:") brushStyleLabel.setBuddy(self.brushStyleComboBox) otherOptionsLabel = QLabel("Other Options:") self.antialiasingCheckBox = QCheckBox("&Antialiasing") self.transformationsCheckBox = QCheckBox("&Transformations") self.shapeComboBox.activated.connect(self.shapeChanged) self.penWidthSpinBox.valueChanged.connect(self.penChanged) self.penStyleComboBox.activated.connect(self.penChanged) self.penCapComboBox.activated.connect(self.penChanged) self.penJoinComboBox.activated.connect(self.penChanged) self.brushStyleComboBox.activated.connect(self.brushChanged) self.antialiasingCheckBox.toggled.connect(self.renderArea.setAntialiased) self.transformationsCheckBox.toggled.connect(self.renderArea.setTransformed) mainLayout = QGridLayout() mainLayout.setColumnStretch(0, 1) mainLayout.setColumnStretch(3, 1) mainLayout.addWidget(self.renderArea, 0, 0, 1, 4) mainLayout.setRowMinimumHeight(1, 6) mainLayout.addWidget(shapeLabel, 2, 1, Qt.AlignRight) mainLayout.addWidget(self.shapeComboBox, 2, 2) mainLayout.addWidget(penWidthLabel, 3, 1, Qt.AlignRight) mainLayout.addWidget(self.penWidthSpinBox, 3, 2) mainLayout.addWidget(penStyleLabel, 4, 1, Qt.AlignRight) mainLayout.addWidget(self.penStyleComboBox, 4, 2) mainLayout.addWidget(penCapLabel, 5, 1, Qt.AlignRight) mainLayout.addWidget(self.penCapComboBox, 5, 2) mainLayout.addWidget(penJoinLabel, 6, 1, Qt.AlignRight) mainLayout.addWidget(self.penJoinComboBox, 6, 2) mainLayout.addWidget(brushStyleLabel, 7, 1, Qt.AlignRight) mainLayout.addWidget(self.brushStyleComboBox, 7, 2) mainLayout.setRowMinimumHeight(8, 6) mainLayout.addWidget(otherOptionsLabel, 9, 1, Qt.AlignRight) mainLayout.addWidget(self.antialiasingCheckBox, 9, 2) mainLayout.addWidget(self.transformationsCheckBox, 10, 2) self.setLayout(mainLayout) self.shapeChanged() self.penChanged() self.brushChanged() self.antialiasingCheckBox.setChecked(True) self.setWindowTitle("Basic Drawing") def shapeChanged(self): shape = self.shapeComboBox.itemData(self.shapeComboBox.currentIndex(), IdRole) self.renderArea.setShape(shape) def penChanged(self): width = self.penWidthSpinBox.value() style = Qt.PenStyle(self.penStyleComboBox.itemData( self.penStyleComboBox.currentIndex(), IdRole)) cap = Qt.PenCapStyle(self.penCapComboBox.itemData( self.penCapComboBox.currentIndex(), IdRole)) join = Qt.PenJoinStyle(self.penJoinComboBox.itemData( self.penJoinComboBox.currentIndex(), IdRole)) self.renderArea.setPen(QPen(Qt.blue, width, style, cap, join)) def brushChanged(self): style = Qt.BrushStyle(self.brushStyleComboBox.itemData( self.brushStyleComboBox.currentIndex(), IdRole)) if style == Qt.LinearGradientPattern: linearGradient = QLinearGradient(0, 0, 100, 100) linearGradient.setColorAt(0.0, Qt.white) linearGradient.setColorAt(0.2, Qt.green) linearGradient.setColorAt(1.0, Qt.black) self.renderArea.setBrush(QBrush(linearGradient)) elif style == Qt.RadialGradientPattern: radialGradient = QRadialGradient(50, 50, 50, 70, 70) radialGradient.setColorAt(0.0, Qt.white) radialGradient.setColorAt(0.2, Qt.green) radialGradient.setColorAt(1.0, Qt.black) self.renderArea.setBrush(QBrush(radialGradient)) elif style == Qt.ConicalGradientPattern: conicalGradient = QConicalGradient(50, 50, 150) conicalGradient.setColorAt(0.0, Qt.white) conicalGradient.setColorAt(0.2, Qt.green) conicalGradient.setColorAt(1.0, Qt.black) self.renderArea.setBrush(QBrush(conicalGradient)) elif style == Qt.TexturePattern: self.renderArea.setBrush(QBrush(QPixmap(':/images/brick.png'))) else: self.renderArea.setBrush(QBrush(Qt.green, style))
class SelectParameterWidget(GenericParameterWidget): """Widget class for List parameter.""" def __init__(self, parameter, parent=None): """Constructor :param parameter: A ListParameter object. :type parameter: ListParameter """ super().__init__(parameter, parent) self.input = QComboBox() index = -1 current_index = -1 for opt in self._parameter.options_list: index += 1 if opt == self._parameter.value: current_index = index self.input.addItem(opt) self.input.setItemData(index, opt, Qt.UserRole) self.input.setCurrentIndex(current_index) self.inner_input_layout.addWidget(self.input) def raise_invalid_type_exception(self): message = 'Expecting element type of %s' % ( self._parameter.element_type.__name__) err = ValueError(message) return err def get_parameter(self): """Obtain list parameter object from the current widget state. :returns: A ListParameter from the current state of widget """ current_index = self.input.currentIndex() selected_value = self.input.itemData(current_index, Qt.UserRole) if hasattr(selected_value, 'toPyObject'): selected_value = selected_value.toPyObject() try: self._parameter.value = selected_value except ValueError: err = self.raise_invalid_type_exception() raise err return self._parameter def set_choice(self, choice): """Set choice value by item's string. :param choice: The choice. :type choice: str :returns: True if success, else False. :rtype: bool """ # Find index of choice choice_index = self._parameter.options_list.index(choice) if choice_index < 0: return False else: self.input.setCurrentIndex(choice_index) return True
class frmMain(QMainWindow, Ui_frmMain): def __init__(self, mem, parent = 0, flags = False): QMainWindow.__init__(self, None) self.setupUi(self) self.showMaximized() self.mem=mem self.mem.con.inactivity_timeout.connect(self.inactivity_timeout) database_update(self.mem.con, "caloriestracker", __versiondatetime__, "Qt") self.w=QWidget() self.statusBar.addWidget(QLabel(self.mem.con.url_string())) self.mem.load_db_data() ##CARGA TODOS LOS DATOS Y LOS VINCULA if self.mem.con.is_superuser(): self.setWindowTitle(self.tr("Calories Tracker 2019-{0} \xa9 (Admin mode)").format(__versiondatetime__.year))#print ("Xulpymoney 2010-{0} © €".encode('unicode-escape')) self.setWindowIcon(self.mem.qicon_admin()) else: self.setWindowTitle(self.tr("Calories Tracker 2019-{0} \xa9").format(__versiondatetime__.year)) self.lblUsers=QLabel() self.lblUsers.setText(self.tr("Select a user")+" ") self.tbUsers.addWidget(self.lblUsers) self.cmbUsers=QComboBox() self.tbUsers.addWidget(self.cmbUsers) self.mem.data.users.qcombobox(self.cmbUsers, self.mem.user, icons=True) self.cmbUsers.currentIndexChanged.connect(self.on_cmbUsers_currentIndexChanged) # Adds biometric data if empty if self.mem.user.last_biometrics.datetime==None: self.on_actionBiometricsAdd_triggered() @pyqtSlot(int) def on_cmbUsers_currentIndexChanged(self, index): self.mem.user=self.mem.data.users.find_by_id(int(self.cmbUsers.itemData(self.cmbUsers.currentIndex()))) print(self.mem.user, index) self.mem.settings.setValue("mem/currentuser", self.mem.user.id) self.on_actionBiometrics_triggered() qmessagebox("Changed user to {}".format(self.mem.user.name)) def inactivity_timeout(self): self.hide() qmessagebox(self.tr("Disconnecting due to {} minutes of inactivity.".format(self.mem.con.connectionTimeout()/60))) self.on_actionExit_triggered() @pyqtSlot() def on_actionExit_triggered(self): self.mem.__del__() print ("App correctly closed") self.close() self.destroy() @pyqtSlot() def on_actionAbout_triggered(self): from caloriestracker.ui.frmAbout import frmAbout fr=frmAbout(self.mem) fr.exec_() @pyqtSlot() def on_actionMemory_triggered(self): self.mem.data.load() @pyqtSlot() def on_actionReportIssue_triggered(self): QDesktopServices.openUrl(QUrl("https://github.com/turulomio/caloriestracker/issues/new")) @pyqtSlot() def on_actionHelp_triggered(self): def in_external(): QDesktopServices.openUrl(QUrl(self.mem.url_wiki)) try: user=environ['USER'] except: user=None try: ## Remove when qwebwenginewidgets work again from caloriestracker.ui.frmHelp import frmHelp if user!=None and user=="root": in_external() else: w=frmHelp(self.mem, self) w.show() except: in_external() @pyqtSlot() def on_actionSettings_triggered(self): w=frmSettings(self.mem, self) w.exec_() self.retranslateUi(self) @pyqtSlot() def on_actionMeals_triggered(self): from caloriestracker.ui.wdgMeals import wdgMeals self.w.close() self.w=wdgMeals(self.mem, self) self.layout.addWidget(self.w) self.w.show() @pyqtSlot() def on_actionMealsMost_triggered(self): from caloriestracker.ui.wdgMealsMost import wdgMealsMost self.w.close() self.w=wdgMealsMost(self.mem, self) self.layout.addWidget(self.w) self.w.show() @pyqtSlot() def on_actionUsers_triggered(self): from caloriestracker.ui.wdgUsers import wdgUsers self.w.close() self.w=wdgUsers(self.mem, self) self.layout.addWidget(self.w) self.w.show() @pyqtSlot() def on_actionUserCurrent_triggered(self): from caloriestracker.ui.frmUsersAdd import frmUsersAdd w=frmUsersAdd(self.mem, self.mem.data.users.selected, self) w.exec_() @pyqtSlot() def on_actionCuriosities_triggered(self): self.w.close() self.w=wdgCuriosities(self.mem, self) self.layout.addWidget(self.w) self.w.show() @pyqtSlot() def on_actionBiometrics_triggered(self): from caloriestracker.ui.wdgBiometrics import wdgBiometrics self.w.close() self.w=wdgBiometrics(self.mem, self) self.layout.addWidget(self.w) self.w.show() @pyqtSlot() def on_actionBiometricsAdd_triggered(self): from caloriestracker.ui.frmBiometricsAdd import frmBiometricsAdd w=frmBiometricsAdd(self.mem, None, self) w.exec_() @pyqtSlot() def on_actionCompanies_triggered(self): from caloriestracker.ui.wdgCompanies import wdgCompanies self.w.close() self.w=wdgCompanies(self.mem, self) self.layout.addWidget(self.w) self.w.show() self.w.txt.setFocus() @pyqtSlot() def on_actionCompaniesAdd_triggered(self): from caloriestracker.ui.frmCompaniesAdd import frmCompaniesAdd w=frmCompaniesAdd(self.mem, None, self) w.exec_() @pyqtSlot() def on_actionElaboratedProducts_triggered(self): from caloriestracker.ui.wdgProductsElaborated import wdgProductsElaborated self.w.close() self.w=wdgProductsElaborated(self.mem, self) self.layout.addWidget(self.w) self.w.show() self.w.txt.setFocus() @pyqtSlot() def on_actionElaboratedProductAdd_triggered(self): from caloriestracker.ui.frmProductsElaboratedAdd import frmProductsElaboratedAdd w=frmProductsElaboratedAdd(self.mem, None, self) w.exec_() elaborated=w.elaboratedproduct w=frmProductsElaboratedAdd(self.mem, elaborated, self) w.exec_() @pyqtSlot() def on_actionProductsUpdate_triggered(self): p=ProductManager(self.mem) p.update_from_internet() self.actionProductsUpdate.setText(self.tr("Update products from Internet")) self.actionProductsUpdate.setIcon(QIcon(":/caloriestracker/cloud_download.png")) @pyqtSlot() def on_actionProducts_triggered(self): self.w.close() from caloriestracker.ui.wdgProducts import wdgProducts self.w=wdgProducts(self.mem, self) self.layout.addWidget(self.w) self.w.show() self.w.txt.setFocus()
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 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 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 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 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 RegExpDialog(QDialog): MaxCaptures = 6 def __init__(self, parent=None): super(RegExpDialog, self).__init__(parent) self.patternComboBox = QComboBox() self.patternComboBox.setEditable(True) self.patternComboBox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) patternLabel = QLabel("&Pattern:") patternLabel.setBuddy(self.patternComboBox) self.escapedPatternLineEdit = QLineEdit() self.escapedPatternLineEdit.setReadOnly(True) palette = self.escapedPatternLineEdit.palette() palette.setBrush(QPalette.Base, palette.brush(QPalette.Disabled, QPalette.Base)) self.escapedPatternLineEdit.setPalette(palette) escapedPatternLabel = QLabel("&Escaped Pattern:") escapedPatternLabel.setBuddy(self.escapedPatternLineEdit) self.syntaxComboBox = QComboBox() self.syntaxComboBox.addItem("Regular expression v1", QRegExp.RegExp) self.syntaxComboBox.addItem("Regular expression v2", QRegExp.RegExp2) self.syntaxComboBox.addItem("Wildcard", QRegExp.Wildcard) self.syntaxComboBox.addItem("Fixed string", QRegExp.FixedString) syntaxLabel = QLabel("&Pattern Syntax:") syntaxLabel.setBuddy(self.syntaxComboBox) self.textComboBox = QComboBox() self.textComboBox.setEditable(True) self.textComboBox.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) textLabel = QLabel("&Text:") textLabel.setBuddy(self.textComboBox) self.caseSensitiveCheckBox = QCheckBox("Case &Sensitive") self.caseSensitiveCheckBox.setChecked(True) self.minimalCheckBox = QCheckBox("&Minimal") indexLabel = QLabel("Index of Match:") self.indexEdit = QLineEdit() self.indexEdit.setReadOnly(True) matchedLengthLabel = QLabel("Matched Length:") self.matchedLengthEdit = QLineEdit() self.matchedLengthEdit.setReadOnly(True) self.captureLabels = [] self.captureEdits = [] for i in range(self.MaxCaptures): self.captureLabels.append(QLabel("Capture %d:" % i)) self.captureEdits.append(QLineEdit()) self.captureEdits[i].setReadOnly(True) self.captureLabels[0].setText("Match:") checkBoxLayout = QHBoxLayout() checkBoxLayout.addWidget(self.caseSensitiveCheckBox) checkBoxLayout.addWidget(self.minimalCheckBox) checkBoxLayout.addStretch(1) mainLayout = QGridLayout() mainLayout.addWidget(patternLabel, 0, 0) mainLayout.addWidget(self.patternComboBox, 0, 1) mainLayout.addWidget(escapedPatternLabel, 1, 0) mainLayout.addWidget(self.escapedPatternLineEdit, 1, 1) mainLayout.addWidget(syntaxLabel, 2, 0) mainLayout.addWidget(self.syntaxComboBox, 2, 1) mainLayout.addLayout(checkBoxLayout, 3, 0, 1, 2) mainLayout.addWidget(textLabel, 4, 0) mainLayout.addWidget(self.textComboBox, 4, 1) mainLayout.addWidget(indexLabel, 5, 0) mainLayout.addWidget(self.indexEdit, 5, 1) mainLayout.addWidget(matchedLengthLabel, 6, 0) mainLayout.addWidget(self.matchedLengthEdit, 6, 1) for i in range(self.MaxCaptures): mainLayout.addWidget(self.captureLabels[i], 7 + i, 0) mainLayout.addWidget(self.captureEdits[i], 7 + i, 1) self.setLayout(mainLayout) self.patternComboBox.editTextChanged.connect(self.refresh) self.textComboBox.editTextChanged.connect(self.refresh) self.caseSensitiveCheckBox.toggled.connect(self.refresh) self.minimalCheckBox.toggled.connect(self.refresh) self.syntaxComboBox.currentIndexChanged.connect(self.refresh) self.patternComboBox.addItem("[A-Za-z_]+([A-Za-z_0-9]*)") self.textComboBox.addItem("(10 + delta4)* 32") self.setWindowTitle("RegExp") self.setFixedHeight(self.sizeHint().height()) self.refresh() def refresh(self): self.setUpdatesEnabled(False) pattern = self.patternComboBox.currentText() text = self.textComboBox.currentText() escaped = str(pattern) escaped.replace("\\", "\\\\") escaped.replace('"', '\\"') self.escapedPatternLineEdit.setText('"' + escaped + '"') rx = QRegExp(pattern) cs = (Qt.CaseSensitive if self.caseSensitiveCheckBox.isChecked() else Qt.CaseInsensitive) rx.setCaseSensitivity(cs) rx.setMinimal(self.minimalCheckBox.isChecked()) syntax = self.syntaxComboBox.itemData( self.syntaxComboBox.currentIndex()) rx.setPatternSyntax(syntax) palette = self.patternComboBox.palette() if rx.isValid(): palette.setColor(QPalette.Text, self.textComboBox.palette().color(QPalette.Text)) else: palette.setColor(QPalette.Text, Qt.red) self.patternComboBox.setPalette(palette) self.indexEdit.setText(str(rx.indexIn(text))) self.matchedLengthEdit.setText(str(rx.matchedLength())) for i in range(self.MaxCaptures): self.captureLabels[i].setEnabled(i <= rx.captureCount()) self.captureEdits[i].setEnabled(i <= rx.captureCount()) self.captureEdits[i].setText(rx.cap(i)) self.setUpdatesEnabled(True)
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 = [] 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(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_gossip = _("""If this option is enabled, Electrum will download the network channels graph and compute payment path locally, instead of using trampoline payments. """) gossip_cb = QCheckBox(_("Enable gossip (disable trampoline)")) gossip_cb.setToolTip(help_gossip) gossip_cb.setChecked(bool(self.config.get('use_gossip', False))) def on_gossip_checked(x): use_gossip = bool(x) 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) gossip_cb.stateChanged.connect(on_gossip_checked) lightning_widgets.append((gossip_cb, None)) 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 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)) 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)) 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')), (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 Window(QWidget): def __init__(self): super(Window, self).__init__() self.proxyModel = SortFilterProxyModel() self.proxyModel.setDynamicSortFilter(True) self.sourceGroupBox = QGroupBox("Original Model") self.proxyGroupBox = QGroupBox("Sorted/Filtered Model") self.sourceView = QTreeView() self.sourceView.setRootIsDecorated(False) self.sourceView.setAlternatingRowColors(True) self.proxyView = QTreeView() self.proxyView.setRootIsDecorated(False) self.proxyView.setAlternatingRowColors(True) self.proxyView.setModel(self.proxyModel) self.proxyView.setSortingEnabled(True) self.sortCaseSensitivityCheckBox = QCheckBox("Case sensitive sorting") self.filterCaseSensitivityCheckBox = QCheckBox("Case sensitive filter") self.filterPatternLineEdit = QLineEdit() self.filterPatternLabel = QLabel("&Filter pattern:") self.filterPatternLabel.setBuddy(self.filterPatternLineEdit) self.filterSyntaxComboBox = QComboBox() self.filterSyntaxComboBox.addItem("Regular expression", QRegExp.RegExp) self.filterSyntaxComboBox.addItem("Wildcard", QRegExp.Wildcard) self.filterSyntaxComboBox.addItem("Fixed string", QRegExp.FixedString) self.filterSyntaxLabel = QLabel("Filter &syntax:") self.filterSyntaxLabel.setBuddy(self.filterSyntaxComboBox) self.filterColumnComboBox = QComboBox() self.filterColumnComboBox.addItem("Subject") self.filterColumnComboBox.addItem("Sender") self.filterColumnComboBox.addItem("Date") self.filterColumnLabel = QLabel("Filter &column:") self.filterColumnLabel.setBuddy(self.filterColumnComboBox) self.filterPatternLineEdit.textChanged.connect( self.filterRegExpChanged) self.filterSyntaxComboBox.currentIndexChanged.connect( self.filterRegExpChanged) self.filterColumnComboBox.currentIndexChanged.connect( self.filterColumnChanged) self.filterCaseSensitivityCheckBox.toggled.connect( self.filterRegExpChanged) self.sortCaseSensitivityCheckBox.toggled.connect(self.sortChanged) sourceLayout = QHBoxLayout() sourceLayout.addWidget(self.sourceView) self.sourceGroupBox.setLayout(sourceLayout) proxyLayout = QGridLayout() proxyLayout.addWidget(self.proxyView, 0, 0, 1, 3) proxyLayout.addWidget(self.filterPatternLabel, 1, 0) proxyLayout.addWidget(self.filterPatternLineEdit, 1, 1, 1, 2) proxyLayout.addWidget(self.filterSyntaxLabel, 2, 0) proxyLayout.addWidget(self.filterSyntaxComboBox, 2, 1, 1, 2) proxyLayout.addWidget(self.filterColumnLabel, 3, 0) proxyLayout.addWidget(self.filterColumnComboBox, 3, 1, 1, 2) proxyLayout.addWidget(self.filterCaseSensitivityCheckBox, 4, 0, 1, 2) proxyLayout.addWidget(self.sortCaseSensitivityCheckBox, 4, 2) self.proxyGroupBox.setLayout(proxyLayout) mainLayout = QVBoxLayout() mainLayout.addWidget(self.sourceGroupBox) mainLayout.addWidget(self.proxyGroupBox) self.setLayout(mainLayout) self.setWindowTitle("Basic Sort/Filter Model") self.resize(500, 450) self.proxyView.sortByColumn(SENDER, Qt.AscendingOrder) self.filterColumnComboBox.setCurrentIndex(SENDER) self.filterPatternLineEdit.setText("Andy|Grace") self.filterCaseSensitivityCheckBox.setChecked(True) self.sortCaseSensitivityCheckBox.setChecked(True) def setSourceModel(self, model): self.proxyModel.setSourceModel(model) self.sourceView.setModel(model) def filterRegExpChanged(self): syntax_nr = self.filterSyntaxComboBox.itemData( self.filterSyntaxComboBox.currentIndex()) syntax = QRegExp.PatternSyntax(syntax_nr) if self.filterCaseSensitivityCheckBox.isChecked(): caseSensitivity = Qt.CaseSensitive else: caseSensitivity = Qt.CaseInsensitive regExp = QRegExp(self.filterPatternLineEdit.text(), caseSensitivity, syntax) self.proxyModel.setFilterRegExp(regExp) def filterColumnChanged(self): self.proxyModel.setFilterKeyColumn( self.filterColumnComboBox.currentIndex()) def sortChanged(self): if self.sortCaseSensitivityCheckBox.isChecked(): caseSensitivity = Qt.CaseSensitive else: caseSensitivity = Qt.CaseInsensitive self.proxyModel.setSortCaseSensitivity(caseSensitivity)
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 EditorGeneral(QWidget): def __init__(self, parent): super().__init__(parent) self._preferences = parent vbox = QVBoxLayout(self) self._font = settings.FONT # Group widgets group_typo = QGroupBox( translations.TR_PREFERENCES_EDITOR_GENERAL_TYPOGRAPHY) group_scheme = QGroupBox("Editor Color Scheme") # Font settings grid_typo = QGridLayout(group_typo) self._btn_editor_font = QPushButton('') grid_typo.addWidget( QLabel(translations.TR_PREFERENCES_EDITOR_GENERAL_EDITOR_FONT), 0, 0) grid_typo.addWidget(self._btn_editor_font, 0, 1) self._check_font_antialiasing = QCheckBox("Antialiasing") grid_typo.addWidget(self._check_font_antialiasing, 1, 0) # Scheme settings box = QVBoxLayout(group_scheme) self._combo_themes = QComboBox() box.addWidget(self._combo_themes) schemes = json_manager.load_editor_schemes() for scheme_name, colors in schemes.items(): self._combo_themes.addItem(scheme_name, colors) self.__current_scheme = settings.EDITOR_SCHEME # category_name, # Add group widgets vbox.addWidget(group_typo) vbox.addWidget(group_scheme) vbox.addItem( QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) # Initial Settings btn_text = ', '.join(self._font.toString().split(',')[0:2]) self._btn_editor_font.setText(btn_text) self._check_font_antialiasing.setChecked(settings.FONT_ANTIALIASING) self._combo_themes.setCurrentText(settings.EDITOR_SCHEME) # Connections self._btn_editor_font.clicked.connect(self._load_editor_font) self._preferences.savePreferences.connect(self._save) def _load_editor_font(self): font, ok = QFontDialog.getFont(self._font, self) if ok: self._font = font btn_text = ', '.join(self._font.toString().split(',')[0:2]) self._btn_editor_font.setText(btn_text) def _save(self): qsettings = IDE.editor_settings() settings.FONT = self._font qsettings.setValue("editor/general/default_font", settings.FONT) settings.FONT_ANTIALIASING = self._check_font_antialiasing.isChecked() qsettings.setValue("editor/general/font_antialiasing", settings.FONT_ANTIALIASING) settings.EDITOR_SCHEME = self._combo_themes.currentText() qsettings.setValue("editor/general/scheme", settings.EDITOR_SCHEME) scheme = self._combo_themes.currentText() if scheme != self.__current_scheme: index = self._combo_themes.currentIndex() colors = self._combo_themes.itemData(index) resources.COLOR_SCHEME = colors main = IDE.get_service("main_container") main.restyle_editor()
class ConfDialog(QDialog): """config dialog""" def __init__(self, mainwin, parent=None): super(ConfDialog, self).__init__(parent) # self.setAttribute(Qt.WA_StyledBackground) # self.setAutoFillBackground(True) self._app = QApplication.instance() # 获取app实例 self.setWindowFlags(Qt.Tool | Qt.WindowStaysOnTopHint) self.win = mainwin self.leftList = QListWidget() self.rightStack = QStackedWidget() self.optionActions = {} self.changedOptions = {} self.initUI() self.initConfOptions() self.initOptionActions() recentlist = self.win.config["file.recent"] if recentlist: self.win.recent.setList(recentlist) self.alertChLang = False def initUI(self): mainLayout = QVBoxLayout() layH1 = QHBoxLayout() layH2 = QHBoxLayout() layH1.addWidget(self.leftList) layH1.addWidget(self.rightStack) self.cancelbtn = QPushButton(self.tr("Cancel")) self.okbtn = QPushButton(self.tr("OK")) # self.cancelbtn.setVisible(False) self.okbtn.setDefault(True) layH2.addStretch(1) layH2.addWidget(self.cancelbtn) layH2.addWidget(self.okbtn) mainLayout.addLayout(layH1) mainLayout.addLayout(layH2) self.setLayout(mainLayout) # left list self.leftList.addItem(self.tr("General")) self.leftList.addItem(self.tr("Editor")) self.leftList.addItem(self.tr("Update")) self.leftList.setMaximumWidth(150) # right stack w = QWidget() layw = QVBoxLayout() # general g = QGroupBox(self.tr("General")) glayout = QFormLayout() label1 = QLabel(self.tr("select UI language:")) self.langCombo = QComboBox() self.fillLangItems(self.langCombo) # .addItems(self.getLangList()) self.langCombo.setMinimumWidth(150) label2 = QLabel(self.tr("Number of recent files:")) self.recentcountspin = QSpinBox() self.recentcountspin.setMinimum(1) self.recentcountspin.setMaximum(30) glayout.addRow(label1, self.langCombo) glayout.addRow(label2, self.recentcountspin) g.setLayout(glayout) layw.addWidget(g) # advanced g2 = QGroupBox(self.tr("Advanced")) labeladv1 = QLabel(self.tr("Export qss When save qsst:")) self.checkboxAutoExportQss = QCheckBox() self.checkboxAutoExportQss.setToolTip( self. tr("Option for whether export qss when save qsst file each time.")) layh1 = QHBoxLayout() layh1.addWidget(labeladv1) layh1.addStretch(1) layh1.addWidget(self.checkboxAutoExportQss) g2layout = QVBoxLayout() g2layout.addLayout(layh1) g2.setLayout(g2layout) layw.addWidget(g2) # advanced g3 = QGroupBox(self.tr("UI Skin")) labeladv1 = QLabel(self.tr("Select the ui skin:")) self.skinCombo = QComboBox() self.skinCombo.setToolTip(self.tr("Select the ui skin.")) self.skindir = os.path.join(os.path.dirname(__file__), "skin") for f in os.listdir(self.skindir): if f.endswith(".qss") or f.endswith(".QSS"): self.skinCombo.addItem(os.path.basename(f)) layh1 = QHBoxLayout() layh1.addWidget(labeladv1) layh1.addStretch(1) layh1.addWidget(self.skinCombo) labelskin2 = QLabel(self.tr("Manage skins:")) skinaddr = QPushButton(self.tr("Skin Management")) skinaddr.setToolTip(self.tr("Open the skin directory.")) skinaddr.clicked.connect(lambda: os.startfile(self.skindir)) layh2 = QHBoxLayout() layh2.addWidget(labelskin2) layh2.addStretch(1) layh2.addWidget(skinaddr) g3layout = QVBoxLayout() g3layout.addLayout(layh1) g3layout.addLayout(layh2) g3.setLayout(g3layout) layw.addWidget(g3) layw.addStretch(1) w.setLayout(layw) self.rightStack.addWidget(w) # CodeEditor SettingPannel self.rightStack.addWidget(self.win.editor.settings.settingPanel()) # right stack for update w3 = QWidget() layw3 = QVBoxLayout() # update setting g31 = QGroupBox(self.tr("update check")) self.checkboxUpdate = QCheckBox(self.tr("auto check for update")) labtmp = QLabel(self.tr("update checking frequency:")) self.updateCombo = QComboBox() self.updateCombo.setToolTip( self.tr("setup frequency for checking update")) self.updateCombo.addItem(self.tr("Each startup"), "start") self.updateCombo.addItem(self.tr("Every day"), "day") self.updateCombo.addItem(self.tr("Every week"), "week") self.updateCombo.addItem(self.tr("Every month"), "month") self.updateCombo.setEnabled(False) self.checkboxUpdate.stateChanged.connect(self.updateCombo.setEnabled) ltmpv = QVBoxLayout() ltmph = QHBoxLayout() ltmpv.addWidget(self.checkboxUpdate) ltmpv.addLayout(ltmph) ltmph.addWidget(labtmp) ltmph.addStretch(1) ltmph.addWidget(self.updateCombo) g31.setLayout(ltmpv) tmp1 = self.win.config["update.autocheck"] tmp2 = self.win.config["update.checkfreq"] if not isinstance(tmp1, bool): tmp1 = True self.checkboxUpdate.setChecked(tmp1) if not tmp2: tmp2 = "start" tmpi = self.updateCombo.findData(tmp2) if tmpi: self.updateCombo.setCurrentIndex(tmpi) layw3.addWidget(g31) layw3.addStretch(1) w3.setLayout(layw3) self.rightStack.addWidget(w3) # action for dialog self.leftList.currentRowChanged.connect( self.rightStack.setCurrentIndex) self.cancelbtn.clicked.connect(lambda: (self.cancel(), self.close())) self.okbtn.clicked.connect(lambda: (self.apply(), self.close())) # actions self.recentcountspin.valueChanged.connect( lambda x: self.changedOptions.__setitem__("file.recentcount", x)) self.langCombo.currentIndexChanged.connect( lambda i: self.changedOptions.__setitem__( "general.language", self.langCombo.itemData(i))) self.checkboxUpdate.stateChanged.connect( lambda b: self.changedOptions.update({"update.autocheck": b})) self.updateCombo.currentIndexChanged.connect( lambda i: self.changedOptions.__setitem__( "update.checkfreq", self.updateCombo.itemData(i))) self.checkboxAutoExportQss.stateChanged.connect( lambda b: self.changedOptions.update({"advance.autoexportqss": b})) self.skinCombo.currentTextChanged.connect(lambda t: (self.applyskin( t), self.changedOptions.update({"general.skin": t}))) def applyskin(self, skinfile): try: with open(os.path.join(self.skindir, skinfile), 'r', encoding='utf-8') as f: self.win.currentUIqss = f.read() self.win.setStyleSheet(self.win.currentUIqss) except Exception: QMessageBox.information( self, "Skin Error", self.tr("Apply skin error, please check the qss skin."), QMessageBox.Ok, QMessageBox.Ok) # def showEvent(self, QShowEvent): def initConfOptions(self): """load option and display on ui when dialog first start""" count = self.win.config["file.recentcount"] if count: self.recentcountspin.setValue(count) # lang = self.win.config.get("general.language","en") #self.win.config.getSec("general").get("language", "en") # lang = self.win.config["general.language"] # if lang is None: # lang = "en" skin = self.win.config["general.skin"] if not skin: skin = "default.qss" self.skinCombo.setCurrentText(skin) self.applyskin(skin) from i18n.language import Language lang = Language.lang for l in Language.getLangs(): if l["lang"].replace("-", "_") == lang.replace("-", "_"): self.langCombo.setCurrentText(l["nativename"]) break self.checkboxAutoExportQss.setChecked( bool(self.win.config["advance.autoexportqss"])) def initOptionActions(self): self.optionActions = { # option: [applyaction, updateUIaction] "general.language": [ lambda l: (self.chLang(l), self.win.config.setChild("general.language", l)), self.updateLangCombo ], "file.recentcount": [ lambda n: (setValue(self.win.recent.maxcount) (n), self.win.config.setChild("file.recentcount", n)), self.recentcountspin.setValue ], "advance.autoexportqss": [ lambda b: self.win.config.setChild("advance.autoexportqss", bool(b)), self.checkboxAutoExportQss.setChecked ], "general.skin": [ lambda t: (self.win.config.setChild("general.skin", t), self.applyskin(t)), self.skinCombo.setCurrentText ], "update.autocheck": [ lambda t: self.win.config.setChild("update.autocheck", bool( t)), self.checkboxUpdate.setChecked ], "update.checkfreq": [ lambda t: self.win.config.setChild("update.checkfreq", t), lambda t: self.updateCombo.setCurrentIndex( self.updateCombo.findData(t)) ], } def fillLangItems(self, combo): """set combo list for language """ from i18n.language import Language langs = Language.getLangs() for l in langs: combo.addItem(l["nativename"], l["lang"]) return True def chLang(self, lang="en"): """change ui luanguage setting.""" # print("Change language to "+lang) # try: # if lang.lower()=="english": # self._app.removeTranslator(self.win.trans) # return # self.win.trans.load("i18n-"+lang+".qm") # self._app.installTranslator(self.win.trans) # #self._app.retranslateUi(self)# 重新翻译界面 # except Exception as Argument: # print(Argument) lang = self.langCombo.currentData() print("Setting Language to " + lang) self.win.config["general.language"] = lang print("restart soft to enable.") if self.alertChLang: QMessageBox.information( self, self.tr("Change Language Info"), self.tr("You must restart soft to enable luanguage change.")) def updateLangCombo(self, lang=None): """update setting ui to lang""" from i18n.language import Language if not lang: lang = Language.lang for l in Language.getLangs(): if l["lang"].replace("-", "_") == lang.replace("-", "_"): self.langCombo.setCurrentText(l["nativename"]) break def apply(self): """get config and apply to app. """ recentlist = self.win.config["file.recent"] self.win.recent.setList(recentlist) for option, val in self.changedOptions.items(): self.optionActions[option][0](val) self.changedOptions.clear() self.win.editor.settings.apply() def cancel(self): for option in self.changedOptions.keys(): if option in self.win.config: self.optionActions[option][1](self.win.config[option]) else: self.optionActions[option][1]( self.win.config.defaultOptions[option]) self.changedOptions.clear() self.win.editor.settings.cancel()
class PlayerControls(QWidget): play = pyqtSignal() pause = pyqtSignal() stop = pyqtSignal() next = pyqtSignal() previous = pyqtSignal() changeVolume = pyqtSignal(int) changeMuting = pyqtSignal(bool) changeRate = pyqtSignal(float) def __init__(self, parent=None): super(PlayerControls, self).__init__(parent) self.playerState = QMediaPlayer.StoppedState self.playerMuted = False self.playButton = QToolButton(clicked=self.playClicked) self.playButton.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay)) self.stopButton = QToolButton(clicked=self.stop) self.stopButton.setIcon(self.style().standardIcon(QStyle.SP_MediaStop)) self.stopButton.setEnabled(False) self.nextButton = QToolButton(clicked=self.next) self.nextButton.setIcon( self.style().standardIcon(QStyle.SP_MediaSkipForward)) self.previousButton = QToolButton(clicked=self.previous) self.previousButton.setIcon( self.style().standardIcon(QStyle.SP_MediaSkipBackward)) self.muteButton = QToolButton(clicked=self.muteClicked) self.muteButton.setIcon( self.style().standardIcon(QStyle.SP_MediaVolume)) self.volumeSlider = QSlider(Qt.Horizontal, sliderMoved=self.changeVolume) self.volumeSlider.setRange(0, 100) self.rateBox = QComboBox(activated=self.updateRate) self.rateBox.addItem("0.5x", 0.5) self.rateBox.addItem("1.0x", 1.0) self.rateBox.addItem("2.0x", 2.0) self.rateBox.setCurrentIndex(1) layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.stopButton) layout.addWidget(self.previousButton) layout.addWidget(self.playButton) layout.addWidget(self.nextButton) layout.addWidget(self.muteButton) layout.addWidget(self.volumeSlider) layout.addWidget(self.rateBox) self.setLayout(layout) def state(self): return self.playerState def setState(self,state): if state != self.playerState: self.playerState = state if state == QMediaPlayer.StoppedState: self.stopButton.setEnabled(False) self.playButton.setIcon( self.style().standardIcon(QStyle.SP_MediaPlay)) elif state == QMediaPlayer.PlayingState: self.stopButton.setEnabled(True) self.playButton.setIcon( self.style().standardIcon(QStyle.SP_MediaPause)) elif state == QMediaPlayer.PausedState: self.stopButton.setEnabled(True) self.playButton.setIcon( self.style().standardIcon(QStyle.SP_MediaPlay)) def volume(self): return self.volumeSlider.value() def setVolume(self, volume): self.volumeSlider.setValue(volume) def isMuted(self): return self.playerMuted def setMuted(self, muted): if muted != self.playerMuted: self.playerMuted = muted self.muteButton.setIcon( self.style().standardIcon( QStyle.SP_MediaVolumeMuted if muted else QStyle.SP_MediaVolume)) def playClicked(self): if self.playerState in (QMediaPlayer.StoppedState, QMediaPlayer.PausedState): self.play.emit() elif self.playerState == QMediaPlayer.PlayingState: self.pause.emit() def muteClicked(self): self.changeMuting.emit(not self.playerMuted) def playbackRate(self): return self.rateBox.itemData(self.rateBox.currentIndex()) def setPlaybackRate(self, rate): for i in range(self.rateBox.count()): if qFuzzyCompare(rate, self.rateBox.itemData(i)): self.rateBox.setCurrentIndex(i) return self.rateBox.addItem("%dx" % rate, rate) self.rateBox.setCurrentIndex(self.rateBox.count() - 1) def updateRate(self): self.changeRate.emit(self.playbackRate())
class Property(QWidget): def _title(self, t): l = QLabel(t, self) l.setStyleSheet("font-weight: bold") return l def __init__(self, parent: typing.Optional['QWidget']): super().__init__(parent=parent) self.scrollView = QScrollArea(self) self.scrollView.setWidgetResizable(True) self.scrollView.setHorizontalScrollBarPolicy( QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.textAttr = QWidget(self) self.textAttr.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred) self.textAttrBox = QVBoxLayout() self.textAttr.setLayout(self.textAttrBox) self.cascades = SvgBar(self) self.cascades.setVisible(False) self.cascades.onDelete = self.deleteCascade self.cascades.onDrag = self.resortCascade self.cascades.onCopy = self.onCopy self.textAttrBox.addWidget(self.cascades) self.svgId = QLabel('N/A', self) self.textAttrBox.addWidget(self._title(TR('Type'))) self.textAttrBox.addWidget(self.svgId) self.startXOffsetLabel = self._title(TR('Overlay: Start X Percentage')) self.textAttrBox.addWidget(self.startXOffsetLabel) self.startXOffset = QSlider(self) self.startXOffset.setOrientation(QtCore.Qt.Orientation.Horizontal) self.startXOffset.setMinimum(0) self.startXOffset.setMaximum(3) self.startXOffset.valueChanged.connect( lambda e: self.offsetChanged('xo', self.startXOffset.value() * 0.25)) self.textAttrBox.addWidget(self.startXOffset) self.textAttrBox.addWidget(self._title(TR('Text'))) self.text = QTextEdit(self) self.text.textChanged.connect(self.textChanged) self.text.installEventFilter(self) self.textAttrBox.addWidget(self.text) self.textFont = QComboBox(self) fontFamilies = QFontDatabase() for s in fontFamilies.families(): self.textFont.addItem(s) self.textFont.currentIndexChanged.connect(self.fontChanged) self.textFont.setEditable(True) self.textSize = QComboBox(self) for i in range(8, 150, 1): if i <= 32: self.textSize.addItem(str(i)) elif i <= 80 and i % 2 == 0: self.textSize.addItem(str(i)) elif i % 10 == 0: self.textSize.addItem(str(i)) self.textSize.currentIndexChanged.connect(self.sizeChanged) self.textSize.setEditable(True) self._addBiBoxInTextAttrBox(self._title(TR("Font Family")), self.textFont, self._title(TR("Font Size")), self.textSize) self.textAlign = QComboBox(self) self.textPlace = QComboBox(self) for c in [self.textAlign, self.textPlace]: c.addItem(TR('Center'), 'c') c.addItem(TR('Top'), 't') c.addItem(TR('Bottom'), 'b') c.addItem(TR('Left'), 'l') c.addItem(TR('Right'), 'r') self.textAlign.currentIndexChanged.connect(self.alignChanged) self.textPlace.currentIndexChanged.connect(self.placeChanged) self._addBiBoxInTextAttrBox(self._title(TR("Alignment")), self.textAlign, self._title(TR("Placement")), self.textPlace) self.textX = QSpinBox(self) self.textY = QSpinBox(self) for c in [self.textX, self.textY]: c.setValue(0) c.setMinimum(-1e5) c.setMaximum(1e5) self.textX.valueChanged.connect(lambda e: self.offsetChanged('x', e)) self.textY.valueChanged.connect(lambda e: self.offsetChanged('y', e)) self._addBiBoxInTextAttrBox(self._title(TR("Offset X")), self.textX, self._title(TR("Offset Y")), self.textY) # self.setLayout(self.textAttrBox) self.scrollView.setWidget(self.textAttr) box = QVBoxLayout() box.addWidget(self.scrollView) box.setContentsMargins(0, 0, 0, 0) self.setLayout(box) self.show() self.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.MinimumExpanding) self.updating = False def _genVBox(p, w1, w2, m=0): placeAttr = QWidget(p) placeAttrBox = QVBoxLayout() placeAttrBox.setContentsMargins(m, m, m, m) placeAttr.setLayout(placeAttrBox) placeAttrBox.addWidget(w1) placeAttrBox.addWidget(w2) return placeAttr def eventFilter(self, a0, a1: QtCore.QEvent) -> bool: if a1.type() == QtCore.QEvent.FocusIn and a0 is self.text: self.findMainWin().mapview.data.begin() self.oldTextStore = [] for x in self.selection(): self.oldTextStore.append(x.dup()) if a1.type() == QtCore.QEvent.FocusOut and a0 is self.text: for x in self.selection(): self._getMapData()._appendHistory(self.oldTextStore.pop(0), x) self.oldTextStore.clear() return super().eventFilter(a0, a1) def _addBiBoxInTextAttrBox(self, t1, w1, t2, w2): placeAttr = QWidget(self) placeAttrBox = QHBoxLayout() placeAttrBox.setContentsMargins(0, 0, 0, 0) placeAttr.setLayout(placeAttrBox) placeAttrBox.addWidget(Property._genVBox(self, t1, w1)) placeAttrBox.addWidget(Property._genVBox(self, t2, w2)) self.textAttrBox.addWidget(placeAttr) def textChanged(self): for x in self.selection(): x.text = self.text.toPlainText() self.findMainWin().mapview.pan(0, 0) def _getMapData(self) -> MapData: return self.findMainWin().mapview.data def onCopy(self, src): if not src: return v = json.dumps([MapDataElement(src).todict()]) QApplication.clipboard().setText(v) self.findMainWin().mapview.setFocus() def deleteCascade(self, idx: int): self._getMapData().begin() item = self.selection()[0] old = item.pack() if idx == 0: item.src = item.cascades[0] item.cascades = item.cascades[1:] else: item.cascades = item.cascades[:idx - 1] + item.cascades[idx - 1 + 1:] self._getMapData()._appendHistoryPacked(old, item.pack()) self.findMainWin().mapview.setFocus() self.update() def resortCascade(self, fr: int, to: int): self._getMapData().begin() item: MapDataElement = self.selection()[0] last = item.pack() if to == 0: fr = fr - 1 old = item.src item.src = item.cascades[fr] item.cascades = [old] + item.cascades[:fr] + item.cascades[fr + 1:] elif fr == 0: old = item.src item.src = item.cascades[0] item.cascades = item.cascades[1:to] + [old] + item.cascades[to:] else: fr, to = fr - 1, to - 1 fold = item.cascades[fr] item.cascades = item.cascades[:fr] + item.cascades[fr + 1:] item.cascades = item.cascades[:to] + [fold] + item.cascades[to:] self._getMapData()._appendHistoryPacked(last, item.pack()) self.findMainWin().mapview.setFocus() self.update() def _foreach(self, f): mainData = self.findMainWin().mapview.data mainData.begin() for x in self.selection(): old = x.dup() f(x) # print(old.pack(), x.pack()) mainData._appendHistory(old, x) self.findMainWin().mapview.pan(0, 0) def fontChanged(self, e): self._foreach(lambda x: x.set("textFont", self.textFont.itemText(e))) def offsetChanged(self, t, v): if t == 'x' or t == 'y': self._foreach(lambda x: x.set(t == 'x' and 'textX' or 'textY', v)) else: # xo self.startXOffsetLabel.setText( TR('Overlay: Start X Percentage') + ' ' + str(int(v * 100)) + "%") self._foreach(lambda x: x.set("startXOffset", v)) def sizeChanged(self, e): self._foreach( lambda x: x.set("textSize", int(self.textSize.itemText(e)))) def alignChanged(self, e): self._foreach(lambda x: x.set("textAlign", self.textAlign.itemData(e))) def placeChanged(self, e): self._foreach( lambda x: x.set("textPlacement", self.textPlace.itemData(e))) def findMainWin(self): p = self.parent() while not isinstance(p, QMainWindow): p = p.parent() return p def selection(self): if self.updating: return [] return list( map(lambda l: l.data, self.findMainWin().mapview.selector.labels)) def update(self): data = self.findMainWin().mapview.data self.findMainWin().barHistory.setText('{}@{}'.format( data.historyCap, len(data.history))) self.findMainWin().barHistory.setStyleSheet( data.historyCap and "background: #80f0f000" or "") def toggle(v): for k in self.__dict__: # disbale all widgets w = self.__dict__[k] if isinstance(w, QWidget) and w != self.scrollView: w.setEnabled(v) toggle(False) self.cascades.setVisible(False) items = self.selection() if not items: self.svgId.setText("N/A") return toggle(True) e = items[-1] self.updating = True self.svgId.setText(e.src.svgId) if e.cascades and len(items) == 1: self.cascades.update([( e.src.svgId, SvgSource.Search.fullpath(e.src.svgId) )] + list( map(lambda x: (x.svgId, SvgSource.Search.fullpath(x.svgId)), e.cascades))) self.cascades.setVisible(True) self.text.setText(e.text) self.textFont.setEditText(e.textFont) self.textSize.setEditText(str(e.textSize)) self._setAP(self.textAlign, e.textAlign) self._setAP(self.textPlace, e.textPlacement) self.textX.setValue(e.textX) self.textY.setValue(e.textY) self.startXOffset.setValue(int(e.startXOffset / 0.25)) self.updating = False def _setAP(self, w: QComboBox, d): for i in range(0, w.count()): if w.itemData(i) == d: w.setCurrentIndex(i) break
class Settings_Net(QWidget): def __init__(self, parent: QWidget): super(Settings_Net, self).__init__(parent) self._layout = QVBoxLayout() self.setLayout(self._layout) # construct layout min_width = 150 self.user_agents = dict() self.user_agents['chrome_win7_x64'] = ( 'Chrome 41, Windows 7 x64', 'Mozilla/5.0 (Windows NT 6.1; WOW64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/41.0.2227.0 Safari/537.36') self.user_agents['chrome_linux_64'] = ( 'Chrome 41, Linux x86_64', 'Mozilla/5.0 (X11; Linux x86_64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/41.0.2227.0 Safari/537.36') self.user_agents['chrome_android'] = ( 'Chrome 47, Android 4.3 Galaxy-S3', 'Mozilla/5.0 (Linux; Android 4.3; GT-I9300 Build/JSS15J) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/47.0.2526.83 Mobile Safari/537.36') self.user_agents['firefox_win32'] = ( 'Firefox 40, Windows 7 32-bit', 'Mozilla/5.0 (Windows NT 6.1; rv:40.0) ' 'Gecko/20100101 Firefox/40.1') self.user_agents['firefox_android'] = ( 'Firefox, Android 4.3 Galaxy-S3', 'Mozilla/5.0 (Android 4.3; Mobile; rv:43.0) ' 'Gecko/43.0 Firefox/43.0') self.user_agents['edge_win10'] = ( 'Microsoft Edge, Windows 10 x64', 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) ' 'AppleWebKit/537.36 (KHTML, like Gecko) ' 'Chrome/42.0.2311.135 Safari/537.36 Edge/12.246') # server URL self._l_surl = QHBoxLayout() self._l_ua = QHBoxLayout() self._lbl_surl = QLabel(self.tr('Server URL:'), self) self._lbl_surl.setMinimumWidth(min_width) self._le_surl = QLineEdit(self) self._l_surl.addWidget(self._lbl_surl) self._l_surl.addWidget(self._le_surl) self._layout.addLayout(self._l_surl) # emulate browser combo box self._l_eb = QHBoxLayout() self._lbl_eb = QLabel(self.tr('Emulate browser:'), self) self._lbl_eb.setMinimumWidth(min_width) self._cb_eb = QComboBox(self) self._cb_eb.setEditable(False) self._cb_eb.setInsertPolicy(QComboBox.InsertAtBottom) ua_keylist = [i for i in self.user_agents.keys()] ua_keylist.sort() for key_id in ua_keylist: b_tuple = self.user_agents[key_id] display_string = b_tuple[0] self._cb_eb.addItem(display_string, QVariant(str(key_id))) self._cb_eb.addItem(self.tr('<Custom>'), QVariant('custom')) self._l_eb.addWidget(self._lbl_eb) self._l_eb.addWidget(self._cb_eb) self._layout.addLayout(self._l_eb) # custom user-agent string self._lbl_ua = QLabel(self.tr('User-agent string:'), self) self._lbl_ua.setMinimumWidth(min_width) self._le_ua = QLineEdit(self) self._l_ua.addWidget(self._lbl_ua) self._l_ua.addWidget(self._le_ua) self._layout.addLayout(self._l_ua) # proxy settings self._l_proxy = QHBoxLayout() self._lbl_proxy = QLabel(self.tr('Proxy type:'), self) self._lbl_proxy.setMinimumWidth(min_width) self._cb_proxy = QComboBox(self) self._cb_proxy.setEditable(False) self._cb_proxy.addItem(self.tr('No proxy'), QVariant('none')) self._cb_proxy.addItem(self.tr('HTTP proxy'), QVariant('http')) self._cb_proxy.addItem(self.tr('SOCKS5 proxy'), QVariant('socks5')) self._l_proxy.addWidget(self._lbl_proxy) self._l_proxy.addWidget(self._cb_proxy) self._layout.addLayout(self._l_proxy) self._l_proxy_s = QHBoxLayout() self._lbl_proxy_s = QLabel(self.tr('Proxy addr:port:'), self) self._lbl_proxy_s.setMinimumWidth(min_width) self._le_proxy_addr = QLineEdit(self) self._l_proxy_s.addWidget(self._lbl_proxy_s) self._l_proxy_s.addWidget(self._le_proxy_addr) self._layout.addLayout(self._l_proxy_s) # all connections self._cb_eb.currentIndexChanged.connect( self.on_cb_eb_current_index_changed) self._cb_proxy.currentIndexChanged.connect( self.on_cb_proxy_current_index_changed) # finalize self._layout.addStretch() @pyqtSlot(int) def on_cb_eb_current_index_changed(self, index: int): key_id = str(self._cb_eb.currentData(Qt.UserRole)) if key_id == 'custom': self._le_ua.setEnabled(True) return self._le_ua.setEnabled(False) if key_id in self.user_agents: b_tuple = self.user_agents[key_id] ua_str = b_tuple[1] self._le_ua.setText(ua_str) @pyqtSlot(int) def on_cb_proxy_current_index_changed(self, index: int): if index == 0: self._le_proxy_addr.setEnabled(False) else: self._le_proxy_addr.setEnabled(True) def ua_select(self, key_id: str): cnt = self._cb_eb.count() for i in range(cnt): item_key_id = str(self._cb_eb.itemData(i, Qt.UserRole)) if item_key_id == key_id: self._cb_eb.setCurrentIndex(i) break def load_from_config(self, cfg: configparser.ConfigParser): # defaults xnova_url = 'uni4.xnova.su' user_agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0' user_agent_id = 'custom' proxy = '' if 'net' in cfg: xnova_url = cfg['net']['xnova_url'] user_agent = cfg['net']['user_agent'] user_agent_id = cfg['net']['user_agent_id'] proxy = cfg['net']['proxy'] self._le_surl.setText(xnova_url) self._le_surl.setEnabled( False) # cannot be edited by user, for safety! # deal with user-agent self._le_ua.setText(user_agent) if user_agent_id == 'custom': self._le_ua.setEnabled(True) else: self._le_ua.setEnabled(False) self.ua_select(user_agent_id) # deal with proxy if proxy == '': self._le_proxy_addr.setText('') self._cb_proxy.setCurrentIndex(0) self._le_proxy_addr.setEnabled(False) elif proxy.startswith('http://'): self._cb_proxy.setCurrentIndex(1) proxy_addr = proxy[7:] self._le_proxy_addr.setText(proxy_addr) self._le_proxy_addr.setEnabled(True) elif proxy.startswith('socks5://'): self._cb_proxy.setCurrentIndex(2) proxy_addr = proxy[9:] self._le_proxy_addr.setText(proxy_addr) self._le_proxy_addr.setEnabled(True) else: raise ValueError('Invalid proxy setting: ' + proxy) def save_to_config(self, cfg: configparser.ConfigParser): # ensure there is a 'net' section if 'net' not in cfg: cfg.add_section('net') # skip server url # deal with user-agent user_agent_id = '' user_agent = '' idx = self._cb_eb.currentIndex() if idx >= 0: user_agent_id = str(self._cb_eb.itemData(idx, Qt.UserRole)) cfg['net']['user_agent_id'] = user_agent_id user_agent = self._le_ua.text().strip() if user_agent != '': cfg['net']['user_agent'] = user_agent # deal with proxy idx = self._cb_proxy.currentIndex() proxy_addr = self._le_proxy_addr.text().strip() if idx == 0: cfg['net']['proxy'] = '' elif idx == 1: cfg['net']['proxy'] = 'http://' + proxy_addr elif idx == 2: cfg['net']['proxy'] = 'socks5://' + proxy_addr logger.debug('Saved network config')
def _setAP(self, w: QComboBox, d): for i in range(0, w.count()): if w.itemData(i) == d: w.setCurrentIndex(i) break
class PianoWidget(QWidget): noteOn = pyqtSignal(music.Pitch, int) noteOff = pyqtSignal(music.Pitch) def __init__(self, parent, app): super().__init__(parent) self._app = app self.setFocusPolicy(Qt.StrongFocus) layout = QVBoxLayout() self.setLayout(layout) toolbar = QHBoxLayout() layout.addLayout(toolbar) self.focus_indicator = QLed(self) self.focus_indicator.setMinimumSize(24, 24) self.focus_indicator.setMaximumSize(24, 24) toolbar.addWidget(self.focus_indicator) toolbar.addSpacing(10) self._keyboard_listener = None self.keyboard_selector = QComboBox() self.keyboard_selector.addItem("None", userData=None) for device_id, port_info in self._app.midi_hub.list_devices(): self.keyboard_selector.addItem( "%s (%s)" % (port_info.name, port_info.client_info.name), userData=device_id) self.keyboard_selector.currentIndexChanged.connect( self.onKeyboardDeviceChanged) toolbar.addWidget(self.keyboard_selector) toolbar.addSpacing(10) # speaker icon should go here... #tb = QToolButton(self) #tb.setIcon(QIcon.fromTheme('multimedia-volume-control')) #toolbar.addWidget(tb) self.volume = QSlider(Qt.Horizontal, self) self.volume.setMinimumWidth(200) self.volume.setMinimum(0) self.volume.setMaximum(127) self.volume.setValue(127) self.volume.setTickPosition(QSlider.TicksBothSides) toolbar.addWidget(self.volume) toolbar.addStretch(1) self.piano_keys = PianoKeys(self) layout.addWidget(self.piano_keys) def close(self): if not super().close(): # pragma: no coverage return False if self._keyboard_listener is not None: self._keyboard_listener.remove() self._keyboard_listener = None return True def onKeyboardDeviceChanged(self, index): if self._keyboard_listener is not None: self._keyboard_listener.remove() self._keyboard_listener = None device_id = self.keyboard_selector.itemData(index) if device_id is not None: self._keyboard_listener = self._app.midi_hub.listeners.add( device_id, self.midiEvent) def focusInEvent(self, event): event.accept() self.focus_indicator.setValue(True) def focusOutEvent(self, event): event.accept() self.focus_indicator.setValue(False) def keyPressEvent(self, event): self.piano_keys.keyPressEvent(event) def keyReleaseEvent(self, event): self.piano_keys.keyReleaseEvent(event) def midiEvent(self, event): self.piano_keys.midiEvent(event)
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} 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 CSVOptionsWindow(QWidget): def __init__(self, mainwindow): QWidget.__init__(self, mainwindow, Qt.Window) self._setupUi() self.doc = mainwindow.doc self.model = mainwindow.model.csv_options self.tableModel = CSVOptionsTableModel(self.model, self.tableView) self.model.view = self self.encodingComboBox.addItems(SUPPORTED_ENCODINGS) self.cancelButton.clicked.connect(self.hide) self.continueButton.clicked.connect(self.model.continue_import) self.targetComboBox.currentIndexChanged.connect(self.targetIndexChanged) self.layoutComboBox.currentIndexChanged.connect(self.layoutIndexChanged) self.rescanButton.clicked.connect(self.rescanClicked) def _setupUi(self): self.setWindowTitle(tr("CSV Options")) self.resize(526, 369) self.verticalLayout = QVBoxLayout(self) msg = tr( "Specify which CSV columns correspond to which transaction fields. You must also " "uncheck the \"Import\" column for lines that don\'t represent a transaction " "(header, footer, comments)." ) self.label = QLabel(msg) self.label.setWordWrap(True) self.verticalLayout.addWidget(self.label) self.gridLayout = QGridLayout() self.label_2 = QLabel(tr("Layout:")) self.gridLayout.addWidget(self.label_2, 0, 0, 1, 1) self.layoutComboBox = QComboBox(self) self.layoutComboBox.setMinimumSize(QtCore.QSize(160, 0)) self.gridLayout.addWidget(self.layoutComboBox, 0, 1, 1, 1) self.label_4 = QLabel(tr("Delimiter:")) self.gridLayout.addWidget(self.label_4, 0, 3, 1, 1) self.fieldSeparatorEdit = QLineEdit(self) self.fieldSeparatorEdit.setMaximumSize(QtCore.QSize(30, 16777215)) self.gridLayout.addWidget(self.fieldSeparatorEdit, 0, 4, 1, 1) self.targetComboBox = QComboBox(self) self.gridLayout.addWidget(self.targetComboBox, 1, 1, 1, 1) self.label_3 = QLabel(tr("Target:")) self.gridLayout.addWidget(self.label_3, 1, 0, 1, 1) self.encodingComboBox = QComboBox(self) self.gridLayout.addWidget(self.encodingComboBox, 1, 4, 1, 1) spacerItem = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.gridLayout.addItem(spacerItem, 2, 2, 1, 1) self.horizontalLayout_2 = QHBoxLayout() self.horizontalLayout_2.setSpacing(0) spacerItem1 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout_2.addItem(spacerItem1) self.rescanButton = QPushButton(tr("Rescan")) sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.rescanButton.sizePolicy().hasHeightForWidth()) self.rescanButton.setSizePolicy(sizePolicy) self.horizontalLayout_2.addWidget(self.rescanButton) self.gridLayout.addLayout(self.horizontalLayout_2, 2, 3, 1, 2) self.label_5 = QLabel(tr("Encoding:")) self.gridLayout.addWidget(self.label_5, 1, 3, 1, 1) self.verticalLayout.addLayout(self.gridLayout) self.tableView = QTableView(self) self.tableView.setEditTriggers(QAbstractItemView.NoEditTriggers) self.tableView.setSelectionBehavior(QAbstractItemView.SelectRows) self.tableView.setShowGrid(False) self.tableView.horizontalHeader().setHighlightSections(False) self.tableView.verticalHeader().setVisible(False) self.tableView.verticalHeader().setDefaultSectionSize(18) self.verticalLayout.addWidget(self.tableView) self.horizontalLayout = QHBoxLayout() spacerItem2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) self.horizontalLayout.addItem(spacerItem2) self.cancelButton = QPushButton(tr("Cancel")) self.cancelButton.setShortcut("Esc") self.horizontalLayout.addWidget(self.cancelButton) self.continueButton = QPushButton(tr("Continue Import")) self.continueButton.setDefault(True) self.horizontalLayout.addWidget(self.continueButton) self.verticalLayout.addLayout(self.horizontalLayout) # --- Private def _newLayout(self): title = tr("New Layout") msg = tr("Choose a name for your new layout:") name, ok = QInputDialog.getText(self, title, msg) if ok and name: self.model.new_layout(name) def _renameLayout(self): title = tr("Rename Layout") msg = tr("Choose a name for your layout:") name, ok = QInputDialog.getText(self, title, msg) if ok and name: self.model.rename_selected_layout(name) # --- Event Handling def layoutIndexChanged(self, index): # This one is a little complicated. We want to only be able to select the layouts. If # anything else is clicked, we revert back to the old index. If the item has user data, # it means that an action has to be performed. if index < 0: return elif index < len(self.model.layout_names): layout_name = None if index == 0 else str(self.layoutComboBox.itemText(index)) self.model.select_layout(layout_name) else: self.layoutComboBox.setCurrentIndex(self.layoutComboBox.findText(self.model.layout.name)) data = str(self.layoutComboBox.itemData(index)) if data == NEW_LAYOUT: self._newLayout() elif data == RENAME_LAYOUT: self._renameLayout() elif data == DELETE_LAYOUT: self.model.delete_selected_layout() def rescanClicked(self): self.model.encoding_index = self.encodingComboBox.currentIndex() self.model.field_separator = str(self.fieldSeparatorEdit.text()) self.model.rescan() def targetIndexChanged(self, index): self.model.selected_target_index = index # --- model --> view # hide() is called from the model, but is already covered by QWidget def refresh_columns(self): self.tableModel.beginResetModel() self.tableModel.endResetModel() def refresh_columns_name(self): self.tableModel.refreshColumnsName() def refresh_layout_menu(self): self.layoutComboBox.currentIndexChanged.disconnect(self.layoutIndexChanged) self.layoutComboBox.clear() self.layoutComboBox.addItems(self.model.layout_names) self.layoutComboBox.insertSeparator(self.layoutComboBox.count()) self.layoutComboBox.addItem(tr("New Layout..."), NEW_LAYOUT) self.layoutComboBox.addItem(tr("Rename Selected Layout..."), RENAME_LAYOUT) self.layoutComboBox.addItem(tr("Delete Selected Layout"), DELETE_LAYOUT) self.layoutComboBox.setCurrentIndex(self.layoutComboBox.findText(self.model.layout.name)) self.layoutComboBox.currentIndexChanged.connect(self.layoutIndexChanged) def refresh_lines(self): self.tableModel.beginResetModel() self.tableModel.endResetModel() self.fieldSeparatorEdit.setText(self.model.field_separator) def refresh_targets(self): self.targetComboBox.currentIndexChanged.disconnect(self.targetIndexChanged) self.targetComboBox.clear() self.targetComboBox.addItems(self.model.target_account_names) self.targetComboBox.currentIndexChanged.connect(self.targetIndexChanged) def show(self): # For non-modal dialogs, show() is not enough to bring the window at the forefront, we have # to call raise() as well QWidget.show(self) self.raise_() def show_message(self, msg): title = "Warning" QMessageBox.warning(self, title, msg)
class AddDialog(QDialog): worker = None selected_show = None results = [] def __init__(self, parent, worker, current_status, default=None): QDialog.__init__(self, parent) self.resize(950, 700) self.setWindowTitle('Search/Add from Remote') self.worker = worker self.current_status = current_status self.default = default if default: self.setWindowTitle('Search/Add from Remote for new show: %s' % default) # Get available search methods and default to keyword search if not reported by the API search_methods = self.worker.engine.mediainfo.get( 'search_methods', [utils.SEARCH_METHOD_KW]) layout = QVBoxLayout() # Create top layout top_layout = QHBoxLayout() if utils.SEARCH_METHOD_KW in search_methods: self.search_rad = QRadioButton('By keyword:') self.search_rad.setChecked(True) self.search_txt = QLineEdit() self.search_txt.setClearButtonEnabled(True) self.search_txt.returnPressed.connect(self.s_search) if default: self.search_txt.setText(default) self.search_btn = QPushButton('Search') self.search_btn.clicked.connect(self.s_search) top_layout.addWidget(self.search_rad) top_layout.addWidget(self.search_txt) else: top_layout.setAlignment(QtCore.Qt.AlignRight) top_layout.addWidget(self.search_btn) # Create filter line filters_layout = QHBoxLayout() if utils.SEARCH_METHOD_SEASON in search_methods: self.season_rad = QRadioButton('By season:') self.season_combo = QComboBox() self.season_combo.addItem('Winter', utils.SEASON_WINTER) self.season_combo.addItem('Spring', utils.SEASON_SPRING) self.season_combo.addItem('Summer', utils.SEASON_SUMMER) self.season_combo.addItem('Fall', utils.SEASON_FALL) self.season_year = QSpinBox() today = date.today() current_season = (today.month - 1) / 3 self.season_year.setRange(1900, today.year) self.season_year.setValue(today.year) self.season_combo.setCurrentIndex(current_season) filters_layout.addWidget(self.season_rad) filters_layout.addWidget(self.season_combo) filters_layout.addWidget(self.season_year) filters_layout.setAlignment(QtCore.Qt.AlignLeft) filters_layout.addWidget(QSplitter()) else: filters_layout.setAlignment(QtCore.Qt.AlignRight) view_combo = QComboBox() view_combo.addItem('Card view') view_combo.addItem('Table view') view_combo.currentIndexChanged.connect(self.s_change_view) filters_layout.addWidget(view_combo) # Create central content self.contents = QStackedWidget() # Set up views tableview = AddTableDetailsView(None, self.worker) tableview.changed.connect(self.s_selected) cardview = AddCardView(api_info=self.worker.engine.api_info) cardview.changed.connect(self.s_selected) cardview.doubleClicked.connect(self.s_show_details) self.contents.addWidget(cardview) self.contents.addWidget(tableview) # Use for testing #self.set_results([{'id': 1, 'title': 'Hola', 'image': 'https://omaera.org/icon.png'}]) bottom_buttons = QDialogButtonBox() bottom_buttons.addButton("Cancel", QDialogButtonBox.RejectRole) self.add_btn = bottom_buttons.addButton("Add", QDialogButtonBox.AcceptRole) self.add_btn.setEnabled(False) bottom_buttons.accepted.connect(self.s_add) bottom_buttons.rejected.connect(self.close) # Finish layout layout.addLayout(top_layout) layout.addLayout(filters_layout) layout.addWidget(self.contents) layout.addWidget(bottom_buttons) self.setLayout(layout) if utils.SEARCH_METHOD_SEASON in search_methods: self.search_txt.setFocus() def worker_call(self, function, ret_function, *args, **kwargs): # Run worker in a thread self.worker.set_function(function, ret_function, *args, **kwargs) self.worker.start() def _enable_widgets(self, enable): self.search_btn.setEnabled(enable) self.contents.currentWidget().setEnabled(enable) def set_results(self, results): self.results = results self.contents.currentWidget().setResults(self.results) # Slots def s_show_details(self): detailswindow = DetailsDialog(self, self.worker, self.selected_show) detailswindow.setModal(True) detailswindow.show() def s_change_view(self, item): self.contents.currentWidget().getModel().setResults(None) self.contents.setCurrentIndex(item) self.contents.currentWidget().getModel().setResults(self.results) def s_search(self): if self.search_rad.isChecked(): criteria = self.search_txt.text().strip() if not criteria: return method = utils.SEARCH_METHOD_KW elif self.season_rad.isChecked(): criteria = (self.season_combo.itemData( self.season_combo.currentIndex()), self.season_year.value()) method = utils.SEARCH_METHOD_SEASON self.contents.currentWidget().clearSelection() self.selected_show = None self._enable_widgets(False) self.add_btn.setEnabled(False) self.worker_call('search', self.r_searched, criteria, method) def s_selected(self, show): self.selected_show = show self.add_btn.setEnabled(True) def s_add(self): if self.selected_show: self.worker_call('add_show', self.r_added, self.selected_show, self.current_status) # Worker responses def r_searched(self, result): self._enable_widgets(True) if result['success']: self.set_results(result['result']) """ if self.table.currentRow() is 0: # Row number hasn't changed but the data probably has! self.s_show_selected(self.table.item(0, 0)) self.table.setCurrentItem(self.table.item(0, 0))""" else: self.set_results(None) def r_added(self, result): if result['success']: if self.default: self.accept() else: QMessageBox.information(self, 'Information', "Show added successfully")
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 ChartLab(QWidget): def __init__(self, datahub_entry: DataHubEntry, factor_center: FactorCenter): super(ChartLab, self).__init__() # ---------------- ext var ---------------- self.__data_hub = datahub_entry self.__factor_center = factor_center self.__data_center = self.__data_hub.get_data_center( ) if self.__data_hub is not None else None self.__data_utility = self.__data_hub.get_data_utility( ) if self.__data_hub is not None else None self.__inited = False self.__plot_table = {} self.__paint_data = None # ------------- plot resource ------------- self.__figure = plt.figure() self.__canvas = FigureCanvas(self.__figure) # -------------- ui resource -------------- self.__data_frame_widget = None self.__combo_factor = QComboBox() self.__label_comments = QLabel('') # Parallel comparison self.__radio_parallel_comparison = QRadioButton('横向比较') self.__combo_year = QComboBox() self.__combo_quarter = QComboBox() self.__combo_industry = QComboBox() # Longitudinal comparison self.__radio_longitudinal_comparison = QRadioButton('纵向比较') self.__combo_stock = SecuritiesSelector(self.__data_utility) # Limitation self.__line_lower = QLineEdit('') self.__line_upper = QLineEdit('') self.__button_draw = QPushButton('绘图') self.__button_show = QPushButton('数据') self.init_ui() # ---------------------------------------------------- UI Init ----------------------------------------------------- def init_ui(self): self.__layout_control() self.__config_control() def __layout_control(self): main_layout = QVBoxLayout() self.setLayout(main_layout) self.setMinimumSize(1280, 800) bottom_layout = QHBoxLayout() main_layout.addWidget(self.__canvas, 99) main_layout.addLayout(bottom_layout, 1) group_box, group_layout = create_v_group_box('因子') bottom_layout.addWidget(group_box, 2) group_layout.addWidget(self.__combo_factor) group_layout.addWidget(self.__label_comments) group_box, group_layout = create_v_group_box('比较方式') bottom_layout.addWidget(group_box, 2) line = QHBoxLayout() line.addWidget(self.__radio_parallel_comparison, 1) line.addWidget(self.__combo_industry, 5) line.addWidget(self.__combo_year, 5) line.addWidget(self.__combo_quarter, 5) group_layout.addLayout(line) line = QHBoxLayout() line.addWidget(self.__radio_longitudinal_comparison, 1) line.addWidget(self.__combo_stock, 10) group_layout.addLayout(line) group_box, group_layout = create_v_group_box('范围限制') bottom_layout.addWidget(group_box, 1) line = QHBoxLayout() line.addWidget(QLabel('下限')) line.addWidget(self.__line_lower) group_layout.addLayout(line) line = QHBoxLayout() line.addWidget(QLabel('上限')) line.addWidget(self.__line_upper) group_layout.addLayout(line) col = QVBoxLayout() col.addWidget(self.__button_draw) col.addWidget(self.__button_show) bottom_layout.addLayout(col, 1) def __config_control(self): for year in range(now().year, 1989, -1): self.__combo_year.addItem(str(year), str(year)) self.__combo_year.setCurrentIndex(1) self.__combo_quarter.addItem('一季报', '03-31') self.__combo_quarter.addItem('中报', '06-30') self.__combo_quarter.addItem('三季报', '09-30') self.__combo_quarter.addItem('年报', '12-31') self.__combo_quarter.setCurrentIndex(3) self.__combo_industry.addItem('全部', '全部') identities = self.__data_utility.get_all_industries() for identity in identities: self.__combo_industry.addItem(identity, identity) if self.__factor_center is not None: factors = self.__factor_center.get_all_factors() for fct in factors: self.__combo_factor.addItem(fct, fct) self.on_factor_updated(0) self.__combo_stock.setEnabled(False) self.__radio_parallel_comparison.setChecked(True) self.__radio_parallel_comparison.setToolTip(TIP_PARALLEL_COMPARISON) self.__radio_longitudinal_comparison.setToolTip( TIP_LONGITUDINAL_COMPARISON) self.__line_lower.setToolTip(TIP_LIMIT_UPPER_LOWER) self.__line_upper.setToolTip(TIP_LIMIT_UPPER_LOWER) self.__button_show.setToolTip(TIP_BUTTON_SHOW) self.__button_draw.clicked.connect(self.on_button_draw) self.__button_show.clicked.connect(self.on_button_show) self.__combo_factor.currentIndexChanged.connect(self.on_factor_updated) self.__radio_parallel_comparison.clicked.connect( self.on_radio_comparison) self.__radio_longitudinal_comparison.clicked.connect( self.on_radio_comparison) mpl.rcParams['font.sans-serif'] = ['Microsoft YaHei'] mpl.rcParams['axes.unicode_minus'] = False def on_factor_updated(self, value): self.__line_lower.setText('') self.__line_upper.setText('') factor = self.__combo_factor.itemData(value) comments = self.__factor_center.get_factor_comments(factor) self.__label_comments.setText(comments) def on_button_draw(self): factor = self.__combo_factor.currentData() lower = str2float_safe(self.__line_lower.text(), None) upper = str2float_safe(self.__line_upper.text(), None) if self.__radio_parallel_comparison.isChecked(): year = self.__combo_year.currentData() month_day = self.__combo_quarter.currentData() period = year + '-' + month_day industry = self.__combo_industry.currentData() self.plot_factor_parallel_comparison(factor, industry, text_auto_time(period), lower, upper) else: securities = self.__combo_stock.get_input_securities() self.plot_factor_longitudinal_comparison(factor, securities) def on_button_show(self): if self.__data_frame_widget is not None and \ self.__data_frame_widget.isVisible(): self.__data_frame_widget.close() if self.__paint_data is not None: self.__data_frame_widget = DataFrameWidget(self.__paint_data) self.__data_frame_widget.show() def on_radio_comparison(self): if self.__radio_parallel_comparison.isChecked(): self.__combo_year.setEnabled(True) self.__combo_quarter.setEnabled(True) self.__line_lower.setEnabled(True) self.__line_upper.setEnabled(True) self.__combo_stock.setEnabled(False) else: self.__combo_year.setEnabled(False) self.__combo_quarter.setEnabled(False) self.__line_lower.setEnabled(False) self.__line_upper.setEnabled(False) self.__combo_stock.setEnabled(True) # --------------------------------------------------------------------------------------- def plot_factor_parallel_comparison(self, factor: str, industry: str, period: datetime.datetime, lower: float, upper: float): identities = '' if industry != '全部': identities = self.__data_utility.get_industry_stocks(industry) df = self.__data_center.query_from_factor('Factor.Finance', identities, (period, period), fields=[factor], readable=True) s1 = df[factor] if lower is not None and upper is not None: s1 = s1.apply(lambda x: (x if x < upper else upper) if x > lower else lower) elif lower is not None: s1 = s1.apply(lambda x: x if x > lower else lower) elif upper is not None: s1 = s1.apply(lambda x: x if x < upper else upper) plt.clf() plt.subplot(1, 1, 1) s1.hist(bins=100) plt.title(factor) self.__canvas.draw() self.__canvas.flush_events() self.__paint_data = df self.__paint_data.sort_values(factor, inplace=True) def plot_factor_longitudinal_comparison(self, factor: str, securities: str): df = self.__data_center.query_from_factor('Factor.Finance', securities, None, fields=[factor], readable=True) # Only for annual report df = df[df['period'].dt.month == 12] df['报告期'] = df['period'] df.set_index('报告期', inplace=True) s1 = df[factor] plt.clf() plt.subplot(1, 1, 1) s1.plot.line() plt.title(factor) self.__canvas.draw() self.__canvas.flush_events() self.__paint_data = df self.__paint_data.sort_values('period', ascending=False, inplace=True) # --------------------------------------------------------------------------------------- def plot(self): self.plot_histogram_statistics() def plot_histogram_statistics(self): # --------------------------- The Data and Period We Want to Check --------------------------- stock = '' period = (text_auto_time('2018-12-01'), text_auto_time('2018-12-31')) # --------------------------------------- Query Pattern -------------------------------------- # fields_balance_sheet = ['货币资金', '资产总计', '负债合计', # '短期借款', '一年内到期的非流动负债', '其他流动负债', # '长期借款', '应付债券', '其他非流动负债', '流动负债合计', # '应收票据', '应收账款', '其他应收款', '预付款项', # '交易性金融资产', '可供出售金融资产', # '在建工程', '商誉', '固定资产'] # fields_income_statement = ['营业收入', '营业总收入', '减:营业成本', '息税前利润'] # # df, result = batch_query_readable_annual_report_pattern( # self.__data_hub, stock, period, fields_balance_sheet, fields_income_statement) # if result is not None: # return result # df_balance_sheet, result = query_readable_annual_report_pattern( # self.__data_hub, 'Finance.BalanceSheet', stock, period, fields_balance_sheet) # if result is not None: # print('Data Error') # # df_income_statement, result = query_readable_annual_report_pattern( # self.__data_hub, 'Finance.IncomeStatement', stock, period, fields_income_statement) # if result is not None: # print('Data Error') # -------------------------------- Merge and Pre-processing -------------------------------- # df = pd.merge(df_balance_sheet, # df_income_statement, # how='left', on=['stock_identity', 'period']) # df = df.sort_values('period') # df = df.reset_index() # df = df.fillna(0) # df = df.replace(0, 1) # ------------------------------------- Calc and Plot ------------------------------------- mpl.rcParams['font.sans-serif'] = ['Microsoft YaHei'] mpl.rcParams['axes.unicode_minus'] = False # font = matplotlib.font_manager.FontProperties(fname='C:/Windows/Fonts/msyh.ttf') # mpl.rcParams['axes.unicode_minus'] = False # df['应收款'] = df['应收账款'] + df['应收票据'] # df['净资产'] = df['资产总计'] - df['负债合计'] # df['短期负债'] = df['短期借款'] + df['一年内到期的非流动负债'] + df['其他流动负债'] # df['有息负债'] = df['短期负债'] + df['长期借款'] + df['应付债券'] + df['其他非流动负债'] # df['金融资产'] = df['交易性金融资产'] + df['可供出售金融资产'] # # df['财务费用正'] = df['减:财务费用'].apply(lambda x: x if x > 0 else 0) # df['三费'] = df['减:销售费用'] + df['减:管理费用'] + df['财务费用正'] df = self.__data_utility.auto_query( '', period, ['减:财务费用', '减:销售费用', '减:管理费用', '营业总收入', '营业收入', '减:营业成本'], ['stock_identity', 'period']) df['毛利润'] = df['营业收入'] - df['减:营业成本'] df['财务费用正'] = df['减:财务费用'].apply(lambda x: x if x > 0 else 0) df['三费'] = df['减:销售费用'] + df['减:管理费用'] + df['财务费用正'] s1 = df['三费'] / df['营业总收入'] s1 = s1.apply(lambda x: (x if x < 1 else 1) if x > -0.1 else -0.1) plt.subplot(2, 1, 1) s1.hist(bins=100) plt.title('三费/营业总收入') s2 = df['三费'] / df['毛利润'] s2 = s2.apply(lambda x: (x if x < 1 else 1) if x > -0.1 else -0.1) plt.subplot(2, 1, 2) s2.hist(bins=100) plt.title('三费/毛利润') # s1 = df['货币资金'] / df['有息负债'] # s1 = s1.apply(lambda x: x if x < 10 else 10) # plt.subplot(2, 1, 1) # s1.hist(bins=100) # plt.title('货币资金/有息负债') # # s2 = df['有息负债'] / df['资产总计'] # plt.subplot(2, 1, 2) # s2.hist(bins=100) # plt.title('有息负债/资产总计') # s1 = df['应收款'] / df['营业收入'] # s1 = s1.apply(lambda x: x if x < 2 else 2) # plt.subplot(4, 1, 1) # s1.hist(bins=100) # plt.title('应收款/营业收入') # # s2 = df['其他应收款'] / df['营业收入'] # s2 = s2.apply(lambda x: x if x < 1 else 1) # plt.subplot(4, 1, 2) # s2.hist(bins=100) # plt.title('其他应收款/营业收入') # # s3 = df['预付款项'] / df['营业收入'] # s3 = s3.apply(lambda x: x if x < 1 else 1) # plt.subplot(4, 1, 3) # s3.hist(bins=100) # plt.title('预付款项/营业收入') # # s4 = df['预付款项'] / df['减:营业成本'] # s4 = s4.apply(lambda x: x if x < 1 else 1) # plt.subplot(4, 1, 4) # s4.hist(bins=100) # plt.title('预付款项/营业成本') # s1 = df['商誉'] / df['净资产'] # s1 = s1.apply(lambda x: (x if x < 1 else 1) if x > 0 else 0) # plt.subplot(3, 1, 1) # s1.hist(bins=100) # plt.title('商誉/净资产') # # s2 = df['在建工程'] / df['净资产'] # s2 = s2.apply(lambda x: (x if x < 1 else 1) if x > 0 else 0) # plt.subplot(3, 1, 2) # s2.hist(bins=100) # plt.title('在建工程/净资产') # # s2 = df['在建工程'] / df['资产总计'] # s2 = s2.apply(lambda x: (x if x < 1 else 1) if x > 0 else 0) # plt.subplot(3, 1, 3) # s2.hist(bins=100) # plt.title('在建工程/资产总计') # s1 = df['固定资产'] / df['资产总计'] # s1 = s1.apply(lambda x: (x if x < 1 else 1) if x > 0 else 0) # plt.subplot(2, 1, 1) # s1.hist(bins=100) # plt.title('固定资产/资产总计') # # s2 = df['息税前利润'] / df['固定资产'] # s2 = s2.apply(lambda x: (x if x < 10 else 10) if x > -10 else -10) # plt.subplot(2, 1, 2) # s2.hist(bins=100) # plt.title('息税前利润/固定资产') # self.plot_proportion([ # ChartLab.PlotItem('固定资产', '资产总计', 0, 1), # ChartLab.PlotItem('息税前利润', '固定资产', -10, 10), # ], text_auto_time('2018-12-31')) self.repaint() class PlotItem: def __init__(self, num: str, den: str, lower: float or None = None, upper: float or None = None, bins: int = 100): self.numerator = num self.denominator = den self.limit_lower = lower self.limit_upper = upper self.plot_bins = bins def plot_proportion(self, plot_set: [PlotItem], period: datetime.datetime): df = self.prepare_plot_data(plot_set, period) plot_count = len(plot_set) for plot_index in range(plot_count): plot_item = plot_set[plot_index] s = df[plot_item.numerator] / df[plot_item.denominator] if plot_item.limit_lower is not None: s = s.apply(lambda x: max(x, plot_item.limit_lower)) if plot_item.limit_upper is not None: s = s.apply(lambda x: min(x, plot_item.limit_upper)) plt.subplot(plot_count, 1, plot_index + 1) s.hist(bins=plot_item.plot_bins) plt.title(plot_item.numerator + '/' + plot_item.denominator) def prepare_plot_data(self, plot_set: [PlotItem], period: datetime.datetime) -> pd.DataFrame: fields = [] for plot_item in plot_set: fields.append(plot_item.numerator) fields.append(plot_item.denominator) fields = list(set(fields)) return self.__data_utility.auto_query( '', (period - datetime.timedelta(days=1), period), fields, ['stock_identity', 'period'])
class AddDialog(QDialog): worker = None selected_show = None results = [] def __init__(self, parent, worker, current_status, default=None): QDialog.__init__(self, parent) self.resize(950, 700) self.setWindowTitle('Search/Add from Remote') self.worker = worker self.current_status = current_status self.default = default if default: self.setWindowTitle('Search/Add from Remote for new show: %s' % default) # Get available search methods and default to keyword search if not reported by the API search_methods = self.worker.engine.mediainfo.get('search_methods', [utils.SEARCH_METHOD_KW]) layout = QVBoxLayout() # Create top layout top_layout = QHBoxLayout() if utils.SEARCH_METHOD_KW in search_methods: self.search_rad = QRadioButton('By keyword:') self.search_rad.setChecked(True) self.search_txt = QLineEdit() self.search_txt.returnPressed.connect(self.s_search) if default: self.search_txt.setText(default) self.search_btn = QPushButton('Search') self.search_btn.clicked.connect(self.s_search) top_layout.addWidget(self.search_rad) top_layout.addWidget(self.search_txt) else: top_layout.setAlignment(QtCore.Qt.AlignRight) top_layout.addWidget(self.search_btn) # Create filter line filters_layout = QHBoxLayout() if utils.SEARCH_METHOD_SEASON in search_methods: self.season_rad = QRadioButton('By season:') self.season_combo = QComboBox() self.season_combo.addItem('Winter', utils.SEASON_WINTER) self.season_combo.addItem('Spring', utils.SEASON_SPRING) self.season_combo.addItem('Summer', utils.SEASON_SUMMER) self.season_combo.addItem('Fall', utils.SEASON_FALL) self.season_year = QSpinBox() today = date.today() current_season = (today.month - 1) / 3 self.season_year.setRange(1900, today.year) self.season_year.setValue(today.year) self.season_combo.setCurrentIndex(current_season) filters_layout.addWidget(self.season_rad) filters_layout.addWidget(self.season_combo) filters_layout.addWidget(self.season_year) filters_layout.setAlignment(QtCore.Qt.AlignLeft) filters_layout.addWidget(QSplitter()) else: filters_layout.setAlignment(QtCore.Qt.AlignRight) view_combo = QComboBox() view_combo.addItem('Table view') view_combo.addItem('Card view') view_combo.currentIndexChanged.connect(self.s_change_view) filters_layout.addWidget(view_combo) # Create central content self.contents = QStackedWidget() # Set up views tableview = AddTableDetailsView(None, self.worker) tableview.changed.connect(self.s_selected) self.contents.addWidget(tableview) cardview = AddCardView(api_info=self.worker.engine.api_info) cardview.changed.connect(self.s_selected) cardview.activated.connect(self.s_show_details) self.contents.addWidget(cardview) # Use for testing #self.set_results([{'id': 1, 'title': 'Hola', 'image': 'https://omaera.org/icon.png'}]) bottom_buttons = QDialogButtonBox() bottom_buttons.addButton("Cancel", QDialogButtonBox.RejectRole) self.add_btn = bottom_buttons.addButton("Add", QDialogButtonBox.AcceptRole) self.add_btn.setEnabled(False) bottom_buttons.accepted.connect(self.s_add) bottom_buttons.rejected.connect(self.close) # Finish layout layout.addLayout(top_layout) layout.addLayout(filters_layout) layout.addWidget(self.contents) layout.addWidget(bottom_buttons) self.setLayout(layout) if utils.SEARCH_METHOD_SEASON in search_methods: self.search_txt.setFocus() def worker_call(self, function, ret_function, *args, **kwargs): # Run worker in a thread self.worker.set_function(function, ret_function, *args, **kwargs) self.worker.start() def _enable_widgets(self, enable): self.search_btn.setEnabled(enable) self.contents.currentWidget().setEnabled(enable) def set_results(self, results): self.results = results self.contents.currentWidget().setResults(self.results) # Slots def s_show_details(self): detailswindow = DetailsDialog(self, self.worker, self.selected_show) detailswindow.setModal(True) detailswindow.show() def s_change_view(self, item): self.contents.currentWidget().getModel().setResults(None) self.contents.setCurrentIndex(item) self.contents.currentWidget().getModel().setResults(self.results) def s_search(self): if self.search_rad.isChecked(): criteria = self.search_txt.text().strip() if not criteria: return method = utils.SEARCH_METHOD_KW elif self.season_rad.isChecked(): criteria = (self.season_combo.itemData(self.season_combo.currentIndex()), self.season_year.value()) method = utils.SEARCH_METHOD_SEASON self.contents.currentWidget().clearSelection() self.selected_show = None self._enable_widgets(False) self.add_btn.setEnabled(False) self.worker_call('search', self.r_searched, criteria, method) def s_selected(self, show): self.selected_show = show self.add_btn.setEnabled(True) def s_add(self): if self.selected_show: self.worker_call('add_show', self.r_added, self.selected_show, self.current_status) # Worker responses def r_searched(self, result): self._enable_widgets(True) if result['success']: self.set_results(result['result']) """ if self.table.currentRow() is 0: # Row number hasn't changed but the data probably has! self.s_show_selected(self.table.item(0, 0)) self.table.setCurrentItem(self.table.item(0, 0))""" else: self.set_results(None) def r_added(self, result): if result['success']: if self.default: self.accept()
class NumericParameterWidget(GenericParameterWidget): """Widget class for Numeric parameter.""" def __init__(self, parameter, parent=None): """Constructor .. versionadded:: 2.2 :param parameter: A NumericParameter object. :type parameter: NumericParameter """ super().__init__(parameter, parent) self._input = QWidget() self._unit_widget = QLabel() self.set_unit() # Size policy self._spin_box_size_policy = QSizePolicy( QSizePolicy.Fixed, QSizePolicy.Fixed) label_policy = QSizePolicy( QSizePolicy.Minimum, QSizePolicy.Fixed) self._unit_widget.setSizePolicy(label_policy) # Add unit description to description description = self.help_label.text() if self._parameter.allowed_units: description += '<br><br>Available units:' description += '<ul>' for allowed_unit in self._parameter.allowed_units: description += '<li>' description += '<b>' + allowed_unit.name + '</b>: ' description += allowed_unit.description or '' description += '</li>' description += '</ul>' self.help_label.setText(description) def get_parameter(self): """Obtain numeric parameter object from the current widget state. :returns: A NumericParameter from the current state of widget """ self._parameter.value = self._input.value() if len(self._parameter.allowed_units) > 1: current_index = self._unit_widget.currentIndex() unit = self._unit_widget.itemData(current_index, Qt.UserRole) if hasattr(unit, 'toPyObject'): unit = unit.toPyObject() self._parameter.unit = unit return self._parameter def set_unit(self): """Set up label or combo box for unit.""" if len(self._parameter.allowed_units) == 1: self._unit_widget = QLabel(self._parameter.unit.name) self._unit_widget.setToolTip(self._parameter.unit.help_text) elif len(self._parameter.allowed_units) > 1: self._unit_widget = QComboBox() index = -1 current_index = -1 for allowed_unit in self._parameter.allowed_units: name = allowed_unit.name tooltip = allowed_unit.help_text index += 1 if allowed_unit.guid == self._parameter.unit.guid: current_index = index self._unit_widget.addItem(name) self._unit_widget.setItemData(index, tooltip, Qt.ToolTipRole) self._unit_widget.setItemData(index, allowed_unit, Qt.UserRole) self._unit_widget.setCurrentIndex(current_index) self._unit_widget.setToolTip('Select your preferred unit') self._unit_widget.currentIndex() def set_value(self, value): """Set the value of the input :param value: The new value :type value: int, float """ self._input.setValue(value)
class EditorAssembly(QWidget): """ Class implementing the editor assembly widget containing the navigation combos and the editor widget. """ def __init__(self, dbs, fn=None, vm=None, filetype="", editor=None, tv=None): """ Constructor @param dbs reference to the debug server object @param fn name of the file to be opened (string). If it is None, a new (empty) editor is opened @param vm reference to the view manager object (ViewManager.ViewManager) @param filetype type of the source file (string) @param editor reference to an Editor object, if this is a cloned view @param tv reference to the task viewer object """ super(EditorAssembly, self).__init__() self.__layout = QGridLayout(self) self.__layout.setContentsMargins(0, 0, 0, 0) self.__layout.setSpacing(1) self.__globalsCombo = QComboBox() self.__membersCombo = QComboBox() from .Editor import Editor self.__editor = Editor(dbs, fn, vm, filetype, editor, tv) self.__layout.addWidget(self.__globalsCombo, 0, 0) self.__layout.addWidget(self.__membersCombo, 0, 1) self.__layout.addWidget(self.__editor, 1, 0, 1, -1) self.__module = None self.__globalsCombo.activated[int].connect(self.__globalsActivated) self.__membersCombo.activated[int].connect(self.__membersActivated) self.__editor.cursorLineChanged.connect(self.__editorCursorLineChanged) self.__parseTimer = QTimer(self) self.__parseTimer.setSingleShot(True) self.__parseTimer.setInterval(5 * 1000) self.__parseTimer.timeout.connect(self.__parseEditor) self.__editor.textChanged.connect(self.__resetParseTimer) self.__editor.refreshed.connect(self.__resetParseTimer) self.__selectedGlobal = "" self.__selectedMember = "" self.__globalsBoundaries = {} self.__membersBoundaries = {} QTimer.singleShot(0, self.__parseEditor) def shutdownTimer(self): """ Public method to stop and disconnect the timer. """ self.__parseTimer.stop() self.__parseTimer.timeout.disconnect(self.__parseEditor) self.__editor.textChanged.disconnect(self.__resetParseTimer) self.__editor.refreshed.disconnect(self.__resetParseTimer) def getEditor(self): """ Public method to get the reference to the editor widget. @return reference to the editor widget (Editor) """ return self.__editor def __globalsActivated(self, index, moveCursor=True): """ Private method to jump to the line of the selected global entry and to populate the members combo box. @param index index of the selected entry (integer) @keyparam moveCursor flag indicating to move the editor cursor (boolean) """ # step 1: go to the line of the selected entry lineno = self.__globalsCombo.itemData(index) if lineno is not None: if moveCursor: txt = self.__editor.text(lineno - 1).rstrip() pos = len(txt.replace(txt.strip(), "")) self.__editor.gotoLine( lineno, pos if pos == 0 else pos + 1, True) self.__editor.setFocus() # step 2: populate the members combo, if the entry is a class self.__membersCombo.clear() self.__membersBoundaries = {} self.__membersCombo.addItem("") memberIndex = 0 entryName = self.__globalsCombo.itemText(index) if self.__module: if entryName in self.__module.classes: entry = self.__module.classes[entryName] elif entryName in self.__module.modules: entry = self.__module.modules[entryName] # step 2.0: add module classes items = {} for cl in entry.classes.values(): if cl.isPrivate(): icon = UI.PixmapCache.getIcon("class_private.png") elif cl.isProtected(): icon = UI.PixmapCache.getIcon( "class_protected.png") else: icon = UI.PixmapCache.getIcon("class.png") items[cl.name] = (icon, cl.lineno, cl.endlineno) for key in sorted(items.keys()): itm = items[key] self.__membersCombo.addItem(itm[0], key, itm[1]) memberIndex += 1 self.__membersBoundaries[(itm[1], itm[2])] = \ memberIndex else: return # step 2.1: add class methods from Utilities.ModuleParser import Function items = {} for meth in entry.methods.values(): if meth.modifier == Function.Static: icon = UI.PixmapCache.getIcon("method_static.png") elif meth.modifier == Function.Class: icon = UI.PixmapCache.getIcon("method_class.png") elif meth.isPrivate(): icon = UI.PixmapCache.getIcon("method_private.png") elif meth.isProtected(): icon = UI.PixmapCache.getIcon("method_protected.png") else: icon = UI.PixmapCache.getIcon("method.png") items[meth.name] = (icon, meth.lineno, meth.endlineno) for key in sorted(items.keys()): itm = items[key] self.__membersCombo.addItem(itm[0], key, itm[1]) memberIndex += 1 self.__membersBoundaries[(itm[1], itm[2])] = memberIndex # step 2.2: add class instance attributes items = {} for attr in entry.attributes.values(): if attr.isPrivate(): icon = UI.PixmapCache.getIcon("attribute_private.png") elif attr.isProtected(): icon = UI.PixmapCache.getIcon( "attribute_protected.png") else: icon = UI.PixmapCache.getIcon("attribute.png") items[attr.name] = (icon, attr.lineno) for key in sorted(items.keys()): itm = items[key] self.__membersCombo.addItem(itm[0], key, itm[1]) # step 2.3: add class attributes items = {} icon = UI.PixmapCache.getIcon("attribute_class.png") for glob in entry.globals.values(): items[glob.name] = (icon, glob.lineno) for key in sorted(items.keys()): itm = items[key] self.__membersCombo.addItem(itm[0], key, itm[1]) def __membersActivated(self, index, moveCursor=True): """ Private method to jump to the line of the selected members entry. @param index index of the selected entry (integer) @keyparam moveCursor flag indicating to move the editor cursor (boolean) """ lineno = self.__membersCombo.itemData(index) if lineno is not None and moveCursor: txt = self.__editor.text(lineno - 1).rstrip() pos = len(txt.replace(txt.strip(), "")) self.__editor.gotoLine(lineno, pos if pos == 0 else pos + 1, True) self.__editor.setFocus() def __resetParseTimer(self): """ Private slot to reset the parse timer. """ self.__parseTimer.stop() self.__parseTimer.start() def __parseEditor(self): """ Private method to parse the editor source and repopulate the globals combo. """ from Utilities.ModuleParser import Module, getTypeFromTypeName self.__module = None sourceType = getTypeFromTypeName(self.__editor.determineFileType()) if sourceType != -1: src = self.__editor.text() if src: fn = self.__editor.getFileName() if fn is None: fn = "" self.__module = Module("", fn, sourceType) self.__module.scan(src) # remember the current selections self.__selectedGlobal = self.__globalsCombo.currentText() self.__selectedMember = self.__membersCombo.currentText() self.__globalsCombo.clear() self.__membersCombo.clear() self.__globalsBoundaries = {} self.__membersBoundaries = {} self.__globalsCombo.addItem("") index = 0 # step 1: add modules items = {} for module in self.__module.modules.values(): items[module.name] = (UI.PixmapCache.getIcon("module.png"), module.lineno, module.endlineno) for key in sorted(items.keys()): itm = items[key] self.__globalsCombo.addItem(itm[0], key, itm[1]) index += 1 self.__globalsBoundaries[(itm[1], itm[2])] = index # step 2: add classes items = {} for cl in self.__module.classes.values(): if cl.isPrivate(): icon = UI.PixmapCache.getIcon("class_private.png") elif cl.isProtected(): icon = UI.PixmapCache.getIcon("class_protected.png") else: icon = UI.PixmapCache.getIcon("class.png") items[cl.name] = (icon, cl.lineno, cl.endlineno) for key in sorted(items.keys()): itm = items[key] self.__globalsCombo.addItem(itm[0], key, itm[1]) index += 1 self.__globalsBoundaries[(itm[1], itm[2])] = index # step 3: add functions items = {} for func in self.__module.functions.values(): if func.isPrivate(): icon = UI.PixmapCache.getIcon("method_private.png") elif func.isProtected(): icon = UI.PixmapCache.getIcon("method_protected.png") else: icon = UI.PixmapCache.getIcon("method.png") items[func.name] = (icon, func.lineno, func.endlineno) for key in sorted(items.keys()): itm = items[key] self.__globalsCombo.addItem(itm[0], key, itm[1]) index += 1 self.__globalsBoundaries[(itm[1], itm[2])] = index # step 4: add attributes items = {} for glob in self.__module.globals.values(): if glob.isPrivate(): icon = UI.PixmapCache.getIcon("attribute_private.png") elif glob.isProtected(): icon = UI.PixmapCache.getIcon( "attribute_protected.png") else: icon = UI.PixmapCache.getIcon("attribute.png") items[glob.name] = (icon, glob.lineno) for key in sorted(items.keys()): itm = items[key] self.__globalsCombo.addItem(itm[0], key, itm[1]) # reset the currently selected entries without moving the # text cursor index = self.__globalsCombo.findText(self.__selectedGlobal) if index != -1: self.__globalsCombo.setCurrentIndex(index) self.__globalsActivated(index, moveCursor=False) index = self.__membersCombo.findText(self.__selectedMember) if index != -1: self.__membersCombo.setCurrentIndex(index) self.__membersActivated(index, moveCursor=False) def __editorCursorLineChanged(self, lineno): """ Private slot handling a line change of the cursor of the editor. @param lineno line number of the cursor (integer) """ lineno += 1 # cursor position is zero based, code info one based # step 1: search in the globals for (lower, upper), index in self.__globalsBoundaries.items(): if upper == -1: upper = 1000000 # it is the last line if lower <= lineno <= upper: break else: index = 0 self.__globalsCombo.setCurrentIndex(index) self.__globalsActivated(index, moveCursor=False) # step 2: search in members for (lower, upper), index in self.__membersBoundaries.items(): if upper == -1: upper = 1000000 # it is the last line if lower <= lineno <= upper: break else: index = 0 self.__membersCombo.setCurrentIndex(index) self.__membersActivated(index, moveCursor=False)
class ManageWindow(QWidget): __BASE_HEIGHT__ = 400 signal_user_res = pyqtSignal(bool) signal_table_update = pyqtSignal() def __init__(self, i18n: dict, icon_cache: MemoryCache, manager: SoftwareManager, disk_cache: bool, download_icons: bool, screen_size, suggestions: bool, display_limit: int, config: Configuration, context: ApplicationContext, notifications: bool, tray_icon=None): super(ManageWindow, self).__init__() self.i18n = i18n self.manager = manager self.tray_icon = tray_icon self.working = False # restrict the number of threaded actions self.pkgs = [] # packages current loaded in the table self.pkgs_available = [] # all packages loaded in memory self.pkgs_installed = [] # cached installed packages self.display_limit = display_limit self.icon_cache = icon_cache self.disk_cache = disk_cache self.download_icons = download_icons self.screen_size = screen_size self.config = config self.context = context self.notifications = notifications self.icon_app = QIcon(resource.get_path('img/logo.svg')) self.resize(ManageWindow.__BASE_HEIGHT__, ManageWindow.__BASE_HEIGHT__) self.setWindowIcon(self.icon_app) self.layout = QVBoxLayout() self.setLayout(self.layout) self.toolbar_top = QToolBar() self.toolbar_top.addWidget(new_spacer()) self.label_status = QLabel() self.label_status.setText('') self.label_status.setStyleSheet("font-weight: bold") self.toolbar_top.addWidget(self.label_status) self.toolbar_search = QToolBar() self.toolbar_search.setStyleSheet("spacing: 0px;") self.toolbar_search.setContentsMargins(0, 0, 0, 0) label_pre_search = QLabel() label_pre_search.setStyleSheet( "background: white; border-top-left-radius: 5px; border-bottom-left-radius: 5px;" ) self.toolbar_search.addWidget(label_pre_search) self.input_search = QLineEdit() self.input_search.setMaxLength(20) self.input_search.setFrame(False) self.input_search.setPlaceholderText( self.i18n['window_manage.input_search.placeholder'] + "...") self.input_search.setToolTip( self.i18n['window_manage.input_search.tooltip']) self.input_search.setStyleSheet( "QLineEdit { background-color: white; color: gray; spacing: 0; height: 30px; font-size: 12px; width: 300px}" ) self.input_search.returnPressed.connect(self.search) self.toolbar_search.addWidget(self.input_search) label_pos_search = QLabel() label_pos_search.setPixmap(QPixmap( resource.get_path('img/search.svg'))) label_pos_search.setStyleSheet( "background: white; padding-right: 10px; border-top-right-radius: 5px; border-bottom-right-radius: 5px;" ) self.toolbar_search.addWidget(label_pos_search) self.ref_toolbar_search = self.toolbar_top.addWidget( self.toolbar_search) self.toolbar_top.addWidget(new_spacer()) self.layout.addWidget(self.toolbar_top) self.toolbar = QToolBar() self.toolbar.setStyleSheet( 'QToolBar {spacing: 4px; margin-top: 15px; margin-bottom: 5px}') self.checkbox_updates = QCheckBox() self.checkbox_updates.setText(self.i18n['updates'].capitalize()) self.checkbox_updates.stateChanged.connect(self._handle_updates_filter) self.ref_checkbox_updates = self.toolbar.addWidget( self.checkbox_updates) self.checkbox_only_apps = QCheckBox() self.checkbox_only_apps.setText( self.i18n['manage_window.checkbox.only_apps']) self.checkbox_only_apps.setChecked(True) self.checkbox_only_apps.stateChanged.connect( self._handle_filter_only_apps) self.ref_checkbox_only_apps = self.toolbar.addWidget( self.checkbox_only_apps) self.any_type_filter = 'any' self.cache_type_filter_icons = {} self.combo_filter_type = QComboBox() self.combo_filter_type.setStyleSheet('QLineEdit { height: 2px}') self.combo_filter_type.setEditable(True) self.combo_filter_type.lineEdit().setReadOnly(True) self.combo_filter_type.lineEdit().setAlignment(Qt.AlignCenter) self.combo_filter_type.activated.connect(self._handle_type_filter) self.combo_filter_type.addItem( load_icon(resource.get_path('img/logo.svg'), 14), self.i18n[self.any_type_filter].capitalize(), self.any_type_filter) self.ref_combo_filter_type = self.toolbar.addWidget( self.combo_filter_type) self.input_name_filter = InputFilter(self.apply_filters_async) self.input_name_filter.setMaxLength(10) self.input_name_filter.setPlaceholderText( self.i18n['manage_window.name_filter.placeholder'] + '...') self.input_name_filter.setToolTip( self.i18n['manage_window.name_filter.tooltip']) self.input_name_filter.setStyleSheet( "QLineEdit { background-color: white; color: gray;}") self.input_name_filter.setFixedWidth(130) self.ref_input_name_filter = self.toolbar.addWidget( self.input_name_filter) self.toolbar.addWidget(new_spacer()) self.bt_installed = QPushButton() self.bt_installed.setToolTip( self.i18n['manage_window.bt.installed.tooltip']) self.bt_installed.setIcon(QIcon(resource.get_path('img/disk.png'))) self.bt_installed.setText( self.i18n['manage_window.bt.installed.text'].capitalize()) self.bt_installed.clicked.connect(self._show_installed) self.bt_installed.setStyleSheet(toolbar_button_style('#A94E0A')) self.ref_bt_installed = self.toolbar.addWidget(self.bt_installed) self.bt_refresh = QPushButton() self.bt_refresh.setToolTip(i18n['manage_window.bt.refresh.tooltip']) self.bt_refresh.setIcon(QIcon(resource.get_path('img/refresh.svg'))) self.bt_refresh.setText(self.i18n['manage_window.bt.refresh.text']) self.bt_refresh.setStyleSheet(toolbar_button_style('#2368AD')) self.bt_refresh.clicked.connect( lambda: self.refresh_apps(keep_console=False)) self.ref_bt_refresh = self.toolbar.addWidget(self.bt_refresh) self.bt_upgrade = QPushButton() self.bt_upgrade.setToolTip(i18n['manage_window.bt.upgrade.tooltip']) self.bt_upgrade.setIcon(QIcon(resource.get_path('img/app_update.svg'))) self.bt_upgrade.setText(i18n['manage_window.bt.upgrade.text']) self.bt_upgrade.setStyleSheet(toolbar_button_style('#20A435')) self.bt_upgrade.clicked.connect(self.update_selected) self.ref_bt_upgrade = self.toolbar.addWidget(self.bt_upgrade) self.layout.addWidget(self.toolbar) self.table_apps = AppsTable(self, self.icon_cache, disk_cache=self.disk_cache, download_icons=self.download_icons) self.table_apps.change_headers_policy() self.layout.addWidget(self.table_apps) toolbar_console = QToolBar() self.checkbox_console = QCheckBox() self.checkbox_console.setText( self.i18n['manage_window.checkbox.show_details']) self.checkbox_console.stateChanged.connect(self._handle_console) self.checkbox_console.setVisible(False) self.ref_checkbox_console = toolbar_console.addWidget( self.checkbox_console) toolbar_console.addWidget(new_spacer()) self.label_displayed = QLabel() toolbar_console.addWidget(self.label_displayed) self.layout.addWidget(toolbar_console) self.textarea_output = QPlainTextEdit(self) self.textarea_output.resize(self.table_apps.size()) self.textarea_output.setStyleSheet("background: black; color: white;") self.layout.addWidget(self.textarea_output) self.textarea_output.setVisible(False) self.textarea_output.setReadOnly(True) self.toolbar_substatus = QToolBar() self.toolbar_substatus.addWidget(new_spacer()) self.label_substatus = QLabel() self.toolbar_substatus.addWidget(self.label_substatus) self.toolbar_substatus.addWidget(new_spacer()) self.layout.addWidget(self.toolbar_substatus) self._change_label_substatus('') self.thread_update = self._bind_async_action( UpdateSelectedApps(self.manager, self.i18n), finished_call=self._finish_update_selected) self.thread_refresh = self._bind_async_action( RefreshApps(self.manager), finished_call=self._finish_refresh_apps, only_finished=True) self.thread_uninstall = self._bind_async_action( UninstallApp(self.manager, self.icon_cache), finished_call=self._finish_uninstall) self.thread_get_info = self._bind_async_action( GetAppInfo(self.manager), finished_call=self._finish_get_info) self.thread_get_history = self._bind_async_action( GetAppHistory(self.manager, self.i18n), finished_call=self._finish_get_history) self.thread_search = self._bind_async_action( SearchPackages(self.manager), finished_call=self._finish_search, only_finished=True) self.thread_downgrade = self._bind_async_action( DowngradeApp(self.manager, self.i18n), finished_call=self._finish_downgrade) self.thread_suggestions = self._bind_async_action( FindSuggestions(man=self.manager), finished_call=self._finish_search, only_finished=True) self.thread_run_app = self._bind_async_action( LaunchApp(self.manager), finished_call=self._finish_run_app, only_finished=False) self.thread_custom_action = self._bind_async_action( CustomAction(manager=self.manager), finished_call=self._finish_custom_action) self.thread_apply_filters = ApplyFilters() self.thread_apply_filters.signal_finished.connect( self._finish_apply_filters_async) self.thread_apply_filters.signal_table.connect( self._update_table_and_upgrades) self.signal_table_update.connect( self.thread_apply_filters.stop_waiting) self.thread_install = InstallPackage(manager=self.manager, disk_cache=self.disk_cache, icon_cache=self.icon_cache, locale_keys=self.i18n) self._bind_async_action(self.thread_install, finished_call=self._finish_install) self.thread_animate_progress = AnimateProgress() self.thread_animate_progress.signal_change.connect( self._update_progress) self.thread_verify_models = VerifyModels() self.thread_verify_models.signal_updates.connect( self._notify_model_data_change) self.toolbar_bottom = QToolBar() self.toolbar_bottom.setIconSize(QSize(16, 16)) self.toolbar_bottom.setStyleSheet('QToolBar { spacing: 3px }') self.toolbar_bottom.addWidget(new_spacer()) self.progress_bar = QProgressBar() self.progress_bar.setMaximumHeight(10 if QApplication.instance().style( ).objectName().lower() == 'windows' else 4) self.progress_bar.setTextVisible(False) self.ref_progress_bar = self.toolbar_bottom.addWidget( self.progress_bar) self.toolbar_bottom.addWidget(new_spacer()) self.combo_styles = StylesComboBox( parent=self, i18n=i18n, show_panel_after_restart=bool(tray_icon)) self.combo_styles.setStyleSheet('QComboBox {font-size: 12px;}') self.ref_combo_styles = self.toolbar_bottom.addWidget( self.combo_styles) bt_settings = IconButton( icon_path=resource.get_path('img/app_settings.svg'), action=self._show_settings_menu, background='#12ABAB', tooltip=self.i18n['manage_window.bt_settings.tooltip']) self.ref_bt_settings = self.toolbar_bottom.addWidget(bt_settings) self.layout.addWidget(self.toolbar_bottom) qt_utils.centralize(self) self.filter_only_apps = True self.type_filter = self.any_type_filter self.filter_updates = False self._maximized = False self.progress_controll_enabled = True self.recent_installation = False self.dialog_about = None self.first_refresh = suggestions self.thread_warnings = ListWarnings(man=manager, locale_keys=i18n) self.thread_warnings.signal_warnings.connect(self._show_warnings) def set_tray_icon(self, tray_icon): self.tray_icon = tray_icon self.combo_styles.show_panel_after_restart = bool(tray_icon) def _update_process_progress(self, val: int): if self.progress_controll_enabled: self.thread_animate_progress.set_progress(val) def apply_filters_async(self): self.label_status.setText(self.i18n['manage_window.status.filtering'] + '...') self.ref_toolbar_search.setVisible(False) if self.ref_input_name_filter.isVisible(): self.input_name_filter.setReadOnly(True) self.thread_apply_filters.filters = self._gen_filters() self.thread_apply_filters.pkgs = self.pkgs_available self.thread_apply_filters.start() def _update_table_and_upgrades(self, pkgs_info: dict): self._update_table(pkgs_info=pkgs_info, signal=True) self.update_bt_upgrade(pkgs_info) def _finish_apply_filters_async(self, success: bool): self.label_status.setText('') self.ref_toolbar_search.setVisible(True) if self.ref_input_name_filter.isVisible(): self.input_name_filter.setReadOnly(False) def _bind_async_action(self, action: AsyncAction, finished_call, only_finished: bool = False) -> AsyncAction: action.signal_finished.connect(finished_call) if not only_finished: action.signal_confirmation.connect(self._ask_confirmation) action.signal_output.connect(self._update_action_output) action.signal_message.connect(self._show_message) action.signal_status.connect(self._change_label_status) action.signal_substatus.connect(self._change_label_substatus) action.signal_progress.connect(self._update_process_progress) self.signal_user_res.connect(action.confirm) return action def _ask_confirmation(self, msg: dict): self.thread_animate_progress.pause() diag = ConfirmationDialog(title=msg['title'], body=msg['body'], locale_keys=self.i18n, components=msg['components'], confirmation_label=msg['confirmation_label'], deny_label=msg['deny_label']) res = diag.is_confirmed() self.thread_animate_progress.animate() self.signal_user_res.emit(res) def _show_message(self, msg: dict): self.thread_animate_progress.pause() dialog.show_message(title=msg['title'], body=msg['body'], type_=msg['type']) self.thread_animate_progress.animate() def _show_warnings(self, warnings: List[str]): if warnings: dialog.show_message(title=self.i18n['warning'].capitalize(), body='<p>{}</p>'.format( '<br/><br/>'.join(warnings)), type_=MessageType.WARNING) def show(self): super(ManageWindow, self).show() if not self.thread_warnings.isFinished(): self.thread_warnings.start() def verify_warnings(self): self.thread_warnings.start() def _show_installed(self): if self.pkgs_installed: self.finish_action() self.ref_bt_upgrade.setVisible(True) self.ref_checkbox_only_apps.setVisible(True) self.input_search.setText('') self.input_name_filter.setText('') self.update_pkgs(new_pkgs=None, as_installed=True) def _show_about(self): if self.dialog_about is None: self.dialog_about = AboutDialog(self.i18n) self.dialog_about.show() def _handle_updates_filter(self, status: int): self.filter_updates = status == 2 self.apply_filters_async() def _handle_filter_only_apps(self, status: int): self.filter_only_apps = status == 2 self.apply_filters_async() def _handle_type_filter(self, idx: int): self.type_filter = self.combo_filter_type.itemData(idx) self.apply_filters_async() def _notify_model_data_change(self): self.table_apps.fill_async_data() def changeEvent(self, e: QEvent): if isinstance(e, QWindowStateChangeEvent): self._maximized = self.isMaximized() policy = QHeaderView.Stretch if self._maximized else QHeaderView.ResizeToContents self.table_apps.change_headers_policy(policy) def closeEvent(self, event): if self.tray_icon: event.ignore() self.hide() self._handle_console_option(False) def _handle_console(self, checked: bool): if checked: self.textarea_output.show() else: self.textarea_output.hide() def _handle_console_option(self, enable: bool): if enable: self.textarea_output.clear() self.ref_checkbox_console.setVisible(enable) self.checkbox_console.setChecked(False) self.textarea_output.hide() def refresh_apps(self, keep_console: bool = True, top_app: PackageView = None, pkg_types: Set[Type[SoftwarePackage]] = None): self.recent_installation = False self.type_filter = None self.input_search.clear() if not keep_console: self._handle_console_option(False) self.ref_checkbox_updates.setVisible(False) self.ref_checkbox_only_apps.setVisible(False) self._begin_action(self.i18n['manage_window.status.refreshing'], keep_bt_installed=False, clear_filters=True) self.thread_refresh.app = top_app # the app will be on top when refresh happens self.thread_refresh.pkg_types = pkg_types self.thread_refresh.start() def _finish_refresh_apps(self, res: dict, as_installed: bool = True): self.finish_action() self.ref_checkbox_only_apps.setVisible(bool(res['installed'])) self.ref_bt_upgrade.setVisible(True) self.update_pkgs(res['installed'], as_installed=as_installed, types=res['types']) self.first_refresh = False def uninstall_app(self, app: PackageView): pwd = None requires_root = self.manager.requires_root('uninstall', app.model) if not is_root() and requires_root: pwd, ok = ask_root_password(self.i18n) if not ok: return self._handle_console_option(True) self._begin_action('{} {}'.format( self.i18n['manage_window.status.uninstalling'], app.model.name)) self.thread_uninstall.app = app self.thread_uninstall.root_password = pwd self.thread_uninstall.start() def run_app(self, app: PackageView): self._begin_action( self.i18n['manage_window.status.running_app'].format( app.model.name)) self.thread_run_app.app = app self.thread_run_app.start() def _finish_uninstall(self, pkgv: PackageView): self.finish_action() if pkgv: if self._can_notify_user(): util.notify_user('{} ({}) {}'.format(pkgv.model.name, pkgv.model.get_type(), self.i18n['uninstalled'])) self.refresh_apps(pkg_types={pkgv.model.__class__}) else: if self._can_notify_user(): util.notify_user('{}: {}'.format( pkgv.model.name, self.i18n['notification.uninstall.failed'])) self.checkbox_console.setChecked(True) def _can_notify_user(self): return self.notifications and (self.isHidden() or self.isMinimized()) def _finish_downgrade(self, res: dict): self.finish_action() if res['success']: if self._can_notify_user(): util.notify_user('{} {}'.format(res['app'], self.i18n['downgraded'])) self.refresh_apps(pkg_types={res['app'].model.__class__}) if self.tray_icon: self.tray_icon.verify_updates(notify_user=False) else: if self._can_notify_user(): util.notify_user(self.i18n['notification.downgrade.failed']) self.checkbox_console.setChecked(True) def _change_label_status(self, status: str): self.label_status.setText(status) def _change_label_substatus(self, substatus: str): self.label_substatus.setText('<p>{}</p>'.format(substatus)) if not substatus: self.toolbar_substatus.hide() elif not self.toolbar_substatus.isVisible(): self.toolbar_substatus.show() def _update_table(self, pkgs_info: dict, signal: bool = False): self.pkgs = pkgs_info['pkgs_displayed'] self.table_apps.update_pkgs( self.pkgs, update_check_enabled=pkgs_info['not_installed'] == 0) if not self._maximized: self.table_apps.change_headers_policy(QHeaderView.Stretch) self.table_apps.change_headers_policy() self.resize_and_center(accept_lower_width=len(self.pkgs) > 0) self.label_displayed.setText('{} / {}'.format( len(self.pkgs), len(self.pkgs_available))) else: self.label_displayed.setText('') if signal: self.signal_table_update.emit() def update_bt_upgrade(self, pkgs_info: dict = None): show_bt_upgrade = False if not pkgs_info or pkgs_info['not_installed'] == 0: for app_v in (pkgs_info['pkgs_displayed'] if pkgs_info else self.pkgs): if app_v.update_checked: show_bt_upgrade = True break self.ref_bt_upgrade.setVisible(show_bt_upgrade) def change_update_state(self, pkgs_info: dict, trigger_filters: bool = True): self.update_bt_upgrade(pkgs_info) if pkgs_info['updates'] > 0: if pkgs_info['not_installed'] == 0: if not self.ref_checkbox_updates.isVisible(): self.ref_checkbox_updates.setVisible(True) if not self.filter_updates: self._change_checkbox(self.checkbox_updates, True, 'filter_updates', trigger_filters) if pkgs_info['napp_updates'] > 0 and self.filter_only_apps: self._change_checkbox(self.checkbox_only_apps, False, 'filter_only_apps', trigger_filters) else: self._change_checkbox(self.checkbox_updates, False, 'filter_updates', trigger_filters) self.ref_checkbox_updates.setVisible(False) def _change_checkbox(self, checkbox: QCheckBox, checked: bool, attr: str = None, trigger: bool = True): if not trigger: checkbox.blockSignals(True) checkbox.setChecked(checked) if not trigger: setattr(self, attr, checked) checkbox.blockSignals(False) def _gen_filters(self, updates: int = 0, ignore_updates: bool = False) -> dict: return { 'only_apps': self.filter_only_apps, 'type': self.type_filter, 'updates': False if ignore_updates else self.filter_updates, 'name': self.input_name_filter.get_text().lower() if self.input_name_filter.get_text() else None, 'display_limit': self.display_limit if updates <= 0 else None } def update_pkgs(self, new_pkgs: List[SoftwarePackage], as_installed: bool, types: Set[type] = None, ignore_updates: bool = False): self.input_name_filter.setText('') pkgs_info = commons.new_pkgs_info() filters = self._gen_filters(ignore_updates) if new_pkgs is not None: old_installed = None if as_installed: old_installed = self.pkgs_installed self.pkgs_installed = [] for pkg in new_pkgs: app_model = PackageView(model=pkg) commons.update_info(app_model, pkgs_info) commons.apply_filters(app_model, filters, pkgs_info) if old_installed and types: for pkgv in old_installed: if not pkgv.model.__class__ in types: commons.update_info(pkgv, pkgs_info) commons.apply_filters(pkgv, filters, pkgs_info) else: # use installed for pkgv in self.pkgs_installed: commons.update_info(pkgv, pkgs_info) commons.apply_filters(pkgv, filters, pkgs_info) if pkgs_info['apps_count'] == 0: if self.first_refresh: self._begin_search('') self.thread_suggestions.start() return else: self._change_checkbox(self.checkbox_only_apps, False, 'filter_only_apps', trigger=False) self.checkbox_only_apps.setCheckable(False) else: self.checkbox_only_apps.setCheckable(True) self._change_checkbox(self.checkbox_only_apps, True, 'filter_only_apps', trigger=False) self.change_update_state(pkgs_info=pkgs_info, trigger_filters=False) self._apply_filters(pkgs_info, ignore_updates=ignore_updates) self.change_update_state(pkgs_info=pkgs_info, trigger_filters=False) self.pkgs_available = pkgs_info['pkgs'] if as_installed: self.pkgs_installed = pkgs_info['pkgs'] self.pkgs = pkgs_info['pkgs_displayed'] if self.pkgs: self.ref_input_name_filter.setVisible(True) self._update_type_filters(pkgs_info['available_types']) self._update_table(pkgs_info=pkgs_info) if new_pkgs: self.thread_verify_models.apps = self.pkgs self.thread_verify_models.start() if self.pkgs_installed: self.ref_bt_installed.setVisible(not as_installed and not self.recent_installation) self.resize_and_center(accept_lower_width=self.pkgs_installed) def _apply_filters(self, pkgs_info: dict, ignore_updates: bool): pkgs_info['pkgs_displayed'] = [] filters = self._gen_filters(updates=pkgs_info['updates'], ignore_updates=ignore_updates) for pkgv in pkgs_info['pkgs']: commons.apply_filters(pkgv, filters, pkgs_info) def _update_type_filters(self, available_types: dict = None): if available_types is None: self.ref_combo_filter_type.setVisible( self.combo_filter_type.count() > 1) else: self.type_filter = self.any_type_filter if available_types and len(available_types) > 1: if self.combo_filter_type.count() > 1: for _ in range(self.combo_filter_type.count() - 1): self.combo_filter_type.removeItem(1) for app_type, icon_path in available_types.items(): icon = self.cache_type_filter_icons.get(app_type) if not icon: icon = load_icon(icon_path, 14) self.cache_type_filter_icons[app_type] = icon self.combo_filter_type.addItem(icon, app_type.capitalize(), app_type) self.ref_combo_filter_type.setVisible(True) else: self.ref_combo_filter_type.setVisible(False) def resize_and_center(self, accept_lower_width: bool = True): if self.pkgs: new_width = reduce(operator.add, [ self.table_apps.columnWidth(i) for i in range(self.table_apps.columnCount()) ]) if self.ref_bt_upgrade.isVisible( ) or self.ref_bt_settings.isVisible(): new_width *= 1.07 else: new_width = self.toolbar_top.width() if accept_lower_width or new_width > self.width(): self.resize(new_width, self.height()) if self.ref_bt_upgrade.isVisible( ) and self.bt_upgrade.visibleRegion().isEmpty(): self.adjustSize() qt_utils.centralize(self) def update_selected(self): if self.pkgs: requires_root = False to_update = [] for app_v in self.pkgs: if app_v.update_checked: to_update.append(app_v) if self.manager.requires_root('update', app_v.model): requires_root = True if to_update and dialog.ask_confirmation( title=self.i18n['manage_window.upgrade_all.popup.title'], body=self.i18n['manage_window.upgrade_all.popup.body'], locale_keys=self.i18n, widgets=[ UpdateToggleButton( None, self, self.i18n, clickable=False) ]): pwd = None if not is_root() and requires_root: pwd, ok = ask_root_password(self.i18n) if not ok: return self._handle_console_option(True) self.progress_controll_enabled = len(to_update) == 1 self._begin_action(self.i18n['manage_window.status.upgrading']) self.thread_update.apps_to_update = to_update self.thread_update.root_password = pwd self.thread_update.start() def _finish_update_selected(self, res: dict): self.finish_action() if res['success']: if self._can_notify_user(): util.notify_user('{} {}'.format( res['updated'], self.i18n['notification.update_selected.success'])) self.refresh_apps(pkg_types=res['types']) if self.tray_icon: self.tray_icon.verify_updates() else: if self._can_notify_user(): util.notify_user( self.i18n['notification.update_selected.failed']) self.ref_bt_upgrade.setVisible(True) self.checkbox_console.setChecked(True) def _update_action_output(self, output: str): self.textarea_output.appendPlainText(output) def _begin_action(self, action_label: str, keep_search: bool = False, keep_bt_installed: bool = True, clear_filters: bool = False): self.ref_input_name_filter.setVisible(False) self.ref_combo_filter_type.setVisible(False) self.ref_bt_settings.setVisible(False) self.thread_animate_progress.stop = False self.thread_animate_progress.start() self.ref_progress_bar.setVisible(True) self.ref_combo_styles.setVisible(False) self.label_status.setText(action_label + "...") self.ref_bt_upgrade.setVisible(False) self.ref_bt_refresh.setVisible(False) self.checkbox_only_apps.setEnabled(False) self.table_apps.setEnabled(False) self.checkbox_updates.setEnabled(False) if not keep_bt_installed: self.ref_bt_installed.setVisible(False) elif self.ref_bt_installed.isVisible(): self.ref_bt_installed.setEnabled(False) if keep_search: self.ref_toolbar_search.setVisible(True) else: self.ref_toolbar_search.setVisible(False) if clear_filters: self._update_type_filters({}) else: self.combo_filter_type.setEnabled(False) def finish_action(self): self.ref_combo_styles.setVisible(True) self.thread_animate_progress.stop = True self.thread_animate_progress.wait() self.ref_progress_bar.setVisible(False) self.progress_bar.setValue(0) self.progress_bar.setTextVisible(False) self._change_label_substatus('') self.ref_bt_settings.setVisible(True) self.ref_bt_refresh.setVisible(True) self.checkbox_only_apps.setEnabled(True) self.table_apps.setEnabled(True) self.input_search.setEnabled(True) self.label_status.setText('') self.label_substatus.setText('') self.ref_toolbar_search.setVisible(True) self.ref_toolbar_search.setEnabled(True) self.combo_filter_type.setEnabled(True) self.checkbox_updates.setEnabled(True) self.progress_controll_enabled = True if self.pkgs: self.ref_input_name_filter.setVisible(True) self.update_bt_upgrade() self._update_type_filters() if self.ref_bt_installed.isVisible(): self.ref_bt_installed.setEnabled(True) def downgrade(self, pkgv: PackageView): pwd = None requires_root = self.manager.requires_root('downgrade', pkgv.model) if not is_root() and requires_root: pwd, ok = ask_root_password(self.i18n) if not ok: return self._handle_console_option(True) self._begin_action('{} {}'.format( self.i18n['manage_window.status.downgrading'], pkgv.model.name)) self.thread_downgrade.app = pkgv self.thread_downgrade.root_password = pwd self.thread_downgrade.start() def get_app_info(self, pkg: dict): self._handle_console_option(False) self._begin_action(self.i18n['manage_window.status.info']) self.thread_get_info.app = pkg self.thread_get_info.start() def get_app_history(self, app: dict): self._handle_console_option(False) self._begin_action(self.i18n['manage_window.status.history']) self.thread_get_history.app = app self.thread_get_history.start() def _finish_get_info(self, app_info: dict): self.finish_action() dialog_info = InfoDialog(app=app_info, icon_cache=self.icon_cache, locale_keys=self.i18n, screen_size=self.screen_size) dialog_info.exec_() def _finish_get_history(self, res: dict): self.finish_action() if res.get('error'): self._handle_console_option(True) self.textarea_output.appendPlainText(res['error']) self.checkbox_console.setChecked(True) else: dialog_history = HistoryDialog(res['history'], self.icon_cache, self.i18n) dialog_history.exec_() def _begin_search(self, word): self._handle_console_option(False) self.ref_checkbox_only_apps.setVisible(False) self.ref_checkbox_updates.setVisible(False) self.filter_updates = False self._begin_action('{} {}'.format( self.i18n['manage_window.status.searching'], word if word else ''), clear_filters=True) def search(self): word = self.input_search.text().strip() if word: self._begin_search(word) self.thread_search.word = word self.thread_search.start() def _finish_search(self, res: dict): self.finish_action() if not res['error']: self.ref_bt_upgrade.setVisible(False) self.update_pkgs(res['pkgs_found'], as_installed=False, ignore_updates=True) else: dialog.show_message(title=self.i18n['warning'].capitalize(), body=self.i18n[res['error']], type_=MessageType.WARNING) def install(self, pkg: PackageView): pwd = None requires_root = self.manager.requires_root('install', pkg.model) if not is_root() and requires_root: pwd, ok = ask_root_password(self.i18n) if not ok: return self._handle_console_option(True) self._begin_action('{} {}'.format( self.i18n['manage_window.status.installing'], pkg.model.name)) self.thread_install.pkg = pkg self.thread_install.root_password = pwd self.thread_install.start() def _finish_install(self, res: dict): self.input_search.setText('') self.finish_action() console_output = self.textarea_output.toPlainText() if console_output: log_path = '/tmp/bauh/logs/install/{}/{}'.format( res['pkg'].model.get_type(), res['pkg'].model.name) try: Path(log_path).mkdir(parents=True, exist_ok=True) log_file = log_path + '/{}.log'.format(int(time.time())) with open(log_file, 'w+') as f: f.write(console_output) self.textarea_output.appendPlainText( self.i18n['console.install_logs.path'].format( '"{}"'.format(log_file))) except: self.textarea_output.appendPlainText( "[warning] Could not write install log file to '{}'". format(log_path)) if res['success']: self.recent_installation = True if self._can_notify_user(): util.notify_user(msg='{} ({}) {}'.format( res['pkg'].model.name, res['pkg'].model.get_type(), self.i18n['installed'])) self._finish_refresh_apps({ 'installed': [res['pkg'].model], 'total': 1, 'types': None }) self.ref_bt_installed.setVisible(False) self.ref_checkbox_only_apps.setVisible(False) else: if self._can_notify_user(): util.notify_user('{}: {}'.format( res['pkg'].model.name, self.i18n['notification.install.failed'])) self.checkbox_console.setChecked(True) def _update_progress(self, value: int): self.progress_bar.setValue(value) def _finish_run_app(self, success: bool): self.finish_action() def execute_custom_action(self, pkg: PackageView, action: PackageAction): pwd = None if not is_root() and action.requires_root: pwd, ok = ask_root_password(self.i18n) if not ok: return self._handle_console_option(True) self._begin_action('{} {}'.format(self.i18n[action.i18n_status_key], pkg.model.name)) self.thread_custom_action.pkg = pkg self.thread_custom_action.root_password = pwd self.thread_custom_action.custom_action = action self.thread_custom_action.start() def _finish_custom_action(self, res: dict): self.finish_action() if res['success']: self.refresh_apps(pkg_types={res['pkg'].model.__class__}) else: self.checkbox_console.setChecked(True) def show_gems_selector(self): gem_panel = GemSelectorPanel(window=self, manager=self.manager, i18n=self.i18n, config=self.config, show_panel_after_restart=bool( self.tray_icon)) gem_panel.show() def _show_settings_menu(self): menu_row = QMenu() if isinstance(self.manager, GenericSoftwareManager): action_gems = QAction(self.i18n['manage_window.settings.gems']) action_gems.setIcon(self.icon_app) action_gems.triggered.connect(self.show_gems_selector) menu_row.addAction(action_gems) action_about = QAction(self.i18n['manage_window.settings.about']) action_about.setIcon(QIcon(resource.get_path('img/about.svg'))) action_about.triggered.connect(self._show_about) menu_row.addAction(action_about) menu_row.adjustSize() menu_row.popup(QCursor.pos()) menu_row.exec_()
class GlyphWindow(BaseMainWindow): def __init__(self, glyph, parent=None): super().__init__(parent) self.setAttribute(Qt.WA_DeleteOnClose, False) self.setUnifiedTitleAndToolBarOnMac(True) self.view = GlyphCanvasView(self) # create tools and buttons toolBars self._tools = [] self._toolsToolBar = QToolBar(self.tr("Tools"), self) self._toolsToolBar.setMovable(False) self._buttons = [] self._buttonsToolBar = QToolBar(self.tr("Buttons"), self) self._buttonsToolBar.setMovable(False) self.addToolBar(self._toolsToolBar) self.addToolBar(self._buttonsToolBar) # http://www.setnode.com/blog/right-aligning-a-button-in-a-qtoolbar/ self._layersToolBar = QToolBar(self.tr("Layers"), self) spacer = QWidget() spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self._currentLayerBox = QComboBox(self) self._currentLayerBox.currentIndexChanged.connect( self._layerChanged) self._layersToolBar.addWidget(spacer) self._layersToolBar.addWidget(self._currentLayerBox) self._layersToolBar.setContentsMargins(0, 0, 2, 0) self._layersToolBar.setMovable(False) self.addToolBar(self._layersToolBar) self.setGlyph(glyph) app = QApplication.instance() tools = app.drawingTools() for index, tool in enumerate(tools): action = self.installTool(tool) if not index: action.trigger() app.dispatcher.addObserver( self, "_drawingToolRegistered", "drawingToolRegistered") # TODO: drawingToolUnregistered self.installButton(RemoveOverlapButton) self.setCentralWidget(self.view) self.view.setFocus(True) self.readSettings() def readSettings(self): geometry = settings.glyphWindowGeometry() if geometry: self.restoreGeometry(geometry) def writeSettings(self): # TODO: save current tool? settings.setGlyphWindowGeometry(self.saveGeometry()) def setupMenu(self, menuBar): fileMenu = menuBar.fetchMenu(Entries.File) fontWindow = self.parent() if fontWindow is not None: fileMenu.fetchAction(Entries.File_Save, fontWindow.saveFile) fileMenu.fetchAction(Entries.File_Save_As, fontWindow.saveFileAs) fileMenu.fetchAction(Entries.File_Close, self.close) if fontWindow is not None: fileMenu.fetchAction(Entries.File_Reload, fontWindow.reloadFile) editMenu = menuBar.fetchMenu(Entries.Edit) self._undoAction = editMenu.fetchAction(Entries.Edit_Undo, self.undo) self._redoAction = editMenu.fetchAction(Entries.Edit_Redo, self.redo) editMenu.addSeparator() cutAction = editMenu.fetchAction(Entries.Edit_Cut, self.cutOutlines) copyAction = editMenu.fetchAction(Entries.Edit_Copy, self.copyOutlines) self._selectActions = (cutAction, copyAction) editMenu.fetchAction(Entries.Edit_Paste, self.pasteOutlines) editMenu.fetchAction(Entries.Edit_Select_All, self.selectAll) editMenu.fetchAction(Entries.Edit_Find, self.changeGlyph) # TODO: sort this out # editMenu.fetchAction(self.tr("&Deselect"), self.deselect, "Ctrl+D") # glyphMenu = menuBar.fetchMenu(self.tr("&Glyph")) # self._layerAction = glyphMenu.fetchAction( # self.tr("&Layer Actions…"), self.layerActions, "L") viewMenu = menuBar.fetchMenu(Entries.View) viewMenu.fetchAction(Entries.View_Zoom_In, lambda: self.view.zoom(1)) viewMenu.fetchAction(Entries.View_Zoom_Out, lambda: self.view.zoom(-1)) viewMenu.fetchAction(Entries.View_Reset_Zoom, self.view.fitScaleBBox) viewMenu.addSeparator() viewMenu.fetchAction( Entries.View_Next_Glyph, lambda: self.glyphOffset(1)) viewMenu.fetchAction( Entries.View_Previous_Glyph, lambda: self.glyphOffset(-1)) self._updateUndoRedo() # ---------- # Menu items # ---------- def saveFile(self): glyph = self.view.glyph() font = glyph.font if None not in (font, font.path): font.save() def glyphOffset(self, offset): currentGlyph = self.view.glyph() font = currentGlyph.font glyphOrder = font.glyphOrder # should be enforced in fontView already if not (glyphOrder and len(glyphOrder)): return index = glyphOrder.index(currentGlyph.name) newIndex = (index + offset) % len(glyphOrder) glyph = font[glyphOrder[newIndex]] self.setGlyph(glyph) def changeGlyph(self): glyph = self.view.glyph() newGlyph, ok = FindDialog.getNewGlyph(self, glyph) if ok and newGlyph is not None: self.setGlyph(newGlyph) def layerActions(self): glyph = self.view.glyph() newLayer, action, ok = LayerActionsDialog.getLayerAndAction( self, glyph) if ok and newLayer is not None: # TODO: whole glyph for now, but consider selection too if glyph.name not in newLayer: newLayer.newGlyph(glyph.name) otherGlyph = newLayer[glyph.name] otherGlyph.holdNotifications() if action == "Swap": tempGlyph = glyph.__class__() otherGlyph.drawPoints(tempGlyph.getPointPen()) tempGlyph.width = otherGlyph.width otherGlyph.clearContours() glyph.drawPoints(otherGlyph.getPointPen()) otherGlyph.width = glyph.width if action != "Copy": glyph.holdNotifications() glyph.clearContours() if action == "Swap": tempGlyph.drawPoints(glyph.getPointPen()) glyph.width = tempGlyph.width glyph.releaseHeldNotifications() otherGlyph.releaseHeldNotifications() def undo(self): glyph = self.view.glyph() glyph.undo() def redo(self): glyph = self.view.glyph() glyph.redo() def cutOutlines(self): glyph = self.view.glyph() self.copyOutlines() deleteUISelection(glyph) def copyOutlines(self): glyph = self.view.glyph() clipboard = QApplication.clipboard() mimeData = QMimeData() copyGlyph = glyph.getRepresentation("TruFont.FilterSelection") mimeData.setData("application/x-trufont-glyph-data", pickle.dumps([copyGlyph.serialize( blacklist=("name", "unicode") )])) clipboard.setMimeData(mimeData) def pasteOutlines(self): glyph = self.view.glyph() clipboard = QApplication.clipboard() mimeData = clipboard.mimeData() if mimeData.hasFormat("application/x-trufont-glyph-data"): data = pickle.loads(mimeData.data( "application/x-trufont-glyph-data")) if len(data) == 1: pen = glyph.getPointPen() pasteGlyph = glyph.__class__() pasteGlyph.deserialize(data[0]) # TODO: if we serialize selected state, we don't need to do # this pasteGlyph.selected = True if len(pasteGlyph) or len(pasteGlyph.components) or \ len(pasteGlyph.anchors): glyph.prepareUndo() pasteGlyph.drawPoints(pen) def selectAll(self): glyph = self.view.glyph() if glyph.selected: for anchor in glyph.anchors: anchor.selected = True for component in glyph.components: component.selected = True else: glyph.selected = True def deselect(self): glyph = self.view.glyph() for anchor in glyph.anchors: anchor.selected = False for component in glyph.components: component.selected = False glyph.selected = False def lockToolBars(self): action = self.sender() movable = not action.isChecked() for toolBar in ( self._toolsToolBar, self._buttonsToolBar, self._layersToolBar): toolBar.setMovable(movable) # -------------------------- # Tools & buttons management # -------------------------- def installTool(self, tool): action = self._toolsToolBar.addAction( QIcon(tool.iconPath), tool.name, self._setViewTool) action.setCheckable(True) num = len(self._tools) action.setData(num) action.setShortcut(QKeySequence(str(num + 1))) self._tools.append(tool(parent=self.view.widget())) return action def uninstallTool(self, tool): pass # XXX def _setViewTool(self): action = self.sender() index = action.data() newTool = self._tools[index] if newTool == self.view.currentTool(): action.setChecked(True) return ok = self.view.setCurrentTool(newTool) # if view did change tool, disable them all and enable the one we want # otherwise, just disable the tool that was clicked. # previously we used QActionGroup to have exclusive buttons, but doing # it manually allows us to NAK a button change. if ok: for act in self._toolsToolBar.actions(): act.setChecked(False) action.setChecked(ok) def installButton(self, button): action = self._buttonsToolBar.addAction( QIcon(button.iconPath), button.name, self._buttonAction) action.setData(len(self._buttons)) self._buttons.append(button(parent=self.view)) return action def uninstallButton(self, button): pass # XXX def _buttonAction(self): action = self.sender() index = action.data() button = self._buttons[index] button.clicked() # ------------- # Notifications # ------------- # app def _drawingToolRegistered(self, notification): tool = notification.data["tool"] self.installTool(tool) # glyph def _subscribeToGlyph(self, glyph): if glyph is not None: glyph.addObserver(self, "_glyphNameChanged", "Glyph.NameChanged") glyph.addObserver( self, "_glyphSelectionChanged", "Glyph.SelectionChanged") undoManager = glyph.undoManager undoManager.canUndoChanged.connect(self._setUndoEnabled) undoManager.canRedoChanged.connect(self._setRedoEnabled) self._subscribeToFontAndLayerSet(glyph.font) def _unsubscribeFromGlyph(self, glyph): if glyph is not None: glyph.removeObserver(self, "Glyph.NameChanged") glyph.removeObserver(self, "Glyph.SelectionChanged") undoManager = glyph.undoManager undoManager.canUndoChanged.disconnect(self._setUndoEnabled) undoManager.canRedoChanged.disconnect(self._setRedoEnabled) self._unsubscribeFromFontAndLayerSet(glyph.font) def _glyphNameChanged(self, notification): glyph = self.view.glyph() self.setWindowTitle(glyph.name, glyph.font) def _glyphSelectionChanged(self, notification): self._updateSelection() # layers & font def _subscribeToFontAndLayerSet(self, font): """Note: called by _subscribeToGlyph.""" if font is None: return font.info.addObserver(self, "_fontInfoChanged", "Info.Changed") layerSet = font.layers if layerSet is None: return layerSet.addObserver( self, '_layerSetLayerDeleted', 'LayerSet.LayerDeleted') for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged', 'LayerSet.LayerOrderChanged'): layerSet.addObserver(self, '_layerSetEvents', event) def _unsubscribeFromFontAndLayerSet(self, font): """Note: called by _unsubscribeFromGlyph.""" if font is None: return font.info.removeObserver(self, "Info.Changed") layerSet = font.layers if layerSet is None: return for event in ('LayerSet.LayerAdded', 'LayerSet.LayerChanged', 'LayerSet.LayerOrderChanged', 'LayerSet.LayerDeleted'): layerSet.removeObserver(self, event) def _fontInfoChanged(self, notification): glyph = self.view.glyph() self.setWindowTitle(glyph.name, glyph.font) def _layerSetEvents(self, notification): self._updateLayerControls() def _layerSetLayerDeleted(self, notification): self._layerSetEvents(notification) self._currentLayerBox.setCurrentIndex(0) # other updaters def _updateSelection(self): def hasSelection(): glyph = self.view.glyph() for contour in glyph: if len(contour.selection): return True for anchor in glyph.anchors: if anchor.selected: return True for component in glyph.components: if component.selected: return True return False if not hasattr(self, "_selectActions"): return hasSelection = hasSelection() for action in self._selectActions: action.setEnabled(hasSelection) def _updateUndoRedo(self): glyph = self.view.glyph() self._setUndoEnabled(glyph.canUndo()) self._setRedoEnabled(glyph.canRedo()) def _setUndoEnabled(self, value): if not hasattr(self, "_undoAction"): return self._undoAction.setEnabled(value) def _setRedoEnabled(self, value): if not hasattr(self, "_redoAction"): return self._redoAction.setEnabled(value) # -------------- # Public Methods # -------------- def setGlyph(self, glyph): currentGlyph = self.view.glyph() self._unsubscribeFromGlyph(currentGlyph) self.view.setGlyph(glyph) self._subscribeToGlyph(glyph) self._updateLayerControls() self._updateUndoRedo() self._updateSelection() self.setWindowTitle(glyph.name, glyph.font) # setting the layer-glyph here app = QApplication.instance() app.setCurrentGlyph(glyph) # ----------------- # Layers management # ----------------- def _layerChanged(self, newLayerIndex): glyph = self.view.glyph() layer = self._currentLayerBox.itemData(newLayerIndex) if layer is None: layer = self._makeLayer() if layer is None: # restore comboBox to active index layerSet = glyph.layerSet index = layerSet.layerOrder.index(glyph.layer.name) self._setLayerBoxIndex(index) return if glyph.name in layer: newGlyph = layer[glyph.name] else: # TODO: make sure we mimic defcon ufo3 APIs for that newGlyph = self._makeLayerGlyph(layer, glyph) self.setGlyph(newGlyph) def _makeLayer(self): # TODO: what with duplicate names? glyph = self.view.glyph() newLayerName, color, ok = AddLayerDialog.getNewLayerNameAndColor(self) if ok: layerSet = glyph.layerSet layer = layerSet.newLayer(newLayerName) layer.color = color return layer return None def _makeLayerGlyph(self, layer, currentGlyph): glyph = layer.newGlyph(currentGlyph.name) glyph.width = currentGlyph.width glyph.template = True return glyph def _updateLayerControls(self): comboBox = self._currentLayerBox glyph = self.view.glyph() comboBox.blockSignals(True) comboBox.clear() for layer in glyph.layerSet: comboBox.addItem(layer.name, layer) comboBox.setCurrentText(glyph.layer.name) comboBox.addItem(self.tr("New layer…"), None) comboBox.blockSignals(False) if not hasattr(self, "_layerAction"): return self._layerAction.setEnabled(len(glyph.layerSet) > 1) def _setLayerBoxIndex(self, index): comboBox = self._currentLayerBox comboBox.blockSignals(True) comboBox.setCurrentIndex(index) comboBox.blockSignals(False) # --------------------- # QMainWindow functions # --------------------- def sizeHint(self): return QSize(1100, 750) def moveEvent(self, event): self.writeSettings() resizeEvent = moveEvent def event(self, event): if event.type() == QEvent.WindowActivate: app = QApplication.instance() app.setCurrentGlyph(self.view.glyph()) return super().event(event) def showEvent(self, event): app = QApplication.instance() data = dict(window=self) app.postNotification("glyphWindowWillOpen", data) super().showEvent(event) app.postNotification("glyphWindowOpened", data) def closeEvent(self, event): super().closeEvent(event) if event.isAccepted(): app = QApplication.instance() app.dispatcher.removeObserver(self, "drawingToolRegistered") data = dict(window=self) app.postNotification("glyphWindowWillClose", data) glyph = self.view.glyph() self._unsubscribeFromGlyph(glyph) self.view.closeEvent(event) def setWindowTitle(self, title, font=None): if font is not None: title = "%s – %s %s" % ( title, font.info.familyName, font.info.styleName) super().setWindowTitle(title)
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 self.heap is None: return if not is_process_suspended(): answer = askyn_c( ASKBTN_YES, "HIDECANCEL\nThe process must be suspended to reload the info.\n\ Do you want to suspend it?") if answer == ASKBTN_NO: return if not suspend_process(): warning("Unable to suspend the process") return try: RefreshDebuggerMemory() if not self.heap.get_heap_base(): self.show_warning("Heap not initialized") return if not get_libc_base(): self.show_warning("Unable to resolve glibc base address.") 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() except Exception as e: self.show_warning(e.message) warning(traceback.format_exc()) 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 as e: warning(str(e)) self.show_warning('Invalid config file content') def populate_arenas(self): old_arena = self.cur_arena self.cb_arenas.blockSignals(True) 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) self.cb_arenas.addItem("main_arena2", 1) else: self.cb_arenas.addItem("0x%x" % addr, addr) idx = self.cb_arenas.findData(old_arena) if idx != -1: self.cb_arenas.setCurrentIndex(idx) self.cb_arenas.blockSignals(False) 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 EditorGeneral(QWidget): def __init__(self, parent): super().__init__(parent) self._preferences = parent vbox = QVBoxLayout(self) self._font = settings.FONT # Group widgets group_typo = QGroupBox( translations.TR_PREFERENCES_EDITOR_GENERAL_TYPOGRAPHY) group_scheme = QGroupBox("Editor Color Scheme") # Font settings grid_typo = QGridLayout(group_typo) self._btn_editor_font = QPushButton('') grid_typo.addWidget(QLabel( translations.TR_PREFERENCES_EDITOR_GENERAL_EDITOR_FONT), 0, 0) grid_typo.addWidget(self._btn_editor_font, 0, 1) self._check_font_antialiasing = QCheckBox("Antialiasing") grid_typo.addWidget(self._check_font_antialiasing, 1, 0) # Scheme settings box = QVBoxLayout(group_scheme) self._combo_themes = QComboBox() box.addWidget(self._combo_themes) schemes = json_manager.load_editor_schemes() for scheme_name, colors in schemes.items(): self._combo_themes.addItem(scheme_name, colors) self.__current_scheme = settings.EDITOR_SCHEME # self._list_view_scheme = QListView() # schemes = json_manager.load_editor_schemes() # from collections import namedtuple # CategoryEntry = namedtuple('CategoryEntry', 'name color') # list_of_categories = [] # for scheme_name, categories in schemes.items(): # for category_name in categories.keys(): # category = CategoryEntry( # category_name, # categories[category_name]['color'] # ) # list_of_categories.append(category) # model = ListModelScheme(list_of_categories) # model.set_font(self._font) # self._list_view_scheme.setModel(model) # box.addWidget(self._list_view_scheme) # Add group widgets vbox.addWidget(group_typo) vbox.addWidget(group_scheme) vbox.addItem( QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Expanding)) # Initial Settings btn_text = ', '.join(self._font.toString().split(',')[0:2]) self._btn_editor_font.setText(btn_text) self._check_font_antialiasing.setChecked(settings.FONT_ANTIALIASING) self._combo_themes.setCurrentText(settings.EDITOR_SCHEME) # Connections self._btn_editor_font.clicked.connect(self._load_editor_font) self._preferences.savePreferences.connect(self._save) def _load_editor_font(self): font, ok = QFontDialog.getFont(self._font, self) if ok: self._font = font btn_text = ', '.join(self._font.toString().split(',')[0:2]) self._btn_editor_font.setText(btn_text) def _save(self): qsettings = IDE.editor_settings() settings.FONT = self._font qsettings.setValue("editor/general/default_font", settings.FONT) settings.FONT_ANTIALIASING = self._check_font_antialiasing.isChecked() qsettings.setValue("editor/general/font_antialiasing", settings.FONT_ANTIALIASING) settings.EDITOR_SCHEME = self._combo_themes.currentText() qsettings.setValue("editor/general/scheme", settings.EDITOR_SCHEME) scheme = self._combo_themes.currentText() if scheme != self.__current_scheme: index = self._combo_themes.currentIndex() colors = self._combo_themes.itemData(index) resources.COLOR_SCHEME = colors main = IDE.get_service("main_container") main.restyle_editor()
class Window(QWidget): def __init__(self): super(Window, self).__init__() self.renderArea = RenderArea() self.shapeComboBox = QComboBox() self.shapeComboBox.addItem("Polygon", RenderArea.Polygon) self.shapeComboBox.addItem("Rectangle", RenderArea.Rect) self.shapeComboBox.addItem("Rounded Rectangle", RenderArea.RoundedRect) self.shapeComboBox.addItem("Ellipse", RenderArea.Ellipse) self.shapeComboBox.addItem("Pie", RenderArea.Pie) self.shapeComboBox.addItem("Chord", RenderArea.Chord) self.shapeComboBox.addItem("Path", RenderArea.Path) self.shapeComboBox.addItem("Line", RenderArea.Line) self.shapeComboBox.addItem("Polyline", RenderArea.Polyline) self.shapeComboBox.addItem("Arc", RenderArea.Arc) self.shapeComboBox.addItem("Points", RenderArea.Points) self.shapeComboBox.addItem("Text", RenderArea.Text) self.shapeComboBox.addItem("Pixmap", RenderArea.Pixmap) shapeLabel = QLabel("&Shape:") shapeLabel.setBuddy(self.shapeComboBox) self.penWidthSpinBox = QSpinBox() self.penWidthSpinBox.setRange(0, 20) self.penWidthSpinBox.setSpecialValueText("0 (cosmetic pen)") penWidthLabel = QLabel("Pen &Width:") penWidthLabel.setBuddy(self.penWidthSpinBox) self.penStyleComboBox = QComboBox() self.penStyleComboBox.addItem("Solid", Qt.SolidLine) self.penStyleComboBox.addItem("Dash", Qt.DashLine) self.penStyleComboBox.addItem("Dot", Qt.DotLine) self.penStyleComboBox.addItem("Dash Dot", Qt.DashDotLine) self.penStyleComboBox.addItem("Dash Dot Dot", Qt.DashDotDotLine) self.penStyleComboBox.addItem("None", Qt.NoPen) penStyleLabel = QLabel("&Pen Style:") penStyleLabel.setBuddy(self.penStyleComboBox) self.penCapComboBox = QComboBox() self.penCapComboBox.addItem("Flat", Qt.FlatCap) self.penCapComboBox.addItem("Square", Qt.SquareCap) self.penCapComboBox.addItem("Round", Qt.RoundCap) penCapLabel = QLabel("Pen &Cap:") penCapLabel.setBuddy(self.penCapComboBox) self.penJoinComboBox = QComboBox() self.penJoinComboBox.addItem("Miter", Qt.MiterJoin) self.penJoinComboBox.addItem("Bevel", Qt.BevelJoin) self.penJoinComboBox.addItem("Round", Qt.RoundJoin) penJoinLabel = QLabel("Pen &Join:") penJoinLabel.setBuddy(self.penJoinComboBox) self.brushStyleComboBox = QComboBox() self.brushStyleComboBox.addItem("Linear Gradient", Qt.LinearGradientPattern) self.brushStyleComboBox.addItem("Radial Gradient", Qt.RadialGradientPattern) self.brushStyleComboBox.addItem("Conical Gradient", Qt.ConicalGradientPattern) self.brushStyleComboBox.addItem("Texture", Qt.TexturePattern) self.brushStyleComboBox.addItem("Solid", Qt.SolidPattern) self.brushStyleComboBox.addItem("Horizontal", Qt.HorPattern) self.brushStyleComboBox.addItem("Vertical", Qt.VerPattern) self.brushStyleComboBox.addItem("Cross", Qt.CrossPattern) self.brushStyleComboBox.addItem("Backward Diagonal", Qt.BDiagPattern) self.brushStyleComboBox.addItem("Forward Diagonal", Qt.FDiagPattern) self.brushStyleComboBox.addItem("Diagonal Cross", Qt.DiagCrossPattern) self.brushStyleComboBox.addItem("Dense 1", Qt.Dense1Pattern) self.brushStyleComboBox.addItem("Dense 2", Qt.Dense2Pattern) self.brushStyleComboBox.addItem("Dense 3", Qt.Dense3Pattern) self.brushStyleComboBox.addItem("Dense 4", Qt.Dense4Pattern) self.brushStyleComboBox.addItem("Dense 5", Qt.Dense5Pattern) self.brushStyleComboBox.addItem("Dense 6", Qt.Dense6Pattern) self.brushStyleComboBox.addItem("Dense 7", Qt.Dense7Pattern) self.brushStyleComboBox.addItem("None", Qt.NoBrush) brushStyleLabel = QLabel("&Brush Style:") brushStyleLabel.setBuddy(self.brushStyleComboBox) otherOptionsLabel = QLabel("Other Options:") self.antialiasingCheckBox = QCheckBox("&Antialiasing") self.transformationsCheckBox = QCheckBox("&Transformations") self.shapeComboBox.activated.connect(self.shapeChanged) self.penWidthSpinBox.valueChanged.connect(self.penChanged) self.penStyleComboBox.activated.connect(self.penChanged) self.penCapComboBox.activated.connect(self.penChanged) self.penJoinComboBox.activated.connect(self.penChanged) self.brushStyleComboBox.activated.connect(self.brushChanged) self.antialiasingCheckBox.toggled.connect( self.renderArea.setAntialiased) self.transformationsCheckBox.toggled.connect( self.renderArea.setTransformed) mainLayout = QGridLayout() mainLayout.setColumnStretch(0, 1) mainLayout.setColumnStretch(3, 1) mainLayout.addWidget(self.renderArea, 0, 0, 1, 4) mainLayout.setRowMinimumHeight(1, 6) mainLayout.addWidget(shapeLabel, 2, 1, Qt.AlignRight) mainLayout.addWidget(self.shapeComboBox, 2, 2) mainLayout.addWidget(penWidthLabel, 3, 1, Qt.AlignRight) mainLayout.addWidget(self.penWidthSpinBox, 3, 2) mainLayout.addWidget(penStyleLabel, 4, 1, Qt.AlignRight) mainLayout.addWidget(self.penStyleComboBox, 4, 2) mainLayout.addWidget(penCapLabel, 5, 1, Qt.AlignRight) mainLayout.addWidget(self.penCapComboBox, 5, 2) mainLayout.addWidget(penJoinLabel, 6, 1, Qt.AlignRight) mainLayout.addWidget(self.penJoinComboBox, 6, 2) mainLayout.addWidget(brushStyleLabel, 7, 1, Qt.AlignRight) mainLayout.addWidget(self.brushStyleComboBox, 7, 2) mainLayout.setRowMinimumHeight(8, 6) mainLayout.addWidget(otherOptionsLabel, 9, 1, Qt.AlignRight) mainLayout.addWidget(self.antialiasingCheckBox, 9, 2) mainLayout.addWidget(self.transformationsCheckBox, 10, 2) self.setLayout(mainLayout) self.shapeChanged() self.penChanged() self.brushChanged() self.antialiasingCheckBox.setChecked(True) self.setWindowTitle("Basic Drawing") def shapeChanged(self): shape = self.shapeComboBox.itemData(self.shapeComboBox.currentIndex(), IdRole) self.renderArea.setShape(shape) def penChanged(self): width = self.penWidthSpinBox.value() style = Qt.PenStyle( self.penStyleComboBox.itemData( self.penStyleComboBox.currentIndex(), IdRole)) cap = Qt.PenCapStyle( self.penCapComboBox.itemData(self.penCapComboBox.currentIndex(), IdRole)) join = Qt.PenJoinStyle( self.penJoinComboBox.itemData(self.penJoinComboBox.currentIndex(), IdRole)) self.renderArea.setPen(QPen(Qt.blue, width, style, cap, join)) def brushChanged(self): style = Qt.BrushStyle( self.brushStyleComboBox.itemData( self.brushStyleComboBox.currentIndex(), IdRole)) if style == Qt.LinearGradientPattern: linearGradient = QLinearGradient(0, 0, 100, 100) linearGradient.setColorAt(0.0, Qt.white) linearGradient.setColorAt(0.2, Qt.green) linearGradient.setColorAt(1.0, Qt.black) self.renderArea.setBrush(QBrush(linearGradient)) elif style == Qt.RadialGradientPattern: radialGradient = QRadialGradient(50, 50, 50, 70, 70) radialGradient.setColorAt(0.0, Qt.white) radialGradient.setColorAt(0.2, Qt.green) radialGradient.setColorAt(1.0, Qt.black) self.renderArea.setBrush(QBrush(radialGradient)) elif style == Qt.ConicalGradientPattern: conicalGradient = QConicalGradient(50, 50, 150) conicalGradient.setColorAt(0.0, Qt.white) conicalGradient.setColorAt(0.2, Qt.green) conicalGradient.setColorAt(1.0, Qt.black) self.renderArea.setBrush(QBrush(conicalGradient)) elif style == Qt.TexturePattern: self.renderArea.setBrush(QBrush(QPixmap(':/images/brick.png'))) else: self.renderArea.setBrush(QBrush(Qt.green, style))
class Window(QWidget): def __init__(self): super(Window, self).__init__() self.proxyModel = SortFilterProxyModel() self.proxyModel.setDynamicSortFilter(True) self.sourceGroupBox = QGroupBox("Original Model") self.proxyGroupBox = QGroupBox("Sorted/Filtered Model") self.sourceView = QTreeView() self.sourceView.setRootIsDecorated(False) self.sourceView.setAlternatingRowColors(True) self.proxyView = QTreeView() self.proxyView.setRootIsDecorated(False) self.proxyView.setAlternatingRowColors(True) self.proxyView.setModel(self.proxyModel) self.proxyView.setSortingEnabled(True) self.sortCaseSensitivityCheckBox = QCheckBox("Case sensitive sorting") self.filterCaseSensitivityCheckBox = QCheckBox("Case sensitive filter") self.filterPatternLineEdit = QLineEdit() self.filterPatternLabel = QLabel("&Filter pattern:") self.filterPatternLabel.setBuddy(self.filterPatternLineEdit) self.filterSyntaxComboBox = QComboBox() self.filterSyntaxComboBox.addItem("Regular expression", QRegExp.RegExp) self.filterSyntaxComboBox.addItem("Wildcard", QRegExp.Wildcard) self.filterSyntaxComboBox.addItem("Fixed string", QRegExp.FixedString) self.filterSyntaxLabel = QLabel("Filter &syntax:") self.filterSyntaxLabel.setBuddy(self.filterSyntaxComboBox) self.filterColumnComboBox = QComboBox() self.filterColumnComboBox.addItem("Subject") self.filterColumnComboBox.addItem("Sender") self.filterColumnComboBox.addItem("Date") self.filterColumnLabel = QLabel("Filter &column:") self.filterColumnLabel.setBuddy(self.filterColumnComboBox) self.filterPatternLineEdit.textChanged.connect(self.filterRegExpChanged) self.filterSyntaxComboBox.currentIndexChanged.connect(self.filterRegExpChanged) self.filterColumnComboBox.currentIndexChanged.connect(self.filterColumnChanged) self.filterCaseSensitivityCheckBox.toggled.connect(self.filterRegExpChanged) self.sortCaseSensitivityCheckBox.toggled.connect(self.sortChanged) sourceLayout = QHBoxLayout() sourceLayout.addWidget(self.sourceView) self.sourceGroupBox.setLayout(sourceLayout) proxyLayout = QGridLayout() proxyLayout.addWidget(self.proxyView, 0, 0, 1, 3) proxyLayout.addWidget(self.filterPatternLabel, 1, 0) proxyLayout.addWidget(self.filterPatternLineEdit, 1, 1, 1, 2) proxyLayout.addWidget(self.filterSyntaxLabel, 2, 0) proxyLayout.addWidget(self.filterSyntaxComboBox, 2, 1, 1, 2) proxyLayout.addWidget(self.filterColumnLabel, 3, 0) proxyLayout.addWidget(self.filterColumnComboBox, 3, 1, 1, 2) proxyLayout.addWidget(self.filterCaseSensitivityCheckBox, 4, 0, 1, 2) proxyLayout.addWidget(self.sortCaseSensitivityCheckBox, 4, 2) self.proxyGroupBox.setLayout(proxyLayout) mainLayout = QVBoxLayout() mainLayout.addWidget(self.sourceGroupBox) mainLayout.addWidget(self.proxyGroupBox) self.setLayout(mainLayout) self.setWindowTitle("Basic Sort/Filter Model") self.resize(500, 450) self.proxyView.sortByColumn(SENDER, Qt.AscendingOrder) self.filterColumnComboBox.setCurrentIndex(SENDER) self.filterPatternLineEdit.setText("Andy|Grace") self.filterCaseSensitivityCheckBox.setChecked(True) self.sortCaseSensitivityCheckBox.setChecked(True) def setSourceModel(self, model): self.proxyModel.setSourceModel(model) self.sourceView.setModel(model) def filterRegExpChanged(self): syntax_nr = self.filterSyntaxComboBox.itemData(self.filterSyntaxComboBox.currentIndex()) syntax = QRegExp.PatternSyntax(syntax_nr) if self.filterCaseSensitivityCheckBox.isChecked(): caseSensitivity = Qt.CaseSensitive else: caseSensitivity = Qt.CaseInsensitive regExp = QRegExp(self.filterPatternLineEdit.text(), caseSensitivity, syntax) self.proxyModel.setFilterRegExp(regExp) def filterColumnChanged(self): self.proxyModel.setFilterKeyColumn(self.filterColumnComboBox.currentIndex()) def sortChanged(self): if self.sortCaseSensitivityCheckBox.isChecked(): caseSensitivity = Qt.CaseSensitive else: caseSensitivity = Qt.CaseInsensitive self.proxyModel.setSortCaseSensitivity(caseSensitivity)
class Window(QWidget): def __init__(self): super(Window, self).__init__() self.proxyModel = MySortFilterProxyModel(self) self.proxyModel.setDynamicSortFilter(True) self.sourceView = QTreeView() self.sourceView.setRootIsDecorated(False) self.sourceView.setAlternatingRowColors(True) sourceLayout = QHBoxLayout() sourceLayout.addWidget(self.sourceView) sourceGroupBox = QGroupBox("Original Model") sourceGroupBox.setLayout(sourceLayout) self.filterCaseSensitivityCheckBox = QCheckBox("Case sensitive filter") self.filterCaseSensitivityCheckBox.setChecked(True) self.filterPatternLineEdit = QLineEdit() self.filterPatternLineEdit.setText("Grace|Sports") filterPatternLabel = QLabel("&Filter pattern:") filterPatternLabel.setBuddy(self.filterPatternLineEdit) self.filterSyntaxComboBox = QComboBox() self.filterSyntaxComboBox.addItem("Regular expression", QRegExp.RegExp) self.filterSyntaxComboBox.addItem("Wildcard", QRegExp.Wildcard) self.filterSyntaxComboBox.addItem("Fixed string", QRegExp.FixedString) self.fromDateEdit = QDateEdit() self.fromDateEdit.setDate(QDate(2006, 12, 22)) self.fromDateEdit.setCalendarPopup(True) fromLabel = QLabel("F&rom:") fromLabel.setBuddy(self.fromDateEdit) self.toDateEdit = QDateEdit() self.toDateEdit.setDate(QDate(2007, 1, 5)) self.toDateEdit.setCalendarPopup(True) toLabel = QLabel("&To:") toLabel.setBuddy(self.toDateEdit) self.filterPatternLineEdit.textChanged.connect(self.textFilterChanged) self.filterSyntaxComboBox.currentIndexChanged.connect( self.textFilterChanged) self.filterCaseSensitivityCheckBox.toggled.connect( self.textFilterChanged) self.fromDateEdit.dateChanged.connect(self.dateFilterChanged) self.toDateEdit.dateChanged.connect(self.dateFilterChanged) self.proxyView = QTreeView() self.proxyView.setRootIsDecorated(False) self.proxyView.setAlternatingRowColors(True) self.proxyView.setModel(self.proxyModel) self.proxyView.setSortingEnabled(True) self.proxyView.sortByColumn(1, Qt.AscendingOrder) self.textFilterChanged() self.dateFilterChanged() proxyLayout = QGridLayout() proxyLayout.addWidget(self.proxyView, 0, 0, 1, 3) proxyLayout.addWidget(filterPatternLabel, 1, 0) proxyLayout.addWidget(self.filterPatternLineEdit, 1, 1) proxyLayout.addWidget(self.filterSyntaxComboBox, 1, 2) proxyLayout.addWidget(self.filterCaseSensitivityCheckBox, 2, 0, 1, 3) proxyLayout.addWidget(fromLabel, 3, 0) proxyLayout.addWidget(self.fromDateEdit, 3, 1, 1, 2) proxyLayout.addWidget(toLabel, 4, 0) proxyLayout.addWidget(self.toDateEdit, 4, 1, 1, 2) proxyGroupBox = QGroupBox("Sorted/Filtered Model") proxyGroupBox.setLayout(proxyLayout) mainLayout = QVBoxLayout() mainLayout.addWidget(sourceGroupBox) mainLayout.addWidget(proxyGroupBox) self.setLayout(mainLayout) self.setWindowTitle("Custom Sort/Filter Model") self.resize(500, 450) def setSourceModel(self, model): self.proxyModel.setSourceModel(model) self.sourceView.setModel(model) def textFilterChanged(self): syntax = QRegExp.PatternSyntax( self.filterSyntaxComboBox.itemData( self.filterSyntaxComboBox.currentIndex())) caseSensitivity = (self.filterCaseSensitivityCheckBox.isChecked() and Qt.CaseSensitive or Qt.CaseInsensitive) regExp = QRegExp(self.filterPatternLineEdit.text(), caseSensitivity, syntax) self.proxyModel.setFilterRegExp(regExp) def dateFilterChanged(self): self.proxyModel.setFilterMinimumDate(self.fromDateEdit.date()) self.proxyModel.setFilterMaximumDate(self.toDateEdit.date())
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())
class DeviceBar(QWidget): """ DeviceBar Signals: onDeviceUpdated() onDeviceSelected(str) # str = id onDeviceChanged(str) # str = id """ onDeviceUpdated = pyqtSignal(str, name="onDeviceUpdated") onDeviceSelected = pyqtSignal(str, name="onDeviceSelected") onDeviceChanged = pyqtSignal(str, name="onDeviceChanged") def __init__(self, parent=None, device_type='usb'): super().__init__(parent=parent) # dont show for local if device_type != 'usb': return self.parent = parent self.wait_for_devtype = device_type self.is_waiting = True self._adb = Adb() if not self._adb.min_required: return self._git = Git() self.setAutoFillBackground(True) self.setStyleSheet('background-color: crimson; color: white; font-weight: bold; margin: 0; padding: 10px;') self.setup() self._timer = QTimer() self._timer.setInterval(500) self._timer.timeout.connect(self._on_timer) self._timer.start() self._timer_step = 0 frida.get_device_manager().on('added', self._on_device) frida.get_device_manager().on('removed', self._on_device) self.devices_thread = DevicesUpdateThread(self) self.devices_thread.onAddDevice.connect(self.on_add_deviceitem) self.devices_thread.onDevicesUpdated.connect(self._on_devices_finished) self._update_thread = FridaUpdateThread(self) self._update_thread._adb = self._adb self._update_thread.onStatusUpdate.connect(self._update_statuslbl) self._update_thread.onFinished.connect(self._frida_updated) self._update_thread.onError.connect(self._on_download_error) self.updated_frida_version = '' self.updated_frida_assets_url = {} self._device_id = None self._devices = [] remote_frida = self._git.get_frida_version() if remote_frida is None: self.updated_frida_version = '' self.updated_frida_assets_url.clear() else: remote_frida = remote_frida[0] self.updated_frida_version = remote_frida['tag_name'] for asset in remote_frida['assets']: try: name = asset['name'] tag_start = name.index('android-') if name.index('server') >= 0: tag = name[tag_start + 8:-3] self.updated_frida_assets_url[tag] = asset['browser_download_url'] except ValueError: pass def setup(self): """ Setup ui """ h_box = QHBoxLayout() h_box.setContentsMargins(0, 0, 0, 0) self.update_label = QLabel('Waiting for Device') self.update_label.setFixedWidth(self.parent.width()) self.update_label.setOpenExternalLinks(True) self.update_label.setTextFormat(Qt.RichText) self.update_label.setFixedHeight(35) self.update_label.setTextInteractionFlags(Qt.TextBrowserInteraction) self._install_btn = QPushButton('Install Frida', self.update_label) self._install_btn.setStyleSheet('padding: 0; border-color: white;') self._install_btn.setGeometry(self.update_label.width() - 110, 5, 100, 25) self._install_btn.clicked.connect(self._on_install_btn) self._install_btn.setVisible(False) self._start_btn = QPushButton('Start Frida', self.update_label) self._start_btn.setStyleSheet('padding: 0; border-color: white;') self._start_btn.setGeometry(self.update_label.width() - 110, 5, 100, 25) self._start_btn.clicked.connect(self._on_start_btn) self._start_btn.setVisible(False) self._update_btn = QPushButton('Update Frida', self.update_label) self._update_btn.setStyleSheet('padding: 0; border-color: white;') self._update_btn.setGeometry(self.update_label.width() - 110, 5, 100, 25) self._update_btn.clicked.connect(self._on_install_btn) self._update_btn.setVisible(False) self._restart_btn = QPushButton('Restart Frida', self.update_label) self._restart_btn.setStyleSheet('padding: 0; border-color: white;') self._restart_btn.setGeometry(self.update_label.width() - 110, 5, 100, 25) self._restart_btn.clicked.connect(self._on_restart_btn) self._restart_btn.setVisible(False) self._devices_combobox = QComboBox(self.update_label) self._devices_combobox.setStyleSheet('padding: 2px 5px; border-color: white;') self._devices_combobox.setGeometry(self.update_label.width() - 320, 5, 200, 25) self._devices_combobox.currentIndexChanged.connect(self._on_device_changed) self._devices_combobox.setVisible(False) h_box.addWidget(self.update_label) self.setLayout(h_box) def on_add_deviceitem(self, device_ident): """ Adds an Item to the DeviceComboBox """ if device_ident['type'] == self.wait_for_devtype: if device_ident['name'] not in self._devices: self._devices.append(device_ident) self._timer_step = -1 self.is_waiting = False def _on_device_changed(self, index): device = None device_id = self._devices_combobox.itemData(index) if device_id: try: device = frida.get_device(device_id) except: return if device: self._device_id = device.id self._check_device(device) self.onDeviceChanged.emit(self._device_id) def _check_device(self, frida_device): self.update_label.setStyleSheet('background-color: crimson;') self._install_btn.setVisible(False) self._update_btn.setVisible(False) self._start_btn.setVisible(False) self._restart_btn.setVisible(False) self._adb.device = frida_device.id self._device_id = frida_device.id if self._adb.available(): self.update_label.setText('Device: ' + frida_device.name) # try getting frida version device_frida = self._adb.get_frida_version() # frida not found show install button if device_frida is None: self._install_btn.setVisible(True) else: # frida is old show update button if self.updated_frida_version != device_frida: self._start_btn.setVisible(True) self._update_btn.setVisible(False) # old frida is running allow use of this version if self._adb.is_frida_running(): self._start_btn.setVisible(False) if self.updated_frida_assets_url: self._update_btn.setVisible(True) self.update_label.setStyleSheet('background-color: yellowgreen;') self.onDeviceUpdated.emit(frida_device.id) # frida not running show start button elif device_frida and not self._adb.is_frida_running(): self._start_btn.setVisible(True) # frida is running with last version show restart button elif device_frida and self._adb.is_frida_running(): self.update_label.setStyleSheet('background-color: yellowgreen;') self._restart_btn.setVisible(True) self.onDeviceUpdated.emit(frida_device.id) elif self._adb.non_root_available(): self.update_label.setText('Device: ' + frida_device.name + ' (NOROOT!)') self.onDeviceUpdated.emit(frida_device.id) def _on_devices_finished(self): if self._devices: if len(self._devices) > 1: self._devices_combobox.clear() self._devices_combobox.setVisible(True) self.update_label.setText('Please select the Device: ') for device in self._devices: self._devices_combobox.addItem(device['name'], device['id']) else: self._devices_combobox.setVisible(False) try: device = frida.get_device(self._devices[0]['id']) self._check_device(device) except: pass def _on_timer(self): if self._timer_step == -1: self._timer.stop() return if self._timer_step == 0: self.update_label.setText(self.update_label.text() + ' .') self._timer_step = 1 elif self._timer_step == 1: self.update_label.setText(self.update_label.text() + '.') self._timer_step = 2 elif self._timer_step == 2: self.update_label.setText(self.update_label.text() + '.') self._timer_step = 3 else: self.update_label.setText(self.update_label.text()[:-self._timer_step]) self._timer_step = 0 if self.is_waiting and self.devices_thread is not None: if not self.devices_thread.isRunning(): self.devices_thread.start() def _on_download_error(self, text): self._timer_step = -1 self.update_label.setStyleSheet('background-color: crimson;') self.update_label.setText(text) self._install_btn.setVisible(True) self._update_btn.setVisible(False) def _on_device(self): self.update_label.setText('Waiting for Device ...') self._timer_step = 3 self.is_waiting = True self._on_timer() def _on_install_btn(self): # urls are empty if not self.updated_frida_assets_url: return arch = self._adb.get_device_arch() request_url = '' if arch is not None and len(arch) > 1: arch = arch.join(arch.split()) if arch == 'arm64' or arch == 'arm64-v8a': request_url = self.updated_frida_assets_url['arm64'] elif arch == 'armeabi-v7a': request_url = self.updated_frida_assets_url['arm'] else: if arch in self.updated_frida_assets_url: request_url = self.updated_frida_assets_url[arch] try: if self._adb.available() and request_url.index('https://') == 0: self._install_btn.setVisible(False) self._update_btn.setVisible(False) qApp.processEvents() if self._update_thread is not None: if not self._update_thread.isRunning(): self._update_thread.frida_update_url = request_url self._update_thread.adb = self._adb self._update_thread.start() except ValueError: # something wrong in .git_cache folder print("request_url not set") def _update_statuslbl(self, text): self._timer.stop() self._timer_step = 0 self._timer.start() self.update_label.setText(text) def _frida_updated(self): #self._timer_step = 3 #self.is_waiting = True self._on_devices_finished() def _on_start_btn(self): if self._adb.available(): self._start_btn.setVisible(False) qApp.processEvents() if self._adb.start_frida(): #self.onDeviceUpdated.emit(self._device_id) self._on_devices_finished() else: self._start_btn.setVisible(True) def _on_restart_btn(self): if self._adb.available(): self._restart_btn.setVisible(False) qApp.processEvents() if self._adb.start_frida(restart=True): self._restart_btn.setVisible(True) #self.onDeviceUpdated.emit(self._device_id) self._on_devices_finished()