diff --git a/lib/solaar/ui/about.py b/lib/solaar/ui/about.py deleted file mode 100644 index 52494f4b4..000000000 --- a/lib/solaar/ui/about.py +++ /dev/null @@ -1,102 +0,0 @@ -## Copyright (C) 2012-2013 Daniel Pavel -## Revisions Copyright (C) Contributors to the Solaar project. -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation; either version 2 of the License, or -## (at your option) any later version. -## -## 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, write to the Free Software Foundation, Inc., -## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -import logging - -from gi.repository import Gtk - -from solaar import NAME -from solaar import __version__ -from solaar.i18n import _ - -_dialog = None - - -def _create(): - about = Gtk.AboutDialog() - - about.set_program_name(NAME) - about.set_version(__version__) - about.set_comments(_("Manages Logitech receivers,\nkeyboards, mice, and tablets.")) - about.set_icon_name(NAME.lower()) - about.set_logo_icon_name(NAME.lower()) - - about.set_copyright("© 2012-2024 Daniel Pavel and contributors to the Solaar project") - about.set_license_type(Gtk.License.GPL_2_0) - - about.set_authors(("Daniel Pavel http://github.com/pwr",)) - try: - about.add_credit_section(_("Additional Programming"), ("Filipe Laíns", "Peter F. Patel-Schneider")) - about.add_credit_section(_("GUI design"), ("Julien Gascard", "Daniel Pavel")) - about.add_credit_section( - _("Testing"), - ( - "Douglas Wagner", - "Julien Gascard", - "Peter Wu http://www.lekensteyn.nl/logitech-unifying.html", - ), - ) - about.add_credit_section( - _("Logitech documentation"), - ( - "Julien Danjou http://julien.danjou.info/blog/2012/logitech-unifying-upower", - "Nestor Lopez Casado http://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28", - ), - ) - except TypeError: - # gtk3 < ~3.6.4 has incorrect gi bindings - logging.exception("failed to fully create the about dialog") - except Exception: - # the Gtk3 version may be too old, and the function does not exist - logging.exception("failed to fully create the about dialog") - - about.set_translator_credits( - "\n".join( - ( - "gogo (croatian)", - "Papoteur, David Geiger, Damien Lallement (français)", - "Michele Olivo (italiano)", - "Adrian Piotrowicz (polski)", - "Drovetto, JrBenito (Portuguese-BR)", - "Daniel Pavel (română)", - "Daniel Zippert, Emelie Snecker (svensk)", - "Dimitriy Ryazantcev (Russian)", - "El Jinete Sin Cabeza (Español)", - "Ferdina Kusumah (Indonesia)", - ) - ) - ) - - about.set_website("https://pwr-solaar.github.io/Solaar") - about.set_website_label(NAME) - - about.connect("response", lambda x, y: x.hide()) - - def _hide(dialog, event): - dialog.hide() - return True - - about.connect("delete-event", _hide) - - return about - - -def show_window(trigger=None): - global _dialog - if _dialog is None: - _dialog = _create() - _dialog.present() diff --git a/lib/solaar/ui/about/__init__.py b/lib/solaar/ui/about/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/lib/solaar/ui/about/about.py b/lib/solaar/ui/about/about.py new file mode 100644 index 000000000..6760efdc8 --- /dev/null +++ b/lib/solaar/ui/about/about.py @@ -0,0 +1,36 @@ +## Copyright (C) Solaar Contributors +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## 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, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from solaar.ui.about.model import AboutModel +from solaar.ui.about.presenter import Presenter +from solaar.ui.about.view import AboutView + + +def show(model=None, view=None): + """Opens the About dialog.""" + if model is None: + model = AboutModel() + if view is None: + view = AboutView() + presenter = Presenter(model, view) + presenter.run() + + +if __name__ == "__main__": + from gi.repository import Gtk + + show() + Gtk.main() diff --git a/lib/solaar/ui/about/model.py b/lib/solaar/ui/about/model.py new file mode 100644 index 000000000..8b489c6c6 --- /dev/null +++ b/lib/solaar/ui/about/model.py @@ -0,0 +1,82 @@ +## Copyright (C) Solaar Contributors +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## 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, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from __future__ import annotations + +from datetime import datetime +from typing import List +from typing import Tuple + +from solaar import __version__ +from solaar.i18n import _ + + +def _get_current_year() -> int: + return datetime.now().year + + +class AboutModel: + def get_version(self) -> str: + return __version__ + + def get_description(self) -> str: + return _("Manages Logitech receivers,\nkeyboards, mice, and tablets.") + + def get_copyright(self) -> str: + return f"© 2012-{_get_current_year()} Daniel Pavel and contributors to the Solaar project" + + def get_authors(self) -> List[str]: + return [ + "Daniel Pavel http://github.com/pwr", + ] + + def get_translators(self) -> List[str]: + return [ + "gogo (croatian)", + "Papoteur, David Geiger, Damien Lallement (français)", + "Michele Olivo (italiano)", + "Adrian Piotrowicz (polski)", + "Drovetto, JrBenito (Portuguese-BR)", + "Daniel Pavel (română)", + "Daniel Zippert, Emelie Snecker (svensk)", + "Dimitriy Ryazantcev (Russian)", + "El Jinete Sin Cabeza (Español)", + "Ferdina Kusumah (Indonesia)", + ] + + def get_credit_sections(self) -> List[Tuple[str, List[str]]]: + return [ + (_("Additional Programming"), ["Filipe Laíns", "Peter F. Patel-Schneider"]), + (_("GUI design"), ["Julien Gascard", "Daniel Pavel"]), + ( + _("Testing"), + [ + "Douglas Wagner", + "Julien Gascard", + "Peter Wu http://www.lekensteyn.nl/logitech-unifying.html", + ], + ), + ( + _("Logitech documentation"), + [ + "Julien Danjou http://julien.danjou.info/blog/2012/logitech-unifying-upower", + "Nestor Lopez Casado http://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28", + ], + ), + ] + + def get_website(self): + return "https://pwr-solaar.github.io/Solaar" diff --git a/lib/solaar/ui/about/presenter.py b/lib/solaar/ui/about/presenter.py new file mode 100644 index 000000000..b63791de2 --- /dev/null +++ b/lib/solaar/ui/about/presenter.py @@ -0,0 +1,95 @@ +## Copyright (C) Solaar Contributors +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## 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, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +from __future__ import annotations + +from typing_extensions import Protocol + +from solaar.ui.about.model import AboutModel + + +class AboutViewProtocol(Protocol): + def init_ui(self) -> None: + ... + + def update_version_info(self, version: str) -> None: + ... + + def update_description(self, comments: str) -> None: + ... + + def update_copyright(self, copyright): + ... + + def update_authors(self, authors: list[str]) -> None: + ... + + def update_translators(self, translators: list[str]) -> None: + ... + + def update_website(self, website): + ... + + def update_credits(self, credit_sections: list[tuple[str, list[str]]]) -> None: + ... + + def show(self) -> None: + ... + + +class Presenter: + def __init__(self, model: AboutModel, view: AboutViewProtocol) -> None: + self.model = model + self.view = view + + def update_version_info(self) -> None: + version = self.model.get_version() + self.view.update_version_info(version) + + def update_credits(self) -> None: + credit_sections = self.model.get_credit_sections() + self.view.update_credits(credit_sections) + + def update_description(self) -> None: + comments = self.model.get_description() + self.view.update_description(comments) + + def update_copyright(self) -> None: + copyright = self.model.get_copyright() + self.view.update_copyright(copyright) + + def update_authors(self) -> None: + authors = self.model.get_authors() + self.view.update_authors(authors) + + def update_translators(self) -> None: + translators = self.model.get_translators() + self.view.update_translators(translators) + + def update_website(self) -> None: + website = self.model.get_website() + self.view.update_website(website) + + def run(self) -> None: + self.view.init_ui() + self.update_version_info() + self.update_description() + self.update_website() + self.update_copyright() + self.update_authors() + self.update_credits() + self.update_translators() + self.view.show() diff --git a/lib/solaar/ui/about/view.py b/lib/solaar/ui/about/view.py new file mode 100644 index 000000000..6670ac462 --- /dev/null +++ b/lib/solaar/ui/about/view.py @@ -0,0 +1,67 @@ +## Copyright (C) Solaar Contributors +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation; either version 2 of the License, or +## (at your option) any later version. +## +## 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, write to the Free Software Foundation, Inc., +## 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + + +from typing import List +from typing import Tuple +from typing import Union + +from gi.repository import Gtk + +from solaar import NAME + + +class AboutView: + def __init__(self) -> None: + self.view: Union[Gtk.AboutDialog, None] = None + + def init_ui(self) -> None: + self.view = Gtk.AboutDialog() + self.view.set_program_name(NAME) + self.view.set_icon_name(NAME.lower()) + self.view.set_license_type(Gtk.License.GPL_2_0) + + self.view.connect("response", lambda x, y: self.handle_close(x)) + + def update_version_info(self, version: str) -> None: + self.view.set_version(version) + + def update_description(self, comments: str) -> None: + self.view.set_comments(comments) + + def update_copyright(self, copyright_text: str): + self.view.set_copyright(copyright_text) + + def update_authors(self, authors: List[str]) -> None: + self.view.set_authors(authors) + + def update_credits(self, credit_sections: List[Tuple[str, List[str]]]) -> None: + for section_name, people in credit_sections: + self.view.add_credit_section(section_name, people) + + def update_translators(self, translators: List[str]) -> None: + translator_credits = "\n".join(translators) + self.view.set_translator_credits(translator_credits) + + def update_website(self, website): + self.view.set_website_label(NAME) + self.view.set_website(website) + + def show(self) -> None: + self.view.present() + + def handle_close(self, event) -> None: + event.hide() diff --git a/lib/solaar/ui/tray.py b/lib/solaar/ui/tray.py index 69c0e4a37..c9d382f14 100644 --- a/lib/solaar/ui/tray.py +++ b/lib/solaar/ui/tray.py @@ -51,7 +51,7 @@ def _create_menu(quit_handler): menu.append(no_receiver) menu.append(Gtk.SeparatorMenuItem.new()) - menu.append(action.make_image_menu_item(_("About %s") % NAME, "help-about", about.show_window)) + menu.append(action.make_image_menu_item(_("About %s") % NAME, "help-about", about.show)) menu.append(action.make_image_menu_item(_("Quit %s") % NAME, "application-exit", quit_handler)) menu.show_all() diff --git a/lib/solaar/ui/window.py b/lib/solaar/ui/window.py index f28ad7ce8..847963c2f 100644 --- a/lib/solaar/ui/window.py +++ b/lib/solaar/ui/window.py @@ -305,7 +305,7 @@ def _create_window_layout(): bottom_buttons_box.set_spacing(20) quit_button = _new_button(_("Quit %s") % NAME, "application-exit", _SMALL_BUTTON_ICON_SIZE, clicked=destroy) bottom_buttons_box.add(quit_button) - about_button = _new_button(_("About %s") % NAME, "help-about", _SMALL_BUTTON_ICON_SIZE, clicked=about.show_window) + about_button = _new_button(_("About %s") % NAME, "help-about", _SMALL_BUTTON_ICON_SIZE, clicked=about.show) bottom_buttons_box.add(about_button) diversion_button = _new_button( _("Rule Editor"), "", _SMALL_BUTTON_ICON_SIZE, clicked=lambda *_trigger: diversion_rules.show_window(_model) diff --git a/tests/solaar/ui/test_about_dialog.py b/tests/solaar/ui/test_about_dialog.py new file mode 100644 index 000000000..03da235dd --- /dev/null +++ b/tests/solaar/ui/test_about_dialog.py @@ -0,0 +1,27 @@ +from solaar.ui.about import about +from solaar.ui.about.model import AboutModel + + +def test_about_model(): + expected_name = "Daniel Pavel" + model = AboutModel() + + authors = model.get_authors() + + assert expected_name in authors[0] + + +def test_about_dialog(mocker): + view_mock = mocker.Mock() + + about.show(view=view_mock) + + assert view_mock.init_ui.call_count == 1 + assert view_mock.update_version_info.call_count == 1 + assert view_mock.update_description.call_count == 1 + assert view_mock.update_authors.call_count == 1 + assert view_mock.update_credits.call_count == 1 + assert view_mock.update_copyright.call_count == 1 + assert view_mock.update_translators.call_count == 1 + assert view_mock.update_website.call_count == 1 + assert view_mock.show.call_count == 1