import argparse from os import path from operator import itemgetter from collections import OrderedDict import yaml from PyQt5 import QtCore from PyQt5.QtCore import Qt class _OrderedLoader(yaml.Loader): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def _construct_mapping(loader, node): loader.flatten_mapping(node) result = OrderedDict(sorted(loader.construct_pairs(node), key=itemgetter(0))) return result _OrderedLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, _construct_mapping) def _validate_settings_not_blank(setting): settings_have_values = False for key, value in setting.items(): if value == str() or key == 'display_settings' or key == 'connect': pass else: settings_have_values = True break return settings_have_values def _append_parent_attribute(data: OrderedDict): for child in data.values(): if isinstance(child, OrderedDict): child.parent = data _append_parent_attribute(child) class SpecialDict(OrderedDict): def __init__(self, *args, **kwargs): super().__init__(sorted(kwargs.items())) def __getitem__(self, index): if isinstance(index, tuple): item = self for key in index: if item != (): item = item[key] return item else: return super().__getitem__(index) def __setitem__(self, key, value): if isinstance(key, tuple): item = self previous_item = None for k in key: if item != (): previous_item = item item = item[k] previous_item[key[-1]] = value else: return super().__setitem__(key, value) class SettingsModel(QtCore.QAbstractItemModel): command_prompt_signal = QtCore.pyqtSignal(str) # website name, color set_text_color_signal = QtCore.pyqtSignal(str, str) # website name, args instantiate_website = QtCore.pyqtSignal(str, list) # website name, activate/deactivate manage_website_state = QtCore.pyqtSignal(str, bool) show_website_missing = QtCore.pyqtSignal(str, bool) def __init__(self, data=None, parent=None): super().__init__(parent) if data is None: data = self._get_settings_helper() _append_parent_attribute(data) self.root = data self.my_index = {} # Needed to stop garbage collection def _get_args(self): parser = argparse.ArgumentParser() parser.add_argument('--settings_path', nargs='?', action='store') return parser.parse_args() def _get_settings_helper(self): main_dir = path.dirname(path.realpath(__file__)) main_dir = path.realpath(path.join(main_dir, '..', '..')) default_filepath = path.join(main_dir, 'default_settings.yml') user_filepath = path.join(main_dir, 'settings.yml') args = self._get_args() if args.settings_path: user_filepath = args.settings_path # open the default file and get version information with open(default_filepath) as default_filestream: default_filesettings = yaml.load(default_filestream) # FIXME: not used current_version = default_filesettings['version'].split('.') # flake8: noqa if path.exists(user_filepath): filepath = user_filepath else: filepath = default_filepath with open(filepath) as setting_file: self.settings = yaml.load(setting_file, _OrderedLoader) return SpecialDict(**self.settings) def index(self, row, column, parent): """Returns QModelIndex to row, column in parent (QModelIndex)""" if parent.isValid() and parent.column() != 0: return QtCore.QModelIndex() if parent.isValid(): parent_pointer = parent.internalPointer() parent_dict = self.root[parent_pointer] else: parent_dict = self.root parent_pointer = () row_key = list(parent_dict.keys())[row] child_pointer = (*parent_pointer, row_key) try: child_pointer = self.my_index[child_pointer] except KeyError: self.my_index[child_pointer] = child_pointer index = self.createIndex(row, column, child_pointer) return index def setData(self, index, value, role=Qt.EditRole): pointer = self.my_index[index.internalPointer()] self.root[pointer] = value self.dataChanged.emit(index, index) return True def flags(self, index): if not index.isValid(): return 0 if index.column() == 0: return Qt.ItemIsEnabled | Qt.ItemIsSelectable return Qt.ItemIsEditable | Qt.ItemIsEnabled | Qt.ItemIsSelectable def get_row(self, key): """Returns the row of the given key (list of keys) in its parent""" if key: parent = key[:-1] if not parent: return 0 return list(self.root[parent].keys()).index(key[-1]) else: return 0 def parent(self, index): """ Returns the parent (QModelIndex) of the given item (QModelIndex) Top level returns QModelIndex() """ if not index.isValid(): return QtCore.QModelIndex() child_key_list = index.internalPointer() if child_key_list: parent_key_list = child_key_list[:-1] if parent_key_list == ((),): return QtCore.QModelIndex() try: parent_key_list = self.my_index[parent_key_list] except KeyError: self.my_index[parent_key_list] = parent_key_list return self.createIndex(self.get_row(parent_key_list), 0, parent_key_list) else: return QtCore.QModelIndex() def rowCount(self, parent): """Returns number of rows in parent (QModelIndex)""" if parent.column() > 0: return 0 # only keys have children, not values if parent.isValid(): indexPtr = parent.internalPointer() parentValue = self.root[indexPtr] if isinstance(parentValue, OrderedDict): return len(self.root[indexPtr]) else: return 0 else: return len(self.root) def columnCount(self, parent): return 2 # Key & value def data(self, index, role): """Returns data for given role for given index (QModelIndex)""" if not index.isValid(): return None if role != Qt.DisplayRole and role != Qt.EditRole: return None indexPtr = index.internalPointer() if index.column() == 1: # Column 1, send the value return self.root[indexPtr] else: # Column 0, send the key return indexPtr[-1]