|
| 1 | +# Code Style |
| 2 | + |
| 3 | +Most of the style guidelines can be checked, fixed, and enforced via Ruff. Older code may not be adhering to all of these guidelines, in which case _"do as I say, not as I do"..._ |
| 4 | + |
| 5 | +- Do your best to write clear, concise, and modular code. |
| 6 | + - This should include making methods private by default (e.g. `__method()`) |
| 7 | + - Methods should only be protected (e.g. `_method()`) or public (e.g. `method()`) when needed and warranted |
| 8 | +- Keep a maximum column width of no more than **100** characters. |
| 9 | +- Code comments should be used to help describe sections of code that can't speak for themselves. |
| 10 | +- Use [Google style](https://google.github.io/styleguide/pyguide.html#s3.8-comments-and-docstrings) docstrings for any classes and functions you add. |
| 11 | + - If you're modifying an existing function that does _not_ have docstrings, you don't _have_ to add docstrings to it... but it would be pretty cool if you did ;) |
| 12 | +- Imports should be ordered alphabetically. |
| 13 | +- Lists of values should be ordered using their [natural sort order](https://en.wikipedia.org/wiki/Natural_sort_order). |
| 14 | + - Some files have their methods ordered alphabetically as well (i.e. [`thumb_renderer`](https://github.com/TagStudioDev/TagStudio/blob/main/src/tagstudio/qt/widgets/thumb_renderer.py)). If you're working in a file and notice this, please try and keep to the pattern. |
| 15 | +- When writing text for window titles or form titles, use "[Title Case](https://apastyle.apa.org/style-grammar-guidelines/capitalization/title-case)" capitalization. Your IDE may have a command to format this for you automatically, although some may incorrectly capitalize short prepositions. In a pinch you can use a website such as [capitalizemytitle.com](https://capitalizemytitle.com/) to check. |
| 16 | +- If it wasn't mentioned above, then stick to [**PEP-8**](https://peps.python.org/pep-0008/)! |
| 17 | + |
| 18 | +## QT |
| 19 | +As of writing this section, the QT part of the code base is quite unstructured and the View and Controller parts are completely intermixed[^1]. This makes maintenance, fixes and general understanding of the code base quite challenging, because the interesting parts you are looking for are entangled in a bunch of repetitive UI setup code. To address this we are aiming to more strictly separate the view and controller aspects of the QT frontend. |
| 20 | + |
| 21 | +The general structure of the QT code base should look like this: |
| 22 | +``` |
| 23 | +qt |
| 24 | +├── controller |
| 25 | +│ ├── widgets |
| 26 | +│ │ └── preview_panel_controller.py |
| 27 | +│ └── main_window_controller.py |
| 28 | +├── view |
| 29 | +│ ├── widgets |
| 30 | +│ │ └── preview_panel_view.py |
| 31 | +│ └── main_window_view.py |
| 32 | +├── ts_qt.py |
| 33 | +└── mixed.py |
| 34 | +``` |
| 35 | + |
| 36 | +In this structure there are the `view` and `controller` sub-directories. They have the exact same structure and for every `<component>_view.py` there is a `<component>_controller.py` at the same location in the other subdirectory and vice versa. |
| 37 | + |
| 38 | +Typically the classes should look like this: |
| 39 | +```py |
| 40 | +# my_cool_widget_view.py |
| 41 | +class MyCoolWidgetView(QWidget): |
| 42 | + def __init__(self): |
| 43 | + super().__init__() |
| 44 | + self.__button = QPushButton() |
| 45 | + self.__color_dropdown = QComboBox() |
| 46 | + # ... |
| 47 | + self.__connect_callbacks() |
| 48 | + |
| 49 | + def __connect_callbacks(self): |
| 50 | + self.__button.clicked.connect(self._button_click_callback) |
| 51 | + self.__color_dropdown.currentIndexChanged.connect( |
| 52 | + lambda idx: self._color_dropdown_callback(self.__color_dropdown.itemData(idx)) |
| 53 | + ) |
| 54 | + |
| 55 | + def _button_click_callback(self): |
| 56 | + raise NotImplementedError() |
| 57 | +``` |
| 58 | +```py |
| 59 | +# my_cool_widget_controller.py |
| 60 | +class MyCoolWidget(MyCoolWidgetView): |
| 61 | + def __init__(self): |
| 62 | + super().__init__() |
| 63 | + |
| 64 | + def _button_click_callback(self): |
| 65 | + print("Button was clicked!") |
| 66 | + |
| 67 | + def _color_dropdown_callback(self, color: Color): |
| 68 | + print(f"The selected color is now: {color}") |
| 69 | +``` |
| 70 | + |
| 71 | +Observe the following key aspects of this example: |
| 72 | +- The Controller is just called `MyCoolWidget` instead of `MyCoolWidgetController` as it will be directly used by other code |
| 73 | +- The UI elements are in private variables |
| 74 | + - This enforces that the controller shouldn't directly access UI elements |
| 75 | + - Instead the view should provide a protected API (e.g. `_get_color()`) for things like setting/getting the value of a dropdown, etc. |
| 76 | + - Instead of `_get_color()` there could also be a `_color` method marked with `@property` |
| 77 | +- The callback methods are already defined as protected methods with NotImplementedErrors |
| 78 | + - Defines the interface the callbacks |
| 79 | + - Enforces that UI events be handled |
| 80 | + |
| 81 | + |
| 82 | +[^1]: For an explanation of the Model-View-Controller (MVC) Model, checkout this article: [MVC Framework Introduction](https://www.geeksforgeeks.org/mvc-framework-introduction/). |
0 commit comments