Пример #1
0
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)
Пример #2
0
    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)
Пример #3
0
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()
Пример #4
0
    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
Пример #5
0
    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))
Пример #6
0
    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)
Пример #7
0
    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()
Пример #8
0
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
Пример #9
0
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
Пример #10
0
    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)
Пример #11
0
    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)

Пример #12
0
 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)
Пример #13
0
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()
Пример #14
0
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)
Пример #15
0
 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)
Пример #16
0
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)
Пример #17
0
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()
Пример #18
0
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)
Пример #19
0
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)
Пример #20
0
    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)
Пример #21
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
Пример #22
0
def test_mij():
    settings = QSettings()
    print(settings)
    # settings.beginGroup("Java Runtime Environment")
    print(settings.value("HKEY_LOCAL_MACHINE\\SOFTWARE\\JavaSoft\\Java Runtime Environment\\CurrentVersion"))
Пример #23
0
 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))
Пример #25
0
    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())
Пример #26
0
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
Пример #27
0
    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
Пример #28
0
# 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:
Пример #29
0
    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()
Пример #30
0
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()