Example #1
0
    def choose_directory(self, folder, override=False):
        """
        Open the file dialog to allow the user to select a path for
        the given folder.

        :param folder:
        :param override: set to True if this is for a profile
            dir-override
        :return:
        """

        # fixme: this doesn't seem to actually show the current folder if there
        # is one...maybe that's a Qt bug, though. Or maybe it's because of the
        # hidden folder in the path?

        start = self._selected_profile.diroverride(folder) if override else self.paths[folder]

        # noinspection PyTypeChecker
        chosen = QFileDialog.getExistingDirectory(self,
                                                  "Select directory",
                                                  start or "")

        if check_path(chosen):

            if override:
                self.override_boxes[folder].setText(chosen)
            else:
                self.path_boxes[folder].setText(chosen)
                if folder in self.indicator_labels.keys():
                    self.indicator_labels[folder].setVisible(False)
Example #2
0
    def choose_directory(self, folder, override=False):
        """
        Open the file dialog to allow the user to select a path for
        the given folder.

        :param folder:
        :param override: set to True if this is for a profile
            dir-override
        :return:
        """

        # -f-i-x-m-e-:this doesn't seem to actually show the current folder if there
        # is one...maybe that's a Qt bug, though. Or maybe it's because of the
        # hidden folder in the path?

        # update: ok...so the 'default' dialog was crap and didn't work
        # right. For some reason, adding an option (in this case
        # 'DontResolveSymlinks') caused a different dialog to be used
        # (one that looked more familiar to me) that worked MUCH better
        # and started in the correct directory.
        # Wondering if this was perhaps the 'non-native' dialog and the
        # native one was just bad on my system, I changed the options to
        # include 'UseNonNativeDialog'--but this showed a *different*
        # dialog than the other two, which seemed to be between the
        # others as far as functionality went. Presumably the "good"
        # dialog was the native one, which is reassuring.
        # Anyway, I still don't really know what's going on, but it
        # seems to work ok for now...

        ovrdict = self._override_mapping
        # ovrdict = self._override_paths[self._selected_profile.name]

        start = self._override_mapping[
            folder].path if override else self.paths[folder]

        # noinspection PyTypeChecker
        chosen = QFileDialog.getExistingDirectory(
            self,
            "Select directory",
            directory=start or "",
            options=QFileDialog.DontResolveSymlinks)

        if check_path(chosen):

            if override:
                self.override_boxes[folder].setText(chosen)
                ovrdict[folder] = ovrdict[folder]._replace(path=chosen)
            else:
                self.path_boxes[folder].setText(chosen)
                if folder in self.indicator_labels:
                    self.indicator_labels[folder].setVisible(False)
            # enable apply button if needed
            self._mark_changed()
Example #3
0
    def setRootPath(self, path=None):
        """
        Using this instead of a setter just for API-similarity with
        QFileSystemModel. That's the same reason rootPathChanged is
        emitted at the end of the method, as well.

        :param str path: the absolute filesystem path to the active
            mod's data folder. If passed as ``None``, the model is
            reset to empty
        """

        if path == self.rootpath: return

        # commit any changes we've made so far
        self.save()

        # drop the undo stack
        self.undostack.clear()

        if path is None: # reset Model to show nothing
            self.beginResetModel()
            self.rootpath=None
            self.rootitem=None
            self.modname=None
            self.rootPathChanged.emit(path)
            self.endResetModel()

        elif check_path(path):

            # tells the view to get ready to redisplay its contents
            self.beginResetModel()

            self.rootpath = path
            self.modname = os.path.basename(path)

            self._setup_or_reload_tree()

            # tells the view it should get new
            # data from model & reset itself
            self.endResetModel()

            # emit notifier signal
            self.rootPathChanged.emit(path)
Example #4
0
    def setupMoreUI(self):
        """More adjustments to the UI"""

        # make sure the General tab is showing
        self.prefs_tabwidget.setCurrentIndex(0)

        ##=================================
        ## Tab 1: General/App dirs
        ##---------------------------------

        # -- checkboxes should reflect current settings
        self.cbox_restore_size.setChecked(
            app_settings.Get("ManagerWindow", UI.RESTORE_WINSIZE))
        # enable Apply button after clicking
        self.cbox_restore_size.toggled.connect(self._mark_changed)

        self.cbox_restore_pos.setChecked(
            app_settings.Get("ManagerWindow", UI.RESTORE_WINPOS))
        self.cbox_restore_pos.toggled.connect(self._mark_changed)

        # check the appropriate radio button based on current policy;
        # associate a change in the radio selection with updating
        # _selected_plp
        for plp, rb in self.radios.items():
            if plp.value == self._selected_plp:
                rb.setChecked(True)

            # chain each button's toggled(bool) signal to the
            # profilePolicyChanged signal, which includes the value of
            # the button's associated policy
            rb.toggled.connect(
                partial(self.profilePolicyChanged.emit, plp.value))

        # noinspection PyUnresolvedReferences
        # and connect this signal to the handler
        # which updates _selected_plp
        self.profilePolicyChanged.connect(self.on_profile_policy_changed)

        ##=================================
        ## Tab 3: Profiles
        ##---------------------------------

        # make sure to check the 'default' box if necessary
        self.check_default()

        # we don't care about the value it sends, so we just as easily
        # could have used 'textchanged' rather than index, but this
        # seems lighter/more appropriate
        self.combo_profiles.currentIndexChanged.connect(self.change_profile)

        self.cbox_default.toggled.connect(self.set_default_profile)

        ##=================================
        ## The Big Loop
        ##---------------------------------
        ## for each of the application-directories,
        ## setup any UI-element associated with it to
        ## the correct initial status.

        # so many things are keyed with the app directory
        for d in D:
            dpath = self.paths[d]

            # if the box is empty, show the default
            self.path_boxes[d].setPlaceholderText(self.defpaths[d])

            # if a custom path has been set, show path text
            if dpath != self.defpaths[d]:
                self.path_boxes[d].setText(dpath)

            # connect dir-chooser btns
            self.path_choosers[d].clicked.connect(
                partial(self.choose_directory, d))

            # essentially all but the Profiles dir
            if d in constants.overrideable_dirs:

                # handle indicator labels
                lbl = self.indicator_labels[d]
                if not dpath:
                    self._set_label_status(lbl, 'missing')
                elif not os.path.isabs(dpath):
                    self._set_label_status(lbl, 'notabs')
                elif not check_path(dpath):
                    self._set_label_status(lbl, 'invalid')
                else:
                    # hide the label for valid paths
                    lbl.hide()

                # have the line edits with an indicator label emit a signal
                # when editing is finished that contains their key-string
                self.path_boxes[d].editingFinished.connect(
                    partial(self.on_path_edit, d))

                ##---------------------##
                # override buttons/choosers

                # x is just a shortcut for the stupid-long dict selector
                # x = self._override_paths[self._selected_profile.name]
                x = self._override_mapping

                # record current value of override
                x[d] = self._selected_profile.diroverride(d)

                if x[d].path:
                    self.override_boxes[d].setText(x[d].path)
                else:
                    self.override_boxes[d].clear()

                obtn = self.override_buttons[d]
                is_enabled = x[d].enabled
                # if override is enabled in profile, check the button
                obtn.setChecked(is_enabled)

                # connect toggle signal to profile-config-updater
                obtn.toggled.connect(partial(self.on_override_toggled, d))

                # the buttons are already set to toggle the enable status of
                # the entry field/dir chooser when pressed, so make sure those
                # are in the correct enable state to begin with
                self.override_boxes[d].setEnabled(is_enabled)
                self.override_choosers[d].setEnabled(is_enabled)

                # connect the editFinished signal
                self.override_boxes[d].editingFinished.connect(
                    partial(self.on_override_edit, d))

                # connect override chooser buttons to file dialog
                self.override_choosers[d].clicked.connect(
                    partial(self.choose_override_dir, d))

        # record initial text
        self.current_text = {
            D.PROFILES: self.le_profdir.text(),
            D.SKYRIM: self.le_dirskyrim.text(),
            D.MODS: self.le_dirmods.text(),
            D.VFS: self.le_dirvfs.text()
        }

        # disable profile-dir chooser (no validation is currently
        # performed on it and having an invalid profiles directory would
        # cause some serious problems, so don't allow it to be changed
        # until we implement all that)
        self._gbox_appdirs.setEnabled(False)

        ## apply button ##
        self.btn_apply.clicked.connect(self.on_apply_button_pressed)

        # also apply changes when clicking OK
        # noinspection PyUnresolvedReferences
        self.accepted.connect(self.apply_changes)
Example #5
0
def test_checkpath_expand(path, expect):
    assert bool(check_path(path, True)) == expect
Example #6
0
def test_checkpath_noexpand(path, expect):
    assert bool(check_path(path)) == expect
Example #7
0
    def setupMoreUI(self):
        """More adjustments to the UI"""

        # make sure the General tab is showing
        self.prefs_tabwidget.setCurrentIndex(0)


        ##=================================
        ## Tab 1: General/App dirs
        ##---------------------------------

        # -- checkboxes should reflect current settings
        self.cbox_restore_size.setChecked(
            app_settings.Get(UI.RESTORE_WINSIZE))

        self.cbox_restore_pos.setChecked(
            app_settings.Get(UI.RESTORE_WINPOS))

        # check the appropriate radio button based on current policy;
        # associate a change in the radio selection with updating
        # _selected_plp
        for plp, rb in self.radios.items():
            if plp == self._selected_plp:
                rb.setChecked(True)

            # chain each button's toggled(bool) signal to the
            # profilePolicyChanged signal, which includes the value of
            # the button's associated policy
            rb.toggled.connect(partial(self.profilePolicyChanged.emit,
                                       plp.value))

        # noinspection PyUnresolvedReferences
        # and connect this signal to the handler
        # which updates _selected_plp
        self.profilePolicyChanged.connect(
            self.on_profile_policy_changed)


        ##=================================
        ## Tab 3: Profiles
        ##---------------------------------

        # make sure to check the 'default' box if necessary
        self.check_default()

        # we don't care about the value it sends, so we just as easily
        # could have used 'textchanged' rather than index, but this
        # seems lighter/more appropriate
        self.combo_profiles.currentIndexChanged.connect(
            self.change_profile)

        self.cbox_default.toggled.connect(self.set_default_profile)

        ##=================================
        ## The Big Loop
        ##---------------------------------
        ## for each of the application-directories,
        ## setup any UI-element associated with it to
        ## the correct initial status.

        # game-related
        gdirs = (D.SKYRIM, D.MODS, D.VFS)

        # so many things are keyed with the app directory
        for d in D:
            dpath = self.paths[d]
            # show path text
            self.path_boxes[d].setText(dpath)

            # connect dir-chooser btns
            self.path_choosers[d].clicked.connect(
                partial(self.choose_directory, d))

            # essentially all but the Profiles dir
            if d in gdirs:

                # handle indicator labels
                lbl = self.indicator_labels[d]
                if not dpath:
                    self._mark_missing_path(lbl)
                elif not check_path(dpath):
                    self._mark_invalid_path(lbl)
                elif not os.path.isabs(dpath):
                    self._mark_nonabs_path(lbl)
                else:
                    # hide the label for valid paths
                    lbl.hide()

                # have the line edits with an indicator label emit a signal
                # when editing is finished that contains their key-string
                self.path_boxes[d].editingFinished.connect(
                    partial(self.pathEditFinished.emit, d))

                ##---------------------##
                # override buttons/choosers
                self.override_boxes[d].setText(self._selected_profile.diroverride(d))

                obtn = self.override_buttons[d]
                # if override is enabled in profile, check the button
                obtn.setChecked(self._selected_profile.override_enabled(d))

                # connect toggle signal to profile-config-updater
                obtn.toggled.connect(partial(self.on_override_toggled, d))

                # the buttons are already set to toggle the enable status of
                # the entry field/dir chooser when pressed, so make sure those
                # are in the correct enable state to begin with
                self.override_boxes[d].setEnabled(obtn.isChecked())
                self.override_choosers[d].setEnabled(obtn.isChecked())

                # connect override chooser buttons to file dialog
                self.override_choosers[d].clicked.connect(
                    partial(self.choose_override_dir, d))


        # connect pathEditFinished signal to our validation handler
        # noinspection PyUnresolvedReferences
        self.pathEditFinished.connect(self.on_path_edit)

        ## apply button ##
        # btn_apply = self.prefs_btnbox.button(QDialogButtonBox.Apply)
        self.prefs_btnbox.button(QDialogButtonBox.Apply
                                 ).clicked.connect(self.apply_changes)

        # also apply changes when clicking OK
        # noinspection PyUnresolvedReferences
        self.accepted.connect(self.apply_changes)
Example #8
0
    def _load_data_dirs(self, config):
        """
        Lookup the paths for the directories of game-related data (e.g.
        the main Skyrim folder, the mods-installation directory, and the
        virtual fs mount point). If the user has not configured these, use
        the default.

        :param configparser.ConfigParser config:
        """

        for evar, path_key in (
                (EnvVars.SKYDIR, _KEY_SKYDIR),
                (EnvVars.MOD_DIR, _KEY_MODDIR),
                (EnvVars.VFS_MOUNT, _KEY_VFSMNT),
        ):
            p = None  # type: Path

            # first, check if the user has specified an environment variable
            envval = self._environment[evar]
            if envval:
                if check_path(envval):
                    p = Path(envval)
                else:
                    self.path_errors[path_key].append(envval)

            # if they didn't or it didn't exist, pull the config value
            if p is None:
                try:
                    config_val = self._load_config_value(config, _SECTION_DIRS, path_key)
                except exceptions.MissingConfigKeyError as e:
                    self.missing_keys.append(e)
                else:
                    if check_path(config_val):
                        p = Path(config_val)
                    else:
                        self.path_errors[path_key].append(config_val)

            if p is None:
                # if key wasn't in config file for some reason,
                # check that we have a default value (skydir, for example,
                # does not (i.e. the default val is ""))
                def_path = \
                    _DEFAULT_CONFIG_[_SECTION_DIRS][
                        path_key]

                # if we have a default and it exists, use that.
                # otherwise log the error
                # noinspection PyTypeChecker
                if check_path(def_path):
                    p = Path(def_path)
                else:
                    # noinspection PyTypeChecker
                    self.path_errors[path_key].append(
                        "default invalid: " + def_path)

            # finally, if we have successfully deduced the path, set
            # it on the ConfigPaths object
            if p is not None:
                self.paths[path_key] = p
                # setattr(self.paths, path_key, p)

            # update config-file mirror
            self.currentValues[_SECTION_DIRS][path_key] = self[path_key]

        if self.path_errors:
            for att, errlist in self.path_errors.items():
                for err in errlist:
                    self.LOGGER << "Path error [" + att + "]: " + err
Example #9
0
def test_checkpath_expand(path, expect):
    assert bool(check_path(path, True)) == expect
Example #10
0
def test_checkpath_noexpand(path, expect):
    assert bool(check_path(path)) == expect
Example #11
0
    def _load_data_dirs(self, config):
        """
        Lookup the paths for the directories of game-related data (e.g.
        the main Skyrim folder, the mods-installation directory, and the
        virtual fs mount point). If the user has not configured these,
        use the default.

        :param configparser.ConfigParser config:
        """

        for evar, path_key in (
            (EnvVars.SKYDIR, _KEY_SKYDIR),
            (EnvVars.MOD_DIR, _KEY_MODDIR),
            (EnvVars.VFS_MOUNT, _KEY_VFSMNT),
        ):
            p = None  # type: Path

            # first, check if the user has specified an environment var
            envval = self._environment[evar]
            if envval:
                if check_path(envval):
                    p = Path(envval)
                else:
                    self.path_errors[path_key].append(envval)

            # if they didn't or it didn't exist, pull the config value
            if p is None:
                try:
                    config_val = self._get_value_from(config, _SECTION_DIRS,
                                                      path_key)
                except exceptions.MissingConfigKeyError as e:
                    self.missing_keys.append((e.section, path_key))
                else:
                    if check_path(config_val):
                        p = Path(config_val)
                    else:
                        self.path_errors[path_key].append(config_val)

            if p is None:
                # if key wasn't in config file for some reason,
                # check that we have a default value (skydir, for
                # example, does not (i.e. the default val is ""))
                def_path = _DEFAULT_CONFIG_[_SECTION_DIRS][path_key]

                # if we have a default and it exists, use that.
                # otherwise log the error
                if check_path(def_path):
                    p = Path(def_path)
                else:
                    # noinspection PyTypeChecker
                    self.path_errors[path_key].append(
                        f"default invalid: '{def_path}'")

            # finally, if we have successfully deduced the path, set
            # it on the PathManager
            if p is not None:
                self.LOGGER << f"Setting appfolder {path_key!r} to {p}"

                # force notification since this is the first 'official'
                # contact with the folder paths
                self.mainmanager.Folders[path_key].set_path(p,
                                                            force_notify=True)

            # and update config-file mirror
            # note -- have to use strings to keep config parser happy
            self._set_value(_SECTION_DIRS, path_key, "" if not p else str(p))

        if self.path_errors:
            for att, errlist in self.path_errors.items():
                for err in errlist:
                    self.LOGGER.warning(f"Path error [{att}]: {err}")