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)
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()
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)
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)
def test_checkpath_expand(path, expect): assert bool(check_path(path, True)) == expect
def test_checkpath_noexpand(path, expect): assert bool(check_path(path)) == expect
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)
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
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}")