<предыдущая статья серии

Приветствую, земляне! Прошу прощения за задержку, хоть материал настоящей статьи я начал готовить уже давно, но тем не менее, чем я дольше работал со своим творением, тем больше росло мое недовольство принятыми ранее решениями, сейчас не буду их перечислять, по ходу повествования станет понятно, что к чему. Также для приличия надо было дать народу документацию, и решил я для этих дел запустить отдельный сайт. К тому же, прохладная реакция сообщества на сею концепцию несколько демотивировала, всего чуть более 3 тыс. просмотров за все время, интересно, этим кто-то воспользовался данной системой кроме меня, отпишитесь если чё.

Ладно, лучше поздно, чем никогда, и «мы продолжаем продолжать». Сегодня будем разбирать на части систему PUSSY, как и обещал в речь пойдет о свойствах и контейнерах для них, и как это работает в составе Менеджера.  Свойства является ключевой фичей системы, благодаря которой возможно реализовать быстрое написание конфигурации для хранения пользовательских настроек, программа сама организует их хранение на жестком диске, сама отрендерит интерфейс для ввода информации. Данную часть фреймворка можно использовать в собственных программах, например, это дает возможность создавать формы для ввода данных и прочего. В данной статье я буду не только рассказывать, как и что работает, но и поделись ходом своих мыслей при принятии тех или иных решений (в процессе работы над статье и документацией некоторые решения начали казаться спорными, ну работа в процессе, что-то будет в будущем изменено, поэтому текст статьи относится к актуальной реализации; как обычно все обновления можно отследить в моем Telegram-канале). Лишний раз напомню, что я самоучка и на кодера не учился ни на курсах, ни в академиях; я делаю то, что мне интересно и так как считаю нужным. Я предупредил.

Свойства

Начну свое повествование с самого базового элемента этой истории – Свойства. Отойдем на минуточку от реализации и кода, всему свое время, а сейчас немного пофантазируем.

Что представляет из себя Свойство? Это просто ячейка, которая хранит в себе некоторые важные (или нет) данные, у нее есть собственные средства для визуального взаимодействия с этими данными, также имеется набор регуляторов, которые определяют поведение ячейки и само собой она реализует набор средств для взаимодействия с ней извне.

Ячейка с данным типа int
Ячейка с данным типа int

Так можно представить упомянутую «ячейку», как электронное устройств с дисплеем для отображения информации, набором ползунков для его настройки и разъемом для подключения к внешним устройствам. Вообще удобно представлять компоненты из диджитал мира в виде осязаемых аналогов, такие аналогии используются повсеместно, не мне об этом рассказывать. Главная идея, которую я преследовал при проектировании – добиться максимальной независимости данного типа элементов от внешнего окружения.

Все, довольно витать в облаках, возвращаемся на землю. Начнем со знакомства с терминологией вселенной Свойств:

· Значение – это важные данные, которые Свойство будет в себе хранить;

· Параметры – это служебные данные, которые определяют поведение Свойства;

· Виджет – этот термин должен быть знаком все мастерам Qt приложений, это элемент графического интерфейса, через который пользователь взаимодействует со значением.

Набросаю на основе предыдущего рассказа схемку нашего типового Свойства.

так устроено свойство
так устроено свойство

ШБР2# PUSSY. Разбираем по кирпичикам систему СвойствЧто же касается правил взаимодействия с внешним миром, то я определил следующий набор действий:

· чтение и запись значения;

· экспорт и импорт значений параметров;

· получение виджета и извлечение данных из него и обновление значения.

Реализация

Надеюсь, с фундаментальной концепцией все понятно, теперь, приступим к реализации, в моем случае свойства разделяются на два типа, о них мы поговорим подробнее далее, но все они наследуются от AbstractProperty, который имеет 9 методов:

·  __init__() – метод для инициализации свойства, в качестве аргументов ему нужно передать: дефолтное значение свойства, значения параметров, отображаемое имя свойства и так далее, каждое свойство имеет разные типы значений и набор параметров;

· value(), set_value() – методы для получения и установки значения (тех самых важных данных);

· get_parameters_dict() – метод который упаковывает и возвращает словарь со значениями параметров свойства;

· set_parameters_from_dict() – этот метод делает обратную операцию: устанавливает параметры в соответствии со значениями, которые находятся в переданном словаре;

· get_input_widget() — данный метод дает возможность конечному пользователю взаимодействовать с данными, которое свойство хранит в себе, он возвращает элемент графического интерфейса(виджет), свойство должно сохранить ссылку на него, чтобы иметь возможно извлечь в будущем данные из виджета;

· extract_widget_data() – извлекает данные из виджета и обновляет значение свойства;

· get_name() – возвращает имя свойства или его перевод, это имя используется для подписи виджета в пользовательском интерфейсе;

· retranslate() – переводит отображаемые данные виджета на другие языки, например, элементы выпадающего списка, всплывающая подсказка и так далее.

Если что, весь код доступен в репозитории.

Итак, одну остановку мы прошли, теперь, посмотрим на Свойства более конкретно, рассмотрев базовую реализацию классов свойств, на данный момент существует два типа свойств: с валидацией значения по заданным параметрам и без нее. Кстати, я не сразу пришел к идее валидации значения, но затем подумал: «вот будет программа изменять параметры, тогда значение изменять придется, а то вдруг оно не вписывается…», и чтобы не заботиться об этом и было решено добавить механизм валидации, который приведет значение в соответствие с заданными параметрами.

Начнем с рассмотрения свойства без валидации (класс Property (унаследован от абстрактного класса AbstractProperty)), ниже его код

class Property(AbstractProperty):
    def __init__(self, default_value: Any, name: str = "Unnamed", tool_tip="") -> None:
        """Инициализация экземпляра свойства"""
        self._value = default_value
        self.p_name = name
        self.p_tool_tip = tool_tip

    def value(self) -> Any:
        """Возвращает значение свойства"""
        return self._value

    def set_value(self, value: Any) -> None:
        """ Устанавливает значение свойства """
        self._value = value

    def get_parameters_dict(self) -> dict[str, Any]:
        """ Возвращает словарь параметров свойства"""
        return {key: value for key, value in vars(self).items() if key.startswith("p_")}

    def set_parameters_from_dict(self, params: dict[str, Any]) -> None:
        """ Устанавливает параметры свойства из словаря"""
        for key, val in params.items():
            if hasattr(self, key) and type(getattr(self, key)) is type(val):
                setattr(self, key, val)

    def get_input_widget(self) -> QWidget:
        """Возвращает ссылку на виджет"""
        pass

    def extract_widget_data(self) -> bool:
        """Извлекает данные из виджета
        Возвращает True, если оригинальное значение было изменено, False – в противном случае"""
        pass

    def get_name(self) -> str:
        """Возвращает имя свойства или перевод, если в словарях есть перевод"""
        return QCoreApplication.translate("properties", self.p_name)

    def retranslate(self) -> None:
"""Перевод отображаемой информации в виджете на другие языки, данные берутся из словарей"""
        pass

В данном базовом классе реализованы только универсальные методы, которые подходят для всех типов свойств, остальные же более специфичны и требуют собственной реализации для каждого отдельного случая.

 В методе __init__() мы задаем значение свойства и его параметры, параметры это просто атрибуты объекта, но чтобы они хоть как-то выделялись от остальных было решено ввести соглашение имен: «имена параметров должны начинаться с префикса «p_». Ниже список стандартных аргументов, которые должны присутствовать в каждом свойстве:

· default_value - значение свойства по умолчанию;

· name - отображаемое имя свойства;

· tool_tip - всплывающая подсказка, которая появляется при наведении курсора на виджет.

Метод get_parameters_dict() просто отбирает атрибуты с именами, которые начинаются на «p_» и упаковывает их в словарь, ничего замысловатого в этом нет,  а метод set_parameters_from_dict() производит обратную операцию: устанавливает значения, которые имеются в переданном ему словаре, но при условии, что типы значений совпадают, в противном случае значения не будут изменены, проверка типов добавлена на случай, если по каким то причинам имена и типы свойств изменятся, а в базе данных будут храниться неактуальные значения.

Теперь, поглядим на реализацию Свойства с механизмом валидации - PropertyValidated (унаследован от класса Property), вот его код

class PropertyValidated(Property):

    def __init__(self, default_value: Any, name: str = "Unnamed", tool_tip="") -> None:
        self._set_validation(False)
        super().__init__(default_value, name, tool_tip)

    def __setattr__(self, key, value) -> None:
        self.__dict__[key] = value

        if self._do_validation:
            self.validate_value(key)


    def validate_value(self, changed_attr:str="") -> None:
        """Выполняет валидацию значения свойства
        Принимает: changed_attr – имя изменяемого атрибута"""
        check_list = ["_value"]
        if not changed_attr or (changed_attr in check_list):
            pass

    def _set_validation(self, state:bool=True):
        self._do_validation = state

    def set_parameters_from_dict(self, params: dict[str, Any]) -> None: 
        for key, val in params.items():
            if hasattr(self, key) and type(getattr(self, key)) is type(val):
                self.__dict__[key] = val

        self.validate_value()

Тут все предельно просто, здесь мы переопределяем магический метод  __setattr__(), который вызывается всякий раз, когда объекту присваивается атрибут, в нем производится проверка флага _do_validation (он устанавливается методом _set_validation()),  и если он установлен как True, то вызывается метод validate_value(). Также был изменен метод set_parameters_from_dict(), в нем присвоение значение производится через словарь __dict__, чтобы лишний раз не вызывать метод __setattr__() и не войти в бесконечный цикл. Вот, собственно, и все отличия.

Итоговая реализация свойств

Итак, мы рассмотрели реализацию общих методов, теперь, мы рассмотрим разработку на конкретных примерах. Этот раздел можно использовать как гайд для написания кастомных свойств, также можно почитать гайд из документации.

Свойство без валидации значения

Есть в готовом наборе свойство для ввода булевого значения, называется оно BoolProperty. При разработке собственного класса свойства необходимо решить вопрос по поводу того, какой виджет будет использован для ввод/вывода значения, тип самого значения и набор параметров, которые необходимы для установки поведения свойства. В данном случае значению подойдет встроенный тип bool и чекбокс в качестве виджета, в качестве параметров будут стандартные: имя свойства и всплывающая подсказка. Ниже представлен код класса

class BoolProperty(Property):

    def __init__(self, default_value:bool=False, name="Unnamed", tool_tip=""):
        self._value = default_value
        self.p_name = name
        self.p_tool_tip = tool_tip

    def get_input_widget(self) -> QCheckBox:
        self._widget_ref = QCheckBox()
        self._widget_ref.setCheckState(Qt.Checked if self._value else Qt.Unchecked)
        self.retranslate()
        return self._widget_ref

    def extract_widget_data(self) -> bool:
        has_value_changed: bool = False
        if hasattr(self, "_widget_ref"):
            widget_value: bool = self._widget_ref.checkState() == Qt.Checked
            if self._value != widget_value:
                self._value = widget_value
                has_value_changed = True
        return has_value_changed

    def retranslate(self) -> None:
        self._widget_ref.setToolTip(QCoreApplication.translate("properties", self.p_tool_tip))

Все предельно просто. В методе __init__() производится установка значения и параметров, еще раз напомню, что имена параметров начинаются на «p_», значение желательно сохранять в поле _value, чтобы методы получения и установки значения (value() и set_value()) могли к нему обратиться, в противном случае придется переопределить их реализацию, чтобы все работало путем.

В методе get_input_widget() мы создаем наш виджет, инициализируем его и возвращаем в качестве результата. Значение свойства не обновляются в реальном времени, это делается по требованию при помощи метода extract_widget_data(), в нем производится сравнение значения внутри свойства и того, что записано в виджете, если они не совпадают, то значение обновляется и метод возвращает True, если обновление не требуется, то вернет False.

В методе retranslate() нужно произвести перевод отображаемых данных, если не в курсе как работает метод translate() класса QCoreApplication отсылаю к документации к PySide или к одной из моих статей.

Свойство с валидацией значения

Итак, сделаем еще одно свойство – для ввода целого числа (уже есть в базовом комплекте). Как обычно определяем тип хранимого значения – int; виджет – поле ввода целых чисел (QSpinBox); для конфигурации нам кроме общих потребуются следующие параметры: минимальное и максимальное значения, шаг изменения значения в виджете при нажатии на стрелки с краю в поле ввода.

class IntProperty(PropertyValidated):

    def __init__(self, default_value:int = 0, name="Unnamed", minimum=1, maximum=10, single_step=1, tool_tip=""):
        self._set_validation(False)
        self.p_name = name
        self.p_minimum = minimum
        self.p_maximum = maximum
        self.p_single_step = single_step
        self.p_tool_tip = tool_tip
        self._set_validation(True)
        self._value = default_value

    def get_input_widget(self) -> QSpinBox:
        self._widget_ref = QSpinBox()
        self._widget_ref.setMinimum(self.p_minimum)
        self._widget_ref.setMaximum(self.p_maximum)
        self._widget_ref.setSingleStep(self.p_single_step)
        self._widget_ref.setValue(self._value)
        self.retranslate()

        sizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
        self._widget_ref.setSizePolicy(sizePolicy)

        return self._widget_ref

    def extract_widget_data(self) -> bool:
        has_value_changed: bool = False
        if (hasattr(self, "_widget_ref")) and (self._value != self._widget_ref.value()):
            self._value = self._widget_ref.value()
            has_value_changed = True
        return has_value_changed

    def retranslate(self) -> None:
        self._widget_ref.setToolTip(QCoreApplication.translate("properties", self.p_tool_tip))

    def validate_value(self, changed_attr:str=""):
        check_list = ["_value", "p_maximum", "p_minimum"]
        if (changed_attr in check_list) or not changed_attr:
            value = self._value
            if value < self.p_minimum:
                self.__dict__["_value"] = self.p_minimum
            elif value > self.p_maximum:
                self.__dict__["_value"] = self.p_maximum

Производим в методе __init__() аналогичные действия, только перед инициализацией параметров вызовем метод _set_validation() и передадим ему значение False, чтобы деактивировать валидацию, так как параметры еще не созданы и валидация попросту невозможна, устанавливаем значения параметров и включаем валидацию передачей True  _set_validation() и соответственно устанавливаем само значение.

Разжевывать методы get_input_widget(), extract_widget_data и retranslate() смысла не вижу, там все аналогично, но со своими особенностями.

Процедура валидации значения прописана в методе validate_value(), в данном случае производится проверка на вхождение значения в заданный диапазон, если оно выходит за его границе, то оно приводится к ближайшей границе диапазона. В переменной check_list содержится список параметров, которые влияют на значение и само поле со значением, если изменяемый атрибут объекта не содержится в списке, то процедура валидации прерывается. Сам метод вызывается при изменении атрибута объекта в методе __setattr__().

Вот и все, теперь, вы знаете как создать собственные свойства, только надо учесть один нюанс: типы значения и параметров надо выбирать из тех, которые могут быть сериализованы с помощью модуля pickle.

Контейнер свойств

КС как понятно из названия вмещает в себя свойства, точнее хранит ссылки на их экземпляры. По своему устройству КС напоминает свойства, мы также можем извлечь значения или установить их, есть возможность сделать это сразу для всех или для каждого отдельно, тоже самое с параметрами, запросить графический интерфейс и извлечь данные из виджетов и тому подобное.  

Собственный контейнер надо объявлять следующим образом:

class MyContainer(PropertyContainer):
    <имя свойства>: < Класс Свойства>(<начальные параметры>)
    <имя свойстваN>: < Класс Свойства>(<начальные параметры>)

в последствии все свойства будут помещены в поле __annotations__, здесь можно объявлять только классы свойств. Ниже весь код класса КС:

Код класса PropertyContainer
class PropertyContainer:

    def __getattr__(self, item) -> Any:
        if item in self.__annotations__:
            return self.get_property_value(item)
        else:
            raise AttributeError(f"<{item}> doesn't exist")

    @classmethod
    def render_layout(cls) -> QWidget:
        """Возвращает экземпляр QWidget с размещенными виджетами для ввода значений свойств"""
        scroll_area = QScrollArea()
        scroll_area.setWidgetResizable(True)
        widget_content = QWidget()
        layout = QFormLayout(widget_content)
        layout.setVerticalSpacing(15)
        layout.setLabelAlignment(Qt.AlignRight | Qt.AlignTrailing | Qt.AlignVCenter)
        row: int = 0
        cls._lable_list = {}
        for key, prop in cls.__annotations__.items():
            cls._lable_list[key] = QLabel(prop.get_name())
            cls._lable_list[key].setWordWrap(True)
            layout.setWidget(row, QFormLayout.LabelRole, cls._lable_list[key])
            layout.setWidget(row, QFormLayout.FieldRole, prop.get_input_widget())
            row += 1
        scroll_area.setWidget(widget_content)
        return scroll_area

    @classmethod
    def get_property(cls, name: str) -> AbstractProperty:
        """ Возвращает ссылку на свойство с именем <name>"""
        return cls.__annotations__[name]

    @classmethod
    def get_property_value(cls, name: str) -> Any:
        """ Возвращает  значение свойства с именем <name> """
        return cls.__annotations__[name].value()

    @classmethod
    def update_data(cls) -> bool:
        """ Извлекает данные из виджетов
        Вернет True если значение любого свойства было изменено, False – в противном случае"""
        is_updated = False

        for key, prop in cls.__annotations__.items():
            update_status = prop.extract_widget_data()
            if not is_updated and update_status:
                is_updated = True
        return is_updated

    @classmethod
    def propvalues_to_dict(cls) -> dict[str, (AbstractProperty, Any)]:
        """ Возвращает словарь со значениями свойств в контейнере"""
        prop_values_dict = {}
        for key, prop in cls.__annotations__.items():
            prop_values_dict[key] = (type(prop), prop.value())
        return prop_values_dict

    @classmethod
    def set_propvalues_from_dict(cls, prop_dict: dict[str, (AbstractProperty, Any)]) -> None:
        """ Принимает словарь со значениями свойств и устанавливает их для свойств в контейнере """
        for key, prop in cls.__annotations__.items():
            if (key in prop_dict) \
                    and (isinstance(prop, prop_dict[key][0]))\
                    and (isinstance(prop_dict[key][1], type(prop.value()))):
                prop.set_value(prop_dict[key][1])

    @classmethod
    def prop_params_to_dict(cls) -> dict[str, (AbstractProperty, dict)]:
        """ Возвращает словарь с параметрами свойств"""
        prop_params_dict = {}
        for name, prop in cls.__annotations__.items():
            prop_params_dict[name] = (type(prop), prop.get_parameters_dict())
        return prop_params_dict

    @classmethod
    def set_prop_params_from_dict(cls, params_dict: dict[str, (AbstractProperty, dict)]) -> None:
        """ Принимает словарь с параметрами свойств и устанавливает для тех, что в контейнере"""
        for name, prop in cls.__annotations__.items():
            if (name in params_dict) \
                    and (isinstance(prop, params_dict[name][0])):
                prop.set_parameters_from_dict(params_dict[name][1])

    @classmethod
    def retranslate(cls) -> None:
        for key, label in cls._lable_list.items():
            label.setText(cls.get_property(key).get_name())

        for key, prop in cls.__annotations__.items():
            prop.retranslate()

Итак, разберемся, что к чему. Магический метод  __getattr__() нам нужен, чтобы удобно вытягивать значения свойств, чтобы это сделать нужно создать экземпляр КС и после оператора «.» указать фактическое имя свойства.

render_layout() здесь этот метод самый интересный, так как наводит на экране красоту, он выдергивает виджеты из свойств и формирует форму, которая будет выведена для редактирования значений свойств внутри контейнера. Менеджер обратится к этому методу, когда пользователь вызовет настройки плагина, чтобы вывести на экран соответствующее диалоговое окно,  если упомянутые термины вызывают вопросы, то нужно обратиться к предыдущей статье или к соответствующему разделу вики. Если кратко, то Менеджер - это программа, предназначенная для эксплуатации плагинов. В данном случае, код создает область с полосой прокрутки и форму, которая представляет из себя таблицу из двух столбцов, где в первом столбце помещается имя свойства (извлекаем его из свойства методом get_name()), а справа сам виджет свойства (извлекается методом get_input_widget()). Виджеты с подписями, содержащими имена свойств помещаются в словарь _lable_list, чтобы потом можно было обратиться к ним в будущем, например, для выполнения перевода на другой язык. Если хочется сделать макет поинтереснее, то этот метод придется переопределить, но у меня есть мысли сделать механизм для декларативного описания макета, с последующей автоматической конвертацией в готовый интерфейс. Также о разработке своего макета можно почитать в материале вики, там рассказано как сгруппировать свойства внутри виджета в виде аккордеона. В методе retranslate() производится обход подписей с именами свойств и установление перевода(при наличии) и отображаемый данных самих свойств.  Когда пользователь нажмет «Ок» Менеджер вызывает update_data() и производится обход всех свойств и для каждого вызывается метод для обновления значений, если хотя бы одно значение было изменено, то update_data() вернет True, иначе – False.

Метод render_layout() сделал свою работу
Метод render_layout() сделал свою работу

Об оставшихся методах сказать особо нечего, они нужны для импорта/экспорта значений и параметров.

Итак, мы рассмотрели как устроены свойства и контейнер для них, как вы видите - все очень просто. В настоящий момент в них реализован функционал для осуществления интернационализации, но, к сожалению пока это не работает на уровне Менеджера.

Как это работает в составе Менеджера

Как вы все уже знаете: свойства хранятся в контейнере, который должен быть ассоциирован с классом плагина, который наследуется от UBWidget, для ассоциации классу надо добавить атрибут ub_settings и присвоить ему ссылку на класс контейнера. При загрузке Менеджера, происходит импорт пакетов с плагинами, а вместе с ними подтянутся классы с контейнерами, если они, конечно, были добавлены разработчиками. Логично будет предположить, что при каждой процедуре импорта значения будут оставаться дефолтными, поэтому обновленную информацию о параметрах и значениях свойств нужно сохранять на жестком диске. Для этой цели используется стандартный модуль shelve, данный модуль очень прост в использовании и действует как словарь, он позволяет обращаться к значениям по ключу, которое представляет из себя строковое значение, он использует модуль pickle для сериализации данных.

Свойства и контейнеры проходят следующий жизненный цикл:

· Менеджер сканирует папку Plugins и те, что указаны юзером в настройках на присутствие плагинов(пакетов Python);

· производится проверка базы данных на наличие данных плагинов, если записей нет, то записываются дефолтные данные значений и параметров;

· если записи присутствуют, то контейнеру передается сначала словарь с параметрами, затем словарь со значениями;

· пользователь может обратиться к настройкам плагина для редактирования значений свойств в контейнере, если он изменит хотя бы значение одного свойства, то после принятия (по нажатию на «Ок») будет осуществлена перезапись информации в базе;

· в коде плагина может производиться изменение параметров свойств, чтобы изменения были сохранены на диске, нужно воспользоваться методом save_settings_parameters() класса UBHelper.

В качестве ключей к записям базы используется адреса папок, где размещаются плагины, поэтому, если по каким-то причинам плагин «переедет» в другое место на диске, то в базе создастся новая запись с дефолтными данными, данные хранятся в специальном датаклассе. 

Минутка самокритики и планы на будущее

Итак, мы прошлись еще по одному аспекту моего программного комбайна. В процессе написания этой статьи и материалов документации я все глубже погружался в детали и прикидывал возможные сценарии использовании и мое недовольство текущим состоянием программы только росло, к сожалению некоторые проблемы не могут быть просто решены из-за особенностей фреймворка Qt.

Главная проблема, которая находится в рамках Qt это то, что интерфейс выполняется только в одном главном потоке приложения, а все плагины в основе своей представляют собой интерфейс и в отдельные потоки не могут быть вынесены, поэтому если один из плагинов случайно или умышленно зависнет, то с ним встанет все приложение. Теоретически данную проблему можно решить следующим способом: в отдельном потоке сделать процедуру, которая периодически будет принимать сигналы от основного потока, если основной поток зависнет, то сигналы перестанут поступать, в таком случае программа будет принудительно остановлена и на диске будет сделана соответствующая запись, при повторном запуске пользователю будет предложено отключить плагины, которые предположительно работают нестабильно.

Что касается свойств и контейнера, то им не хватает системы отправки и обработки сигналов, например, контейнер бы посылал сигнал, когда значение в виджете свойства было изменено, чтобы, например, добавить интерактивности в форму редактирования настроек, сейчас это сделать будет проблематично, так как виджет свойства создается только в момент создания формы редактирования, что создаст только лишние трудности.

Также в целом хочется сделать систему дружелюбнее, чтобы она сама устанавливала нужные пакеты из репозитория pip, а то сейчас это надо делать ручками. И в мечтах: чтобы был каталог плагинов, и все загружалось по клику и устанавливалось по клику.

Наконец добавить полноценную систему интернационализации, в данное момент видится одна проблема: вероятность конфликта словарей, по причине которой может подтянуться перевод не из того словаря. Решение видится в том, чтобы загружать словари только в момент их применения.

В комментариях к предыдущей статье было предложение сделать средства обмена информацией между плагинами, хотя изначально я о таком даже не думал, так как оригинальная идея была сделать некий «комбайн» из маленьких автономных утилит, которые бы автоматизировали некоторые мои задачи и их было бы относительно быстро и легко разрабатывать, а связь между отдельными элементами только все усложнит, так как на личном опыте знаю, что чем больше программа, тем сложнее ее поддерживать, а так, сделал быстро программу, которая бы решала некоторую задачу и на этом все, пусть лежит в «коллекции» и ждет своего часа.

Еще бы я хотел дать совет таким же зеленым разработчикам как я: никогда не надо торопиться с написанием кода, конечно, я понимаю, что хочется получить пощупать готовый продукт как можно скорее, но тщательное проектирование в будущем убережет от лишних потерь пота и крови, когда будете пытаться сохранить совместимость старого и нового кода, особенно это касается фреймворков и библиотек, так как придется городить заплатки, чтобы старый код не развалился на новых версиях. Ну и конечно тестировать во всех возможных сценариях. Всем рабочего кода без багов?.

Ссылки

Комментарии (4)


  1. kozlov_de
    05.07.2024 09:10

    КИСКА - рискованное в приличном обществе название


    1. IronMesh Автор
      05.07.2024 09:10

      В общества с ограниченным лексиконом... Не надо меня в такие звать


  1. FreeNickname
    05.07.2024 09:10
    +1

    Главная проблема, которая находится в рамках Qt это то, что интерфейс выполняется только в одном главном потоке приложения

    Так работают (практически?) все графические фреймворки на всех(?) платформах. По крайней мере, других я не видел.

    а все плагины в основе своей представляют собой интерфейс и в отдельные потоки не могут быть вынесены

    Почему не могут? Обычная практика же. Когда плагину нужно выполнить какое-нибудь потенциально долгое действие, он показывает какую-нибудь UI-индикацию (крутилочку там, например), запускает действие в отдельном потоке, а по окончании действия в главном потоке убирает крутилочку и показывает результат. Без понятия, как это делается в Python, но как-то должно делаться.


    1. IronMesh Автор
      05.07.2024 09:10

      Потоки и индикация это само собой разумеется. Я говорю про те случаи, когда сторонний плагин может быть грубо выполнен, намеренно или нет, и таким макаром повесит все приложение