class Settings(object): def __init__(self, setting_path: str): self.setting_path = setting_path self.settings = QSettings(setting_path, QSettings.IniFormat) self.settings.setFallbacksEnabled(False) self.set_value(index_filter, []) def int_value(self, item: setting_item): return int(self.value(item)) def float_value(self, item: setting_item): return float(self.value(item)) def boolean_value(self, item: setting_item): value = self.value(item) return value.lower() == 'true' if isinstance(value, str) else bool(value) def list_value(self, item: setting_item): value = self.value(item) return value if isinstance(value, list) else [value] def str_value(self, item: setting_item): return str(self.value(item)) def value(self, item: setting_item): return self.settings.value(f'{item.section}/{item.key}', item.default_value) def set_value(self, item: setting_item, value: Any): self.settings.setValue(f'{item.section}/{item.key}', value)
def __init__(self): super(Watcher, self).__init__() """ Constructor for Watcher class, used to setup some public variables(path) and hidden :param path: the path that the watchdog will observe """ # connect # could be deleted, for now it's just to avoid Exceptions when turning # off self.observer = Observer() env_settings = QSettings() self.path = lambda: env_settings.value("sync_path") # Debug < Info < Warning < Error so setting debug will get everything # I need to create a new logger cuz davide's logger is root log self.logger = logging.getLogger("watchdog") self.logger.setLevel(logging.WARNING) formatter = logging.Formatter( '%(asctime)s:%(levelname)s:%(pathname)s:%(process)d:%(message)s') file_handler = logging.FileHandler('log.mer') file_handler.setFormatter(formatter) self.logger.addHandler(file_handler)
def notifySend(message1, message2, time, sound, parent=None): if os_type == OS.LINUX: notifications_path = '/usr/share/sounds/freedesktop/stereo/' elif os_type in OS.BSD_FAMILY: notifications_path = '/usr/local/share/sounds/freedesktop/stereo/' else: notifications_path = '' if sound == 'ok': file = os.path.join(notifications_path, 'complete.oga') playNotification(str(file)) elif sound == 'fail': file = os.path.join(notifications_path, 'dialog-error.oga') playNotification(str(file)) elif sound == 'warning': file = os.path.join(notifications_path, 'bell.oga') playNotification(str(file)) elif sound == 'critical': file = os.path.join(notifications_path, 'power-plug.oga') playNotification(str(file)) elif sound == 'queue': file = os.path.join(notifications_path, 'dialog-information.oga') playNotification(str(file)) # load settings persepolis_setting = QSettings('persepolis_download_manager', 'persepolis') enable_notification = persepolis_setting.value('settings/notification') time = str(time) message1 = str(message1) message2 = str(message2) # using Qt notification or Native system notification if enable_notification == 'QT notification': parent.system_tray_icon.showMessage(message1, message2, QIcon.fromTheme('persepolis-tray', QIcon(':/persepolis-tray.svg')), 10000) else: if os_type in OS.UNIX_LIKE: subprocess.Popen(['notify-send', '--icon', 'persepolis', '--app-name', 'Persepolis Download Manager', '--expire-time', time, message1, message2], stderr=subprocess.PIPE, stdout=subprocess.PIPE, stdin=subprocess.PIPE, shell=False) elif os_type == OS.OSX: notifyMac("Persepolis Download Manager", message1, message2) elif os_type == OS.WINDOWS: message = Windows_Notification(parent=parent, time=time, text1=message1, text2=message2, persepolis_setting=persepolis_setting) message.show()
def showSettingsDialog(self: Any, firstStart: bool = False) -> SettingsWindow: settings = QSettings() settingswindow = SettingsWindow(self, firstStart) settingswindow.setAttribute(Qt.WA_DeleteOnClose) settingswindow.setModal(True) settingswindow.open() settingswindow.finished.connect(lambda: [ self.model.setPaths(Path(str(settings.value('gamePath'))), Path(str(settings.value('configPath')))), self.mainwidget.startscriptmerger.setEnabled( verifyScriptMergerPath( Path(str(settings.value('scriptMergerPath')))) is not None) ]) return settingswindow
def update(self, model: Model) -> None: settings = QSettings() self._colorNewest = QColor(242, 255, 242) \ if settings.value('highlightNewest', 'True') == 'True' else None self._colorRecent = QColor(242, 246, 255) \ if settings.value('highlightRecent', 'True') == 'True' else None self._colorUnmanaged = QColor(250, 240, 240) \ if settings.value('highlightUnmanaged', 'True') == 'True' else None self._colorDisabled = QColor(240, 240, 240) \ if settings.value('highlightDisabled', 'True') == 'True' else None self._colorUnavailable = QColor(240, 240, 240) self.layoutAboutToBeChanged.emit() self.clearCache() self._lastUpdate = model.lastUpdate self._lastInitialization = model.lastInitialization self.layoutChanged.emit() self.dataChanged.emit(self.index(0, 0), self.index(self.rowCount() - 1, self.columnCount() - 1))
def handle(self, *args, **kwargs): try: logger.debug(f"call {func.__name__}...") return func(self, *args, **kwargs) except ServerError as e: logger.debug(f"{func.__name__} exit with error: {str(e)}") # e' possibile che sia scaduto il login provo a rifarlo # e ritento la chiamata logger.debug(f"retry {func.__name__} with new login") env_settings = QSettings() user = env_settings.value("Credentials/user") password = env_settings.value("Credentials/password") api_impl = ApiImplementation() api_impl.login(user, password) return func(self, *args, **kwargs)
def read_settings(self): settings = QSettings() settings.beginGroup(SETTINGS_MAIN_SECTION) if settings.value(SETTINGS_USE_CUSTOM_JAVA, SETTINGS_USE_CUSTOM_JAVA_DEFAULT, bool): self.ui.customJavaRadio.setChecked(True) else: self.ui.defaultJavaRadio.setChecked(True) self.ui.customJavaPathEdit.setText(settings.value(SETTINGS_CUSTOM_JAVA_PATH, SETTINGS_CUSTOM_JAVA_PATH_DEFAULT)) if settings.value(SETTINGS_USE_CUSTOM_PLANTUML, SETTINGS_USE_CUSTOM_PLANTUML_DEFAULT, bool): self.ui.customPlantUmlRadio.setChecked(True) else: self.ui.defaultPlantUmlRadio.setChecked(True) self.ui.customPlantUmlEdit.setText(settings.value(SETTINGS_CUSTOM_PLANTUML_PATH, SETTINGS_CUSTOM_PLANTUML_PATH_DEFAULT)) if settings.value(SETTINGS_USE_CUSTOM_GRAPHVIZ, SETTINGS_USE_CUSTOM_GRAPHVIZ_DEFAULT, bool): self.ui.customGraphvizRadio.setChecked(True) else: self.ui.defaultGraphvizRadio.setChecked(True) self.ui.customGraphvizEdit.setText(settings.value(SETTINGS_CUSTOM_GRAPHVIZ_PATH, SETTINGS_CUSTOM_GRAPHVIZ_PATH_DEFAULT)) settings.endGroup()
class Settings(object): def __init__(self) -> None: self._settings = QSettings('Evelyn Reminder', 'Evelyn Desktop') @staticmethod def _key( section: str, option: str ) -> str: return f'{section}/{option}' def set( self, section: str, option: str, value: Any ) -> None: key = self._key(section, option) self._settings.setValue(key, value) def get( self, section: str, option: str, default: Any = None, type_: Optional[Type] = None ) -> Any: # make key for QSettings key = self._key(section, option) # check not present if not self._settings.contains(key): return default # get value value = self._settings.value(key) # parse special values if type_ is bool: return self._parse_bool(value) # check type if not isinstance(value, type_): return default # done return value @staticmethod def _parse_bool( value: str ) -> Optional[bool]: if value == 'true': return True if value == 'false': return False return None
def check_node_still_exists(path: str) -> Optional[str]: """Ritorna un id se un nodo è presente, None altrimenti""" env_settings = QSettings() path_folder = env_settings.value("sync_path") result = os.path.relpath(path, path_folder) node_name = result.split(os.sep) current_node = tree_builder.get_tree_from_node_id() trovato = False for name in node_name: trovato = False for node in current_node.get_children(): if node.get_name() == name: current_node = node trovato = True break if trovato: return current_node.get_payload().id return None
def setIcons(self) -> None: settings = QSettings() colored = str(settings.value('iconColors', 'True')) == 'True' self._icons: Dict[str, QIcon] = {} pixmap = QPixmap(str(getRuntimePath('resources/icons/dia.ico'))) painter = QPainter(pixmap) painter.setCompositionMode(QPainter.CompositionMode_SourceIn) painter.fillRect(pixmap.rect(), QColor('#427aa1') if colored else QColor('#333333')) painter.end() self._icons['mod'] = QIcon(pixmap) pixmap = QPixmap(str(getRuntimePath('resources/icons/puzzle.ico'))) painter = QPainter(pixmap) painter.setCompositionMode(QPainter.CompositionMode_SourceIn) painter.fillRect(pixmap.rect(), QColor('#aad576') if colored else QColor('#333333')) painter.end() self._icons['dlc'] = QIcon(pixmap) pixmap = QPixmap(str(getRuntimePath('resources/icons/folder.ico'))) painter = QPainter(pixmap) painter.setCompositionMode(QPainter.CompositionMode_SourceIn) painter.fillRect(pixmap.rect(), QColor('#E55934') if colored else QColor('#333333')) painter.end() self._icons['bin'] = QIcon(pixmap) pixmap = QPixmap(str(getRuntimePath('resources/icons/patch.ico'))) painter = QPainter(pixmap) painter.setCompositionMode(QPainter.CompositionMode_SourceIn) painter.fillRect(pixmap.rect(), QColor('#b08968') if colored else QColor('#333333')) painter.end() self._icons['pat'] = QIcon(pixmap) pixmap = QPixmap(str(getRuntimePath('resources/icons/question.ico'))) painter = QPainter(pixmap) painter.setCompositionMode(QPainter.CompositionMode_SourceIn) painter.fillRect(pixmap.rect(), QColor('#ffcf40') if colored else QColor('#333333')) painter.end() self._icons['udf'] = QIcon(pixmap)
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. """ try: from PySide6.QtWidgets import QDateTimeEdit from PySide6.QtCore import QSettings, Qt except: from PyQt5.QtWidgets import QDateTimeEdit from PyQt5.QtCore import QSettings, Qt # import persepolis_setting persepolis_setting = QSettings('persepolis_download_manager', 'persepolis') # check ui_direction RTL or LTR ui_direction = persepolis_setting.value('ui_direction') class MyQDateTimeEdit(QDateTimeEdit): def __init__(self, parent=None): super().__init__(parent) # change ui direction from rtl to ltr if ui_direction == 'rtl': self.setLayoutDirection(Qt.LeftToRight)
def readSettings(self): settings = QSettings("MateWriter") pos = settings.value("pos", QPoint(200, 200)) size = settings.value("size", QSize(400, 400)) self.resize(size) self.move(pos)
class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.setWindowIcon(QtGui.QIcon('icon.ico')) self.font_id = QFontDatabase.addApplicationFont( "Roboto/Roboto-Bold.ttf") if self.font_id == -1: QMessageBox.warning(self, "Внимание", "Пользовательский шрифт не был найден!") self.setupUi(self) self.curFile = '' self.setCurrentFile('') self.createStatusBar() self.textEdit.document().contentsChanged.connect( self.documentWasModified) self.setCurrentFile('') self.settings = QSettings('Matewriter', 'Matewriter') self.exit_action.triggered.connect(QApplication.quit) self.save_action.triggered.connect(self.save) self.open_action.triggered.connect(self.open) self.newfile_action.triggered.connect(self.newFile) self.saveas_action.triggered.connect(self.saveAs) self.open_action.setShortcut('Ctrl+O') self.newfile_action.setShortcut('Ctrl+N') self.save_action.setShortcut('Ctrl+S') # Конфиги окна windowScreenGeometry = self.settings.value("windowScreenGeometry") windowScreenState = self.settings.value("windowScreenState") if windowScreenGeometry: self.restoreGeometry(windowScreenGeometry) else: self.resize(600) if windowScreenState: self.restoreState(windowScreenState) def closeEvent(self, event): self.settings.setValue("windowScreenGeometry", self.saveGeometry()) self.settings.setValue("windowScreenState", self.saveState()) if self.maybeSave(): self.writeSettings() event.accept() else: event.ignore() def newFile(self): if self.maybeSave(): self.textEdit.clear() self.setCurrentFile('') def open(self): if self.maybeSave(): fileName, _ = QFileDialog.getOpenFileName(self) if fileName: self.loadFile(fileName) def save(self): if self.curFile: return self.saveFile(self.curFile) return self.saveAs() def saveAs(self): fileName, _ = QFileDialog.getSaveFileName(self) if fileName: return self.saveFile(fileName) return False def documentWasModified(self): self.setWindowModified(self.textEdit.document().isModified()) def createStatusBar(self): self.statusBar().showMessage("Ready") def readSettings(self): settings = QSettings("MateWriter") pos = settings.value("pos", QPoint(200, 200)) size = settings.value("size", QSize(400, 400)) self.resize(size) self.move(pos) def writeSettings(self): settings = QSettings("MateWriter") settings.setValue("pos", self.pos()) settings.setValue("size", self.size()) def maybeSave(self): if self.textEdit.document().isModified(): ret = QMessageBox.warning( self, "MateWriter", "The document has been modified.\nDo you want to save " "your changes?", QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel) if ret == QMessageBox.Save: return self.save() if ret == QMessageBox.Cancel: return False return True def loadFile(self, fileName): file = QFile(fileName) if not file.open(QFile.ReadOnly | QFile.Text): QMessageBox.warning( self, "MateWriter", "Cannot read file %s:\n%s." % (fileName, file.errorString())) return inf = QTextStream(file) QApplication.setOverrideCursor(Qt.WaitCursor) self.textEdit.setPlainText(inf.readAll()) QApplication.restoreOverrideCursor() self.setCurrentFile(fileName) self.statusBar().showMessage("File loaded", 2000) def saveFile(self, fileName): file = QFile(fileName) if not file.open(QFile.WriteOnly | QFile.Text): QMessageBox.warning( self, "MateWriter", "Cannot write file %s:\n%s." % (fileName, file.errorString())) return False outf = QTextStream(file) QApplication.setOverrideCursor(Qt.WaitCursor) outf << self.textEdit.toPlainText() QApplication.restoreOverrideCursor() self.setCurrentFile(fileName) self.statusBar().showMessage("File saved", 2000) return True def setCurrentFile(self, fileName): self.curFile = fileName self.textEdit.document().setModified(False) self.setWindowModified(False) if self.curFile: shownName = self.strippedName(self.curFile) else: shownName = 'untitled.txt' self.setWindowTitle(" %s[*] - MateWriter" % shownName) def strippedName(self, fullFileName): return QFileInfo(fullFileName).fileName()
class MainWindow(QMainWindow): def __init__(self): """ Constructor """ self.receive_attempts = 0 self.settings = QSettings("gui.ini", QSettings.IniFormat) self.blurrer = None super(MainWindow, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.restore() self.load_weights_options() self.ui.button_source.clicked.connect(self.button_source_clicked) self.ui.button_start.clicked.connect(self.button_start_clicked) self.ui.button_target.clicked.connect(self.button_target_clicked) self.ui.button_abort.clicked.connect(self.button_abort_clicked) self.ui.combo_box_weights.currentIndexChanged.connect( self.setup_blurrer) def load_weights_options(self): self.ui.combo_box_weights.clear() for net_path in glob("./weights/*.pt"): clean_name = os.path.splitext(os.path.basename(net_path))[0] self.ui.combo_box_weights.addItem(clean_name) self.setup_blurrer() def setup_blurrer(self): """ Create and connect a blurrer thread """ weights_name = self.ui.combo_box_weights.currentText() self.blurrer = VideoBlurrer(weights_name) self.blurrer.setMaximum.connect(self.setMaximumValue) self.blurrer.updateProgress.connect(self.setProgress) self.blurrer.finished.connect(self.blurrer_finished) self.blurrer.alert.connect(self.blurrer_alert) msg_box = QMessageBox() msg_box.setText(f"Successfully loaded {weights_name}.pt") msg_box.exec_() def blurrer_alert(self, message: str): """ Display blurrer messages in the GUI :param message: Message to be displayed """ msg_box = QMessageBox() msg_box.setText(message) msg_box.exec_() def button_abort_clicked(self): """ Callback for button_abort """ self.force_blurrer_quit() self.ui.progress.setValue(0) self.ui.button_start.setEnabled(True) self.ui.button_abort.setEnabled(False) self.setup_blurrer() def setProgress(self, value: int): """ Set progress bar's current progress :param value: progress to be set """ self.ui.progress.setValue(value) def setMaximumValue(self, value: int): """ Set progress bar's maximum value :param value: value to be set """ self.ui.progress.setMaximum(value) def button_start_clicked(self): """ Callback for button_start """ self.ui.button_abort.setEnabled(True) self.ui.button_start.setEnabled(False) # read inference size inference_size = int( self.ui.combo_box_scale.currentText()[:-1]) * 16 / 9 # ouch again # set up parameters parameters = { "input_path": self.ui.line_source.text(), "output_path": self.ui.line_target.text(), "blur_size": self.ui.spin_blur.value(), "blur_memory": self.ui.spin_memory.value(), "threshold": self.ui.double_spin_threshold.value(), "roi_multi": self.ui.double_spin_roimulti.value(), "inference_size": inference_size, "quality": self.ui.spin_quality.value() } if self.blurrer: self.blurrer.parameters = parameters self.blurrer.start() else: print("No blurrer object!") print("Blurrer started!") def button_source_clicked(self): """ Callback for button_source """ source_path, _ = QFileDialog.getOpenFileName( self, "Open Video", "", "Video Files (*.mkv *.avi *.mov *.mp4)") self.ui.line_source.setText(source_path) def button_target_clicked(self): """ Callback for button_target """ target_path, _ = QFileDialog.getSaveFileName(self, "Save Video", "", "Video Files (*.mkv)") self.ui.line_target.setText(target_path) def force_blurrer_quit(self): """ Force blurrer thread to quit """ if self.blurrer.isRunning(): self.blurrer.terminate() self.blurrer.wait() def restore(self): """ Restores relevent UI settings from ini file """ for name, obj in inspect.getmembers(self.ui): if isinstance(obj, QSpinBox): name = obj.objectName() value = self.settings.value(name) if value: obj.setValue(int(value)) if isinstance(obj, QDoubleSpinBox): name = obj.objectName() value = self.settings.value(name) if value: obj.setValue(float(value)) if isinstance(obj, QLineEdit): name = obj.objectName() value = self.settings.value(name) if value: obj.setText(value) if isinstance(obj, QRadioButton): name = obj.objectName() value = self.settings.value(name) if value and value == "true": # ouch... obj.setChecked(True) if isinstance(obj, QComboBox): name = obj.objectName() value = self.settings.value(name) if value: index = obj.findText(value) if index == -1: obj.insertItems(0, [value]) index = obj.findText(value) obj.setCurrentIndex(index) else: obj.setCurrentIndex(index) def blurrer_finished(self): """ Create a new blurrer, setup UI and notify the user """ msg_box = QMessageBox() if self.blurrer and self.blurrer.result["success"]: minutes = int(self.blurrer.result["elapsed_time"] // 60) seconds = round(self.blurrer.result["elapsed_time"] % 60) msg_box.setText( f"Video blurred successfully in {minutes} minutes and {seconds} seconds." ) else: msg_box.setText("Blurring resulted in errors.") msg_box.exec_() if not self.blurrer: self.setup_blurrer() self.ui.button_start.setEnabled(True) self.ui.button_abort.setEnabled(False) self.ui.progress.setValue(0) def save(self): """ Save all relevant UI parameters """ for name, obj in inspect.getmembers(self.ui): if isinstance(obj, QSpinBox): name = obj.objectName() value = obj.value() self.settings.setValue(name, value) if isinstance(obj, QDoubleSpinBox): name = obj.objectName() value = obj.value() self.settings.setValue(name, value) if isinstance(obj, QLineEdit): name = obj.objectName() value = obj.text() self.settings.setValue(name, value) if isinstance(obj, QRadioButton): name = obj.objectName() value = obj.isChecked() self.settings.setValue(name, value) if isinstance(obj, QComboBox): index = obj.currentIndex() # get current index from combobox value = obj.itemText(index) self.settings.setValue(name, value) def closeEvent(self, event): """ Overload closeEvent to shut down blurrer and save UI settings :param event: """ self.force_blurrer_quit() self.save() print("saved settings") QMainWindow.closeEvent(self, event)
def readSettings(self): settings = QSettings('Trolltech', 'MDI Example') pos = settings.value('pos', QPoint(200, 200)) size = settings.value('size', QSize(400, 400)) self.move(pos) self.resize(size)
class DecisionEngine(Thread, QObject): Sg_toggle_files_update = Signal(str, bool) def __init__(self, main_model: MainModel, notification_controller: NotificationController, running: bool = False): Thread.__init__(self) QObject.__init__(self) self.setName("Algoritmo V5") self.setDaemon(True) self.env_settings = QSettings() self.settings_model: SettingsModel = main_model.settings_model self.refresh: int = (lambda: self.settings_model.get_sync_time()) self.running = running self.notification_controller = notification_controller # set istanza di NetworkModel nei moduli per poter gestire i segnali di errore tree_builder.set_model(main_model.network_model) os_handler.set_network_model(main_model.network_model) os_handler.set_settings_model(main_model.settings_model) self.main_model = main_model self.compare_snap_client = CompareSnapClient() self.strategy: dict[Policy, Strategy] = { Policy.Client: ClientStrategy(), Policy.Manual: ManualStrategy() } self.logger = logging.getLogger("decision_engine") self.condition = Condition() def set_running(self, running: bool) -> None: self.running = running def run(self): # Override the run() function of Thread class while True: self.condition.acquire() if self.running: self.check() self.condition.wait(max(5, self.refresh())) else: self.condition.wait(5) self.condition.release() @Slot() def Sl_model_changed(self): self.condition.acquire() try: self.condition.notify() finally: self.condition.release() def check(self) -> None: self.logger.info("Avvio sincronizzazione") path = self.env_settings.value("sync_path") if not os.path.isdir(path): self.notification_controller.send_message( "La cartella scelta non è stata trovata", icon=QSystemTrayIcon.Critical) self.main_model.sync_model.set_state(False) return snap_tree = tree_builder.read_dump_client_filesystem(path) client_tree = tree_builder.get_tree_from_system(path) try: if snap_tree is not None: policy = Policy(settings.get_policy()) done_something = self.compare_snap_client.check( snap_tree, client_tree, self.strategy[policy]) if done_something: client_tree = tree_builder.get_tree_from_system(path) remote_tree = tree_builder.get_tree_from_node_id() self.compute_decision(client_tree, remote_tree, snap_tree is not None) tree_builder.dump_client_filesystem(path) self.logger.info("Eseguito snapshot dell'albero locale") self.notification_controller.send_best_message() except LoginError: self.notification_controller.send_message( "Credenziali errate. Eseguire logout e riprovare", icon=QSystemTrayIcon.Critical) self.main_model.sync_model.set_state(False) except (ServerError, APIException): self.notification_controller.send_message( "Errore di connessione al drive Zextras", icon=QSystemTrayIcon.Warning) self.main_model.sync_model.set_state(False) def compute_decision(self, client_tree: TreeNode, remote_tree: TreeNode, snapshot: bool) -> None: result = tree_comparator.compareFolders(client_tree, remote_tree) if len(result) == 0: self.logger.info("Nessuna azione da intraprendere") for r in result: action: Actions = r["action"] node: TreeNode = r["node"] name_node = node.get_name() if action == Actions.CLIENT_NEW_FOLDER: if snapshot: shutil.rmtree(node._payload.path) self.logger.info( f"Eliminata cartella non presente nel server: {name_node}" ) else: id_parent = r["id"] os_handler.upload_folder(node, id_parent) self.logger.info( f"Nuova cartella da caricare nel server: {name_node}") elif action == Actions.CLIENT_NEW_FILE: if snapshot: os.remove(node._payload.path) self.logger.info( f"Eliminato file non presente nel server: {name_node}") else: self.Sg_toggle_files_update.emit(node.get_path(), True) id_parent = r["id"] os_handler.upload_file(node, id_parent) self.logger.info( f"Nuovo file da caricare nel server: {name_node}") self.Sg_toggle_files_update.emit(node.get_path(), False) elif action == Actions.SERVER_NEW_FOLDER: path = r["path"] node_message = os_handler.download_folder(node, path) for item in node_message: item["action"] = Actions.SERVER_NEW_FILE self.notification_controller.add_notification(item) if len(node_message) > 0: self.logger.info(action.name + " " + name_node) elif action == Actions.SERVER_NEW_FILE or action == Actions.SERVER_UPDATE_FILE: folder_path = r["path"] file_path = os.path.join(folder_path, name_node) if action == Actions.SERVER_UPDATE_FILE: self.Sg_toggle_files_update.emit(file_path, True) node_message = os_handler.download_file(node, folder_path) if node_message is not None: node_message["action"] = action self.notification_controller.add_notification(node_message) self.logger.info(action.name + " " + name_node) if action == Actions.SERVER_UPDATE_FILE: self.Sg_toggle_files_update.emit(file_path, False)
class SettingsModel(QObject): Sg_model_changed = Signal() Sg_model_path_changed = Signal() __model = None __create_key = object() @classmethod def get_instance(cls): if SettingsModel.__model is None: SettingsModel.__model = SettingsModel(cls.__create_key) return SettingsModel.__model def __init__(self, create_key): assert (create_key == SettingsModel.__create_key), \ "SettingsModel objects must be created using SettingsModel.get_instance()" super(SettingsModel, self).__init__(None) self.env_settings = QSettings() def get_policy(self) -> Policy: return Policy(settings.get_policy()) def set_policy(self, new_policy: Policy) -> None: settings.update_policy(new_policy.value) self.Sg_model_changed.emit() def get_path(self) -> Optional[str]: return self.env_settings.value("sync_path") def set_path(self, new_path: str) -> None: self.env_settings.setValue("sync_path", new_path) self.env_settings.sync() self.Sg_model_changed.emit() self.Sg_model_path_changed.emit() def set_sync_time(self, new_sync_time: int) -> None: settings.update_sync_time(new_sync_time) self.Sg_model_changed.emit() def get_sync_time(self) -> int: return settings.get_sync_time() def get_quota_disco_raw(self) -> float: """Ritorna il valore grezzo""" return settings.get_quota_disco() def get_quota_disco(self) -> str: """Ritorna il valore con la sua unità adatta""" return self.convert_size(settings.get_quota_disco()) def set_quota_disco(self, new_quota: bitmath.Byte) -> None: # TODO: Il controllo non dovremmo farlo nel controller? folder_size = bitmath.parse_string(self.convert_size(self.get_size())) free_disk = bitmath.parse_string( self.convert_size(self.get_free_disk())) # Controllo che la nuova quota sia minore dello spazio disponibile nell'hdd # e maggiore dello spazio utilizzato dalla cartella corrente if folder_size <= new_quota <= free_disk: settings.update_quota_disco(str(new_quota.value)) self.Sg_model_changed.emit() def get_free_disk(self) -> int: mem = psutil.disk_usage('/') return mem.free @staticmethod def convert_size(size_bytes: int) -> str: if size_bytes == 0: return "0 B" size_name = ("Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB") i = int(math.floor(math.log(size_bytes, 1024))) p = math.pow(1024, i) s = round(size_bytes / p, 2) return "%s %s" % (s, size_name[i]) def get_quota_libera(self) -> float: return self.get_quota_disco_raw() - self.get_size() def get_size(self) -> int: total_size = 0 if not self.get_path(): return 0 for dirpath, dirnames, filenames in os.walk(self.get_path()): for f in filenames: fp = os.path.join(dirpath, f) # skip if it is symbolic link if not os.path.islink(fp): total_size += os.path.getsize(fp) return total_size def get_sync_list(self) -> list: return settings.get_sync_list() def is_id_in_sync_list(self, id: str) -> bool: return id in settings.get_sync_list() def add_id_to_sync_list(self, id: str) -> None: """Aggiungi id a whitelist""" id_list = settings.get_sync_list() if not self.is_id_in_sync_list(id): id_list.append(id) settings.update_sync_list(id_list) self.Sg_model_changed.emit() def remove_id_from_sync_list(self, id: str) -> None: """Rimuovi id da whitelist""" id_list = settings.get_sync_list() if self.is_id_in_sync_list(id): id_list.remove(id) settings.update_sync_list(id_list) self.Sg_model_changed.emit()
class Window(QMainWindow): def __init__(self): QMainWindow.__init__(self) self.images = [] self.index = -1 self.ratio = 1 # ratio for QLabel image self.mouse_position = None self.settings = None # Extensions self.extensions = [] for format in QImageReader.supportedImageFormats(): self.extensions.append(format.data().decode('utf-8')) # Filters self.filters = [] for extension in self.extensions: self.filters.append('*.{0}'.format(str(extension))) # UI self.set_up_ui() # settings self.load_settings() def on_message_received(self, msg): """ on message received from single application Args: msg (string): file path """ self.create_images(msg) self.display_image() def set_up_ui(self): # Status Bar self.status_bar = self.statusBar() self.label_name = QLabel() self.label_numero = QLabel() self.status_bar.addPermanentWidget(self.label_name, 1) self.status_bar.addPermanentWidget(self.label_numero, 0) # Main Window self.setWindowTitle('BaloViewer') self.setWindowIcon(QIcon('baloviewer.ico')) # Label image self.image = QLabel() self.image.setScaledContents(True) # Scroll area self.scroll_area = QScrollArea() self.scroll_area.setWidget(self.image) self.scroll_area.showMaximized() self.scroll_area.setFocusPolicy(Qt.FocusPolicy.NoFocus) self.scroll_area.setAlignment(Qt.AlignmentFlag.AlignCenter) self.scroll_area.viewport().installEventFilter(self) # image list self.image_gallery = ImageGallery() self.image_gallery.itemClicked.connect(self.image_gallery_clicked) self.image_gallery.viewport().installEventFilter(self) self.dock_widget = QDockWidget('Image Gallery', self) self.dock_widget.setWidget(self.image_gallery) self.dock_widget.setFloating(False) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock_widget) # central widget self.setCentralWidget(self.scroll_area) # Action bar self.create_actions() self.create_menubar() self.create_toolbar() # option parser parser = OptionParser() parser.add_option("-f", "--file", dest="filename", help="open a file") (options, args) = parser.parse_args() parser_file = options.filename if parser_file is not None and os.path.isfile(parser_file): self.create_images(parser_file) self.display_image() def create_actions(self): # Action Open self.action_open = QAction(QIcon.fromTheme('document-open'), 'Open', self) self.action_open.setShortcut('Ctrl+O') self.action_open.setStatusTip('Open file') self.action_open.triggered.connect(self.open) # Action Save self.action_save = QAction(QIcon.fromTheme('document-save'), 'Save', self) self.action_save.setShortcut('Ctrl+S') self.action_save.setStatusTip('Save file') self.action_save.triggered.connect(self.save) # Action Copy self.action_copy = QAction(QIcon.fromTheme('edit-copy'), 'Copy', self) self.action_copy.setStatusTip('Copy') self.action_copy.triggered.connect(self.copy) # Action move self.action_move = QAction(QIcon.fromTheme('edit-cut'), 'Move', self) self.action_move.setStatusTip('Move') self.action_move.triggered.connect(self.move) # Action Delete self.action_delete = QAction(QIcon.fromTheme('edit-delete'), 'Delete', self) self.action_delete.setStatusTip('Delete') self.action_delete.triggered.connect(self.delete) # Action Quit self.action_quit = QAction(QIcon.fromTheme('application-exit'), 'Quit', self) self.action_quit.setShortcut('Ctrl+Q') self.action_quit.setStatusTip('Quit') self.action_quit.triggered.connect(self.close) # Action Rotate left self.action_rotate_left = QAction( QIcon.fromTheme('object-rotate-left'), 'Rotate left', self) self.action_rotate_left.setStatusTip('Rotate left') self.action_rotate_left.triggered.connect(self.rotate_left) # Action Rotate right self.action_rotate_right = QAction( QIcon.fromTheme('object-rotate-right'), 'Rotate right', self) self.action_rotate_right.setStatusTip('Rotate right') self.action_rotate_right.triggered.connect(self.rotate_right) # Action Mirror self.action_flip_horizontal = QAction( QIcon.fromTheme('object-flip-horizontal'), 'Flip horizontally', self) self.action_flip_horizontal.setStatusTip('Flip horizontally') self.action_flip_horizontal.triggered.connect(self.flip_horizontal) # Action Flip vertical self.action_flip_vertical = QAction( QIcon.fromTheme('object-flip-vertical'), 'Flip vertically', self) self.action_flip_vertical.setStatusTip('Flip vertically') self.action_flip_vertical.triggered.connect(self.flip_vertical) # Action Previous image self.action_previous_image = QAction(QIcon.fromTheme('go-previous'), 'Previous image', self) self.action_previous_image.setStatusTip('Previous image') self.action_previous_image.triggered.connect(self.previous_image) # Action Full screen self.action_fullscreen = QAction(QIcon.fromTheme('view-fullscreen'), 'Full screen', self) self.action_fullscreen.setStatusTip('Full screen') self.action_fullscreen.triggered.connect(self.fullscreen) # Action Normal size self.action_normal_size = QAction(QIcon.fromTheme('zoom-original'), 'Normal size', self) self.action_normal_size.setStatusTip('Normal Size') self.action_normal_size.triggered.connect(self.normal_size) # Action Fit Screen self.action_fit_screen = QAction(QIcon.fromTheme('zoom-fit-best'), 'Fit to screen', self) self.action_fit_screen.setStatusTip('Fit to screen') self.action_fit_screen.setCheckable(True) self.action_fit_screen.triggered.connect(self.fit_screen) # Action Zoom in self.action_zoom_in = QAction(QIcon.fromTheme('zoom-in'), 'Zoom in', self) self.action_zoom_in.setStatusTip('Zoom in') self.action_zoom_in.triggered.connect(self.zoom_in) # Action Zoom out self.action_zoom_out = QAction(QIcon.fromTheme('zoom-out'), 'Zoom out', self) self.action_zoom_out.setStatusTip('Zoom out') self.action_zoom_out.triggered.connect(self.zoom_out) # Action Fit height self.action_fit_vertical = QAction('Fit vertically', self) self.action_fit_vertical.setStatusTip('Fit vertically') self.action_fit_vertical.setCheckable(True) self.action_fit_vertical.triggered.connect(self.fit_height) # Action Fit width self.action_fit_horizontal = QAction('Fit horizontally', self) self.action_fit_horizontal.setStatusTip('Fit horizontally') self.action_fit_horizontal.setCheckable(True) self.action_fit_horizontal.triggered.connect(self.fit_width) # Action Fit width self.action_fit_horizontal = QAction('Fit horizontally', self) self.action_fit_horizontal.setStatusTip('Fit horizontally') self.action_fit_horizontal.setCheckable(True) self.action_fit_horizontal.triggered.connect(self.fit_width) # Action Image list self.action_image_gallery = QAction('Image gallery', self) self.action_image_gallery.setStatusTip('Image gallery') self.action_image_gallery.setCheckable(True) self.action_image_gallery.triggered.connect( self.image_gallery_triggered) # Action Next_image self.action_next_image = QAction(QIcon.fromTheme('go-next'), 'Next image', self) self.action_next_image.setStatusTip('Next image') self.action_next_image.triggered.connect(self.next_image) # Action First image self.action_first_image = QAction(QIcon.fromTheme('go-first'), 'First image', self) self.action_first_image.setStatusTip('First image') self.action_first_image.triggered.connect(self.first_image) # Action Last image self.action_last_image = QAction(QIcon.fromTheme('go-last'), 'Last image', self) self.action_last_image.setStatusTip('Last image') self.action_last_image.triggered.connect(self.last_image) # Action About self.action_about = QAction(QIcon.fromTheme('help-about'), 'About', self) self.action_about.setStatusTip('About') self.action_about.triggered.connect(self.about) def create_menubar(self): self.menubar = self.menuBar() # File self.menu_file = self.menubar.addMenu('File') self.menu_file.addAction(self.action_open) self.menu_file.addAction(self.action_save) self.menu_file.addSeparator() self.menu_file.addAction(self.action_copy) self.menu_file.addAction(self.action_move) self.menu_file.addAction(self.action_delete) self.menu_file.addSeparator() self.menu_file.addAction(self.action_quit) # Edit self.menu_edit = self.menubar.addMenu('Edit') self.menu_edit.addAction(self.action_rotate_left) self.menu_edit.addAction(self.action_rotate_right) self.menu_edit.addSeparator() self.menu_edit.addAction(self.action_flip_horizontal) self.menu_edit.addAction(self.action_flip_vertical) # View self.menu_view = self.menubar.addMenu('View') self.menu_view.addAction(self.action_fullscreen) self.menu_view.addAction(self.action_normal_size) self.menu_view.addAction(self.action_fit_screen) self.menu_view.addSeparator() self.menu_view.addAction(self.action_zoom_in) self.menu_view.addAction(self.action_zoom_out) self.menu_view.addSeparator() self.menu_view.addAction(self.action_fit_vertical) self.menu_view.addAction(self.action_fit_horizontal) self.menu_view.addSeparator() self.menu_view.addAction(self.action_image_gallery) # Go self.menu_go = self.menubar.addMenu('Go') self.menu_go.addAction(self.action_previous_image) self.menu_go.addAction(self.action_next_image) self.menu_go.addSeparator() self.menu_go.addAction(self.action_first_image) self.menu_go.addAction(self.action_last_image) # About self.menu_about = self.menubar.addMenu('About') self.menu_about.addAction(self.action_about) def create_toolbar(self): self.toolbar = self.addToolBar('Tool bar') self.toolbar.addAction(self.action_open) self.toolbar.addAction(self.action_save) self.toolbar.addSeparator() self.toolbar.addAction(self.action_fullscreen) self.toolbar.addAction(self.action_normal_size) self.toolbar.addAction(self.action_fit_screen) self.toolbar.addSeparator() self.toolbar.addAction(self.action_zoom_in) self.toolbar.addAction(self.action_zoom_out) self.toolbar.addSeparator() self.toolbar.addAction(self.action_rotate_left) self.toolbar.addAction(self.action_rotate_right) self.toolbar.addSeparator() self.toolbar.addAction(self.action_first_image) self.toolbar.addAction(self.action_previous_image) self.toolbar.addAction(self.action_next_image) self.toolbar.addAction(self.action_last_image) self.toolbar.addSeparator() self.toolbar.addAction(self.action_copy) self.toolbar.addAction(self.action_move) def load_settings(self): self.settings = QSettings() check_state = self.settings.value('view/image_gallery', True, type=bool) self.action_image_gallery.setChecked(check_state) self.image_gallery_triggered() def contextMenuEvent(self, QContextMenuEvent): menu = QMenu() menu.addAction(self.action_fullscreen) menu.addSeparator() menu.addAction(self.action_image_gallery) menu.addSeparator() menu.addAction(self.action_previous_image) menu.addAction(self.action_next_image) menu.addSeparator() menu.addAction(self.action_normal_size) menu.addAction(self.action_fit_screen) menu.addAction(self.action_fit_vertical) menu.addAction(self.action_fit_horizontal) menu.addSeparator() menu.addAction(self.action_zoom_in) menu.addAction(self.action_zoom_out) menu.addSeparator() menu.addAction(self.action_copy) menu.addAction(self.action_move) menu.addSeparator() menu.addAction(self.action_delete) menu.exec_(QContextMenuEvent.globalPos()) def eventFilter(self, obj, event): """ filter events for wheel events Args: obj (QWidget): scroll_area event (QEvent): event """ # try: if event.type() == QEvent.Wheel: if event.angleDelta().y() < 0: self.next_image() else: self.previous_image() return True elif event.type() == QEvent.MouseButtonPress and event.button( ) == Qt.RightButton: index = self.image_gallery.select_row_pos() if index > -1: self.index = index self.display_image() return True # pass the event on to the parent class return super(QMainWindow, self).eventFilter(obj, event) def keyPressEvent(self, event): key = event.key() if key == Qt.Key_Delete: self.delete() elif key == Qt.Key_Left: self.previous_image() elif key == Qt.Key_Right: self.next_image() elif key == Qt.Key_PageUp: self.first_image() elif key == Qt.Key_PageDown: self.last_image() elif key == Qt.Key_Escape and self.isFullScreen(): self.fullscreen() else: QWidget.keyPressEvent(self, event) def mouseDoubleClickEvent(self, QMouseEvent): self.fullscreen() def mousePressEvent(self, QMouseEvent): self.mouse_position = QMouseEvent.pos() def mouseMoveEvent(self, QMouseEvent): diff = QPoint(QMouseEvent.pos() - self.mouse_position) self.mouse_position = QMouseEvent.pos() self.scroll_area.verticalScrollBar().setValue( self.scroll_area.verticalScrollBar().value() - diff.y()) self.scroll_area.horizontalScrollBar().setValue( self.scroll_area.horizontalScrollBar().value() - diff.x()) def resizeEvent(self, event): if not self.index == -1: self.display_image() def create_images(self, filename): """Create image list Args: filename (string): file from which to retrieve the list of images in the folder """ self.images.clear() # get images only with an allowed extension for ext in self.extensions: self.images += glob.glob( os.path.join( glob.escape(os.path.dirname(filename)), '*.' + ''.join('[%s%s]' % (e.lower(), e.upper()) for e in ext))) self.images.sort() if filename in self.images: self.index = self.images.index(filename) else: self.index = -1 # iamge list self.image_gallery.add_images(self.images) def remove_index(self): """ remove file from list images and display next or previous image """ del self.images[self.index] self.image_gallery.remove_row(self.index) if len(self.images) == 0: self.images.clear() self.index = -1 self.image.clear() self.image.resize(self.image.minimumSizeHint()) elif self.index < len(self.images) - 1: self.display_image() else: self.index = len(self.images) - 1 self.display_image() def display_image(self): if not self.index == -1: self.image.clear() self.image.resize(self.image.minimumSizeHint()) file = self.images[self.index] if os.path.isfile(file): self.label_name.setText(file) self.label_numero.setText( str(self.index + 1) + ' / ' + str(len(self.images))) # image list self.image_gallery.select_row(self.index) image_reader = QImageReader(file) if image_reader.imageCount() > 1: # Animated image movie = QMovie(file) movie.setCacheMode(QMovie.CacheAll) movie.jumpToFrame(0) movie_size = movie.currentPixmap().size() self.image.setMovie(movie) self.image.resize(movie_size) movie.start() else: self.image.setPixmap(QPixmap(file)) self.image.resize(self.image.pixmap().size()) # fit image if self.action_fit_screen.isChecked(): self.fit_screen() elif self.action_fit_horizontal.isChecked(): self.fit_width() elif self.action_fit_vertical.isChecked(): self.fit_height() else: self.ratio = 1.0 self.action_zoom_in.setEnabled(True) self.action_zoom_out.setEnabled(True) # scrollbar position self.scroll_area.verticalScrollBar().setSliderPosition(0) self.scroll_area.horizontalScrollBar().setSliderPosition(0) def resize_image(self): if self.action_fit_screen.isChecked(): self.fit_screen() elif self.action_fit_horizontal.isChecked(): self.fit_width() elif self.action_fit_vertical.isChecked(): self.fit_height() elif self.image.pixmap(): self.image.resize(self.ratio * self.image.pixmap().size()) elif movie := self.image.movie(): movie.jumpToFrame(0) movie_size = movie.currentPixmap().size() self.image.resize(self.ratio * movie_size)
class FileView(QWidget): Sg_update_files_with_new_path = Signal(str) def __init__(self, model: FileModel, parent=None): super(FileView, self).__init__(parent) self.env_settings = QSettings() self._model = model self.title = QLabel("File locali", self) self.title.setAlignment(Qt.AlignLeft) self.title.setAccessibleName("Title") # scroll area self.scrollArea = QScrollArea() self.scrollArea.setAccessibleName("FileScroll") self.scrollArea.setWidgetResizable(True) self.scrollArea.horizontalScrollBar().setEnabled(False) # contenitore per file self.fileWindow = QWidget(self) self.fileLayout = FlowLayout() self.fileLayout.setContentsMargins(0, 0, 0, 0) self.show_path_button = QPushButton("Apri file manager", self) self.force_sync_button = QPushButton("Sincronizza ora", self) self.fileWindow.setParent(self.scrollArea) self.fileWindow.setLayout(self.fileLayout) self.scrollArea.setWidget(self.fileWindow) header_layout = QHBoxLayout() header_layout.addWidget(self.title) header_layout.addWidget(self.force_sync_button) header_layout.addWidget(self.show_path_button) layout = QVBoxLayout() layout.addLayout(header_layout) layout.addWidget(self.scrollArea) self.setLayout(layout) self.Sl_model_changed() @Slot() def Sl_show_path_button_clicked(self) -> None: path = QUrl.fromUserInput(self.env_settings.value("sync_path")) QDesktopServices.openUrl(path) @Slot() def Sl_model_changed(self) -> None: list_of_files, list_of_dirs = self._model.get_data() for i in reversed(range(self.fileLayout.count())): self.fileLayout.itemAt(i).widget().setParent(None) for i in list_of_dirs: self.fileLayout.addWidget(LocalDirectoryWidget(i, self)) for i in list_of_files: self.fileLayout.addWidget(LocalFileWidget(i)) @Slot(str) def Sl_update_files_with_new_path(self, path: str) -> None: self.Sg_update_files_with_new_path.emit(path) def toggle_files_update(self, file_path: str, is_sync: bool) -> None: for widget in self.fileLayout._item_list: if type(widget.wid) == LocalFileWidget: if os.path.samefile(widget.wid.path, file_path): widget.wid.show_synced(is_sync)
def __init__(self, parent: QWidget, model: Model) -> None: super().__init__(parent) settings = QSettings() self.hoverIndexRow = -1 self.modmodel = model self.installLock = asyncio.Lock() self.setMouseTracking(True) self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSelectionBehavior(QAbstractItemView.SelectRows) self.setWordWrap(False) self.setSortingEnabled(True) self.setFocusPolicy(Qt.StrongFocus) self.setAcceptDrops(True) self.setEditTriggers(QTableView.EditKeyPressed | QTableView.DoubleClicked) self.setShowGrid(False) self.setStyleSheet(''' QTableView { gridline-color: rgba(255,255,255,1); } QTableView::item:!selected:hover { background-color: rgb(217, 235, 249); } ''') self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) self.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel) self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.showContextMenu) self.verticalHeader().hide() self.verticalHeader().setVisible(False) self.setSectionSize(settings.value('compactMode', 'False') == 'True') self.setCornerButtonEnabled(False) self.horizontalHeader().setHighlightSections(False) self.horizontalHeader().setStretchLastSection(True) self.horizontalHeader().setSectionsMovable(True) self.listmodel = ModListModel(self, model) self.filtermodel = ModListFilterModel(self, self.listmodel) self.setModel(self.filtermodel) self.setItemDelegate(ModListItemDelegate(self)) self.setSelectionModel(ModListSelectionModel(self, self.filtermodel)) if len(model): self.modCountLastUpdate = len(model) self.resizeColumnsToContents() else: self.modCountLastUpdate = -1 if settings.value('modlistHorizontalHeaderState'): self.horizontalHeader().restoreState( settings.value('modlistHorizontalHeaderState')) # type: ignore self.horizontalHeader().sectionMoved.connect( lambda: self.headerChangedEvent()) self.horizontalHeader().sectionResized.connect( lambda: self.headerChangedEvent()) self.setFocus() self.sortByColumn(3, Qt.AscendingOrder, False) self.sortByColumn(2, Qt.AscendingOrder, False) self.sortByColumn(1, Qt.AscendingOrder, False) if settings.value('modlistSortColumn') is not None and \ settings.value('modlistSortOrder') is not None: try: self.sortByColumn( cast(int, settings.value('modlistSortColumn', 1, int)), Qt.DescendingOrder if cast(int, settings.value('modlistSortOrder', 1, int)) else Qt.AscendingOrder, False) except Exception as e: logger.exception(f'could not restore sort order: {e}') self.horizontalHeader().sortIndicatorChanged.connect(self.sortByColumn) self.doubleClicked.connect(self.doubleClickEvent) model.updateCallbacks.append(self.modelUpdateEvent) # setup viewport caching to counter slow resizing with many table elements self.resizeTimer = QTimer(self) self.resizeTimer.setSingleShot(True) self.resizeTimer.setInterval(250) self.resizeTimer.timeout.connect(lambda: [ self.resizeTimer.stop(), self.viewport().repaint(), ]) self.viewportCache = QPixmap() self.viewportCacheSize = QSize(0, 0)
def __init__(self, parent: Optional[QWidget] = None, firstStart: bool = False) -> None: super().__init__(parent, ) if parent: self.setWindowTitle('Settings') else: self.setWindowTitle(getTitleString('Settings')) self.setAttribute(Qt.WA_DeleteOnClose) settings = QSettings() mainLayout = QVBoxLayout(self) mainLayout.setContentsMargins(5, 5, 5, 5) # First Start info if firstStart: firstStartInfo = QLabel( ''' <p><strong>Hello! It looks like this is your first time using w3modmanager, or the game installation path recently changed.</strong></p> <p> Please review the settings below. </p> ''', self) firstStartInfo.setWordWrap(True) firstStartInfo.setContentsMargins(10, 10, 10, 10) firstStartInfo.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) mainLayout.addWidget(firstStartInfo) # Game gbGame = QGroupBox('Game Path', self) mainLayout.addWidget(gbGame) gbGameLayout = QVBoxLayout(gbGame) gamePathLayout = QHBoxLayout() self.gamePath = QLineEdit(self) self.gamePath.setPlaceholderText('Path to witcher3.exe...') if settings.value('gamePath'): self.gamePath.setText(str(settings.value('gamePath'))) self.gamePath.textChanged.connect( lambda: self.validateGamePath(self.gamePath.text())) gamePathLayout.addWidget(self.gamePath) self.locateGame = QPushButton('Detect', self) self.locateGame.clicked.connect(self.locateGameEvent) self.locateGame.setToolTip( 'Automatically detect the game path if possible') gamePathLayout.addWidget(self.locateGame) selectGame = QPushButton('Browse', self) selectGame.clicked.connect(self.selectGameEvent) gamePathLayout.addWidget(selectGame) gbGameLayout.addLayout(gamePathLayout) gamePathInfoLayout = QHBoxLayout() self.gamePathInfo = QLabel('', self) self.gamePathInfo.setContentsMargins(4, 4, 4, 4) self.gamePathInfo.setMinimumHeight(40) self.gamePathInfo.setWordWrap(True) gamePathInfoLayout.addWidget(self.gamePathInfo) gbGameLayout.addLayout(gamePathInfoLayout) # Config gbConfig = QGroupBox('Game Config', self) mainLayout.addWidget(gbConfig) gbConfigLayout = QVBoxLayout(gbConfig) configPathLayout = QHBoxLayout() self.configPath = QLineEdit(self) self.configPath.setPlaceholderText('Path to config folder...') if settings.value('configPath'): self.configPath.setText(str(settings.value('configPath'))) self.configPath.textChanged.connect( lambda: self.validateConfigPath(self.configPath.text())) configPathLayout.addWidget(self.configPath) self.locateConfig = QPushButton('Detect', self) self.locateConfig.clicked.connect(self.locateConfigEvent) self.locateConfig.setToolTip( 'Automatically detect the config folder if possible') configPathLayout.addWidget(self.locateConfig) selectConfig = QPushButton('Browse', self) selectConfig.clicked.connect(self.selectConfigEvent) configPathLayout.addWidget(selectConfig) gbConfigLayout.addLayout(configPathLayout) configPathInfoLayout = QHBoxLayout() self.configPathInfo = QLabel('', self) self.configPathInfo.setContentsMargins(4, 4, 4, 4) self.configPathInfo.setMinimumHeight(40) self.configPathInfo.setWordWrap(True) configPathInfoLayout.addWidget(self.configPathInfo) gbConfigLayout.addLayout(configPathInfoLayout) # Script Merger gbScriptMerger = QGroupBox('Script Merger', self) mainLayout.addWidget(gbScriptMerger) gbScriptMergerLayout = QVBoxLayout(gbScriptMerger) scriptMergerPathLayout = QHBoxLayout() self.scriptMergerPath = QLineEdit(self) self.scriptMergerPath.setPlaceholderText( 'Path to WitcherScriptMerger.exe...') if settings.value('scriptMergerPath'): self.scriptMergerPath.setText( str(settings.value('scriptMergerPath'))) self.scriptMergerPath.textChanged.connect( lambda: self.validateScriptMergerPath(self.scriptMergerPath.text() )) scriptMergerPathLayout.addWidget(self.scriptMergerPath) self.locateScriptMerger = QPushButton('Detect', self) self.locateScriptMerger.clicked.connect(self.locateScriptMergerEvent) self.locateScriptMerger.setToolTip( 'Automatically detect the script merger path if possible') scriptMergerPathLayout.addWidget(self.locateScriptMerger) selectScriptMerger = QPushButton('Browse', self) selectScriptMerger.clicked.connect(self.selectScriptMergerEvent) scriptMergerPathLayout.addWidget(selectScriptMerger) gbScriptMergerLayout.addLayout(scriptMergerPathLayout) scriptMergerPathInfoLayout = QHBoxLayout() self.scriptMergerPathInfo = QLabel('', self) self.scriptMergerPathInfo.setOpenExternalLinks(True) self.scriptMergerPathInfo.setContentsMargins(4, 4, 4, 4) self.scriptMergerPathInfo.setMinimumHeight(40) self.scriptMergerPathInfo.setWordWrap(True) scriptMergerPathInfoLayout.addWidget(self.scriptMergerPathInfo) gbScriptMergerLayout.addLayout(scriptMergerPathInfoLayout) # Nexus Mods API gbNexusModsAPI = QGroupBox('Nexus Mods API', self) mainLayout.addWidget(gbNexusModsAPI) gbNexusModsAPILayout = QVBoxLayout(gbNexusModsAPI) self.nexusAPIKey = QLineEdit(self) self.nexusAPIKey.setPlaceholderText('Personal API Key...') if settings.value('nexusAPIKey'): self.nexusAPIKey.setText(str(settings.value('nexusAPIKey'))) self.nexusAPIKey.textChanged.connect( lambda: self.validateApiKey(self.nexusAPIKey.text())) gbNexusModsAPILayout.addWidget(self.nexusAPIKey) self.nexusAPIKeyInfo = QLabel('🌐', self) self.nexusAPIKeyInfo.setOpenExternalLinks(True) self.nexusAPIKeyInfo.setWordWrap(True) self.nexusAPIKeyInfo.setContentsMargins(4, 4, 4, 4) self.nexusAPIKeyInfo.setMinimumHeight(48) gbNexusModsAPILayout.addWidget(self.nexusAPIKeyInfo) self.nexusGetInfo = QCheckBox('Get Mod details after adding a new mod', self) self.nexusGetInfo.setChecked( settings.value('nexusGetInfo', 'True') == 'True') self.nexusGetInfo.setDisabled(True) gbNexusModsAPILayout.addWidget(self.nexusGetInfo) self.nexusCheckUpdates = QCheckBox('Check for Mod updates on startup', self) self.nexusCheckUpdates.setChecked( settings.value('nexusCheckUpdates', 'False') == 'True') self.nexusCheckUpdates.setDisabled(True) gbNexusModsAPILayout.addWidget(self.nexusCheckUpdates) self.nexusCheckClipboard = QCheckBox( 'Monitor the Clipboard for Nexus Mods URLs', self) self.nexusCheckClipboard.setChecked( settings.value('nexusCheckClipboard', 'False') == 'True') self.nexusCheckClipboard.setDisabled(True) gbNexusModsAPILayout.addWidget(self.nexusCheckClipboard) # Output gbOutput = QGroupBox('Output Preferences', self) mainLayout.addWidget(gbOutput) gbOutputLayout = QVBoxLayout(gbOutput) self.unhideOutput = QCheckBox('Auto-show output panel', self) self.unhideOutput.setChecked( settings.value('unhideOutput', 'True') == 'True') gbOutputLayout.addWidget(self.unhideOutput) self.debugOutput = QCheckBox('Show debug output', self) self.debugOutput.setChecked( settings.value('debugOutput', 'False') == 'True') gbOutputLayout.addWidget(self.debugOutput) # Actions actionsLayout = QHBoxLayout() actionsLayout.setAlignment(Qt.AlignRight) self.save = QPushButton('Save', self) self.save.clicked.connect(self.saveEvent) self.save.setAutoDefault(True) self.save.setDefault(True) actionsLayout.addWidget(self.save) cancel = QPushButton('Cancel', self) cancel.clicked.connect(self.cancelEvent) actionsLayout.addWidget(cancel) mainLayout.addLayout(actionsLayout) # Setup if not settings.value('gamePath'): self.locateGameEvent() self.setMinimumSize(QSize(440, 440)) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) self.validGamePath = False self.validConfigPath = False self.validNexusAPIKey = False self.validScriptMergerPath = False self.validateGamePath(self.gamePath.text()) self.validateConfigPath(self.configPath.text()) self.validateApiKey(self.nexusAPIKey.text()) self.validateScriptMergerPath(self.scriptMergerPath.text()) self.updateSaveButton() self.finished.connect( lambda: self.validateApiKey.cancel()) # type: ignore
def test_mij(): settings = QSettings() print(settings) # settings.beginGroup("Java Runtime Environment") print(settings.value("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\CurrentVersion"))
def loadSettings(self): settings = QSettings() self.delay = float(settings.value("delay", 0.5)) self.doubleSpinBoxDelay.setValue(self.delay)
def _init_models_path(self): settings = QSettings() self._models_path = settings.value(self._models_path_key) while self._models_path is None or self._models_path == '': self._select_models_path() self._models_path = Path(str(self._models_path))
def __init__(self, parent: QWidget, model: Model) -> None: super().__init__(parent) logger.add(self.log) settings = QSettings() self.mainlayout = QVBoxLayout() self.mainlayout.setContentsMargins(5, 5, 5, 5) self.setLayout(self.mainlayout) # summary summarylayout = FlowLayout() summarylayout.setContentsMargins(0, 0, 0, 0) self.summary = QWidget() self.summary.setLayout(summarylayout) self.mainlayout.addWidget(self.summary) self.summary.setVisible( settings.value('showSummary', 'True') == 'True') detailslayout = QHBoxLayout() detailslayout.setContentsMargins(1, 0, 0, 0) detailslayout.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) detailslayout.setSpacing(15) details = QWidget() details.setLayout(detailslayout) summarylayout.addWidget(details) self.modstotal = QLabel() detailslayout.addWidget(self.modstotal) self.modsenabled = QLabel() detailslayout.addWidget(self.modsenabled) self.overridden = QLabel() detailslayout.addWidget(self.overridden) self.conflicts = QLabel() detailslayout.addWidget(self.conflicts) buttonslayout = QHBoxLayout() buttonslayout.setContentsMargins(0, 0, 0, 0) buttonslayout.setAlignment(Qt.AlignRight | Qt.AlignVCenter) buttons = QWidget() buttons.setLayout(buttonslayout) summarylayout.addWidget(buttons) self.startscriptmerger = QPushButton('Start Script Merger') self.startscriptmerger.setContentsMargins(0, 0, 0, 0) self.startscriptmerger.setMinimumWidth(140) self.startscriptmerger.setIcon( QIcon(str(getRuntimePath('resources/icons/script.ico')))) self.startscriptmerger.clicked.connect(lambda: [ openExecutable(Path(str(settings.value('scriptMergerPath'))), True) ]) self.startscriptmerger.setEnabled( verifyScriptMergerPath( Path(str(settings.value('scriptMergerPath')))) is not None) buttonslayout.addWidget(self.startscriptmerger) self.startgame = QPushButton('Start Game') self.startgame.setContentsMargins(0, 0, 0, 0) self.startgame.setMinimumWidth(100) self.startgame.setIcon( QIcon(str(getRuntimePath('resources/icons/w3b.ico')))) buttonslayout.addWidget(self.startgame) # splitter self.splitter = QSplitter(Qt.Vertical) self.stack = QStackedWidget() self.splitter.addWidget(self.stack) # mod list widget self.modlistwidget = QWidget() self.modlistlayout = QVBoxLayout() self.modlistlayout.setContentsMargins(0, 0, 0, 0) self.modlistwidget.setLayout(self.modlistlayout) self.stack.addWidget(self.modlistwidget) # search bar self.searchbar = QLineEdit() self.searchbar.setPlaceholderText('Search...') self.modlistlayout.addWidget(self.searchbar) # mod list self.modlist = ModList(self, model) self.modlistlayout.addWidget(self.modlist) self.searchbar.textChanged.connect(lambda e: self.modlist.setFilter(e)) # welcome message welcomelayout = QVBoxLayout() welcomelayout.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) welcomewidget = QWidget() welcomewidget.setLayout(welcomelayout) welcomewidget.dragEnterEvent = self.modlist.dragEnterEvent # type: ignore welcomewidget.dragMoveEvent = self.modlist.dragMoveEvent # type: ignore welcomewidget.dragLeaveEvent = self.modlist.dragLeaveEvent # type: ignore welcomewidget.dropEvent = self.modlist.dropEvent # type: ignore welcomewidget.setAcceptDrops(True) icon = QIcon(str(getRuntimePath('resources/icons/open-folder.ico'))) iconpixmap = icon.pixmap(32, 32) icon = QLabel() icon.setPixmap(iconpixmap) icon.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) icon.setContentsMargins(4, 4, 4, 4) welcomelayout.addWidget(icon) welcome = QLabel('''<p><font> No mod installed yet. Drag a mod into this area to get started! </font></p>''') welcome.setAttribute(Qt.WA_TransparentForMouseEvents) welcome.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter) welcomelayout.addWidget(welcome) self.stack.addWidget(welcomewidget) # output log self.output = QTextEdit(self) self.output.setTextInteractionFlags(Qt.NoTextInteraction) self.output.setReadOnly(True) self.output.setContextMenuPolicy(Qt.NoContextMenu) self.output.setPlaceholderText('Program output...') self.splitter.addWidget(self.output) # TODO: enhancement: show indicator if scripts have to be merged self.splitter.setStretchFactor(0, 1) self.splitter.setStretchFactor(1, 0) self.mainlayout.addWidget(self.splitter) # TODO: incomplete: make start game button functional if len(model): self.stack.setCurrentIndex(0) self.splitter.setSizes([self.splitter.size().height(), 50]) else: self.stack.setCurrentIndex(1) self.splitter.setSizes([self.splitter.size().height(), 0]) model.updateCallbacks.append(self.modelUpdateEvent) asyncio.create_task(model.loadInstalled())
class MainWindow(QMainWindow): def __init__(self): super(MainWindow, self).__init__() self.ui = Ui_MainWindow() self.ui.setupUi(self) self.art_scene = QGraphicsScene(self) self.art_scene.addText('Open an image file.') self.ui.art_view.setScene(self.art_scene) self.symbols_scene = QGraphicsScene(self) self.ui.symbols_view.setScene(self.symbols_scene) self.ui.action_exit.triggered.connect(self.close) self.ui.action_open_art.triggered.connect(self.open_image) self.ui.action_open_words.triggered.connect(self.open_words) self.ui.action_save.triggered.connect(self.save_pdf) self.ui.action_save_png.triggered.connect(self.save_png) self.ui.action_shuffle.triggered.connect(self.shuffle) self.ui.action_sort.triggered.connect(self.sort) self.ui.rows.valueChanged.connect(self.on_options_changed) self.ui.columns.valueChanged.connect(self.on_options_changed) self.ui.word_clues_radio.toggled.connect(self.on_options_changed) self.ui.symbol_clues_radio.toggled.connect(self.on_options_changed) self.word_layout = QGridLayout(self.ui.word_content) self.ui.word_scroll.setWidgetResizable(True) self.word_labels: typing.Dict[str, QLabel] = {} self.word_shuffler = WordShuffler([]) self.clues = None self.pixmap = self.scaled_pixmap = self.mini_pixmap = None self.sliced_pixmap_item: typing.Optional[QGraphicsPixmapItem] = None self.sliced_image: typing.Optional[QImage] = None self.selection_grid: typing.Optional[SelectionGrid] = None self.cells = [] self.art_shuffler: typing.Optional[ArtShuffler] = None self.symbols_source_pixmap_item: typing.Optional[ QGraphicsPixmapItem] = None self.symbols_pixmap_item: typing.Optional[QGraphicsPixmapItem] = None self.symbols_image: typing.Optional[QImage] = None self.symbols_shuffler: typing.Optional[ArtShuffler] = None self.selected_row: typing.Optional[int] = None self.selected_column: typing.Optional[int] = None self.settings = QSettings() self.image_path: typing.Optional[str] = self.settings.value( 'image_path') self.words_path: typing.Optional[str] = self.settings.value( 'words_path') self.dirty_letters = set() self.timer = QTimer() self.timer.setInterval(500) self.timer.setSingleShot(True) # noinspection PyUnresolvedReferences self.timer.timeout.connect(self.on_dirty) self.row_count = self.column_count = 0 self.clue_type = ClueType.words self.ui.rows.setValue(self.settings.value('row_count', 6, int)) self.ui.columns.setValue(self.settings.value('column_count', 4, int)) clue_type_name = self.settings.value('clue_type', ClueType.words.name) try: clue_type = ClueType[clue_type_name] except KeyError: clue_type = ClueType.words if clue_type == ClueType.words: self.ui.word_clues_radio.setChecked(True) else: self.ui.symbol_clues_radio.setChecked(True) self.row_clues: typing.List[QPixmap] = [] self.column_clues: typing.List[QPixmap] = [] self.on_options_changed() def on_dirty(self): for letter in self.dirty_letters: self.word_labels[letter].setText( self.word_shuffler.make_display(letter)) self.settings.setValue(f'word_{letter}', self.word_shuffler[letter]) if self.dirty_letters: self.clues = self.word_shuffler.make_clues() self.art_shuffler.clues = dict(self.clues) self.dirty_letters.clear() self.on_selection_moved() if self.pixmap is not None: x, y, width, height = self.get_selected_fraction() self.settings.setValue('x', x) self.settings.setValue('y', y) self.settings.setValue('width', width) self.settings.setValue('height', height) new_rows = self.ui.rows.value() new_columns = self.ui.columns.value() if self.ui.word_clues_radio.isChecked(): new_clue_type = ClueType.words else: new_clue_type = ClueType.symbols if (new_rows, new_columns, new_clue_type) == (self.row_count, self.column_count, self.clue_type): return self.settings.setValue('row_count', new_rows) self.settings.setValue('column_count', new_columns) self.settings.setValue('clue_type', new_clue_type.name) self.row_count, self.column_count = new_rows, new_columns self.clue_type = new_clue_type word_count = (self.row_count * self.column_count) while self.word_layout.count(): layout_item = self.word_layout.takeAt(0) layout_item.widget().deleteLater() self.word_labels.clear() self.row_clues.clear() self.column_clues.clear() if self.image_path is not None: self.load_image(self.image_path) if self.words_path is not None: self.load_words(self.words_path) letters = [chr(65 + i) for i in range(word_count)] if self.word_shuffler.needs_blank: letters.insert(0, '') word_fields = {} for i, letter in enumerate(letters): word_field = QLineEdit() self.word_layout.addWidget(word_field, i, 0) # noinspection PyUnresolvedReferences word_field.textEdited.connect(partial(self.on_word_edited, letter)) word_label = QLabel() self.word_layout.addWidget(word_label, i, 1) self.word_labels[letter] = word_label word_fields[letter] = word_field for i, letter in enumerate(letters): word = self.settings.value(f'word_{letter}', '') self.word_shuffler[letter] = word self.dirty_letters.add(letter) word_fields[letter].setText(word) def on_options_changed(self, *_): self.timer.start() def shuffle(self): self.clues = self.word_shuffler.make_clues() if self.art_shuffler is not None: self.art_shuffler.shuffle() self.on_selection_moved() def sort(self): if self.art_shuffler is not None: self.art_shuffler.sort() self.on_selection_moved() def open_words(self): word_filter = 'Text files (*.txt)' if self.words_path is None: words_folder = None else: words_folder = str(Path(self.words_path).parent) file_name, _ = QFileDialog.getOpenFileName(self, "Open a words file.", dir=words_folder, filter=word_filter) if not file_name: return self.settings.setValue('words_path', file_name) self.load_words(file_name) def load_words(self, words_path): with open(words_path) as f: choice = 0 if choice == 0: self.word_shuffler = WordShuffler(f) else: self.word_shuffler = WordStripper(f) def open_image(self): formats = QImageReader.supportedImageFormats() patterns = (f'*.{fmt.data().decode()}' for fmt in formats) image_filter = f'Images ({" ".join(patterns)})' if self.image_path is None: image_folder = None else: image_folder = str(Path(self.image_path).parent) file_name, _ = QFileDialog.getOpenFileName(self, "Open an image file.", dir=image_folder, filter=image_filter) if not file_name: return self.settings.setValue('image_path', file_name) self.load_image(file_name) def load_image(self, image_path): self.pixmap = QPixmap(image_path) if self.pixmap.isNull(): self.pixmap = None self.image_path = image_path self.scale_image() def scale_image(self): if self.pixmap is None: return if self.selection_grid is None: x = self.settings.value('x', 0.0, float) y = self.settings.value('y', 0.0, float) width = self.settings.value('width', 1.0, float) height = self.settings.value('height', 1.0, float) else: x, y, width, height = self.get_selected_fraction() self.art_scene.clear() self.cells.clear() view_size = self.ui.art_view.maximumViewportSize() if view_size.width() == 0: return self.art_scene.setSceneRect(0, 0, view_size.width(), view_size.height()) display_size = QSize(view_size.width() * 0.99 / 2, view_size.height() * 0.99) self.scaled_pixmap = self.pixmap.scaled( display_size, aspectMode=Qt.AspectRatioMode.KeepAspectRatio) self.art_scene.addPixmap(self.scaled_pixmap) scaled_size = self.scaled_pixmap.size() self.selection_grid = SelectionGrid(scaled_size.width() * x, scaled_size.height() * y, scaled_size.width() * width, scaled_size.height() * height, row_count=self.row_count, column_count=self.column_count) self.selection_grid.on_moved = self.on_selection_moved self.art_scene.addItem(self.selection_grid) self.sliced_image = QImage(display_size, QImage.Format.Format_ARGB32_Premultiplied) self.check_clues() self.art_shuffler = ArtShuffler(self.selection_grid.row_count, self.selection_grid.column_count, self.sliced_image, QRect(0, 0, display_size.width(), display_size.height()), clues=self.clues, row_clues=self.row_clues, column_clues=self.column_clues) self.sliced_pixmap_item = self.art_scene.addPixmap( QPixmap.fromImage(self.sliced_image)) self.sliced_pixmap_item.setPos(display_size.width(), 0) self.symbols_scene.clear() self.symbols_source_pixmap_item = self.symbols_scene.addPixmap( self.scaled_pixmap) self.symbols_image = QImage(display_size, QImage.Format.Format_ARGB32_Premultiplied) if self.symbols_shuffler is not None: selected_row = self.symbols_shuffler.selected_row selected_column = self.symbols_shuffler.selected_column else: selected_row = 0 selected_column = None self.symbols_shuffler = ArtShuffler(self.selection_grid.row_count, self.selection_grid.column_count, self.symbols_image, QRect(0, 0, display_size.width(), display_size.height()), row_clues=self.row_clues, column_clues=self.column_clues) self.symbols_shuffler.selected_row = selected_row self.symbols_shuffler.selected_column = selected_column self.symbols_pixmap_item = ClickablePixmapItem( QPixmap.fromImage(self.symbols_image)) self.symbols_pixmap_item.on_click = self.on_symbols_clicked self.symbols_scene.addItem(self.symbols_pixmap_item) self.symbols_pixmap_item.setPos(display_size.width(), 0) self.on_selection_moved() def on_symbols_clicked(self, event: QGraphicsSceneMouseEvent): self.symbols_scene.clearSelection() self.symbols_shuffler.select_clue(event.pos().toPoint()) self.on_selection_moved() def on_word_edited(self, letter, word): self.word_shuffler[letter] = word self.dirty_letters.add(letter) self.timer.start() def get_selected_fraction(self): selection_rect = self.selection_grid.rect() selection_pos = self.selection_grid.pos() size = self.scaled_pixmap.size() x = (selection_pos.x() + selection_rect.x()) / size.width() width = selection_rect.width() / size.width() y = (selection_pos.y() + selection_rect.y()) / size.height() height = selection_rect.height() / size.height() return x, y, width, height def on_selection_moved(self): selected_pixmap = self.get_selected_pixmap() self.art_shuffler.draw(selected_pixmap) self.sliced_pixmap_item.setPixmap(QPixmap.fromImage(self.sliced_image)) selected_pixmap = self.get_selected_pixmap() cell_width = (selected_pixmap.width() / self.selection_grid.column_count) cell_height = (selected_pixmap.height() / self.selection_grid.row_count) self.row_clues.clear() self.column_clues.clear() for i in range(self.selection_grid.row_count): clue_image = selected_pixmap.copy(0, i * cell_height, cell_width, cell_height) self.row_clues.append(clue_image) for j in range(self.selection_grid.column_count): clue_image = selected_pixmap.copy(j * cell_width, 0, cell_width, cell_height) self.column_clues.append(clue_image) self.symbols_shuffler.row_clues = self.row_clues self.symbols_shuffler.column_clues = self.column_clues self.symbols_shuffler.draw_grid(selected_pixmap) self.symbols_pixmap_item.setPixmap( QPixmap.fromImage(self.symbols_image)) self.timer.start() def get_selected_pixmap(self) -> QPixmap: x, y, width, height = self.get_selected_fraction() original_size = self.pixmap.size() selected_pixmap = self.pixmap.copy(x * original_size.width(), y * original_size.height(), width * original_size.width(), height * original_size.height()) return selected_pixmap def resizeEvent(self, event: QResizeEvent): super().resizeEvent(event) self.scale_image() def save_pdf(self): pdf_folder = self.settings.value('pdf_folder') file_name, _ = QFileDialog.getSaveFileName(self, "Save a PDF file.", dir=pdf_folder, filter='Documents (*.pdf)') if not file_name: return self.settings.setValue('pdf_folder', os.path.dirname(file_name)) writer = QPdfWriter(file_name) writer.setPageSize(QPageSize(QPageSize.Letter)) writer.setTitle('Sliced Art Puzzle') writer.setCreator('Don Kirkby') self.paint_puzzle(writer) def save_png(self): pdf_folder = self.settings.value('pdf_folder') file_name, _ = QFileDialog.getSaveFileName(self, "Save an image file.", dir=pdf_folder, filter='Images (*.png)') if not file_name: return writer = QPixmap(1000, 2000) self.paint_puzzle(writer) writer.save(file_name) self.settings.setValue('pdf_folder', os.path.dirname(file_name)) def paint_puzzle(self, writer: QPaintDevice): self.check_clues() painter = QPainter(writer) try: print_shuffler = ArtShuffler(self.art_shuffler.rows, self.art_shuffler.cols, writer, QRect(0, 0, writer.width(), round(writer.height() / 2)), clues=self.clues, row_clues=self.row_clues, column_clues=self.column_clues) print_shuffler.cells = self.art_shuffler.cells[:] print_shuffler.is_shuffled = self.art_shuffler.is_shuffled selected_pixmap = self.get_selected_pixmap() print_shuffler.draw(selected_pixmap, painter) print_shuffler.rect.moveTop(writer.height() / 2) print_shuffler.draw_grid(selected_pixmap, painter) finally: painter.end() def check_clues(self): if self.clue_type == ClueType.words: if self.clues is None: self.clues = self.word_shuffler.make_clues() self.row_clues.clear() self.column_clues.clear() else: self.clues = None
async def installFromFile( self, path: Path, installtime: Optional[datetime] = None) -> Tuple[int, int]: originalpath = path installed = 0 errors = 0 archive = path.is_file() source = None md5hash = '' details = None detailsrequest: Optional[asyncio.Task] = None if not installtime: installtime = datetime.now(tz=timezone.utc) try: if archive: # unpack archive, set source and request details md5hash = getMD5Hash(path) source = path settings = QSettings() if settings.value('nexusGetInfo', 'False') == 'True': logger.bind( path=str(path), dots=True).debug('Requesting details for archive') detailsrequest = asyncio.create_task( getModInformation(md5hash)) logger.bind(path=str(path), dots=True).debug('Unpacking archive') path = await extractMod(source) # validate and read mod valid, exhausted = containsValidMod(path, searchlimit=8) if not valid: if not exhausted and self.showContinueSearchDialog( searchlimit=8): if not containsValidMod(path): raise InvalidPathError(path, 'Invalid mod') elif not exhausted: raise InvalidPathError(path, 'Stopped searching for mod') else: raise InvalidPathError(path, 'Invalid mod') mods = await Mod.fromDirectory(path, searchCommonRoot=not archive) installedMods = [] # update mod details and add mods to the model for mod in mods: mod.md5hash = md5hash try: # TODO: incomplete: check if mod is installed, ask if replace await self.modmodel.add(mod) installedMods.append(mod) installed += 1 except ModExistsError: logger.bind(path=source if source else mod.source, name=mod.filename).error(f'Mod exists') errors += 1 continue # wait for details response if requested if detailsrequest: try: details = await detailsrequest except (RequestError, ResponseError, Exception) as e: logger.warning( f'Could not get information for {source.name if source else path.name}: {e}' ) # update mod with additional information if source or details: for mod in installedMods: if source: # set source if it differs from the scan directory, e.g. an archive mod.source = source if details: # set additional details if requested and available try: package = str(details[0]['mod']['name']) summary = str(details[0]['mod']['summary']) modid = int(details[0]['mod']['mod_id']) category = int(details[0]['mod']['category_id']) version = str( details[0]['file_details']['version']) fileid = int(details[0]['file_details']['file_id']) uploadname = str( details[0]['file_details']['name']) uploadtime = str( details[0]['file_details']['uploaded_time']) mod.package = package mod.summary = summary mod.modid = modid mod.category = getCategoryName(category) mod.version = version mod.fileid = fileid mod.uploadname = uploadname uploaddate = dateparser.parse(uploadtime) if uploaddate: mod.uploaddate = uploaddate.astimezone( tz=timezone.utc) else: logger.bind(name=mod.filename).debug( f'Could not parse date {uploadtime} in mod information response' ) except KeyError as e: logger.bind(name=mod.filename).exception( f'Could not find key "{str(e)}" in mod information response' ) try: await self.modmodel.update(mod) except Exception: logger.bind(name=mod.filename).warning( 'Could not update mod details') except ModelError as e: logger.bind(path=e.path).error(e.message) errors += 1 except InvalidPathError as e: # TODO: enhancement: better install error message logger.bind(path=e.path).error(e.message) errors += 1 except FileNotFoundError as e: logger.bind( path=e.filename).error(e.strerror if e.strerror else str(e)) errors += 1 except OSError as e: logger.bind( path=e.filename).error(e.strerror if e.strerror else str(e)) errors += 1 except Exception as e: logger.exception(str(e)) errors += 1 finally: if detailsrequest and not detailsrequest.done(): detailsrequest.cancel() if archive and not path == originalpath: try: util.removeDirectory(path) except Exception: logger.bind(path=path).warning( 'Could not remove temporary directory') self.modmodel.setLastUpdateTime(installtime) self.repaint() return installed, errors
# Before reading this file, please read this link! # this link helps you to understand this codes: # https://aria2.github.io/manual/en/html/aria2c.html#rpc-interface home_address = os.path.expanduser("~") os_type = platform.system() # persepolis setting persepolis_setting = QSettings('persepolis_download_manager', 'persepolis') # host is localhost host = 'localhost' # get port from persepolis_setting port = int(persepolis_setting.value('settings/rpc-port')) # get aria2_path aria2_path = persepolis_setting.value('settings/aria2_path') # xml rpc SERVER_URI_FORMAT = 'http://{}:{:d}/rpc' server_uri = SERVER_URI_FORMAT.format(host, port) server = xmlrpc.client.ServerProxy(server_uri, allow_none=True) # start aria2 with RPC def startAria(): # in Linux and BSD if os_type in OS.UNIX_LIKE:
def log(self, message: Any) -> None: # format log messages to user readable output settings = QSettings() record = message.record message = record['message'] extra = record['extra'] level = record['level'].name.lower() name = str(extra['name'] ) if 'name' in extra and extra['name'] is not None else '' path = str(extra['path'] ) if 'path' in extra and extra['path'] is not None else '' dots = bool( extra['dots'] ) if 'dots' in extra and extra['dots'] is not None else False newline = bool( extra['newline'] ) if 'newline' in extra and extra['newline'] is not None else False output = bool( extra['output'] ) if 'output' in extra and extra['output'] is not None else bool( message) modlist = bool( extra['modlist'] ) if 'modlist' in extra and extra['modlist'] is not None else False if level in ['debug' ] and settings.value('debugOutput', 'False') != 'True': if newline: self.output.append(f'') return n = '<br>' if newline else '' d = '...' if dots else '' if len(name) and len(path): path = f' ({path})' if output: message = html.escape(message, quote=True) if level in ['success', 'error', 'warning']: message = f'<strong>{message}</strong>' if level in ['success']: message = f'<font color="#04c45e">{message}</font>' if level in ['error', 'critical']: message = f'<font color="#ee3b3b">{message}</font>' if level in ['warning']: message = f'<font color="#ff6500">{message}</font>' if level in ['debug', 'trace']: message = f'<font color="#aaa">{message}</font>' path = f'<font color="#aaa">{path}</font>' if path else '' d = f'<font color="#aaa">{d}</font>' if d else '' time = record['time'].astimezone( tz=None).strftime('%Y-%m-%d %H:%M:%S') message = f'<font color="#aaa">{time}</font> {message}' self.output.append( f'{n}{message.strip()}{" " if name or path else ""}{name}{path}{d}' ) else: self.output.append(f'') self.output.verticalScrollBar().setValue( self.output.verticalScrollBar().maximum()) self.output.repaint() if modlist: self.unhideModList() if settings.value('unhideOutput', 'True') == 'True' and output: self.unhideOutput()
class Snippets(QDialog): def __init__(self, context, parent=None): super(Snippets, self).__init__(parent) # Create widgets self.setWindowFlags(self.windowFlags() & ~Qt.WindowContextHelpButtonHint) self.title = QLabel(self.tr("Snippet Editor")) self.saveButton = QPushButton(self.tr("&Save")) self.saveButton.setShortcut(QKeySequence(self.tr("Ctrl+S"))) self.runButton = QPushButton(self.tr("&Run")) self.runButton.setShortcut(QKeySequence(self.tr("Ctrl+R"))) self.closeButton = QPushButton(self.tr("Close")) self.clearHotkeyButton = QPushButton(self.tr("Clear Hotkey")) self.setWindowTitle(self.title.text()) #self.newFolderButton = QPushButton("New Folder") self.browseButton = QPushButton("Browse Snippets") self.browseButton.setIcon(QIcon.fromTheme("edit-undo")) self.deleteSnippetButton = QPushButton("Delete") self.newSnippetButton = QPushButton("New Snippet") indentation = Settings().get_string("snippets.indentation") if Settings().get_bool("snippets.syntaxHighlight"): self.edit = QCodeEditor(SyntaxHighlighter=Pylighter, delimeter=indentation) else: self.edit = QCodeEditor(SyntaxHighlighter=None, delimeter=indentation) self.edit.setPlaceholderText("python code") self.resetting = False self.columns = 3 self.context = context self.keySequenceEdit = QKeySequenceEdit(self) self.currentHotkey = QKeySequence() self.currentHotkeyLabel = QLabel("") self.currentFileLabel = QLabel() self.currentFile = "" self.snippetDescription = QLineEdit() self.snippetDescription.setPlaceholderText("optional description") #Set Editbox Size font = getMonospaceFont(self) self.edit.setFont(font) font = QFontMetrics(font) self.edit.setTabStopDistance( 4 * font.horizontalAdvance(' ')) #TODO, replace with settings API #Files self.files = QFileSystemModel() self.files.setRootPath(snippetPath) self.files.setNameFilters(["*.py"]) #Tree self.tree = QTreeView() self.tree.setModel(self.files) self.tree.setSortingEnabled(True) self.tree.hideColumn(2) self.tree.sortByColumn(0, Qt.AscendingOrder) self.tree.setRootIndex(self.files.index(snippetPath)) for x in range(self.columns): #self.tree.resizeColumnToContents(x) self.tree.header().setSectionResizeMode( x, QHeaderView.ResizeToContents) treeLayout = QVBoxLayout() treeLayout.addWidget(self.tree) treeButtons = QHBoxLayout() #treeButtons.addWidget(self.newFolderButton) treeButtons.addWidget(self.browseButton) treeButtons.addWidget(self.newSnippetButton) treeButtons.addWidget(self.deleteSnippetButton) treeLayout.addLayout(treeButtons) treeWidget = QWidget() treeWidget.setLayout(treeLayout) # Create layout and add widgets buttons = QHBoxLayout() buttons.addWidget(self.clearHotkeyButton) buttons.addWidget(self.keySequenceEdit) buttons.addWidget(self.currentHotkeyLabel) buttons.addWidget(self.closeButton) buttons.addWidget(self.runButton) buttons.addWidget(self.saveButton) description = QHBoxLayout() description.addWidget(QLabel(self.tr("Description: "))) description.addWidget(self.snippetDescription) vlayoutWidget = QWidget() vlayout = QVBoxLayout() vlayout.addLayout(description) vlayout.addWidget(self.edit) vlayout.addLayout(buttons) vlayoutWidget.setLayout(vlayout) hsplitter = QSplitter() hsplitter.addWidget(treeWidget) hsplitter.addWidget(vlayoutWidget) hlayout = QHBoxLayout() hlayout.addWidget(hsplitter) self.showNormal() #Fixes bug that maximized windows are "stuck" #Because you can't trust QT to do the right thing here if (sys.platform == "darwin"): self.settings = QSettings("Vector35", "Snippet Editor") else: self.settings = QSettings("Vector 35", "Snippet Editor") if self.settings.contains("ui/snippeteditor/geometry"): self.restoreGeometry( self.settings.value("ui/snippeteditor/geometry")) else: self.edit.setMinimumWidth(80 * font.averageCharWidth()) self.edit.setMinimumHeight(30 * font.lineSpacing()) # Set dialog layout self.setLayout(hlayout) # Add signals self.saveButton.clicked.connect(self.save) self.closeButton.clicked.connect(self.close) self.runButton.clicked.connect(self.run) self.clearHotkeyButton.clicked.connect(self.clearHotkey) self.tree.selectionModel().selectionChanged.connect(self.selectFile) self.newSnippetButton.clicked.connect(self.newFileDialog) self.deleteSnippetButton.clicked.connect(self.deleteSnippet) #self.newFolderButton.clicked.connect(self.newFolder) self.browseButton.clicked.connect(self.browseSnippets) if self.settings.contains("ui/snippeteditor/selected"): selectedName = self.settings.value("ui/snippeteditor/selected") self.tree.selectionModel().select( self.files.index(selectedName), QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) if self.tree.selectionModel().hasSelection(): self.selectFile(self.tree.selectionModel().selection(), None) self.edit.setFocus() cursor = self.edit.textCursor() cursor.setPosition(self.edit.document().characterCount() - 1) self.edit.setTextCursor(cursor) else: self.readOnly(True) else: self.readOnly(True) @staticmethod def registerAllSnippets(): for action in list( filter(lambda x: x.startswith("Snippets\\"), UIAction.getAllRegisteredActions())): if action == "Snippets\\Snippet Editor...": continue UIActionHandler.globalActions().unbindAction(action) Menu.mainMenu("Tools").removeAction(action) UIAction.unregisterAction(action) for snippet in includeWalk(snippetPath, ".py"): snippetKeys = None (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(snippet) actionText = actionFromSnippet(snippet, snippetDescription) if snippetCode: if snippetKeys == None: UIAction.registerAction(actionText) else: UIAction.registerAction(actionText, snippetKeys) UIActionHandler.globalActions().bindAction( actionText, UIAction(makeSnippetFunction(snippetCode, actionText))) Menu.mainMenu("Tools").addAction(actionText, "Snippets") def clearSelection(self): self.keySequenceEdit.clear() self.currentHotkey = QKeySequence() self.currentHotkeyLabel.setText("") self.currentFileLabel.setText("") self.snippetDescription.setText("") self.edit.clear() self.tree.clearSelection() self.currentFile = "" def askSave(self): return QMessageBox.question( self, self.tr("Save?"), self.tr("Do you want to save changes to {}?").format( self.currentFileLabel.text()), QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) def reject(self): self.settings.setValue("ui/snippeteditor/geometry", self.saveGeometry()) if self.snippetChanged(): save = self.askSave() if save == QMessageBox.Yes: self.save() elif save == QMessageBox.No: self.loadSnippet() elif save == QMessageBox.Cancel: return self.accept() def browseSnippets(self): url = QUrl.fromLocalFile(snippetPath) QDesktopServices.openUrl(url) def newFolder(self): (folderName, ok) = QInputDialog.getText(self, self.tr("Folder Name"), self.tr("Folder Name: ")) if ok and folderName: index = self.tree.selectionModel().currentIndex() selection = self.files.filePath(index) if QFileInfo(selection).isDir(): QDir(selection).mkdir(folderName) else: QDir(snippetPath).mkdir(folderName) def selectFile(self, new, old): if (self.resetting): self.resetting = False return if len(new.indexes()) == 0: self.clearSelection() self.currentFile = "" self.readOnly(True) return newSelection = self.files.filePath(new.indexes()[0]) self.settings.setValue("ui/snippeteditor/selected", newSelection) if QFileInfo(newSelection).isDir(): self.readOnly(True) self.clearSelection() self.currentFile = "" return if old and old.length() > 0: oldSelection = self.files.filePath(old.indexes()[0]) if not QFileInfo(oldSelection).isDir() and self.snippetChanged(): save = self.askSave() if save == QMessageBox.Yes: self.save() elif save == QMessageBox.No: pass elif save == QMessageBox.Cancel: self.resetting = True self.tree.selectionModel().select( old, QItemSelectionModel.ClearAndSelect | QItemSelectionModel.Rows) return False self.currentFile = newSelection self.loadSnippet() def loadSnippet(self): self.currentFileLabel.setText(QFileInfo(self.currentFile).baseName()) (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile) self.snippetDescription.setText( snippetDescription ) if snippetDescription else self.snippetDescription.setText("") self.keySequenceEdit.setKeySequence( snippetKeys ) if snippetKeys else self.keySequenceEdit.setKeySequence( QKeySequence("")) self.edit.setPlainText( snippetCode) if snippetCode else self.edit.setPlainText("") self.readOnly(False) def newFileDialog(self): (snippetName, ok) = QInputDialog.getText(self, self.tr("Snippet Name"), self.tr("Snippet Name: "), flags=self.windowFlags()) if ok and snippetName: if not snippetName.endswith(".py"): snippetName += ".py" index = self.tree.selectionModel().currentIndex() selection = self.files.filePath(index) if QFileInfo(selection).isDir(): path = os.path.join(selection, snippetName) else: path = os.path.join(snippetPath, snippetName) self.readOnly(False) open(path, "w").close() self.tree.setCurrentIndex(self.files.index(path)) log_debug("Snippet %s created." % snippetName) def readOnly(self, flag): self.keySequenceEdit.setEnabled(not flag) self.snippetDescription.setReadOnly(flag) self.edit.setReadOnly(flag) if flag: self.snippetDescription.setDisabled(True) self.edit.setDisabled(True) else: self.snippetDescription.setEnabled(True) self.edit.setEnabled(True) def deleteSnippet(self): selection = self.tree.selectedIndexes()[::self.columns][ 0] #treeview returns each selected element in the row snippetName = self.files.fileName(selection) question = QMessageBox.question( self, self.tr("Confirm"), self.tr("Confirm deletion: ") + snippetName) if (question == QMessageBox.StandardButton.Yes): log_debug("Deleting snippet %s." % snippetName) self.clearSelection() self.files.remove(selection) self.registerAllSnippets() def snippetChanged(self): if (self.currentFile == "" or QFileInfo(self.currentFile).isDir()): return False (snippetDescription, snippetKeys, snippetCode) = loadSnippetFromFile(self.currentFile) if snippetKeys == None and not self.keySequenceEdit.keySequence( ).isEmpty(): return True if snippetKeys != None and snippetKeys != self.keySequenceEdit.keySequence( ).toString(): return True return self.edit.toPlainText() != snippetCode or \ self.snippetDescription.text() != snippetDescription def save(self): log_debug("Saving snippet %s" % self.currentFile) outputSnippet = codecs.open(self.currentFile, "w", "utf-8") outputSnippet.write("#" + self.snippetDescription.text() + "\n") outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n") outputSnippet.write(self.edit.toPlainText()) outputSnippet.close() self.registerAllSnippets() def run(self): if self.context == None: log_warn("Cannot run snippets outside of the UI at this time.") return if self.snippetChanged(): self.save() actionText = actionFromSnippet(self.currentFile, self.snippetDescription.text()) UIActionHandler.globalActions().executeAction(actionText, self.context) log_debug("Saving snippet %s" % self.currentFile) outputSnippet = codecs.open(self.currentFile, "w", "utf-8") outputSnippet.write("#" + self.snippetDescription.text() + "\n") outputSnippet.write("#" + self.keySequenceEdit.keySequence().toString() + "\n") outputSnippet.write(self.edit.toPlainText()) outputSnippet.close() self.registerAllSnippets() def clearHotkey(self): self.keySequenceEdit.clear()