class Settings(AdvancedSettings, ImageSettings, EncoderSettings, StreamingSettings, RecordingSettings, AudioSettings): def __init__(self): super().__init__() organization = "de.dunkelstern" if sys.platform.startswith( 'darwin') else "dunkelstern" self.settings_instance = QSettings(organization, "PiCamPro") self.load() def save(self) -> None: """ Save the settings to permanent storage """ self.validate() for key, value in self.__dict__.items(): if key.startswith('_') or callable(value) or key in ( 'settings_instance', 'dirty_values', 'value_ranges'): continue section = 'unknown' for klass in self.__class__.__mro__: if issubclass(klass, SettingsProto): if key in klass.__dict__: section = klass.__name__.replace('Settings', '').lower() self.settings_instance.setValue('{}/{}'.format(section, key), value) def load(self) -> None: """ Load settings from permanent storage, usually will be performed on initialization. """ for key in self.settings_instance.allKeys(): value = self.settings_instance.value(key) klass, prop = key.split('/') klass = klass.title() + 'Settings' for c in Settings.__mro__: if c.__name__ == klass: klass = c break if klass.__dict__[prop] is True or klass.__dict__[prop] is False: value = bool(value) elif isinstance(klass.__dict__[prop], int): value = int(value) setattr(self, prop, value) self.dirty_values = {}
def test_save_settings(self, temp_settings): settings = temp_settings settings.exercises = [['sample3', [0, 0, 0]]] settings.instrument = 99 settings.save_settings() q_settings = QSettings() saved_instrument = q_settings.value('instrument') saved_exercises = [] q_settings.beginReadArray('exercises') for ex in q_settings.allKeys(): if ex == 'size': continue saved_exercises.append([ex, q_settings.value(ex)]) q_settings.endArray() assert saved_instrument == 99 assert saved_exercises == [['sample3', [0, 0, 0]]]
def test_exercises_exist(self): settings_reader = QSettings() settings_reader.beginReadArray('exercises') assert len(settings_reader.allKeys()) > 1 settings_reader.endArray()
class PluginManager(): """Creates and manages the plugins. Serves as a bridge between the editor and the plugins. """ def __init__(self, parent): self.parent = parent self.settings = QSettings(c.SETTINGS_PATH, QSettings.IniFormat) self.keyboard_settings = QSettings(c.KEYBOARD_SETTINGS_PATH, QSettings.IniFormat) self.theme = self.settings.value(c.THEME, defaultValue=c.THEME_D) self.get_keyboard_shortcuts() self.get_plugins() self.do_not_delete = [ c.SAVE_KEY, c.WORD_BY_WORD_KEY_NEXT, c.FORWARD_KEY, c.PLAY_PAUSE_KEY, c.BACKWARDS_KEY, c.HELP_KEY, c.WORD_BY_WORD_KEY_PREV ] def get_keyboard_shortcuts(self): """Saves the current occupied shortcuts in a list""" self.occupied_keys = [] for key in self.keyboard_settings.allKeys(): keyboard_value = self.keyboard_settings.value(key) if len(keyboard_value) != 0: self.occupied_keys.append(keyboard_value) def get_plugins(self): """Creates all Plugins and saves them in the plugin-list.""" self.plugin_list = [] for (dirpath, dirname, filenames) in walk(c.PLUGIN_PATH): for file in filenames: if c.PLUGIN_POST not in file or "__pycache__" in dirpath: continue try: module_path = os.path.join(dirpath, file) temp_spec = ilu.spec_from_file_location( file.split(".")[0], module_path) temp_module = ilu.module_from_spec(temp_spec) temp_spec.loader.exec_module(temp_module) temp_plugin = temp_module.Plugin(self) if issubclass(type(temp_plugin), IPlugin): self.plugin_list.append(temp_plugin) print("Imported {} as a Plugin".format(file)) except BaseException as e: print("Error while importing {} with Exception {}".format( file, e)) def get_plugin_licences(self): """Gets all used plugin-licences.""" licences = {} for (dirpath, dirname, filenames) in walk(c.PLUGIN_PATH): if dirpath.lower().endswith("licences"): for filename in filenames: licences[filename.split(".")[0]] = dirpath return licences def get_toolbar_actions(self, parent): """Queries all toolbar actions from the plugins. Handles the Keyboard-Shortcuts from this toolbar-actions and prevents overlapping. Args: parent: The Parent-Window. Returns: Returns list of the QActions from all plugins. """ actions = [] for plugin in self.plugin_list: plugin_actions = plugin.get_toolbar_action(parent) plugin_name = plugin.get_name() for action in plugin_actions: if action is None or plugin_name is None or not plugin_name: continue key_name = plugin_name.upper().replace(" ", "_") + "_KEY" saved_key = self.keyboard_settings.value(key_name, defaultValue=None) plugin_shortcut = action.shortcut() if saved_key is None and not plugin_shortcut.isEmpty(): plugin_shortcut_upper = plugin_shortcut.toString().upper() if plugin_shortcut_upper not in self.occupied_keys: self.keyboard_settings.setValue( key_name, plugin_shortcut_upper) self.occupied_keys.append(saved_key) else: self.keyboard_settings.setValue(key_name, "") self.do_not_delete.append(key_name) if saved_key is not None: action.setShortcut(QKeySequence(saved_key)) actions.append(action) self.clear_keyboard_settings() return actions def clear_keyboard_settings(self): """Deletes all unused shortcuts from the keyboard settings.""" all_keys = set(self.keyboard_settings.allKeys()) to_delete = all_keys.difference(set(self.do_not_delete)) for key in to_delete: print("removed", key) self.keyboard_settings.remove(key) def get_word_by_word_actions(self, word, meta_data, pos): """Queries all QPushButtons of the plugins. Args: word: str: The current word word_meta_data: List[dict]: The meta_data from the word. word_pos: int: The current word position """ for plugin in self.plugin_list: plugin.get_word_action(word, meta_data, pos) def project_loaded(self): """Calls the project_loaded method of all plugins.""" for plugin in self.plugin_list: plugin.project_loaded() def add_new_word_by_word_action(self, btns, name, word, word_pos): """Adds a new Word-Action (QPushButton) to the editor word by word list. Args: btns: The QPushButtons from the plugin. name: The Name of the Buttons. word: The word for which these buttons are. word_pos: The position of the word. """ btns = btns for btn in btns: btn.setShortcut(None) self.parent.add_new_word_by_word_action(btns, name, word, word_pos) def get_selection(self): """Gets the current selected word(s) in the editor. Returns: Selected word(s) """ return self.parent.get_selection() def replace_selection(self, new_word): """Replaces the current selection in the editor. Args: new_word: the word(s) which should replace the current selection """ self.parent.replace_selection(new_word) def get_text(self): """Returns the full text from the editor. Returns: The full text from the editor. """ return self.parent.get_text() def set_text(self, new_text): """Sets the text in the editor with the given new_text. Args: new_text: The new text. """ self.parent.set_text(new_text) def get_word_at(self, pos): """Gets a the word at the given position. Args: pos: Position of the desired word. Returns: The word at the position. """ return self.parent.get_word_at(pos) def set_word_at(self, word, pos, replace_old): """Sets the word at a given position. Args: word: The word which should be set. pos: The position on which the word should be set. replace_old: true: replace the old word on the positon, false: set it before. """ self.parent.set_word_at(word, pos, replace_old) def get_setting(self, key, default): """Get a specific setting. Args: key: The settings-key default: The default value if there is no setting for the key. Returns: The settings value for the given key or the default if nothing is there. """ return self.settings.value(key, defaultValue=default) def get_language(self): """Returns the language of the current project. Returns: The language of the current project. """ return self.parent.get_language() def set_hint_text(self, text): """Sets the hint text (left bottom corner) in the editor. Args: text: The Hint text. """ self.parent.set_hint_text(text) def set_text_with_line_breaks(self, new_text): """Sets the Text in the editor but tries to restore the given linebreaks. Args: new_text: The new text. """ self.parent.set_text_with_line_breaks(new_text) def get_project_folder_path(self): """Get the current project folder path Returns: Project folder path. """ return self.parent.get_project_folder_path()
class Warmuppy: def __init__(self): super().__init__() pygame.init() # Instance variables self.settings = QSettings() self.timers = [] self.exercises = [] self.notes = NOTES # Load settings self.bpm = int(self.settings.value('bpm', DEFAULT_BPM)) self.cut = float(self.settings.value('cut', DEFAULT_CUT)) self.step = int(self.settings.value('step', DEFAULT_STEP)) prev = self.settings.value('preview', DEFAULT_PREVIEW) self.preview = prev in ['true', True] prolonged = self.settings.value('prolong', DEFAULT_PROLONG) self.prolong = prolonged in ['true', True] self.preview_time = int( self.settings.value('preview_time', DEFAULT_PREVIEW_TIME) ) self.prolong_time = int( self.settings.value('prolong_time', DEFAULT_PROLONG_TIME) ) self.note = int(self.settings.value('note', 0)) self.octave = int(self.settings.value('octave', 3)) self.instrument = int( self.settings.value('instrument', DEFAULT_INSTRUMENT) ) self.settings.beginReadArray('exercises') for ex in self.settings.allKeys(): if ex == 'size': continue exv = self.settings.value(ex) if isinstance(exv,str): exv = [exv] self.exercises.append([ ex, exv ]) self.settings.endArray() if not self.exercises: self.settings.beginWriteArray('exercises') for ex in DEFAULT_EXERCISES: self.exercises.append(ex) self.settings.setValue(ex[0], ex[1]) self.settings.endArray() self.exercise = DEFAULT_EXERCISE self.settings.setValue('instrument', self.instrument) def change_note(self, note): note_id = self.notes.index(note) logging.debug(f"New note is {note} ({note_id})") self.note = note_id self.settings.setValue('note', note_id) def change_octave(self, octave): logging.debug(f"New octave is {octave}") self.octave = octave self.settings.setValue('octave', octave) def change_exercise(self, i): if not self.exercises: logging.debug("No exercise to change to, skipping") return try: ex = self.exercises[i] self.exercise = i except IndexError: ex = self.exercises[0] self.exercise = 0 logging.debug(f"Selected exercise {ex[0]} ({ex[1]})") # Generate boilerplate setters for x in "bpm cut step preview_time prolong_time".split(): exec(f'''def change_{x}(self,{x}): logging.debug("New {x} : %s",{x}) self.settings.setValue('{x}',{x}) self.{x} = {x}''') def change_preview(self, p): logging.debug(f"Setting preview as {p}") self.preview = p self.settings.setValue('preview', p) def change_prolong(self, p): logging.debug(f"Setting prolong as {p}") self.prolong = p self.settings.setValue('prolong', p) # Go {step} semitones up or down def bump_note(self, up): # Beginning of 3rd octave + selected octave * semitones + note base_note = 24 + (self.octave - 1) * 12 + self.note logging.debug(f"Current note: {base_note}") # Highest playable note is the last of the 8th octave max_note = 8 * 12 + 24 # Lowest is C3 min_note = 25 # Compute new note if up: if base_note < max_note: base_note += self.step else: if base_note > min_note: base_note -= self.step logging.debug(f"New note: {base_note}") # Compute new octave octave = (base_note - 24) // 12 + 1 logging.debug(f"New octave should be #{octave}") # Compute relative note for the UI note_id = (base_note - 24) % 12 note = self.notes[note_id] logging.debug(f"New note should be #{note}") return octave, note # Generate a midi with the exercise and then play it def play_exercise(self): self.stop() # Load selected exercise ex = self.exercises[self.exercise] name = ex[0] seq = ex[1] logging.debug(f"Starting exercise '{name}' (pattern: {seq})") # Init midi file midi_file = tempfile.NamedTemporaryFile(delete=False) mid = MidiFile() track = MidiTrack() mid.tracks.append(track) track.append( Message('program_change', program=self.instrument, time=0) ) # Compute starting note: octave * semitones + relative note base_note = 24 + (self.octave - 1) * 12 + self.note # Seconds per beat seconds = 60 / self.bpm # Note duration is one beat minus the cut, in milliseconds base_duration = (seconds - self.cut) * 1000 # Prepend the base note to the midi if the preview is selected timer_delay = 0 if self.preview: timer_delay = int(base_duration*self.preview_time) track.append( Message('note_on', note=base_note, velocity=100, time=0) ) track.append( Message('note_off', note=base_note, time=timer_delay) ) timer_data = [] # Add the rest of the notes for idx, p in enumerate(seq): # Normalise the note step as a string item = str(p) # Extract the step step = int(sub(r'[^0-9]','',item)) # If the number has dashes or dots, add half a beat or a quarter beat for each, respectively duration = base_duration * (1 + item.count('-')*0.5 + item.count('.')*0.25) # Calculate percentage of current step current_index = idx + 1 percent = (current_index / len(seq)) * 100 # If this is the last note and the user wants to, prolong it if current_index == len(seq) and self.prolong: logging.debug(f"prolonging {step}") delay = int(base_duration*self.prolong_time) else: delay = int(duration) # Append the note to the midi track.append( Message('note_on', note=(base_note+step), velocity=100, time=0) ) track.append( Message('note_off', note=(base_note+step), time=delay) ) timer_data.append([percent, timer_delay]) timer_delay += duration # Save midi file and load it with pygame separately, # to avoid race conditions mid.save(file=midi_file) midi_file.flush() midi_file.close() pygame.mixer.music.load(midi_file.name) pygame.mixer.music.play() # Cleanup if 'WARMUPPY_KEEP_MIDI' in os.environ: copyfile(midi_file.name, os.environ['WARMUPPY_KEEP_MIDI']) os.remove(midi_file.name) return timer_data def stop(self): # Stop the music pygame.mixer.music.stop() # Settings window told us that something changed, so reload everything def reload_settings(self): logging.debug("Settings saved, reloading") self.instrument = int( self.settings.value('instrument', DEFAULT_INSTRUMENT) ) self.exercises = [] self.settings.beginReadArray('exercises') for ex in self.settings.allKeys(): if ex == 'size': continue self.exercises.append([ ex, self.settings.value(ex) ]) self.settings.endArray()
class SettingsWidget(QWidget): """Widget to show and edit the settings.""" def __init__(self): super(SettingsWidget, self).__init__() self.settings = QSettings(c.SETTINGS_PATH, QSettings.IniFormat) self.keyboard_settings = QSettings(c.KEYBOARD_SETTINGS_PATH, QSettings.IniFormat) self.value_dict_settings = {} self.value_dict_keyboard = {} self.f_reg = re.compile("F[1-9]|1[0-2]") self.ctrl_reg = re.compile("CTRL\+[a-xA-X0-9]") self.forbidden_reg = re.compile("CTRL\+[vVcCzZyY]") v_box = QVBoxLayout() theme = QGroupBox("Theme") theme_layout = QFormLayout() theme_value = QComboBox() theme_value.addItem(c.THEME_D) theme_value.addItem(c.THEME_L) theme_value.setCurrentText( self.settings.value(c.THEME, defaultValue=c.THEME_D)) theme_layout.addRow(QLabel(c.THEME.capitalize()), theme_value) theme.setLayout(theme_layout) self.value_dict_settings[c.THEME] = theme_value keyboard = QGroupBox("Keyboard") keyboard_layout = QFormLayout() info_label = QLabel("Only F-Keys and CTRL+ Combinations are allowed.") info_label.setWordWrap(True) keyboard_layout.addRow(info_label) for key in self.keyboard_settings.allKeys(): line_edit = QLineEdit() line_edit.setText(self.keyboard_settings.value(key)) keyboard_layout.addRow(key, line_edit) self.value_dict_keyboard[key] = line_edit keyboard.setLayout(keyboard_layout) plugins = QGroupBox("Plug-ins") plugins_layout = QFormLayout() show_empty_buttons_check = QCheckBox( "Show Plug-ins without Buttons in Word By Word - List") show_empty_buttons_check.setChecked( self.settings.value(c.SHOW_EMPTY_BUTTONS, defaultValue=True, type=bool)) plugins_layout.addRow(show_empty_buttons_check) self.value_dict_settings[ c.SHOW_EMPTY_BUTTONS] = show_empty_buttons_check plugins.setLayout(plugins_layout) self.note = QLabel("Settings will be applied after restart") save = QPushButton("Save") save.clicked.connect(self.save_values) v_box.addWidget(theme) v_box.addWidget(keyboard) v_box.addWidget(plugins) v_box.addWidget(self.note) v_box.addWidget(save) self.setLayout(v_box) def save_values(self): """Saves the current values.""" saved = True for key in self.value_dict_settings.keys(): widget = self.value_dict_settings.get(key) if isinstance(widget, QLineEdit): self.settings.setValue(key, widget.text().strip()) if isinstance(widget, QComboBox): self.settings.setValue(key, widget.currentText().strip()) if isinstance(widget, QCheckBox): self.settings.setValue(key, widget.isChecked()) occupied = [] self.keyboard_settings.clear() for key in self.value_dict_keyboard.keys(): value = self.value_dict_keyboard.get(key).text().strip().replace( " ", "").upper() if len(value) == 0 or ((bool(self.f_reg.search(value)) or bool(self.ctrl_reg.search(value))) and not bool(self.forbidden_reg.search(value))): if value not in occupied: self.keyboard_settings.setValue(key, value) if len(value) != 0: occupied.append(value) else: saved = False self.note.setStyleSheet("QLabel {color: red;}") self.note.setText("Duplicate Values found!") self.keyboard_settings.setValue(key, "") if saved: self.note.setStyleSheet("QLabel {color: green;}") self.note.setText("Saved!") def reset_note(self): """Resets the little now text at the bottom.""" self.note.setStyleSheet("") self.note.setText("Settings will be applied after restart")
class Settings: def __init__(self): # Standard constructor stuff super().__init__() self.settings = QSettings() if not pygame.get_init(): pygame.init() # Instance variables self.exercises = [] self.instrument = int(self.settings.value('instrument')) # Load exercises from stored settings self.settings.beginReadArray('exercises') for ex in self.settings.allKeys(): if ex == 'size': continue self.exercises.append([ex, self.settings.value(ex)]) self.settings.endArray() def remove_exercise(self, exercise_name): # Replace self.exercises with a copy without the selected exercise new_exercises = [] for ex in self.exercises: if ex[0] != exercise_name: new_exercises.append(ex) self.exercises = new_exercises def reload_exercise(self, exercise_name, exercise_text): # Load all exercise names exercise_names = [] for ex in self.exercises: exercise_names.append(ex[0]) new_exercises = [] # If the reloaded exercise is existing then update it in memory, # otherwise just add it exercise_contents = exercise_text.split() if exercise_name in exercise_names: for ex in self.exercises: if ex[0] == exercise_name: new_exercises.append([ex[0], exercise_contents]) else: new_exercises.append([ex[0], ex[1]]) self.exercises = new_exercises else: self.exercises.append([exercise_name, exercise_contents]) def set_instrument(self, instrument_id): self.instrument = instrument_id def save_settings(self): self.settings.beginWriteArray('exercises') for key in self.settings.allKeys(): self.settings.remove(key) if key != 'size' else None for ex in self.exercises: self.settings.setValue(ex[0], ex[1]) self.settings.endArray() self.settings.setValue('instrument', self.instrument) self.settings.sync() def preview(self): pygame.mixer.music.stop() midi_file = tempfile.NamedTemporaryFile(delete=False) mid = MidiFile() track = MidiTrack() mid.tracks.append(track) instrument = self.instrument track.append(Message('program_change', program=instrument, time=0)) note = 60 track.append(Message('note_on', note=note, velocity=100, time=0)) track.append(Message('note_off', note=note, time=2000)) mid.save(file=midi_file) midi_file.flush() midi_file.close() pygame.mixer.music.load(midi_file.name) pygame.mixer.music.play() if 'WARMUPPY_KEEP_MIDI' in os.environ: copyfile(midi_file.name, os.environ['WARMUPPY_KEEP_MIDI']) os.remove(midi_file.name)
class MainWidget(QWidget): port_select = Signal(str) port_connect = Signal() add_wifi = Signal(str, str) show_settings = Signal() load_settings = Signal() def __init__(self, parent=None): super(MainWidget, self).__init__(parent) self.ui = Ui_MainWidget() self.ui.setupUi(self) self.settings = QSettings() self.port: str = '' self.ui.pushButtonConnect.clicked.connect(self.on_connect_click) self.ui.listWidgetAvailablePorts.itemChanged.connect( self.on_port_select) self.ui.listWidgetAvailablePorts.itemClicked.connect( self.on_port_select) self.ui.pushButtonAddWiFiAP.clicked.connect(self.on_add_wifi_click) self.ui.pushButtonShowSettings.clicked.connect(self.on_show_settings) self.ui.pushButtonLoadSettings.clicked.connect( self.on_load_settings_click) def showEvent(self, event: QShowEvent): super().showEvent(event) self.ui.listWidgetAvailablePorts.clear() for port in list(serial.tools.list_ports.comports()): self.ui.listWidgetAvailablePorts.addItem(port.device) # if self.ui.listWidgetAvailablePorts.count() > 0: # self.ui.listWidgetAvailablePorts.setCurrentRow(0) if 'mwg' in self.settings.allKeys(): self.setGeometry(self.settings.value('mwg')) def closeEvent(self, event: PySide2.QtGui.QCloseEvent): super().closeEvent(event) self.settings.setValue('mwg', self.geometry()) def on_connect_click(self): self.port_connect.emit() def on_port_select(self, item): self.port = item.text() self.port_select.emit(self.port) def on_add_wifi_click(self): self.add_wifi.emit(self.ui.lineEditSSID.text(), self.ui.lineEditAPPassword.text()) def on_show_settings(self): self.show_settings.emit() def on_load_settings_click(self): self.load_settings.emit() @Slot() def on_data_received(self, data: str): js = None try: js = json.loads(data) except JSONDecodeError as e: print(f'Error: {e} for message {data}') # self.ui.plainTextEditSerialLog.setPlainText(f'Error: {e} for message {data}') if js is not None: json_text = json.dumps(js, separators=(', ', ':'), indent=4) # print(json_text) self.ui.plainTextEditSerialLog.setPlainText(json_text) else: # self.ui.plainTextEditSerialLog.setPlainText(data) print(data)