sview 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sview-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.1
2
+ Name: sview
3
+ Version: 0.1.0
4
+ Summary: Sequence-aware filesystem browser
5
+ Project-URL: Repository, https://github.com/rsgalloway/sview
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: PySide6>=6.6
9
+ Requires-Dist: pyseq
10
+ Provides-Extra: dev
11
+
12
+ # sview
13
+
14
+ `sview` is a sequence-aware filesystem browser built with PySide6 and `pyseq`.
15
+ It provides a sequence-first view of directories so image sequences can be
16
+ browsed as logical units instead of individual files.
17
+
18
+ ![sview screenshot](sview.png)
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install -U sview
24
+ ```
25
+
26
+ ## Run
27
+
28
+ Launch the app:
29
+
30
+ ```bash
31
+ sview
32
+ ```
33
+
34
+ Open a specific folder:
35
+
36
+ ```bash
37
+ sview /mnt/projects/
38
+ ```
39
+
40
+ Print the version:
41
+
42
+ ```bash
43
+ sview --version
44
+ ```
45
+
46
+ ## Configuration
47
+
48
+ On first launch, `sview` writes a user config file to:
49
+
50
+ ```bash
51
+ ~/.config/sview/config.json
52
+ ```
53
+
54
+ This file controls default file and sequence handlers, including custom
55
+ commands for double-click actions.
sview-0.1.0/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # sview
2
+
3
+ `sview` is a sequence-aware filesystem browser built with PySide6 and `pyseq`.
4
+ It provides a sequence-first view of directories so image sequences can be
5
+ browsed as logical units instead of individual files.
6
+
7
+ ![sview screenshot](sview.png)
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install -U sview
13
+ ```
14
+
15
+ ## Run
16
+
17
+ Launch the app:
18
+
19
+ ```bash
20
+ sview
21
+ ```
22
+
23
+ Open a specific folder:
24
+
25
+ ```bash
26
+ sview /mnt/projects/
27
+ ```
28
+
29
+ Print the version:
30
+
31
+ ```bash
32
+ sview --version
33
+ ```
34
+
35
+ ## Configuration
36
+
37
+ On first launch, `sview` writes a user config file to:
38
+
39
+ ```bash
40
+ ~/.config/sview/config.json
41
+ ```
42
+
43
+ This file controls default file and sequence handlers, including custom
44
+ commands for double-click actions.
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "sview"
7
+ version = "0.1.0"
8
+ description = "Sequence-aware filesystem browser"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ dependencies = [
12
+ "PySide6>=6.6",
13
+ "pyseq",
14
+ ]
15
+
16
+ [project.urls]
17
+ Repository = "https://github.com/rsgalloway/sview"
18
+
19
+ [project.optional-dependencies]
20
+ dev = []
21
+
22
+ [project.scripts]
23
+ sview = "sview.qt.app:main"
24
+
25
+ [tool.setuptools]
26
+ packages = ["sview", "sview.qt"]
sview-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright (c) 2026, Ryan Galloway (ryan@rsgalloway.com)
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # - Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # - Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # - Neither the name of the software nor the names of its contributors
16
+ # may be used to endorse or promote products derived from this software
17
+ # without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+ # -----------------------------------------------------------------------------
31
+
32
+ """
33
+ sview is a simple sequence viewer for quickly inspecting the contents of a directory
34
+ containing image sequences. It is designed to be fast and lightweight, with a focus
35
+ on usability and a clean interface.
36
+ """
37
+
38
+ __version__ = "0.1.0"
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright (c) 2026, Ryan Galloway (ryan@rsgalloway.com)
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # - Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # - Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # - Neither the name of the software nor the names of its contributors
16
+ # may be used to endorse or promote products derived from this software
17
+ # without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+ # -----------------------------------------------------------------------------
31
+
32
+ """
33
+ Contains configuration management for sview, including loading and saving user preferences.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ from importlib.metadata import PackageNotFoundError, metadata
39
+ import json
40
+ import shlex
41
+ from dataclasses import dataclass
42
+ from pathlib import Path
43
+
44
+
45
+ CONFIG_DIR = Path.home() / ".config" / "sview"
46
+ CONFIG_PATH = CONFIG_DIR / "config.json"
47
+ DEFAULT_REPOSITORY_URL = "https://github.com/rsgalloway/sview"
48
+
49
+
50
+ @dataclass
51
+ class HandlerConfig:
52
+ mode: str
53
+ command: str = ""
54
+
55
+
56
+ @dataclass
57
+ class AppConfig:
58
+ file_handler: HandlerConfig
59
+ sequence_handler: HandlerConfig
60
+
61
+ @classmethod
62
+ def default(cls) -> "AppConfig":
63
+ return cls(
64
+ file_handler=HandlerConfig(mode="system"),
65
+ sequence_handler=HandlerConfig(mode="expand"),
66
+ )
67
+
68
+ @classmethod
69
+ def load(cls) -> "AppConfig":
70
+ if not CONFIG_PATH.exists():
71
+ config = cls.default()
72
+ config.save()
73
+ return config
74
+
75
+ try:
76
+ data = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
77
+ except (OSError, json.JSONDecodeError):
78
+ config = cls.default()
79
+ config.save()
80
+ return config
81
+
82
+ handlers = data.get("handlers", {})
83
+ file_handler = handlers.get("file", {})
84
+ sequence_handler = handlers.get("sequence", {})
85
+ return cls(
86
+ file_handler=HandlerConfig(
87
+ mode=file_handler.get("mode", "system"),
88
+ command=file_handler.get("command", ""),
89
+ ),
90
+ sequence_handler=HandlerConfig(
91
+ mode=sequence_handler.get("mode", "expand"),
92
+ command=sequence_handler.get("command", ""),
93
+ ),
94
+ )
95
+
96
+ def save(self) -> None:
97
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
98
+ payload = {
99
+ "handlers": {
100
+ "file": {
101
+ "mode": self.file_handler.mode,
102
+ "command": self.file_handler.command,
103
+ },
104
+ "sequence": {
105
+ "mode": self.sequence_handler.mode,
106
+ "command": self.sequence_handler.command,
107
+ },
108
+ }
109
+ }
110
+ CONFIG_PATH.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
111
+
112
+
113
+ def build_command(command_template: str, path: str) -> list[str]:
114
+ return shlex.split(command_template.format(path=path))
115
+
116
+
117
+ def get_repository_url() -> str:
118
+ try:
119
+ project_metadata = metadata("sview")
120
+ except PackageNotFoundError:
121
+ return DEFAULT_REPOSITORY_URL
122
+
123
+ for key, value in project_metadata.items():
124
+ if key != "Project-URL":
125
+ continue
126
+ label, _, url = value.partition(",")
127
+ if label.strip().lower() == "repository" and url.strip():
128
+ return url.strip()
129
+
130
+ return DEFAULT_REPOSITORY_URL
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright (c) 2026, Ryan Galloway (ryan@rsgalloway.com)
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # - Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # - Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # - Neither the name of the software nor the names of its contributors
16
+ # may be used to endorse or promote products derived from this software
17
+ # without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+ # -----------------------------------------------------------------------------
31
+
32
+ """
33
+ Contains the BrowserController class, which serves as a thin service layer between
34
+ the UI and the Directory Scanner. It manages the current state of the browser,
35
+ including the current path, items, and view mode.
36
+ """
37
+
38
+ from __future__ import annotations
39
+
40
+ from pathlib import Path
41
+
42
+ from sview.model import BrowserItem, ItemType
43
+ from sview.scanner import DirectoryScanner, ScanResult
44
+
45
+
46
+ class BrowserController:
47
+ """Thin service layer between the UI and scanner."""
48
+
49
+ def __init__(self, scanner: DirectoryScanner | None = None) -> None:
50
+ self._scanner = scanner or DirectoryScanner()
51
+ self._current_path = Path.home()
52
+ self._current_items: list[BrowserItem] = []
53
+ self._raw_items: list[BrowserItem] = []
54
+ self._grouped_items: list[BrowserItem] = []
55
+ self._grouped_view = True
56
+ self._expanded_sequence: BrowserItem | None = None
57
+
58
+ @property
59
+ def current_path(self) -> Path:
60
+ return self._current_path
61
+
62
+ @property
63
+ def current_items(self) -> list[BrowserItem]:
64
+ return list(self._current_items)
65
+
66
+ @property
67
+ def grouped_view(self) -> bool:
68
+ return self._grouped_view
69
+
70
+ @property
71
+ def expanded_sequence(self) -> BrowserItem | None:
72
+ return self._expanded_sequence
73
+
74
+ def set_grouped_view(self, enabled: bool) -> None:
75
+ self._grouped_view = enabled
76
+ self._expanded_sequence = None
77
+ self._refresh_current_items()
78
+
79
+ def expand_sequence(self, item: BrowserItem) -> list[BrowserItem]:
80
+ if item.item_type is not ItemType.SEQUENCE or not item.child_paths:
81
+ return self.current_items
82
+
83
+ child_paths = set(item.child_paths)
84
+ self._expanded_sequence = item
85
+ self._current_items = [
86
+ raw_item for raw_item in self._raw_items if raw_item.path in child_paths
87
+ ]
88
+ return self.current_items
89
+
90
+ def collapse_sequence(self) -> list[BrowserItem]:
91
+ self._expanded_sequence = None
92
+ self._refresh_current_items()
93
+ return self.current_items
94
+
95
+ def load_path(self, path: str | Path) -> list[BrowserItem]:
96
+ result = self.scan_path(path)
97
+ return self.apply_scan_result(result)
98
+
99
+ def scan_path(self, path: str | Path, **scan_kwargs) -> ScanResult:
100
+ return self._scanner.scan(path, **scan_kwargs)
101
+
102
+ def apply_scan_result(self, result: ScanResult) -> list[BrowserItem]:
103
+ self._current_path = result.path
104
+ self._grouped_items = result.grouped_items
105
+ self._raw_items = result.raw_items
106
+ self._expanded_sequence = None
107
+ self._refresh_current_items()
108
+ return self.current_items
109
+
110
+ def _refresh_current_items(self) -> None:
111
+ self._current_items = (
112
+ self._grouped_items if self._grouped_view else self._raw_items
113
+ )
@@ -0,0 +1,64 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright (c) 2026, Ryan Galloway (ryan@rsgalloway.com)
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # - Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # - Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # - Neither the name of the software nor the names of its contributors
16
+ # may be used to endorse or promote products derived from this software
17
+ # without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+ # -----------------------------------------------------------------------------
31
+
32
+ """
33
+ Contains data models for representing items in the file browser and user configuration.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ from dataclasses import dataclass
39
+ from enum import Enum
40
+
41
+
42
+ class ItemType(str, Enum):
43
+ DIRECTORY = "directory"
44
+ SEQUENCE = "sequence"
45
+ FILE = "file"
46
+
47
+
48
+ @dataclass
49
+ class BrowserItem:
50
+ path: str
51
+ item_type: ItemType
52
+ name: str
53
+ display_name: str
54
+ frame_range: str | None
55
+ pad: str | None
56
+ count: int
57
+ missing: list[int] | None
58
+ size_bytes: int
59
+ modified_time: float
60
+ child_paths: list[str] | None = None
61
+
62
+ @property
63
+ def missing_count(self) -> int:
64
+ return len(self.missing or [])
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright (c) 2026, Ryan Galloway (ryan@rsgalloway.com)
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # - Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # - Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # - Neither the name of the software nor the names of its contributors
16
+ # may be used to endorse or promote products derived from this software
17
+ # without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+ # -----------------------------------------------------------------------------
31
+
32
+ """
33
+ Contains Qt UI package for sview.
34
+ """
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env python3
2
+ #
3
+ # Copyright (c) 2026, Ryan Galloway (ryan@rsgalloway.com)
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are met:
7
+ #
8
+ # - Redistributions of source code must retain the above copyright notice,
9
+ # this list of conditions and the following disclaimer.
10
+ #
11
+ # - Redistributions in binary form must reproduce the above copyright notice,
12
+ # this list of conditions and the following disclaimer in the documentation
13
+ # and/or other materials provided with the distribution.
14
+ #
15
+ # - Neither the name of the software nor the names of its contributors
16
+ # may be used to endorse or promote products derived from this software
17
+ # without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
+ # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23
+ # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
+ # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
+ # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
+ # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
+ # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
+ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
+ # POSSIBILITY OF SUCH DAMAGE.
30
+ # -----------------------------------------------------------------------------
31
+
32
+ """
33
+ Contains the main application entry point for sview.
34
+ """
35
+
36
+ from __future__ import annotations
37
+
38
+ import sys
39
+ from pathlib import Path
40
+
41
+ from PySide6.QtGui import QColor, QPalette
42
+ from PySide6.QtWidgets import QApplication
43
+
44
+ from sview import __version__
45
+ from sview.qt.main_window import MainWindow
46
+
47
+
48
+ def main() -> int:
49
+ if _wants_version(sys.argv[1:]):
50
+ print(__version__)
51
+ return 0
52
+
53
+ app = QApplication(sys.argv)
54
+ app.setApplicationName("sview")
55
+ app.setApplicationVersion(__version__)
56
+ app.setStyle("Fusion")
57
+ _apply_dark_theme(app)
58
+ initial_path = _parse_initial_path(sys.argv[1:])
59
+ window = MainWindow(initial_path=initial_path)
60
+ window.show()
61
+ return app.exec()
62
+
63
+
64
+ def _parse_initial_path(args: list[str]) -> str | None:
65
+ filtered_args = [arg for arg in args if arg not in {"--version", "-V"}]
66
+ if not filtered_args:
67
+ return None
68
+ candidate = Path(filtered_args[0]).expanduser()
69
+ return str(candidate)
70
+
71
+
72
+ def _wants_version(args: list[str]) -> bool:
73
+ return any(arg in {"--version", "-V"} for arg in args)
74
+
75
+
76
+ def _apply_dark_theme(app: QApplication) -> None:
77
+ palette = QPalette()
78
+ palette.setColor(QPalette.ColorRole.Window, QColor("#232528"))
79
+ palette.setColor(QPalette.ColorRole.WindowText, QColor("#e4e6e8"))
80
+ palette.setColor(QPalette.ColorRole.Base, QColor("#191b1e"))
81
+ palette.setColor(QPalette.ColorRole.AlternateBase, QColor("#202327"))
82
+ palette.setColor(QPalette.ColorRole.ToolTipBase, QColor("#191b1e"))
83
+ palette.setColor(QPalette.ColorRole.ToolTipText, QColor("#f2f3f5"))
84
+ palette.setColor(QPalette.ColorRole.Text, QColor("#e4e6e8"))
85
+ palette.setColor(QPalette.ColorRole.Button, QColor("#2c2f34"))
86
+ palette.setColor(QPalette.ColorRole.ButtonText, QColor("#edf0f2"))
87
+ palette.setColor(QPalette.ColorRole.Highlight, QColor("#454b53"))
88
+ palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#ffffff"))
89
+ palette.setColor(QPalette.ColorRole.PlaceholderText, QColor("#808790"))
90
+ app.setPalette(palette)
91
+ app.setStyleSheet(
92
+ """
93
+ QWidget {
94
+ font-size: 12px;
95
+ }
96
+ QMainWindow, QTreeView, QTableWidget, QLineEdit, QPushButton, QLabel {
97
+ background-color: #25282c;
98
+ color: #e4e6e8;
99
+ }
100
+ QHeaderView::section {
101
+ background-color: #2d3136;
102
+ color: #d9dde1;
103
+ padding: 4px 6px;
104
+ border: 0;
105
+ border-right: 1px solid #3d4248;
106
+ }
107
+ QTreeView, QTableWidget, QLineEdit {
108
+ background-color: #1d2024;
109
+ border: 1px solid #30343a;
110
+ border-radius: 3px;
111
+ }
112
+ QScrollBar:vertical {
113
+ background: #1b1e22;
114
+ width: 10px;
115
+ margin: 2px;
116
+ }
117
+ QScrollBar::handle:vertical {
118
+ background: #474c53;
119
+ min-height: 28px;
120
+ border-radius: 4px;
121
+ }
122
+ QScrollBar::handle:vertical:hover {
123
+ background: #5a6068;
124
+ }
125
+ QScrollBar:horizontal {
126
+ background: #1b1e22;
127
+ height: 10px;
128
+ margin: 2px;
129
+ }
130
+ QScrollBar::handle:horizontal {
131
+ background: #474c53;
132
+ min-width: 28px;
133
+ border-radius: 4px;
134
+ }
135
+ QScrollBar::handle:horizontal:hover {
136
+ background: #5a6068;
137
+ }
138
+ QScrollBar::add-line, QScrollBar::sub-line, QScrollBar::add-page, QScrollBar::sub-page {
139
+ background: transparent;
140
+ border: none;
141
+ }
142
+ QTableWidget::item:selected, QTreeView::item:selected {
143
+ background-color: #40454d;
144
+ color: white;
145
+ }
146
+ QPushButton {
147
+ background-color: #2f3338;
148
+ border: 1px solid #393e45;
149
+ border-radius: 3px;
150
+ padding: 4px 9px;
151
+ min-height: 24px;
152
+ }
153
+ QPushButton:checked {
154
+ background-color: #3b4047;
155
+ border-color: #474d55;
156
+ }
157
+ QPushButton:disabled {
158
+ color: #727881;
159
+ background-color: #272b30;
160
+ }
161
+ QStatusBar {
162
+ background-color: #1d2024;
163
+ color: #bfc4ca;
164
+ }
165
+ QSplitter::handle {
166
+ background-color: #4a4f56;
167
+ width: 1px;
168
+ }
169
+ QLabel#inspectorTitle {
170
+ font-size: 16px;
171
+ font-weight: 600;
172
+ }
173
+ QLabel#inspectorSubtitle {
174
+ color: #959ca5;
175
+ margin-bottom: 6px;
176
+ }
177
+ """
178
+ )
179
+
180
+
181
+ if __name__ == "__main__":
182
+ raise SystemExit(main())