def write(self, file_name, storage_device, mesh_data): Logger.log("i", "In X3gWriter.write") if "x3g" in file_name: scene = Application.getInstance().getController().getScene() gcode_list = getattr(scene, "gcode_list") if gcode_list: # f = storage_device.openFile(file_name, "wt") Logger.log("i", "Writing X3g to file %s", file_name) p = QProcess() p.setReadChannel(1) p.setStandardOutputFile(file_name) p.start("gpx", ["-i"]) p.waitForStarted() for gcode in gcode_list: p.write(gcode) if p.canReadLine(): Logger.log("i", "gpx: %s", p.readLine().data().decode("utf-8")) p.closeWriteChannel() if p.waitForFinished(): Logger.log("i", "gpx: %s", p.readAll().data().decode("utf-8")) p.close() # storage_device.closeFile(f) return True return False
def write(self, file_name, storage_device, mesh_data): Logger.log("i", "In X3gWriter.write") if "x3g" in file_name: scene = Application.getInstance().getController().getScene() gcode_list = getattr(scene, "gcode_list") if gcode_list: # f = storage_device.openFile(file_name, "wt") Logger.log("i", "Writing X3g to file %s", file_name) p = QProcess() p.setReadChannel(1) p.setStandardOutputFile(file_name) p.start("gpx", ["-i"]) p.waitForStarted() for gcode in gcode_list: p.write(gcode) if (p.canReadLine()): Logger.log("i", "gpx: %s", p.readLine().data().decode('utf-8')) p.closeWriteChannel() if p.waitForFinished(): Logger.log("i", "gpx: %s", p.readAll().data().decode('utf-8')) p.close() # storage_device.closeFile(f) return True return False
def open_file_in_editor(self): editor = settings.value('editor/editor_path') if not editor: QMessageBox.critical(self, '', 'You need to set the editor to use that feature!') process = QProcess() process.start('%s %s' % (editor, self.document_viewer.get_current_file())) process.waitForFinished(-1); process.close()
class LinuxRecorder(QWidget): keystroke = pyqtSignal(object) stopped = pyqtSignal() def __init__(self): super().__init__() self.process = QProcess() self.process.readyReadStandardOutput.connect(self.on_output) self.setWindowFlags(self.windowFlags() | QtCore.Qt.WindowStaysOnTopHint | QtCore.Qt.X11BypassWindowManagerHint) layout = QVBoxLayout() btn = QPushButton(tr("MacroRecorder", "Stop recording")) btn.clicked.connect(self.on_stop) layout.addWidget(btn) self.setLayout(layout) def start(self): self.show() center = QApplication.desktop().availableGeometry(self).center() self.move(center.x() - self.width() * 0.5, 0) args = [sys.executable] if os.getenv("APPIMAGE"): args = [os.getenv("APPIMAGE")] elif is_frozen(): args += sys.argv[1:] else: args += sys.argv args += ["--linux-recorder"] self.process.start("pkexec", args, QProcess.Unbuffered | QProcess.ReadWrite) def on_stop(self): self.stop() def stop(self): self.process.write(b"q") self.process.waitForFinished() self.process.close() self.hide() self.stopped.emit() def on_output(self): if self.process.canReadLine(): line = bytes(self.process.readLine()).decode("utf-8") action, key = line.strip().split(":") code = Keycode.find_by_recorder_alias(key) if code is not None: action2cls = {"down": KeyDown, "up": KeyUp} self.keystroke.emit(action2cls[action](code))
class _Converter: """_Converter class to provide conversion functionality.""" def __init__(self, library_path): """Class initializer.""" self._library_path = library_path self._process = QProcess() def setup_converter(self, reader, finisher, process_channel): """Set up the QProcess object.""" self._process.setProcessChannelMode(process_channel) self._process.readyRead.connect(reader) self._process.finished.connect(finisher) def start_converter(self, cmd): """Start the encoding process.""" self._process.start(self._library_path, cmd) def stop_converter(self): """Terminate the encoding process.""" self._process.terminate() if self.converter_is_running: self._process.kill() def converter_finished_disconnect(self, connected): """Disconnect the QProcess.finished method.""" self._process.finished.disconnect(connected) def close_converter(self): """Call QProcess.close method.""" self._process.close() def kill_converter(self): """Call QProcess.kill method.""" self._process.kill() def converter_state(self): """Call QProcess.state method.""" return self._process.state() def converter_exit_status(self): """Call QProcess.exit_status method.""" return self._process.exitStatus() def read_converter_output(self): """Call QProcess.readAll method.""" return str(self._process.readAll()) @property def converter_is_running(self): """Return QProcess state.""" return self._process.state() == QProcess.Running
def connect_speaker_device(self, address): if platform.system() == "Darwin": print("Pairing...") process = QProcess() process.setProcessChannelMode(QProcess.SeparateChannels) process.start(f"blueutil --pair {address}") process.waitForFinished() print("Connecting...") process.start(f"blueutil --connect {address}") process.waitForFinished() print("Connected!") elif platform.system() == "Linux": process = QProcess() process.setProcessChannelMode(QProcess.SeparateChannels) process.start("bluetoothctl") process.waitForStarted() process.waitForReadyRead() process.writeData(bytes("connect B1:20:B5:86:AA:B9\n", 'utf-8')) process.waitForBytesWritten() while True: process.waitForReadyRead(1500) data = bytes(process.readAll()).decode('utf-8').split(" ") print(data) if any("successful" in x for x in data): print("Connection successful") break elif any("Failed" in x for x in data): print(f"ERROR: bluez.failed") self.bluetooth_speaker_cb( BluetoothSpeakerError( "Bluetooth speaker daemon failed to start")) time.sleep(1.0) process.writeData(bytes("exit\n", 'utf-8')) process.waitForBytesWritten() process.waitForReadyRead(1500) process.closeWriteChannel() process.waitForFinished() process.close() print("Waiting for notify_connect to fire") else: print("Unsupported platform")
class MCA_Widget(QWidget): """ """ mcaUpdating = pyqtSignal(int) def __init__(self, parent=None): QWidget.__init__(self, parent) self.mcaDir = '/tmp' self.init_UI() self.init_validation() self.init_signals() self.overrideEpicsCalib() self.mca_x = None self.mca_y = None self.mca_yerr = None self.data = {} self.expressions = {} self.plotColors = {} self.dlg_data = {} self.plotColIndex = {} self.mcaFnames = [] self.mca_MEDM_running = False self.stopMEDMPushButton.setEnabled(False) def init_UI(self): loadUi('./UI_Forms/MCA_Widget.ui', self) self.change_MCA() def closeEvent(self, event): if self.mca_MEDM_running: self.medm.close() def init_pv(self): self.realTimeLineEdit.setPV(self.medm_P + 'mca1.PRTM') self.liveTimeLineEdit.setPV(self.medm_P + 'mca1.PLTM') self.modeComboBox.setPV(self.medm_P + self.medm_D + 'PresetMode') self.currentStatusLabel.setPV(self.medm_P + 'mca1.ACQG', type='str') self.readRealTimeLabel.setPV(self.medm_P + 'mca1.ERTM') self.readLiveTimeLabel.setPV(self.medm_P + 'mca1.ELTM') def init_signals(self): #Signals for MCA scans self.addMCAScanPushButton.clicked.connect( lambda x: self.add_mca_scans(fnames=None)) self.mcaScanListWidget.itemDoubleClicked.connect(self.openDataDialog) self.mcaScanListWidget.itemSelectionChanged.connect( self.scanSelectionChanged) #Signals for mcaPlotWidget #Signals of MCA MEDM setup self.medm_P_LineEdit.returnPressed.connect(self.change_MCA) self.medm_D_LineEdit.returnPressed.connect(self.change_MCA) self.medm_M_LineEdit.returnPressed.connect(self.change_MCA) self.launchMEDMPushButton.clicked.connect(self.launch_MEDM) self.stopMEDMPushButton.clicked.connect(self.stop_MEDM) self.readMCAPushButton.clicked.connect(self.readMCA) self.saveMCAPushButton.clicked.connect( lambda x: self.saveMCA(fname=None)) self.countPushButton.clicked.connect(self.startstopCountMCA) #Signals from MCA Calibration setup self.overrideMCACalibCheckBox.stateChanged.connect( self.overrideEpicsCalib) #Signals for Scan Statistics self.xminLineEdit.returnPressed.connect(self.xmin_xmax_Changed) self.xmaxLineEdit.returnPressed.connect(self.xmin_xmax_Changed) self.statsTableWidget.setSizeAdjustPolicy( QAbstractScrollArea.AdjustToContents) self.calcStatsPushButton.clicked.connect(self.calcStats) def roiRegionChanged(self): self.xmin, self.xmax = self.mcaPlotWidget.roi.getRegion() self.xminLineEdit.setText('%.4f' % self.xmin) self.xmaxLineEdit.setText('%.4f' % self.xmax) def overrideEpicsCalib(self): if self.overrideMCACalibCheckBox.isChecked(): self.offsetLineEdit.setEnabled(True) self.linearLineEdit.setEnabled(True) self.quadraticLineEdit.setEnabled(True) else: self.offsetLineEdit.setEnabled(False) self.linearLineEdit.setEnabled(False) self.quadraticLineEdit.setEnabled(False) def scanSelectionChanged(self): fnames = [ item.text() for item in self.mcaScanListWidget.selectedItems() ] if len(fnames) != 0: self.mcaPlotWidget.Plot(fnames) self.offsetLineEdit.setText( '%.4f' % self.data[fnames[0]]['Counts']['Offset']) self.linearLineEdit.setText( '%.4f' % self.data[fnames[0]]['Counts']['Linear']) self.quadraticLineEdit.setText( '%.4f' % self.data[fnames[0]]['Counts']['Quadratic']) stats_data = [] keys = [ 'Energy', 'real_time', 'live_time', 'sum', 'sum_err', 'corr_sum', 'corr_sum_err' ] #self.dlg_data[fullfnames[0]]['meta'].keys() for fname in fnames: stats_data.append( [self.data[fname]['Counts'][key] for key in keys]) stats_data = np.array(stats_data) self.updateStatisticsTable(stats_data, keys) def updateStatisticsTable(self, data, keys): self.statsTableWidget.setData(data) self.statsTableWidget.setHorizontalHeaderLabels(keys) self.statsTableWidget.resizeColumnsToContents() #def addStatistics(self,): def init_validation(self): self.dbleValidator = QDoubleValidator() self.intValidator = QIntValidator() self.offsetLineEdit.setValidator(self.dbleValidator) self.linearLineEdit.setValidator(self.dbleValidator) self.quadraticLineEdit.setValidator(self.dbleValidator) self.xminLineEdit.setValidator(self.dbleValidator) self.xmaxLineEdit.setValidator(self.dbleValidator) def openDataDialog(self, item): fname = item.text() data_dlg = Data_Dialog(data=self.dlg_data[os.path.join( self.mcaDir, fname)], parent=self, plotIndex=self.plotColIndex[fname], colors=self.plotColors[fname]) if data_dlg.exec_(): self.mcaPlotWidget.remove_data([fname]) self.plotColIndex[fname] = data_dlg.plotColIndex self.dlg_data[fname] = copy.copy(data_dlg.data) self.data[fname] = copy.copy(data_dlg.externalData) self.expressions[fname] = data_dlg.expressions self.plotColors[fname] = data_dlg.plotColors for key in self.data[fname].keys(): self.mcaPlotWidget.add_data(self.data[fname][key]['x'], self.data[fname][key]['y'], yerr=self.data[fname][key]['yerr'], name=fname, color=self.plotColors[fname][key]) self.mcaPlotWidget.Plot([fname]) def add_mca_scans(self, fnames=None): """ Adds MCA scans to the mcaScanListWidget :param fnames: List of mca filenames. If None it will prompt for the files to be imported :return: """ if fnames is None: self.mcaFnames = QFileDialog.getOpenFileNames( self, 'Select MCA files', directory=self.mcaDir)[0] else: self.mcaFnames = fnames if self.mcaFnames != []: self.mcaDir = os.path.dirname(self.mcaFnames[0]) # self.mcaScanListWidget.addItems([os.path.basename(fname) for fname in self.mcaFnames]) self.mcaScanListWidget.addItems(self.mcaFnames) for fname in self.mcaFnames: data_dlg = Data_Dialog(fname=fname, parent=self, colors=None) data_dlg.accept() self.dlg_data[fname] = data_dlg.data self.plotColIndex[fname] = data_dlg.plotColIndex self.data[fname] = data_dlg.externalData self.expressions[fname] = data_dlg.expressions self.plotColors[fname] = data_dlg.plotColors for key in self.data[fname].keys(): self.mcaPlotWidget.add_data(self.data[fname][key]['x'], self.data[fname][key]['y'], \ yerr=self.data[fname][key]['yerr'], name='%s' % (fname), color=self.plotColors[fname][key]) print(self.plotColors[fname][key]) def launch_MEDM(self): self.medm = QProcess() cmd = 'medm -x -macro "P=%s,D=%s, M=%s" ./adl/dxpSaturn.adl' % ( self.medm_P, self.medm_D, self.medm_M) self.medm.start(cmd) self.mca_MEDM_running = True self.launchMEDMPushButton.setEnabled(False) self.stopMEDMPushButton.setEnabled(True) def stop_MEDM(self): if self.mca_MEDM_running: self.medm.close() self.mca_MEDM_running = False self.stopMEDMPushButton.setEnabled(False) self.launchMEDMPushButton.setEnabled(True) # def stop_MEDM(self): # self.medm.terminate() def readMCA(self): self.mca_y = self.mcaPV.value self.mca_yerr = np.sqrt(self.mca_y) xmca = np.arange(len(self.mca_y)) if self.overrideMCACalibCheckBox.isChecked(): self.get_mca_manual_calib() else: self.get_mca_epics_calib() self.energy = epics.caget(BYTES2STR("15IDA:BraggERdbkAO")) self.mca_x = self.mca_offset + self.mca_linear * xmca + self.mca_quadratic * xmca**2 self.mcaPlotWidget.add_data(self.mca_x, self.mca_y, yerr=self.mca_yerr, name='mca1') self.xmin = float(self.xminLineEdit.text()) self.xmax = float(self.xmaxLineEdit.text()) self.performStats(fname=None) keys = [ 'Energy', 'real_time', 'live_time', 'sum', 'sum_err', 'corr_sum', 'corr_sum_err' ] # self.dlg_data[fullfnames[0]]['meta'].keys() stats_data = [[ self.energy, self.mca_realtime, self.mca_livetime, self.sum, self.sum_err, self.corr_sum, self.corr_sum_err ]] self.updateStatisticsTable(stats_data, keys) def calcStats(self): if len(self.mcaFnames) != 0: stats_data = [] for fname in self.mcaFnames: self.mca_x = self.data[fname]['Counts']['x'] self.mca_y = self.data[fname]['Counts']['y'] self.mca_realtime = self.data[fname]['Counts']['real_time'] self.mca_livetime = self.data[fname]['Counts']['live_time'] self.performStats(fname=fname) keys = [ 'Energy', 'real_time', 'live_time', 'sum', 'sum_err', 'corr_sum', 'corr_sum_err' ] # self.dlg_data[fullfnames[0]]['meta'].keys() stats_data.append( [self.data[fname]['Counts'][key] for key in keys]) else: self.performStats(fname=None) keys = [ 'Energy', 'real_time', 'live_time', 'sum', 'sum_err', 'corr_sum', 'corr_sum_err' ] # self.dlg_data[fullfnames[0]]['meta'].keys() stats_data = [[ self.energy, self.mca_realtime, self.mca_livetime, self.sum, self.sum_err, self.corr_sum, self.corr_sum_err ]] self.updateStatisticsTable(stats_data, keys) def performStats(self, fname=None): #self.xmin_xmax_Changed() if self.xmin is None: imin = 0 else: imin = np.where(self.mca_x > self.xmin)[0][0] if self.xmax is None: imax = len(self.mca_x) - 1 else: imax = np.where(self.mca_x < self.xmax)[0][-1] if imax <= imin: imax = imin + 1 if fname is not None: self.data[fname]['Counts']['sum'] = np.sum(self.mca_y[imin:imax + 1]) self.data[fname]['Counts']['sum_err'] = np.sqrt( self.data[fname]['Counts']['sum']) self.data[fname]['Counts']['corr_sum'] = self.data[fname][ 'Counts']['sum'] * self.mca_realtime / self.mca_livetime self.data[fname]['Counts']['corr_sum_err'] = self.data[fname][ 'Counts']['sum_err'] * self.mca_realtime / self.mca_livetime else: self.sum = np.sum(self.mca_y[imin:imax + 1]) self.sum_err = np.sqrt(self.sum) self.corr_sum = self.sum * self.mca_realtime / self.mca_livetime self.corr_sum_err = self.sum_err * self.mca_realtime / self.mca_livetime def xmin_xmax_Changed(self): self.xmin = float(self.xminLineEdit.text()) self.xmax = float(self.xmaxLineEdit.text()) try: self.mcaPlotWidget.roi.setRegion((self.xmin, self.xmax)) except: self.mcaPlotWidget.addROI(values=(self.xmin, self.xmax), orientation='vertical', movable=True) self.mcaPlotWidget.roi.sigRegionChanged.connect( self.roiRegionChanged) try: self.calcStats() except: pass def saveMCA(self, fname=None): if self.mca_y is None: self.readMCA() if fname is None: fname = QFileDialog.getSaveFileName(self, caption="Provide filename", directory=self.mcaDir)[0] data = np.vstack((self.mca_x, self.mca_y, self.mca_yerr)).T header = 'MCA file saved on %s\n' % time.asctime() header += 'Energy=%.4f\n' % self.energy header += 'Offset=%.4e\n' % self.mca_offset header += 'Linear=%.4e\n' % self.mca_linear header += 'Quadratic=%.4e\n' % self.mca_quadratic header += 'real_time=%.4f\n' % self.mca_realtime header += 'live_time=%.4f\n' % self.mca_livetime header += 'sum=%.4f\n' % self.sum #self.data['sum'] header += 'sum_err=%.4f\n' % self.sum_err #self.data['sum_err'] header += 'corr_sum=%.4f\n' % self.corr_sum #self.data['corr_sum'] header += 'corr_sum_err=%.4f\n' % self.corr_sum_err #self.data['corr_sum_err'] header += 'col_names=["Channel","Counts","Err_Counts"]' np.savetxt(fname, data, header=header) self.add_mca_scans(fnames=[fname]) def get_mca_manual_calib(self): """ Get the manual calibration number from the MCA_Widget """ self.mca_offset = float(self.offsetLineEdit.text()) self.mca_linear = float(self.linearLineEdit.text()) self.mca_quadratic = float(self.quadraticLineEdit.text()) self.mca_realtime = epics.caget(BYTES2STR(self.medm_P + "mca1.ERTM")) self.mca_livetime = epics.caget(BYTES2STR(self.medm_P + "mca1.ELTM")) def get_mca_epics_calib(self): """ Get the epics calibration numbers and reset the manual calibration numbers accordingly """ self.mca_offset = epics.caget(BYTES2STR(self.medm_P + "mca1.CALO")) self.mca_linear = epics.caget(BYTES2STR(self.medm_P + "mca1.CALS")) self.mca_quadratic = epics.caget(BYTES2STR(self.medm_P + "mca1.CALQ")) self.mca_realtime = epics.caget(BYTES2STR(self.medm_P + "mca1.ERTM")) self.mca_livetime = epics.caget(BYTES2STR(self.medm_P + "mca1.ELTM")) if not self.overrideMCACalibCheckBox.isChecked(): self.offsetLineEdit.setText('%.5f' % self.mca_offset) self.linearLineEdit.setText('%.5f' % self.mca_linear) self.quadraticLineEdit.setText('%.5f' % self.mca_quadratic) def change_MCA(self): """ Resets the P, D and M values of MCA :return: """ self.medm_P = self.medm_P_LineEdit.text() self.medm_D = self.medm_D_LineEdit.text() self.medm_M = self.medm_M_LineEdit.text() self.mcaPV = epics.PV(BYTES2STR(self.medm_P + self.medm_M)) try: self.mcaStatusPV.clear_callbacks() except: self.mcaStatusPV = epics.PV( BYTES2STR(self.medm_P + self.medm_M + '.ACQG')) self.monitorIndex = self.mcaStatusPV.add_callback(self.mcaChanging) self.mcaUpdating.connect(self.mcaAutoUpdate) self.init_pv() def mcaChanging(self, **kwargs): value = kwargs['value'] if value == 0: self.mcaUpdating.emit(value) def mcaAutoUpdate(self, value): self.readMCA() self.countPushButton.setText('Count') def startstopCountMCA(self): if self.countPushButton.text() == 'Count': epics.caput(BYTES2STR(self.medm_P + 'mca1EraseStart'), 1) self.countPushButton.setText('Stop') else: epics.caput(BYTES2STR(self.medm_P + 'Stop'), 1) self.countPushButton.setText('Count')
class RF_Qt(QMainWindow): """ Class to create the main GUI window. It extends QMainWindow. The GUI allows the user to load up to four font files, provide a new font name, adjust some options, view a preview of font changes, and finally generate new TrueType font files. """ def __init__(self): """ Create the main window. :return: """ super(RF_Qt, self).__init__() # Define variables self.fnt_styles = ["Regular", "Italic", "Bold", "Bold Italic"] self.fnt_sty_combo_list = [] self.fnt_file_name_list = [] self.font_files = None self.font_info = FontInfo() # Create a QProcess object, and connect it to appropriate slots self.cli_process = QProcess(self) self.cli_process.setProcessChannelMode(QProcess.MergedChannels) self.cli_process.readyRead.connect(self.read_proc_output) self.cli_process.started.connect(self.manage_proc) self.cli_process.finished.connect(self.manage_proc) self.cli_process.error.connect(self.manage_proc) # Style for all groupbox labels gb_style = "QGroupBox { font-weight: bold; }" # Top level layout manager for the window. win_layout = QVBoxLayout() gb_fnt_files = QGroupBox("Font Files") gb_fnt_files.setStyleSheet(gb_style) grid_f_f = QGridLayout() grid_pos = 0 # Font Files and styles # # Create a grid of font names with their respective font style combo boxes for i in range(len(self.fnt_styles)): self.fnt_file_name_list.append(QLabel("Load font file...")) cmb = QComboBox() cmb.addItem("") cmb.addItems(self.fnt_styles) cmb.setEnabled(False) cmb.setToolTip( "<qt/>If not automatically detected when the font is added, allows you to select what font " "sub-family the font file belongs to" ) self.fnt_sty_combo_list.append(cmb) row, col = helper.calc_grid_pos(grid_pos, 2) grid_f_f.addWidget(self.fnt_file_name_list[i], row, col) grid_pos += 1 row, col = helper.calc_grid_pos(grid_pos, 2) grid_f_f.addWidget(self.fnt_sty_combo_list[i], row, col) grid_pos += 1 grid_f_f.setColumnStretch(0, 1) gb_fnt_files.setLayout(grid_f_f) win_layout.addWidget(gb_fnt_files) # New Font Name # gb_fnt_name = QGroupBox("Font Family Name") gb_fnt_name.setStyleSheet(gb_style) hb_fnt_name = QHBoxLayout() self.new_fnt_name = QLineEdit() self.new_fnt_name.setToolTip("Enter a name for the modified font.") self.new_fnt_name.textEdited[str].connect(self.set_family_name) hb_fnt_name.addWidget(self.new_fnt_name) gb_fnt_name.setLayout(hb_fnt_name) win_layout.addWidget(gb_fnt_name) # Options # hb_options = QHBoxLayout() ## Kerning, Panose, Alt. Name ## gb_basic_opt = QGroupBox("Basic Options") gb_basic_opt.setStyleSheet(gb_style) hb_basic_opt = QHBoxLayout() self.basic_opt_list = [] basic_tooltips = ( "<qt/>Some readers and software require 'legacy', or 'old style' kerning to be " "present for kerning to work.", "<qt/>Kobo readers can get confused by PANOSE settings. This option sets all " "PANOSE information to 0, or 'any'", "<qt/>Some fonts have issues with renaming. If the generated font does not have " "the same internal font name as you entered, try enabling this option.", ) for opt, tip in zip(("Legacy Kerning", "Clear PANOSE", "Alt. Name"), basic_tooltips): self.basic_opt_list.append(QCheckBox(opt)) self.basic_opt_list[-1].setToolTip(tip) hb_basic_opt.addWidget(self.basic_opt_list[-1]) gb_basic_opt.setLayout(hb_basic_opt) hb_options.addWidget(gb_basic_opt) ## Hinting ## gb_hint_opt = QGroupBox("Hinting Option") gb_hint_opt.setStyleSheet(gb_style) hb_hint_opt = QHBoxLayout() self.hint_opt_list = [] hint_tooltips = ( "<qt/>Keep font hinting as it exists in the orginal font files.<br />" "In most cases, this will look fine on most ebook reading devices.", '<qt/>Some fonts are manually, or "hand" hinted for specific display types (such as LCD). ' "These fonts may not look good on other display types such as e-ink, therefore they can be " "removed.", "<qt/>If you don't like the original hinting, but you want your font to be hinted, " "this option will auto hint your font.", ) for opt, tip in zip(("Keep Existing", "Remove Existing", "AutoHint"), hint_tooltips): self.hint_opt_list.append(QRadioButton(opt)) self.hint_opt_list[-1].setToolTip(tip) self.hint_opt_list[-1].toggled.connect(self.set_hint) hb_hint_opt.addWidget(self.hint_opt_list[-1]) self.hint_opt_list[0].setChecked(Qt.Checked) gb_hint_opt.setLayout(hb_hint_opt) hb_options.addWidget(gb_hint_opt) win_layout.addLayout(hb_options) ## Darken ## gb_dark_opt = QGroupBox("Darken Options") gb_dark_opt.setStyleSheet(gb_style) hb_dark_opt = QHBoxLayout() self.darken_opt = QCheckBox("Darken Font") self.darken_opt.setToolTip("<qt/>Darken, or add weight to a font to make it easier to read on e-ink screens.") self.darken_opt.toggled.connect(self.set_darken_opt) hb_dark_opt.addWidget(self.darken_opt) self.mod_bearing_opt = QCheckBox("Modify Bearings") self.mod_bearing_opt.setToolTip( "<qt/>By default, adding weight to a font increases glyph width. Enable this " "option to set the glyph width to be roughly equal to the original.<br/><br/>" "WARNING: This reduces the spacing between glyphs, and should not be used if " "you have added too much weight." ) self.mod_bearing_opt.toggled.connect(self.set_mod_bearing) self.mod_bearing_opt.setEnabled(False) hb_dark_opt.addWidget(self.mod_bearing_opt) self.lbl = QLabel("Darken Amount:") self.lbl.setEnabled(False) hb_dark_opt.addWidget(self.lbl) self.darken_amount_opt = QSlider(Qt.Horizontal) self.darken_amount_opt.setMinimum(1) self.darken_amount_opt.setMaximum(50) self.darken_amount_opt.setValue(12) self.darken_amount_opt.setEnabled(False) self.darken_amount_opt.setToolTip( "<qt/>Set the amount to darken a font by. 50 is considered turning a " "regular weight font into a bold weight font. It is not recommended to " "darken a font that much however." ) self.darken_amount_opt.valueChanged[int].connect(self.set_darken_amount) hb_dark_opt.addWidget(self.darken_amount_opt) self.darken_amount_lab = QLabel() self.darken_amount_lab.setText(str(self.darken_amount_opt.value())) self.darken_amount_lab.setEnabled(False) hb_dark_opt.addWidget(self.darken_amount_lab) gb_dark_opt.setLayout(hb_dark_opt) win_layout.addWidget(gb_dark_opt) # Buttons # hb_buttons = QHBoxLayout() # hb_buttons.addStretch() self.gen_ttf_btn = QPushButton("Generate TTF") self.gen_ttf_btn.setEnabled(False) self.gen_ttf_btn.setToolTip( "<qt/>Generate a new TrueType font based on the options chosen in this program. " "<br /><br />" "The new fonts are saved in a directory of your choosing." ) self.gen_ttf_btn.clicked.connect(self.gen_ttf) hb_buttons.addWidget(self.gen_ttf_btn) self.load_font_btn = QPushButton("Load Fonts") self.load_font_btn.setToolTip("<qt/>Load font files to modify.") self.load_font_btn.clicked.connect(self.load_fonts) hb_buttons.addWidget(self.load_font_btn) self.prog_bar = QProgressBar() self.prog_bar.setRange(0, 100) hb_buttons.addWidget(self.prog_bar) win_layout.addLayout(hb_buttons) # Output Log # gb_log_win = QGroupBox("Log Window") gb_log_win.setStyleSheet(gb_style) vb_log = QVBoxLayout() out_font = QFont("Courier") out_font.setStyleHint(QFont.Monospace) self.log_win = QTextEdit() self.log_win.setAcceptRichText(False) self.log_win.setFont(out_font) vb_log.addWidget(self.log_win) gb_log_win.setLayout(vb_log) win_layout.addWidget(gb_log_win) # Show Window # self.setCentralWidget(QWidget(self)) self.centralWidget().setLayout(win_layout) self.setWindowTitle("Readify Font") self.show() # Check if fontforge is actually in users PATH. If it isn't, prompt user to provice a location self.ff_path = helper.which("fontforge") if not self.ff_path: self.set_ff_path() def set_ff_path(self): """ Let user choose location of fontforge :return: """ QMessageBox.warning( self, "Fontforge Missing!", "FontForge is not in your PATH! If it is installed, " "please locate it now.", QMessageBox.Ok, QMessageBox.Ok, ) path = QFileDialog.getOpenFileName(self, "Locate FontForge...") if path[0]: self.ff_path = os.path.normpath(path[0]) def set_basic_opt(self): """ Handler to set basic options :return: """ opt = self.sender() if opt.isChecked(): if "kerning" in opt.text().lower(): self.font_info.leg_kern = True if "panose" in opt.text().lower(): self.font_info.strip_panose = True if "alt" in opt.text().lower(): self.font_info.name_hack = True else: if "kerning" in opt.text().lower(): self.font_info.leg_kern = False if "panose" in opt.text().lower(): self.font_info.strip_panose = False if "alt" in opt.text().lower(): self.font_info.name_hack = False def set_family_name(self, name): """ Handler to set name option. Also checks if buttons need enabling :param name: :return: """ if name: if helper.valid_filename(name): self.font_info.font_name = name if self.font_files: self.gen_ttf_btn.setEnabled(True) else: self.gen_ttf_btn.setEnabled(False) else: self.gen_ttf_btn.setEnabled(False) def set_darken_amount(self, amount): """ Set Darken amount slider :param amount: :return: """ self.darken_amount_lab.setText(str(amount)) self.font_info.add_weight = amount def set_hint(self): """ Set hint options :return: """ hint = self.sender() if hint.isChecked(): if "keep" in hint.text().lower(): self.font_info.change_hint = "keep" elif "remove" in hint.text().lower(): self.font_info.change_hint = "remove" elif "auto" in hint.text().lower(): self.font_info.change_hint = "auto" def set_darken_opt(self): """ Set darken options :return: """ if self.sender().isChecked(): self.mod_bearing_opt.setEnabled(True) self.lbl.setEnabled(True) self.darken_amount_lab.setEnabled(True) self.darken_amount_opt.setEnabled(True) self.set_darken_amount(self.darken_amount_opt.value()) else: self.mod_bearing_opt.setEnabled(False) self.lbl.setEnabled(False) self.darken_amount_lab.setEnabled(False) self.darken_amount_opt.setEnabled(False) self.set_darken_amount(0) def set_mod_bearing(self): """ Set mod bearing options :return: """ if self.mod_bearing_opt.isChecked(): self.font_info.mod_bearings = True else: self.font_info.mod_bearings = False def load_fonts(self): """ Load fonts from a directory, and sets appropriate options :return: """ f_f = QFileDialog.getOpenFileNames(self, "Load Fonts", "", "Font Files (*.ttf *.otf)") if f_f[0]: for f_label, f_style in zip(self.fnt_file_name_list, self.fnt_sty_combo_list): f_label.setText("Load font file...") f_style.setCurrentIndex(SEL_NONE) f_style.setEnabled(False) self.font_files = f_f[0] f_f_names = [] for file in self.font_files: file = os.path.normpath(file) base, fn = os.path.split(file) f_f_names.append(fn) for f_file, f_label, f_style in zip(f_f_names, self.fnt_file_name_list, self.fnt_sty_combo_list): f_label.setText(f_file) f_style.setEnabled(True) if "regular" in f_file.lower(): f_style.setCurrentIndex(SEL_REGULAR) elif "bold" in f_file.lower() and "italic" in f_file.lower(): f_style.setCurrentIndex(SEL_BOLDITALIC) elif "bold" in f_file.lower(): f_style.setCurrentIndex(SEL_BOLD) elif "italic" in f_file.lower(): f_style.setCurrentIndex(SEL_ITALIC) if self.new_fnt_name.text(): self.gen_ttf_btn.setEnabled(True) def read_proc_output(self): """ Read any stdout data available from the process and displays it in the output log window. :return: """ if sys.version_info.major == 2: output = unicode(self.cli_process.readAllStandardOutput(), encoding=sys.getdefaultencoding()) else: output = str(self.cli_process.readAllStandardOutput(), encoding=sys.getdefaultencoding()) self.log_win.append(output) def manage_proc(self): """ Manage the progress bar :return: """ proc = self.sender() if proc.state() == QProcess.Running: self.prog_bar.setRange(0, 0) if proc.state() == QProcess.NotRunning: self.prog_bar.setRange(0, 100) self.prog_bar.setValue(100) def gen_ttf(self): """ Generate modified TrueType font files, by calling the CLI script with the appropriate arguments. :param prev: :return: """ self.log_win.clear() if not self.ff_path: self.set_ff_path() if self.ff_path: if not self.font_info.out_dir: save_dir = os.path.normpath( QFileDialog.getExistingDirectory(self, "Select save directory...", options=QFileDialog.ShowDirsOnly) ) if save_dir == "." or save_dir == "": return else: self.font_info.out_dir = save_dir else: save_dir = os.path.normpath( QFileDialog.getExistingDirectory( self, "Select Save directory...", self.font_info.out_dir, options=QFileDialog.ShowDirsOnly ) ) if save_dir == "." or save_dir == "": return else: self.font_info.out_dir = save_dir for file, style in zip(self.font_files, self.fnt_sty_combo_list): if style.currentIndex() == SEL_REGULAR: self.font_info.font_file_reg = file elif style.currentIndex() == SEL_BOLDITALIC: self.font_info.font_file_bi = file elif style.currentIndex() == SEL_BOLD: self.font_info.font_file_bd = file elif style.currentIndex() == SEL_ITALIC: self.font_info.font_file_it = file cli_opt_list = self.font_info.gen_cli_command() self.cli_process.start(self.ff_path, cli_opt_list) def closeEvent(self, event): """ Cleaning up... :param event: :return: """ self.cli_process.close() event.accept()
class MainWin(QMainWindow): def __init__(self): super(MainWin, self).__init__() self.settings = QSettings("RadioBOB", "settings") self.setStyleSheet(mystylesheet(self)) self.radioNames = [] self.radiolist = [] self.channels = [] self.imagelist = [] self.radiofile = "" self.radioStations = "" self.rec_name = "" self.rec_url = "" self.old_meta = "" self.notificationsEnabled = True self.headerlogo = QIcon( os.path.join(os.path.dirname(sys.argv[0]), "headerlogo.png")) self.setContentsMargins(5, 0, 5, 0) self.wg = QWidget() self.er_label = QLabel("Image") self.er_label.setPixmap(self.headerlogo.pixmap(QSize(300, 140))) self.er_label.setScaledContents(False) self.er_label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self.layout = QVBoxLayout() ### combo box self.urlCombo = QComboBox() self.er_label.setAlignment(Qt.AlignCenter) self.layout.addWidget(self.er_label, 0, Qt.AlignCenter) self.layout1 = QHBoxLayout() self.layout1.setContentsMargins(50, 0, 50, 0) self.tIcon = QIcon( os.path.join(os.path.dirname(sys.argv[0]), "logo.png")) self.playIcon = QIcon( os.path.join(os.path.dirname(sys.argv[0]), "media-playback-start.svg")) self.stopIcon = QIcon( os.path.join(os.path.dirname(sys.argv[0]), "media-playback-stop.svg")) self.recordIcon = QIcon( os.path.join(os.path.dirname(sys.argv[0]), "media-record.svg")) self.hideIcon = QIcon( os.path.join(os.path.dirname(sys.argv[0]), "hide.png")) self.outfile = QStandardPaths.standardLocations( QStandardPaths.TempLocation)[0] + "/er_tmp.mp3" self.recording_enabled = False self.is_recording = False spc1 = QSpacerItem(6, 10, QSizePolicy.Expanding, QSizePolicy.Maximum) self.play_btn = QPushButton("", self) self.play_btn.setFixedWidth(btnwidth) self.play_btn.setIcon(self.playIcon) self.layout1.addWidget(self.play_btn) self.stop_btn = QPushButton("", self) self.stop_btn.setFixedWidth(btnwidth) self.stop_btn.setIcon(self.stopIcon) self.layout1.addWidget(self.stop_btn) ### record self.rec_btn = QPushButton("", self) self.rec_btn.setFixedWidth(btnwidth) self.rec_btn.setIcon(self.recordIcon) self.rec_btn.clicked.connect(self.recordRadio) self.rec_btn.setToolTip("Aufnehmen") self.layout1.addWidget(self.rec_btn) ### stop record self.stoprec_btn = QPushButton("", self) self.stoprec_btn.setFixedWidth(btnwidth) self.stoprec_btn.setIcon(self.stopIcon) self.stoprec_btn.clicked.connect(self.stop_recording) self.stoprec_btn.setToolTip("Aufnahme stoppen") self.layout1.addWidget(self.stoprec_btn) ### Hauptfenster verbergen self.hide_btn = QPushButton("", self) self.hide_btn.setFixedWidth(btnwidth) self.hide_btn.setToolTip("Fenster ins Tray minimieren") self.hide_btn.setIcon(self.hideIcon) self.hide_btn.clicked.connect(self.showMain) self.layout1.addWidget(self.hide_btn) self.level_sld = QSlider(self) self.level_sld.setFixedWidth(310) self.level_sld.setToolTip("Lautstärkeregler") self.level_sld.setTickPosition(1) self.level_sld.setOrientation(Qt.Horizontal) self.level_sld.setValue(65) self.level_lbl = QLabel(self) self.level_lbl.setAlignment(Qt.AlignHCenter) self.level_lbl.setText("Lautstärke 65") self.layout.addItem(spc1) self.layout.addWidget(self.level_sld, Qt.AlignCenter) self.layout.addWidget(self.level_lbl, Qt.AlignCenter) self.layout.addItem(spc1) self.layout.addLayout(self.layout1) self.player = RadioPlayer(self) self.player.metaDataChanged.connect(self.metaDataChanged) self.player.error.connect(self.handleError) self.play_btn.clicked.connect(self.playRadioStation) self.stop_btn.clicked.connect(self.stop_preview) self.level_sld.valueChanged.connect(self.set_sound_level) self.urlCombo.currentIndexChanged.connect(self.url_changed) self.current_station = "" self.process = QProcess() self.process.started.connect(self.getPID) self.wg.setLayout(self.layout) self.setCentralWidget(self.wg) self.stoprec_btn.setVisible(False) self.readStations() self.createStatusBar() self.setAcceptDrops(True) self.setWindowTitle("Radio BOB") self.setWindowIcon(self.tIcon) self.stationActs = [] self.layout.addItem(spc1) self.setFixedSize(340, 360) self.move(30, 30) # Init tray icon trayIcon = QIcon(self.tIcon) self.trayIcon = QSystemTrayIcon() self.trayIcon.setIcon(self.headerlogo) self.trayIcon.show() self.trayIcon.activated.connect(self.showMainfromTray) self.geo = self.geometry() self.showWinAction = QAction(QIcon.fromTheme("view-restore"), "Hauptfenster anzeigen", triggered=self.showMain) self.notifAction = QAction(QIcon.fromTheme("dialog-information"), "Tray Meldungen ausschalten", triggered=self.toggleNotif) self.togglePlayerAction = QAction("Wiedergabe stoppen", triggered=self.togglePlay) self.togglePlayerAction.setIcon(QIcon.fromTheme("media-playback-stop")) self.recordAction = QAction(QIcon.fromTheme("media-record"), "Aufnahme starten", triggered=self.recordRadio) self.stopRecordAction = QAction(QIcon.fromTheme("media-playback-stop"), "Aufnahme stoppen", triggered=self.stop_recording) self.findExecutable() self.readSettings() self.makeTrayMenu() self.createWindowMenu() if QSystemTrayIcon.isSystemTrayAvailable(): print("System Tray Icon verfügbar") else: print("System Tray Icon nicht verfügbar") if self.player.state() == QMediaPlayer.StoppedState: self.togglePlayerAction.setText("Wiedergabe starten") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-start")) elif self.player.state() == QMediaPlayer.PlayingState: self.togglePlayerAction.setText("Wiedergabe stoppen") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-stop")) def showTrayMessage(self, title, message, icon, timeout=4000): self.trayIcon.showMessage(title, message, icon, timeout) def handleError(self): print(f"Fehler: {self.player.errorString()}") self.showTrayMessage(f"Error:\n{self.player.errorString()}", self.tIcon, 3000) self.statusLabel.setText(f"Fehler:\n{self.player.errorString()}") def togglePlay(self): if self.togglePlayerAction.text() == "Wiedergabe stoppen": self.stop_preview() self.togglePlayerAction.setText("Wiedergabe starten") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-start")) else: self.playRadioStation() self.togglePlayerAction.setText("Aufnahme stoppen") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-stop")) def createWindowMenu(self): self.tb = self.addToolBar("Menu") self.tb_menu = QMenu() self.tb.setIconSize(QSize(44, 20)) ##### submenus from categories ########## b = self.radioStations.splitlines() for x in reversed(range(len(b))): line = b[x] if line == "": print(f"empty line {x} removed") del (b[x]) i = 0 for x in range(0, len(b)): line = b[x] menu_line = line.split(",") ch = menu_line[0] data = menu_line[1] if len(menu_line) > 2: image = menu_line[2] self.tb_menu.addAction(self.stationActs[i]) i += 1 #################################### toolButton = QToolButton() toolButton.setIcon(self.headerlogo) toolButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) toolButton.setText(" Stationen") toolButton.setFixedWidth(120) toolButton.setMenu(self.tb_menu) toolButton.setPopupMode(QToolButton.InstantPopup) self.tb.addWidget(toolButton) empty = QWidget() self.tb.addWidget(empty) self.tb.setContextMenuPolicy(Qt.PreventContextMenu) self.tb.setMovable(False) self.tb.setAllowedAreas(Qt.TopToolBarArea) def makeTrayMenu(self): self.stationActs = [] self.tray_menu = QMenu() self.tray_menu.addAction(self.togglePlayerAction) ##### submenus from categories ########## b = self.radioStations.splitlines() for x in reversed(range(len(b))): line = b[x] if line == "": print(f"empty line {x} removed") del (b[x]) i = 0 for x in range(0, len(b)): line = b[x] menu_line = line.split(",") ch = menu_line[0] data = menu_line[1] if len(menu_line) > 2: image = menu_line[2] self.stationActs.append( QAction(self.tIcon, ch, triggered=self.openTrayStation)) self.stationActs[i].setData(str(i)) self.tray_menu.addAction(self.stationActs[i]) i += 1 #################################### self.tray_menu.addSeparator() if not self.is_recording: if not self.urlCombo.currentText().startswith("--"): self.tray_menu.addAction(self.recordAction) self.recordAction.setText( f"starte Aufnahme von {self.urlCombo.currentText()}") if self.is_recording: self.tray_menu.addAction(self.stopRecordAction) self.tray_menu.addSeparator() self.tray_menu.addAction(self.showWinAction) self.tray_menu.addSeparator() self.tray_menu.addAction(self.notifAction) self.tray_menu.addSeparator() exitAction = self.tray_menu.addAction( QIcon.fromTheme("application-exit"), "Beenden") exitAction.triggered.connect(self.exitApp) self.trayIcon.setContextMenu(self.tray_menu) def showMain(self): if self.isVisible() == False: self.showWinAction.setText("Hauptfenster verbergen") self.setVisible(True) elif self.isVisible() == True: self.showWinAction.setText("Hauptfenster anzeigen") self.setVisible(False) def showMainfromTray(self): buttons = qApp.mouseButtons() if buttons == Qt.LeftButton: if self.isVisible() == False: self.showWinAction.setText("Hauptfenster verbergen") self.setVisible(True) elif self.isVisible() == True: self.showWinAction.setText("Hauptfenster anzeigen") self.setVisible(False) def toggleNotif(self): if self.notifAction.text() == "Tray Meldungen ausschalten": self.notifAction.setText("Tray Meldungen einschalten") self.notificationsEnabled = False elif self.notifAction.text() == "Tray Meldungen einschalten": self.notifAction.setText("Tray Meldungen ausschalten") self.notificationsEnabled = True print(f"Notifications {self.notificationsEnabled}") self.metaDataChanged() def openTrayStation(self): action = self.sender() if action: ind = action.data() name = action.text() self.urlCombo.setCurrentIndex(self.urlCombo.findText(name)) print(f"swith to Station: {ind} - {self.urlCombo.currentText()}") def exitApp(self): self.close() QApplication.quit() def message(self, message): QMessageBox.information(None, 'Meldung', message) def closeEvent(self, e): self.writeSettings() print("schreibe Konfiguratinsdatei ...\nbis bald, keep on rocking...") QApplication.quit() def readSettings(self): print("lese Konfiguratinsdatei ...") if self.settings.contains("pos"): pos = self.settings.value("pos", QPoint(200, 200)) self.move(pos) else: self.move(0, 26) if self.settings.contains("lastChannel"): lch = self.settings.value("lastChannel") self.urlCombo.setCurrentIndex(self.urlCombo.findText(lch)) if self.settings.contains("notifications"): self.notificationsEnabled = self.settings.value("notifications") if self.settings.value("notifications") == "false": self.notificationsEnabled = False self.notifAction.setText("Tray Meldungen einschalten") else: self.notifAction.setText("Tray Meldungen ausschalten") self.notificationsEnabled = True if self.settings.contains("windowstate"): print(self.settings.value("windowstate")) if self.settings.value("windowstate") == "Hauptfenster anzeigen": self.show() self.showWinAction.setText("Hauptfenster verbergen") else: self.hide() self.showWinAction.setText("Hauptfenster anzeigen") if self.settings.contains("volume"): vol = self.settings.value("volume") print(f"set volume to {vol}") self.level_sld.setValue(int(vol)) def writeSettings(self): self.settings.setValue("pos", self.pos()) self.settings.setValue("index", self.urlCombo.currentIndex()) self.settings.setValue("lastChannel", self.urlCombo.currentText()) self.settings.setValue("notifications", self.notificationsEnabled) if self.isVisible(): self.settings.setValue("windowstate", "Hauptfenster anzeigen") else: self.settings.setValue("windowstate", "Hauptfenster verbergen") self.settings.setValue("volume", self.level_sld.value()) self.settings.sync() def readStations(self): self.urlCombo.clear() self.radiolist = [] self.channels = [] self.imagelist = [] dir = os.path.dirname(sys.argv[0]) self.radiofile = os.path.join(dir, "bob.txt") with open(self.radiofile, 'r') as f: self.radioStations = f.read() f.close() newlist = [list(x) for x in self.radioStations.splitlines()] for lines in self.radioStations.splitlines(): mLine = lines.split(",") if not mLine[0].startswith("--"): self.urlCombo.addItem(self.tIcon, mLine[0], Qt.UserRole - 1) self.radiolist.append(mLine[1]) def findExecutable(self): wget = QStandardPaths.findExecutable("wget") if wget != "": print(f"found wget at {wget} *** recording available") self.statusLabel.setText("Aufnahmen möglich") self.showTrayMessage("Hinweis", "wget gefunden\nAufnahmen möglich", self.tIcon) self.recording_enabled = True else: self.showTrayMessage( "Hinweis", "wget icht gefunden\nkeine Aufnahmen möglich", self.tIcon) print("wget icht gefunden\nkeine Aufnahmen möglich") self.recording_enabled = False def remove_last_line_from_string(self, s): return s[:s.rfind('\n')] def createStatusBar(self): self.statusLabel = QLabel("Info") self.statusLabel.setWordWrap(True) self.statusLabel.setAlignment(Qt.AlignCenter) #self.statusLabel.setStyleSheet("color:#73d216;") self.statusBar = QStatusBar() self.statusBar.setSizeGripEnabled(False) self.setStatusBar(self.statusBar) self.statusLabel.setText("Willkommen bei Radio BOB") self.statusBar.addWidget(self.statusLabel, 1) pixmap = QIcon(self.headerlogo) self.home_label = QPushButton() self.home_label.setIconSize(QSize(52, 26)) self.home_label.setFixedSize(60, 32) self.home_label.setToolTip("Radio BOB Homepage besuchen") self.home_label.setIcon(self.headerlogo) self.home_label.clicked.connect(self.showHomepage) self.statusBar.addPermanentWidget(self.home_label) def showHomepage(self): url = QUrl('https://radiobob.de') QDesktopServices.openUrl(url) def metaDataChanged(self): if self.player.isMetaDataAvailable(): trackInfo = (self.player.metaData("Title")) if trackInfo is None: self.statusLabel.setText( f"playing {self.urlCombo.currentText()}") new_trackInfo = "" new_trackInfo = str(trackInfo) #print(new_trackInfo) if not new_trackInfo == "None" and " - " in new_trackInfo: self.statusLabel.setText( f"{new_trackInfo.split(' - ')[0]}\n{new_trackInfo.split(' - ')[1]}" ) else: self.statusLabel.setText( f" playing {self.urlCombo.currentText()}") mt = new_trackInfo if not mt == "None" and " - " in mt: if self.notificationsEnabled: if not mt == self.old_meta: print(mt) self.showTrayMessage( "Radio BOB", f"{mt.split(' - ')[0]}\n{mt.split(' - ')[1]}", self.tIcon) self.old_meta = mt self.trayIcon.setToolTip(mt) else: self.trayIcon.setToolTip(mt) self.old_meta = mt else: self.statusLabel.setText(f"playing {self.urlCombo}") def url_changed(self): if self.urlCombo.currentIndex() < self.urlCombo.count() - 1: if not self.urlCombo.currentText().startswith("--"): ind = self.urlCombo.currentIndex() url = self.radiolist[ind] self.current_station = url self.player.stop() self.rec_btn.setVisible(True) self.stop_btn.setVisible(True) self.play_btn.setVisible(True) name = self.urlCombo.currentText() print(f"playing {name} from {url}") self.playRadioStation() if self.togglePlayerAction.text() == "Wiedergabe stoppen": self.togglePlayerAction.setText("Wiedergabe starten") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-start")) else: self.togglePlayerAction.setText("Wiedergabe stoppen") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-stop")) else: self.rec_btn.setVisible(False) self.stop_btn.setVisible(False) self.play_btn.setVisible(False) def playRadioStation(self): if self.player.is_on_pause: self.set_running_player() self.player.start() self.stop_btn.setFocus() self.togglePlayerAction.setText("Aufnahme stoppen") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-stop")) if not self.current_station: return self.player.set_media(self.current_station) self.set_running_player() self.player.start() if self.is_recording: self.recordAction.setText(f"stoppe Aufnahme von {self.rec_name}") self.recordAction.setIcon(QIcon.fromTheme("media-playback-stop")) else: self.recordAction.setText( f"starte Aufnahme von {self.urlCombo.currentText()}") self.recordAction.setIcon(QIcon.fromTheme("media-record")) self.statusLabel.setText(f"playing {self.urlCombo.currentText()}") self.setWindowTitle(self.urlCombo.currentText()) def set_running_player(self): self.play_btn.setEnabled(False) self.stop_btn.setEnabled(True) self.rec_btn.setEnabled(True) def stop_preview(self): self.player.finish() self.play_btn.setEnabled(True) self.stop_btn.setEnabled(False) self.rec_btn.setEnabled(False) self.statusLabel.setText("stopped") self.togglePlayerAction.setText("Wiedergabe starten") self.togglePlayerAction.setIcon( QIcon.fromTheme("media-playback-start")) def set_sound_level(self, level): self.player.set_sound_level(level) self.level_lbl.setText("Lautstärke " + str(level)) self.player.setVolume(level) def update_volume_slider(self, level): self.level_lbl.setText("Lautstärke " + str(level)) self.level_sld.blockSignals(True) self.level_sld.setValue(value) self.level_lbl.setText("Lautstärke " + str(level)) self.level_sld.blockSignals(False) def recordRadio(self): if not self.is_recording: self.deleteOutFile() self.rec_url = self.current_station self.rec_name = self.urlCombo.currentText() cmd = ("wget -q " + self.rec_url + " -O " + self.outfile) print(cmd) self.is_recording = True self.process.startDetached(cmd) self.recordAction.setText(f"stoppe Aufnahme von {self.rec_name}") self.recordAction.setIcon(QIcon.fromTheme("media-playback-stop")) self.rec_btn.setVisible(False) self.stoprec_btn.setVisible(True) else: self.stop_recording() def stop_recording(self): if self.is_recording: self.process.close() print("stoppe Aufnahme") self.is_recording = False QProcess.execute("killall wget") self.saveRecord() self.stoprec_btn.setVisible(False) self.rec_btn.setVisible(True) self.recordAction.setText( f"starte Aufnahme von {self.urlCombo.currentText()}") self.recordAction.setIcon(QIcon.fromTheme("media-record")) else: self.showTrayMessage("Hinweis", "keine Aufnahme gestartet", self.tIcon) def saveRecord(self): if not self.is_recording: print("saving Audio") musicfolder = QStandardPaths.standardLocations( QStandardPaths.MusicLocation)[0] recname = self.rec_name.replace("-", " ").replace(" - ", " ") + ".mp3" infile = QFile(self.outfile) savefile, _ = QFileDialog.getSaveFileName( None, "Speichern als...", f'{musicfolder}/{recname}', "Audio (*.mp3)") if (savefile != ""): if QFile(savefile).exists: QFile(savefile).remove() print(f"saving {savefile}") if not infile.copy(savefile): QMessageBox.warning( self, "Fehler", f"File {savefile} {infile.errorString()}") print(f"Prozess-State: {str(self.process.state())}") if QFile(self.outfile).exists: print(f"{self.outfile} existiert") QFile(self.outfile).remove() def deleteOutFile(self): if QFile(self.outfile).exists: print(f"delete file {self.outfile}") if QFile(self.outfile).remove: print(f"{self.outfile} deleted") else: print(f"{self.outfile} not deleted") def getPID(self): print(f"{self.process.pid()} {self.process.processId()}")
def start_subprocess(self, cmd, delay): args = shlex.split(cmd) p = QProcess(self) p.start(args[0], args[1:]) p.waitForFinished(delay) p.close()
class RF_Qt(QMainWindow): """ Class to create the main GUI window. It extends QMainWindow. The GUI allows the user to load up to four font files, provide a new font name, adjust some options, view a preview of font changes, and finally generate new TrueType font files. """ def __init__(self): """ Create the main window. :return: """ super(RF_Qt, self).__init__() # Define variables self.fnt_styles = ['Regular', 'Italic', 'Bold', 'Bold Italic'] self.fnt_sty_combo_list = [] self.fnt_file_name_list = [] self.font_files = None self.font_info = FontInfo() # Create a QProcess object, and connect it to appropriate slots self.cli_process = QProcess(self) self.cli_process.setProcessChannelMode(QProcess.MergedChannels) self.cli_process.readyRead.connect(self.read_proc_output) self.cli_process.started.connect(self.manage_proc) self.cli_process.finished.connect(self.manage_proc) self.cli_process.error.connect(self.manage_proc) # Style for all groupbox labels gb_style = 'QGroupBox { font-weight: bold; }' # Top level layout manager for the window. win_layout = QVBoxLayout() gb_fnt_files = QGroupBox('Font Files') gb_fnt_files.setStyleSheet(gb_style) grid_f_f = QGridLayout() grid_pos = 0 # Font Files and styles # # Create a grid of font names with their respective font style combo boxes for i in range(len(self.fnt_styles)): self.fnt_file_name_list.append(QLabel('Load font file...')) cmb = QComboBox() cmb.addItem('') cmb.addItems(self.fnt_styles) cmb.setEnabled(False) cmb.setToolTip( '<qt/>If not automatically detected when the font is added, allows you to select what font ' 'sub-family the font file belongs to') self.fnt_sty_combo_list.append(cmb) row, col = helper.calc_grid_pos(grid_pos, 2) grid_f_f.addWidget(self.fnt_file_name_list[i], row, col) grid_pos += 1 row, col = helper.calc_grid_pos(grid_pos, 2) grid_f_f.addWidget(self.fnt_sty_combo_list[i], row, col) grid_pos += 1 grid_f_f.setColumnStretch(0, 1) gb_fnt_files.setLayout(grid_f_f) win_layout.addWidget(gb_fnt_files) # New Font Name # gb_fnt_name = QGroupBox('Font Family Name') gb_fnt_name.setStyleSheet(gb_style) hb_fnt_name = QHBoxLayout() self.new_fnt_name = QLineEdit() self.new_fnt_name.setToolTip('Enter a name for the modified font.') self.new_fnt_name.textEdited[str].connect(self.set_family_name) hb_fnt_name.addWidget(self.new_fnt_name) gb_fnt_name.setLayout(hb_fnt_name) win_layout.addWidget(gb_fnt_name) # Options # hb_options = QHBoxLayout() ## Kerning, Panose, Alt. Name ## gb_basic_opt = QGroupBox('Basic Options') gb_basic_opt.setStyleSheet(gb_style) hb_basic_opt = QHBoxLayout() self.basic_opt_list = [] basic_tooltips = ( '<qt/>Some readers and software require \'legacy\', or \'old style\' kerning to be ' 'present for kerning to work.', '<qt/>Kobo readers can get confused by PANOSE settings. This option sets all ' 'PANOSE information to 0, or \'any\'', '<qt/>Some fonts have issues with renaming. If the generated font does not have ' 'the same internal font name as you entered, try enabling this option.' ) for opt, tip in zip(('Legacy Kerning', 'Clear PANOSE', 'Alt. Name'), basic_tooltips): self.basic_opt_list.append(QCheckBox(opt)) self.basic_opt_list[-1].setToolTip(tip) hb_basic_opt.addWidget(self.basic_opt_list[-1]) gb_basic_opt.setLayout(hb_basic_opt) hb_options.addWidget(gb_basic_opt) ## Hinting ## gb_hint_opt = QGroupBox('Hinting Option') gb_hint_opt.setStyleSheet(gb_style) hb_hint_opt = QHBoxLayout() self.hint_opt_list = [] hint_tooltips = ( '<qt/>Keep font hinting as it exists in the orginal font files.<br />' 'In most cases, this will look fine on most ebook reading devices.', '<qt/>Some fonts are manually, or "hand" hinted for specific display types (such as LCD). ' 'These fonts may not look good on other display types such as e-ink, therefore they can be ' 'removed.', '<qt/>If you don\'t like the original hinting, but you want your font to be hinted, ' 'this option will auto hint your font.') for opt, tip in zip(('Keep Existing', 'Remove Existing', 'AutoHint'), hint_tooltips): self.hint_opt_list.append(QRadioButton(opt)) self.hint_opt_list[-1].setToolTip(tip) self.hint_opt_list[-1].toggled.connect(self.set_hint) hb_hint_opt.addWidget(self.hint_opt_list[-1]) self.hint_opt_list[0].setChecked(Qt.Checked) gb_hint_opt.setLayout(hb_hint_opt) hb_options.addWidget(gb_hint_opt) win_layout.addLayout(hb_options) ## Darken ## gb_dark_opt = QGroupBox('Darken Options') gb_dark_opt.setStyleSheet(gb_style) hb_dark_opt = QHBoxLayout() self.darken_opt = QCheckBox('Darken Font') self.darken_opt.setToolTip( '<qt/>Darken, or add weight to a font to make it easier to read on e-ink screens.' ) self.darken_opt.toggled.connect(self.set_darken_opt) hb_dark_opt.addWidget(self.darken_opt) self.mod_bearing_opt = QCheckBox('Modify Bearings') self.mod_bearing_opt.setToolTip( '<qt/>By default, adding weight to a font increases glyph width. Enable this ' 'option to set the glyph width to be roughly equal to the original.<br/><br/>' 'WARNING: This reduces the spacing between glyphs, and should not be used if ' 'you have added too much weight.') self.mod_bearing_opt.toggled.connect(self.set_mod_bearing) self.mod_bearing_opt.setEnabled(False) hb_dark_opt.addWidget(self.mod_bearing_opt) self.lbl = QLabel('Darken Amount:') self.lbl.setEnabled(False) hb_dark_opt.addWidget(self.lbl) self.darken_amount_opt = QSlider(Qt.Horizontal) self.darken_amount_opt.setMinimum(1) self.darken_amount_opt.setMaximum(50) self.darken_amount_opt.setValue(12) self.darken_amount_opt.setEnabled(False) self.darken_amount_opt.setToolTip( '<qt/>Set the amount to darken a font by. 50 is considered turning a ' 'regular weight font into a bold weight font. It is not recommended to ' 'darken a font that much however.') self.darken_amount_opt.valueChanged[int].connect( self.set_darken_amount) hb_dark_opt.addWidget(self.darken_amount_opt) self.darken_amount_lab = QLabel() self.darken_amount_lab.setText(str(self.darken_amount_opt.value())) self.darken_amount_lab.setEnabled(False) hb_dark_opt.addWidget(self.darken_amount_lab) gb_dark_opt.setLayout(hb_dark_opt) win_layout.addWidget(gb_dark_opt) # Buttons # hb_buttons = QHBoxLayout() #hb_buttons.addStretch() self.gen_ttf_btn = QPushButton('Generate TTF') self.gen_ttf_btn.setEnabled(False) self.gen_ttf_btn.setToolTip( '<qt/>Generate a new TrueType font based on the options chosen in this program. ' '<br /><br />' 'The new fonts are saved in a directory of your choosing.') self.gen_ttf_btn.clicked.connect(self.gen_ttf) hb_buttons.addWidget(self.gen_ttf_btn) self.load_font_btn = QPushButton('Load Fonts') self.load_font_btn.setToolTip('<qt/>Load font files to modify.') self.load_font_btn.clicked.connect(self.load_fonts) hb_buttons.addWidget(self.load_font_btn) self.prog_bar = QProgressBar() self.prog_bar.setRange(0, 100) hb_buttons.addWidget(self.prog_bar) win_layout.addLayout(hb_buttons) # Output Log # gb_log_win = QGroupBox('Log Window') gb_log_win.setStyleSheet(gb_style) vb_log = QVBoxLayout() out_font = QFont('Courier') out_font.setStyleHint(QFont.Monospace) self.log_win = QTextEdit() self.log_win.setAcceptRichText(False) self.log_win.setFont(out_font) vb_log.addWidget(self.log_win) gb_log_win.setLayout(vb_log) win_layout.addWidget(gb_log_win) # Show Window # self.setCentralWidget(QWidget(self)) self.centralWidget().setLayout(win_layout) self.setWindowTitle('Readify Font') self.show() # Check if fontforge is actually in users PATH. If it isn't, prompt user to provice a location self.ff_path = helper.which('fontforge') if not self.ff_path: self.set_ff_path() def set_ff_path(self): """ Let user choose location of fontforge :return: """ QMessageBox.warning( self, 'Fontforge Missing!', 'FontForge is not in your PATH! If it is installed, ' 'please locate it now.', QMessageBox.Ok, QMessageBox.Ok) path = QFileDialog.getOpenFileName(self, 'Locate FontForge...') if path[0]: self.ff_path = os.path.normpath(path[0]) def set_basic_opt(self): """ Handler to set basic options :return: """ opt = self.sender() if opt.isChecked(): if 'kerning' in opt.text().lower(): self.font_info.leg_kern = True if 'panose' in opt.text().lower(): self.font_info.strip_panose = True if 'alt' in opt.text().lower(): self.font_info.name_hack = True else: if 'kerning' in opt.text().lower(): self.font_info.leg_kern = False if 'panose' in opt.text().lower(): self.font_info.strip_panose = False if 'alt' in opt.text().lower(): self.font_info.name_hack = False def set_family_name(self, name): """ Handler to set name option. Also checks if buttons need enabling :param name: :return: """ if name: if helper.valid_filename(name): self.font_info.font_name = name if self.font_files: self.gen_ttf_btn.setEnabled(True) else: self.gen_ttf_btn.setEnabled(False) else: self.gen_ttf_btn.setEnabled(False) def set_darken_amount(self, amount): """ Set Darken amount slider :param amount: :return: """ self.darken_amount_lab.setText(str(amount)) self.font_info.add_weight = amount def set_hint(self): """ Set hint options :return: """ hint = self.sender() if hint.isChecked(): if 'keep' in hint.text().lower(): self.font_info.change_hint = 'keep' elif 'remove' in hint.text().lower(): self.font_info.change_hint = 'remove' elif 'auto' in hint.text().lower(): self.font_info.change_hint = 'auto' def set_darken_opt(self): """ Set darken options :return: """ if self.sender().isChecked(): self.mod_bearing_opt.setEnabled(True) self.lbl.setEnabled(True) self.darken_amount_lab.setEnabled(True) self.darken_amount_opt.setEnabled(True) self.set_darken_amount(self.darken_amount_opt.value()) else: self.mod_bearing_opt.setEnabled(False) self.lbl.setEnabled(False) self.darken_amount_lab.setEnabled(False) self.darken_amount_opt.setEnabled(False) self.set_darken_amount(0) def set_mod_bearing(self): """ Set mod bearing options :return: """ if self.mod_bearing_opt.isChecked(): self.font_info.mod_bearings = True else: self.font_info.mod_bearings = False def load_fonts(self): """ Load fonts from a directory, and sets appropriate options :return: """ f_f = QFileDialog.getOpenFileNames(self, 'Load Fonts', '', 'Font Files (*.ttf *.otf)') if f_f[0]: for f_label, f_style in zip(self.fnt_file_name_list, self.fnt_sty_combo_list): f_label.setText('Load font file...') f_style.setCurrentIndex(SEL_NONE) f_style.setEnabled(False) self.font_files = f_f[0] f_f_names = [] for file in self.font_files: file = os.path.normpath(file) base, fn = os.path.split(file) f_f_names.append(fn) for f_file, f_label, f_style in zip(f_f_names, self.fnt_file_name_list, self.fnt_sty_combo_list): f_label.setText(f_file) f_style.setEnabled(True) if 'regular' in f_file.lower(): f_style.setCurrentIndex(SEL_REGULAR) elif 'bold' in f_file.lower() and 'italic' in f_file.lower(): f_style.setCurrentIndex(SEL_BOLDITALIC) elif 'bold' in f_file.lower(): f_style.setCurrentIndex(SEL_BOLD) elif 'italic' in f_file.lower(): f_style.setCurrentIndex(SEL_ITALIC) if self.new_fnt_name.text(): self.gen_ttf_btn.setEnabled(True) def read_proc_output(self): """ Read any stdout data available from the process and displays it in the output log window. :return: """ if sys.version_info.major == 2: output = unicode(self.cli_process.readAllStandardOutput(), encoding=sys.getdefaultencoding()) else: output = str(self.cli_process.readAllStandardOutput(), encoding=sys.getdefaultencoding()) self.log_win.append(output) def manage_proc(self): """ Manage the progress bar :return: """ proc = self.sender() if proc.state() == QProcess.Running: self.prog_bar.setRange(0, 0) if proc.state() == QProcess.NotRunning: self.prog_bar.setRange(0, 100) self.prog_bar.setValue(100) def gen_ttf(self): """ Generate modified TrueType font files, by calling the CLI script with the appropriate arguments. :param prev: :return: """ self.log_win.clear() if not self.ff_path: self.set_ff_path() if self.ff_path: if not self.font_info.out_dir: save_dir = os.path.normpath( QFileDialog.getExistingDirectory( self, 'Select save directory...', options=QFileDialog.ShowDirsOnly)) if save_dir == '.' or save_dir == '': return else: self.font_info.out_dir = save_dir else: save_dir = os.path.normpath( QFileDialog.getExistingDirectory( self, 'Select Save directory...', self.font_info.out_dir, options=QFileDialog.ShowDirsOnly)) if save_dir == '.' or save_dir == '': return else: self.font_info.out_dir = save_dir for file, style in zip(self.font_files, self.fnt_sty_combo_list): if style.currentIndex() == SEL_REGULAR: self.font_info.font_file_reg = file elif style.currentIndex() == SEL_BOLDITALIC: self.font_info.font_file_bi = file elif style.currentIndex() == SEL_BOLD: self.font_info.font_file_bd = file elif style.currentIndex() == SEL_ITALIC: self.font_info.font_file_it = file cli_opt_list = self.font_info.gen_cli_command() self.cli_process.start(self.ff_path, cli_opt_list) def closeEvent(self, event): """ Cleaning up... :param event: :return: """ self.cli_process.close() event.accept()
class PipManager(QObject): """ Manage `pip` processes. """ started = pyqtSignal() finished = pyqtSignal() failed = pyqtSignal() textChanged = pyqtSignal(str) def __init__(self, venv_dir, venv_name, parent=None): super().__init__(parent) self._venv_dir = venv_dir self._venv_name = venv_name self._process = QProcess(self) self._process.setWorkingDirectory(venv_dir) self._process.readyReadStandardOutput.connect( self.on_ready_read_stdout) self._process.readyReadStandardError.connect(self.on_ready_read_stderr) # started self._process.started.connect(self.started) # updated self._process.stateChanged.connect(self.on_state_changed) # finished self._process.finished.connect(self.finished) self._process.finished.connect(self.on_finished) def run_pip(self, command="", options=None): """ Activate the virtual environment and run pip commands. """ if has_bash(): if options is None: options = [] venv_path = os.path.join(self._venv_dir, self._venv_name) pip = f"pip {command} {' '.join(options)};" pipdeptree = f"pipdeptree {' '.join(options)};" task = pipdeptree if command == "pipdeptree" else pip script = (f"source {venv_path}/bin/activate;" f"{task}" "deactivate;") self._process.start("bash", ["-c", script]) def process_stop(self): """Stop the process.""" self._process.close() @pyqtSlot(QProcess.ProcessState) def on_state_changed(self, state): """Show the current process state. """ if state == QProcess.Starting: #print("[PROCESS]: Started") logger.debug("Started") elif state == QProcess.Running: #print("[PROCESS]: Running") logger.debug("Running") elif state == QProcess.NotRunning: #print("[PROCESS]: Stopped") logger.info("Done.") self.textChanged.emit("\n\nPress [ESC] to continue...") @pyqtSlot(int, QProcess.ExitStatus) def on_finished(self, exitCode): """Show exit code when finished. """ #print(f"[PROCESS]: Exit code: {exitCode}") logger.debug(f"Exit code: {exitCode}") self._process.kill() @pyqtSlot() def on_ready_read_stdout(self): """Read from `stdout` and send the output to `update_status()`. """ message = self._process.readAllStandardOutput().data().decode().strip() #print(f"[PIP]: {message}") logger.debug(message) self.textChanged.emit(message) @pyqtSlot() def on_ready_read_stderr(self): """Read from `stderr`, then kill the process. """ message = self._process.readAllStandardError().data().decode().strip() #print(f"[ERROR]: {message}") logger.error(message) self.textChanged.emit(message) self.failed.emit() self._process.kill()
class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self): super(MainWindow, self).__init__() # Set up the user interface from Designer. self.setupUi(self) # Make some local modifications. # self.colorDepthCombo.addItem("2 colors (1 bit per pixel)") # self.setWindowIcon(QIcon('images/icons8-google-news-24.png')) self.crawlNone = 'Nothing had been crawled, try another url.' self.exit.triggered.connect(self.close) if self.stackedWidget.currentIndex() == 0: self.simiBtn.setStyleSheet(""" color: #fff; text-decoration: none; background-color: #28a745; border-color:#28a745; text-decoration: none;""") elif self.stackedWidget.currentIndex() == 1: self.kwBtn.setStyleSheet(""" color: #fff; text-decoration: none; background-color: #28a745; border-color:#28a745; text-decoration: none;""") else: self.settingsBtn.setStyleSheet(""" color: #fff; text-decoration: none; background-color: #28a745; border-color:#28a745; text-decoration: none;""") # Connect up the buttons. self.url1CrawlBtn.clicked.connect(self.url1CrawlBtn_on_click) self.url2CrawlBtn.clicked.connect(self.url2CrawlBtn_on_click) self.detectBtn.clicked.connect(self.detectBtn_on_click) # self.simiBtn.clicked.connect(lambda: self.stackedWidget.setCurrentIndex(0)) # self.kwBtn.clicked.connect(lambda: self.stackedWidget.setCurrentIndex(1)) # self.settingsBtn.clicked.connect(lambda: self.stackedWidget.setCurrentIndex(2)) self.simiBtn.clicked.connect(lambda: self.from_to(0, self.simiBtn)) self.kwBtn.clicked.connect(lambda: self.from_to(1, self.kwBtn)) self.settingsBtn.clicked.connect(lambda: self.from_to(2, self.settingsBtn)) self.kwDetectBtn.clicked.connect(self.kwDetectBtn_on_click) self.getlistBtn.clicked.connect(self.getlistBtn_on_click) self.searchBtn.clicked.connect(self.searchBtn_on_click) self.closeSearchBtn.clicked.connect(self.closeSearchBtn_on_click) self.mpComfirmBtn.clicked.connect(self.mpComfirmBtn_on_click) # QProcess object for external app self.process = QProcess(self) # QProcess emits `readyRead` when there is data to be read self.process.readyRead.connect(self.dataReady) # Just to prevent accidentally running multiple times # Disable the button when process starts, and enable it when it finishes self.process.started.connect(lambda: self.searchBtn.setEnabled(False)) self.process.finished.connect(lambda: self.searchBtn.setEnabled(True)) self.coll_name = None output = subprocess.Popen(["sed -n '74p' ../news_spider/settings.py"], stdout=subprocess.PIPE, shell=True).communicate() self.SERVERTEXT = output[0].decode('utf-8') output = subprocess.Popen(["sed -n '75p' ../news_spider/settings.py"], stdout=subprocess.PIPE, shell=True).communicate() self.PORTTEXT = output[0].decode('utf-8') self.configList.setText(self.SERVERTEXT+self.PORTTEXT) self.PORT = self.PORTTEXT.split('=')[1] # self.threadpool = QThreadPool() # print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount()) def mpComfirmBtn_on_click(self): mpValue = self.mpValue.text() print(mpValue) if mpValue: self.process.start('sed', ['-i', 's/^MONGODB_PORT=.*/MONGODB_PORT={0}/'.format(mpValue), '../news_spider/settings.py']) output = subprocess.Popen(["sed -n '74p' ../news_spider/settings.py"], stdout=subprocess.PIPE, shell=True).communicate() self.SERVERTEXT = output[0].decode('utf-8') output = subprocess.Popen(["sed -n '75p' ../news_spider/settings.py"], stdout=subprocess.PIPE, shell=True).communicate() self.PORTTEXT = output[0].decode('utf-8') self.configList.clear() self.configList.setText(self.SERVERTEXT + self.PORTTEXT) self.PORT = self.PORTTEXT.split('=')[1] def from_to(self, to, toBtn): if self.stackedWidget.currentIndex() == to: pass else: if self.stackedWidget.currentIndex() == 0: self.simiBtn.setStyleSheet("""""") elif self.stackedWidget.currentIndex() == 1: self.kwBtn.setStyleSheet("""""") else: self.settingsBtn.setStyleSheet("""""") self.stackedWidget.setCurrentIndex(to) toBtn.setStyleSheet(""" color: #fff; text-decoration: none; background-color: #28a745; border-color:#28a745; text-decoration: none; """) # def closeStatBtn_on_click(self): # print('stopStat button clicked') # self.statBtn.setEnabled(True) # print(self.p.returncode) # # if self.process.isOpen(): # # self.process.close() # # print('close process') # # def statBtn_on_click(self): # print('stat button clicked') # self.statBtn.setEnabled(False) # self.output.clear() # # self.process.start('ping', ['127.0.0.1']) # self.p = subprocess.Popen("scrapy crawl news_spider", cwd='/home/watmel/PycharmProjects/news_similarity_detection/nnorder/news_spider/news_spider/spiders',shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) # cursor = self.output.textCursor() # for line in self.p.stdout.readlines(): # cursor.insertText(str(line, 'utf-8')) # if self.p.returncode != 0: # cursor.insertText("error") def closeSearchBtn_on_click(self): print('stopStat button clicked') if self.process.isOpen(): self.process.close() print('close process') # def execute_this_fn(self): # pc = post_crawl() # tag_rank = pc.get_tag_rank() # return tag_rank def print_output(self, s): tag_to_str = """<table><tr><td>{0}</td><td>{1}</td></tr>""".format('关键词', '热度值') for t in s: tag_to_str += '<tr><td><b>{0}:</b></td><td>{1}</td></tr>'.format(t['_id'], t['value']) tag_to_str += """</table>""" self.result.setText(tag_to_str) def thread_start(self): self.statBtn.setEnabled(False) print("THREAD START!") def thread_complete(self): self.statBtn.setEnabled(True) print("THREAD COMPLETE!") def searchBtn_on_click(self): # worker = Worker(self.execute_this_fn) # worker.signals.started.connect(self.thread_start) # worker.signals.result.connect(self.print_output) # worker.signals.finished.connect(self.thread_complete) # self.threadpool.start(worker) self.process.setProcessChannelMode(QProcess.MergedChannels) # self.process.start('ping', ['127.0.0.1']) kw_urlstyle = self.keyword.text() print(kw_urlstyle) if kw_urlstyle: self.output.clear() ck = check_kw(kw_urlstyle, self.PORT) flag, coll_name = ck.is_crawled() self.coll_name = coll_name if not flag: self.process.start('sed', ['-i', 's/^\s*kw=".*"/ kw="{0}"/'.format(kw_urlstyle), '../news_spider/spiders/news_spider.py']) self.process.waitForFinished(10) # self.process.start('sed',['-i','s/^MONGODB_COLLECTION=".*"/MONGODB_COLLECTION="{0}"/'.format(self.coll_name),'../news_spider/spiders/news_spider.py']) self.process.start('sed', ['-i', 's/^MONGODB_COLLECTION=".*"/MONGODB_COLLECTION="{0}"/'.format(self.coll_name), '../news_spider/settings.py']) self.process.waitForFinished(10) self.process.start('scrapy crawl news_spider') else: self.output.setPlainText('该关键词已抓取,可以直接点击获取列表按钮') else: pass def getlistBtn_on_click(self): pc = post_crawl(self.coll_name, self.PORT) title_list = pc.get_title_list() print("title_list:" + str(title_list)) self.leftnews.addItems(title_list) self.rightnews.addItems(title_list) self.leftnews.activated.connect(self.left_combox_on_activate) self.rightnews.activated.connect(self.right_combox_on_activate) def kwDetectBtn_on_click(self): text1 = self.leftnewsContent.toPlainText() text2 = self.rightnewsContent.toPlainText() if text1 and text2: s1, t1 = self.get_simhash(text=text1) s2, t2 = self.get_simhash(text=text2) dis = self.get_distance(s1, s2) self.searchDistance.setText('{0}%, 在32位哈希值有{1}位不同'.format(str((1 - dis / 32) * 100), dis.__str__())) def left_combox_on_activate(self): title = self.leftnews.currentText() pc = post_crawl(self.coll_name, self.PORT) text = pc.get_text_by_title(title) self.leftnewsContent.setText(text) def right_combox_on_activate(self): title = self.rightnews.currentText() pc = post_crawl(self.coll_name, self.PORT) text = pc.get_text_by_title(title) self.rightnewsContent.setText(text) def dataReady(self): cursor = self.output.textCursor() cursor.movePosition(cursor.End) cursor.insertText(str(self.process.readAll(), 'utf-8')) self.output.ensureCursorVisible() def url1CrawlBtn_on_click(self): # print('url1CrawlBtn clicked') self.url1.setText('http://news.sina.com.cn/c/2018-06-07/doc-ihcscwwz9278602.shtml') if self.url1.text(): clean_text, title = myGoose(url=self.url1.text()).get_cleaned_text() # clean_text = '123' if clean_text: self.news1.setPlainText(clean_text) self.title1.setText(title) else: self.news1.setPlainText(self.crawlNone) def url2CrawlBtn_on_click(self): # print('url2CrawlBtn clicked') self.url2.setText('http://china.chinadaily.com.cn/2018-06/08/content_36349835.htm') if self.url2.text(): clean_text, title = myGoose(url=self.url2.text()).get_cleaned_text() # clean_text = '123' if clean_text: self.news2.setPlainText(clean_text) self.title2.setText(title) else: self.news2.setPlainText(self.crawlNone) def tag_to_str(self, tag): # tag_to_str='' # for t in tag: # tag_to_str+='<b>{0}</b>: {1}<br/>'.format(t[0],t[1]) tag_to_str = """<table><tr><td>{0}</td><td>{1}</td></tr>""".format('关键词', '权重') for t in tag: tag_to_str += '<tr><td><b>{0}:</b></td><td>{1}</td></tr>'.format(t[0], t[1]) tag_to_str += """</table>""" return tag_to_str def detectBtn_on_click(self): print('detectBtn clicked') text1 = self.news1.toPlainText() text2 = self.news2.toPlainText() if text1 and text2: s1, t1 = self.get_simhash(text=text1) s2, t2 = self.get_simhash(text=text2) self.tag1.setText(self.tag_to_str(t1)) self.tag2.setText(self.tag_to_str(t2)) dis = self.get_distance(s1, s2) self.distance.setText('{0}%, 在32位哈希值有{1}位不同'.format(str((1 - dis / 32) * 100), dis.__str__())) def get_simhash(self, text): pair = jieba.analyse.extract_tags(text, topK=20, withWeight=True) return MySimHash().get_simhash(pair), pair def get_distance(self, s1, s2): return MySimHash().get_distance(s1, s2)
class MainWindow(QMainWindow, Ui_MainWindow): # Create settings for the software settings = QSettings('GGGG', 'Game Genie Good Guy') settings.setFallbacksEnabled(False) version = '1.0.0' def __init__(self, parent=None): QMainWindow.__init__(self, parent) # Load the ui self.ui = Ui_MainWindow() self.ui.setupUi(self) # Set the MainWindow Title self.setWindowTitle('Game Genie Good Guy - ' + self.version) # When the software are closed on console the software are closed signal.signal(signal.SIGINT, signal.SIG_DFL) self.ui.system.addItem("Game Boy/Gear/Master System") self.ui.system.addItem("Genesis/Mega Drive (no SMD roms)") self.ui.system.addItem("Nintendo") self.ui.system.addItem("Super Nintendo") self.ui.browse.clicked.connect(self.browse) self.ui.patch.clicked.connect(self.generateRom) self.ui.ips.clicked.connect(self.generateIPS) self.ui.log.setReadOnly(True) self.process = QProcess() self.process.readyReadStandardOutput.connect(self.printLog) if self.settings.value("rom"): self.ui.rom.setText(self.settings.value("rom")) if self.settings.value("system"): self.ui.system.setCurrentIndex(int(self.settings.value("system"))) # Show the form self.show() def browse(self): options = QFileDialog.Options() options |= QFileDialog.DontUseNativeDialog fileName, _ = QFileDialog.getOpenFileName( self, "QFileDialog.getOpenFileName()", "", "All Files (*);;", options=options) self.ui.rom.setText(str(fileName)) self.settings.setValue("rom", str(fileName)) def printLog(self): self.ui.log.clear() result = self.process.readAllStandardOutput().data().decode() self.ui.log.appendPlainText(result) def generateRom(self): self.rom = str(self.ui.rom.text()) name, ext = os.path.splitext(self.rom) self.newrom = "{name}-{uid}{ext}".format(name=name, uid='new', ext=ext) system = int(self.ui.system.currentIndex()) + 1 codes = str(self.ui.codes.toPlainText()) self.settings.setValue("system", system - 1) system = str(system) if self.rom != '': self.process.start('./GGGG "' + codes + '" ' + system + ' "' + self.rom + '" "' + self.newrom + '"') self.process.waitForFinished() self.process.close() # Based on https://github.com/fbeaudet/ips.py/blob/master/ips.py def generateIPS(self): self.generateRom() FILE_LIMIT = 0x1000000 #16MB PATCH_ASCII = bytes((0x50, 0x41, 0x54, 0x43, 0x48)) EOF_ASCII = bytes((0x45, 0x4f, 0x46)) name, ext = os.path.splitext(self.rom) ipsfile = "{name}{uid}{ext}".format(name=name, uid='', ext='.ips') original = open(self.rom, 'rb').read() modified = open(self.newrom, 'rb').read() patch = open(ipsfile, 'wb') recording = False record = bytearray() size = bytearray(2) if len(modified) > FILE_LIMIT: self.ui.log.appendPlainText( "\nModified file is too large for IPS format. Max: 16MB.") patch.write(PATCH_ASCII) for a in range(len(modified)): if not recording: if len(original) <= a or modified[a] != original[a]: recording = True record = bytearray() offset = bytearray(3) if a == EOF_ASCII: record.append(modified[a - 1]) record.append(modified[a]) for x in range(3): offset[x] = (a >> (16 - x * 8)) % 256 patch.write(offset) if a == len(modified) - 1: recording = False patch.write(b'\x00\x01') patch.write(record) else: if len(original) <= a or modified[a] != original[a]: record.append(modified[a]) if a == len(modified) - 1: recording = False for x in range(2): size[x] = len(record) >> (8 - x * 8) patch.write(size) patch.write(record) else: recording = False for x in range(2): size[x] = len(record) >> (8 - x * 8) patch.write(size) patch.write(record) patch.write(EOF_ASCII) patch.close() self.ui.log.appendPlainText("\nIPS file generated")
class runV2raycore(QObject): """ you should emit a signal to start or stop a program. """ start = pyqtSignal() stop = pyqtSignal() def __init__(self, outputTextEdit, v2rayPath="v2ray", v2rayOption="", bridgetreasureChest=False): super().__init__() self.outputTextEdit = outputTextEdit self.v2rayPath = v2rayPath self.v2rayOption = v2rayOption self.bridgetreasureChest = bridgetreasureChest if not self.bridgetreasureChest: from bridgehouse.extension import bridgetreasureChest self.bridgetreasureChest = bridgetreasureChest.bridgetreasureChest( ) self.v2rayProcess = QProcess() self.v2rayProcess.setProcessChannelMode(QProcess.MergedChannels) self.v2rayProcess.setProcessEnvironment( QProcessEnvironment.systemEnvironment()) self.v2rayProcess.readyRead.connect(self.setoutputTextEdit) self.v2rayProcess.started.connect(self.oncreatePIDFile) self.start.connect(self.onstart) self.stop.connect(self.onstop) self.translate = QCoreApplication.translate self.pidFile = ".v2rayPID" def onstart(self): if (self.v2rayProcess.state() == QProcess.NotRunning): self.outputTextEdit.clear() command = self.translate("runV2raycore", "v2ray file path had no seted.") if (self.v2rayPath): checkSpaces = re.search(" ", self.v2rayPath) if checkSpaces: # in fact, you can just keep this line. # do not need check spaces command = '"' + self.v2rayPath + '" ' + self.v2rayOption else: command = "{} {}".format(self.v2rayPath, self.v2rayOption) self.killOrphanProcess() self.v2rayProcess.start(command, QIODevice.ReadWrite) self.outputTextEdit.insertPlainText("{}\n\n".format(command)) if (self.v2rayProcess.state() == QProcess.NotRunning): self.outputTextEdit.moveCursor(QTextCursor.End) self.outputTextEdit.append("\n") self.outputTextEdit.insertPlainText(str( "{}\n".format(command))) self.outputTextEdit.insertPlainText( str( self.translate("runV2raycore", "{} Error Code:{}").format( self.v2rayProcess.errorString(), self.v2rayProcess.error()))) self.outputTextEdit.moveCursor(QTextCursor.End) self.outputTextEdit.textChanged.connect(self.getV2raycoreVersion) def killOrphanProcess(self): openFile = QFileInfo(self.pidFile) fileName = openFile.fileName() if QFile.exists(fileName): openFile = QFile(fileName) else: return v2rayPID = None try: openFile.open(QIODevice.ReadOnly | QIODevice.Text) v2rayPID = str(openFile.readAll(), "utf-8") except Exception: pass try: os.kill(int(v2rayPID), signal.SIGTERM) except Exception: pass def oncreatePIDFile(self): if self.v2rayProcess.state() == QProcess.NotRunning: return outFile = QFileInfo(self.pidFile) fileName = outFile.fileName() if QFile.exists(fileName): QFile.remove(fileName) outFile = QFile(fileName) v2rayPID = str(self.v2rayProcess.processId()) qDebug("process ID is: {}".format(v2rayPID)) try: outFile.open(QIODevice.WriteOnly | QIODevice.Text) outFile.write(codecs.encode(v2rayPID, "utf-8")) except Exception: pass outFile.close() def getV2raycoreVersion(self): text = self.outputTextEdit.toPlainText() version = re.findall("V2Ray v\d\.\d{1,2}", text) failtostart = re.findall("Failed to start App", text) if (version): version = version[0].split(" ")[1] self.bridgetreasureChest.setV2raycoreVersion(version) if (failtostart): self.outputTextEdit.textChanged.disconnect( self.getV2raycoreVersion) self.onstop() def onstop(self): if (self.v2rayProcess.state() == QProcess.Running): self.v2rayProcess.close() self.v2rayProcess.kill() self.outputTextEdit.moveCursor(QTextCursor.End) self.outputTextEdit.append("\n\n") self.outputTextEdit.insertPlainText( str( self.translate("runV2raycore", "{} is stop now...").format( self.v2rayPath))) self.outputTextEdit.insertPlainText( str( self.translate("runV2raycore", "\n{} is ready to run...").format( self.v2rayPath))) self.outputTextEdit.moveCursor(QTextCursor.End) def setoutputTextEdit(self): self.outputTextEdit.moveCursor(QTextCursor.End) self.outputTextEdit.insertPlainText( str(self.v2rayProcess.readAllStandardOutput(), "utf-8")) self.outputTextEdit.insertPlainText( str(self.v2rayProcess.readAllStandardError(), "utf-8")) self.outputTextEdit.moveCursor(QTextCursor.End)
class Heimdall(QObject): """Heimdall service This service maintains a Raspberry Pi or similar device as an automatic display that works without user interaction. Connection timeline: 0. _setup_reestablish_tunnel - Set up a timer to try to reestablish the tunnel. This step is only performed if the tunnel was previously active and failed, and exist to provide a reconnection delay. 1. start_tunnel - Run SSH to the Raspberry pi 2. try_connect - Attempt to use the SSH connection to contact i3/Sway. If this fails, an automatic retry after a timeout is done. 3. setup - Take over the i3/Sway setup """ def __init__(self, parent=None): super().__init__(parent) self.reconnect_timer = QTimer() self.ssh_proc = QProcess() self.bus = QtDBus.QDBusConnection.sessionBus() self.dbus_adaptor = DBusAdaptor(self) self.contextual_executor = ContextualExecutor(self) if not self.bus.isConnected(): raise Exception("Failed to connect to dbus!") self.bus.registerObject("/heimdall", self) self.bus.registerService("com.troshchinskiy.Heimdall") self.homedir = os.environ['HOME'] + "/.heimdall" self.read_config() self.start_tunnel() def echo(self, text): return text def version(self): return "0.1" def connect(self): self.ssh = Popen(["ssh"], stdout=PIPE) def read_config(self): filename = self.homedir + '/config.json' print("Loading config file {}...\n".format(filename)) with open(filename, 'r') as conf_h: self.config = json.load(conf_h) def start_tunnel(self): if self.ssh_proc and self.ssh_proc.isOpen(): print("Tunnel already running") return print("Starting tunnel...\n") sway_pid = self._run_remote(["pidof", "sway"]) if sway_pid is None: raise Exception('Sway is not running!') home_dir = self._run_remote(["echo", '$HOME']) uid = self._run_remote(["echo", '$UID']) self.remote_socket = "/run/user/" + uid + "/sway-ipc." + uid + "." + sway_pid + ".sock" self.local_socket = self.homedir + "/sway.sock" print("Sway pid: '{}'".format(sway_pid)) print("Home dir: '{}'".format(home_dir)) print("UID : '{}'".format(uid)) print("Socket : '{}'".format(self.remote_socket)) if os.path.exists(self.local_socket): os.remove(self.local_socket) r = self.config['remote'] command_args = [ "-i", r['ssh-key'], "-p", r['port'], "-l", r['user'], "-R", r['backwards-port'] + ":127.0.0.1:" + r['local-ssh-port'], "-L", self.local_socket + ':' + self.remote_socket, r['server'] ] print("Running command: ssh {}".format(command_args)) self.ssh_proc.started.connect(self._ssh_process_started) self.ssh_proc.errorOccurred.connect(self._ssh_process_error) self.ssh_proc.finished.connect(self._ssh_process_finished) self.ssh_proc.start(self.config['commands']['ssh'], command_args) def try_connect(self): """Try to connect to i3/Sway. SSH takes a while to perform the port forwarding, so we may do this several times, until it starts working. """ print("Trying to connect to Sway/i3 at socket {}...".format( self.local_socket)) try: self.i3 = Connection(socket_path=self.local_socket) except ConnectionRefusedError: print("Not connected yet!") return except FileNotFoundError: print("Socket doesn't exist yet!!") return self.connect_timer.stop() self.setup() def setup(self): try: print("Setting up Sway/i3...") self.wm_version = self.i3.get_version() print("Connected to Sway/i3 version {}".format(self.wm_version)) print("Resetting workspace...") for workspace in self.i3.get_workspaces(): print("Deleting workspace {}".format(workspace.name)) self.i3.command('[workspace="{}"] kill'.format(workspace.name)) print("Executing commands...") for cmd in self.config['startup']['remote-run']: print("\tExecuting: {}".format(cmd)) self._run_remote(cmd) print("Setting up workspaces...") wsnum = 0 for wsconf in self.config['startup']['workspaces']: wsnum += 1 self.i3.command("workspace {}".format(wsnum)) self.i3.command('rename workspace "{}" to "{}"'.format( wsnum, wsconf['name'])) for wscmd in wsconf['commands']: self.i3_command(wscmd) except (ConnectionRefusedError, FileNotFoundError): self._setup_reestablish_tunnel() def i3_command(self, command): command = command.replace('$TERM_EXEC_KEEP', self.config['remote']['terminal-exec-keep']) command = command.replace('$TERM_EXEC', self.config['remote']['terminal-exec']) command = command.replace('$TERM', self.config['remote']['terminal']) command = command.replace( '$SSH_TO_HOST', self.config['commands']['ssh'] + " -p " + self.config['remote']['backwards-port'] + " -t " + os.environ['USER'] + '@localhost ') print("Executing command: " + command) self.i3.command(command) def contextual_action(self, environment, path, command): self.contextual_executor.execute(environment, path, command) def stop_tunnel(self): """Stop the tunnel, if it's running""" if self.ssh_proc and self.ssh_proc.isOpen(): print("Stopping ssh\n") self.ssh_proc.kill() self.ssh_proc.close() if os.path.exists(self.local_socket): os.remove(self.local_socket) def _setup_reestablish_tunnel(self): """Re-establish the SSH tunnel and begin again the process of syncing up""" self.stop_tunnel() self.reconnect_timer.timeout.connect(self.start_tunnel()) self.reconnect_timer.singleShot(True) self.reconnect_timer.start(100) def _ssh_process_started(self): print("SSH process started!") self.connect_timer = QTimer() self.connect_timer.timeout.connect(self.try_connect) self.connect_timer.start(50) def _ssh_process_error(self, error): print("SSH process failed with error {}!".format(error)) def _ssh_process_finished(self, exit_code, exit_status): print("SSH process exited with code {}, status {}!".format( exit_code, exit_status)) def _run_remote(self, command): r = self.config['remote'] ssh_command = [ self.config['commands']['ssh'], "-i", r['ssh-key'], "-p", r['port'], "-l", r['user'], r['server'] ] ssh_command += command print("Running: {}".format(ssh_command)) result_raw = subprocess.run(ssh_command, stdout=subprocess.PIPE) result = result_raw.stdout.decode('utf-8').strip() return result