diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4bae5a6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+*/__pycache__
diff --git a/README.md b/README.md
index 2e89abe..4c8920c 100644
--- a/README.md
+++ b/README.md
@@ -65,7 +65,8 @@ pip install -e ./typstwriter/
Run the tests with pytest to make sure that everything is working correctly. Inside the source directory, run:
```
-pytest
+pip install -e .[tests]
+QT_API=pyside6 pytest
```
Activate pre-commit hooks to automatically check changes before committing. Inside the source directory, run:
diff --git a/typstwriter/actions.py b/typstwriter/actions.py
index 2be6cb6..4489bd6 100644
--- a/typstwriter/actions.py
+++ b/typstwriter/actions.py
@@ -148,10 +148,18 @@ def __init__(self, parent):
self.show_compiler_output.setText("Show Compiler Output")
self.show_compiler_output.setCheckable(True)
+ self.open_settings = QtWidgets.QAction(self)
+ self.open_settings.setIcon(QtGui.QIcon.fromTheme("preferences-system"))
+ self.open_settings.setText("Settings")
+
self.open_config = QtWidgets.QAction(self)
self.open_config.setIcon(QtGui.QIcon.fromTheme("configure-symbolic"))
self.open_config.setText("Open config file")
+ self.about = QtWidgets.QAction(self)
+ self.about.setIcon(QtGui.QIcon.fromTheme("help-about"))
+ self.about.setText("About")
+
self.run = util.TogglingAction(self)
self.run.setIcon(
QtGui.QIcon.fromTheme(QtGui.QIcon.MediaPlaybackStart, QtGui.QIcon(util.icon_path("start.svg"))),
diff --git a/typstwriter/mainwindow.py b/typstwriter/mainwindow.py
index 948f0e0..abe27bc 100644
--- a/typstwriter/mainwindow.py
+++ b/typstwriter/mainwindow.py
@@ -16,6 +16,9 @@
from typstwriter import configuration
from typstwriter import globalstate
from typstwriter import arguments
+from typstwriter import settings_gui
+
+from typstwriter import __version__
logger = logging.getLogger(__name__)
config = configuration.Config
@@ -46,13 +49,13 @@ def __init__(self):
self.addToolBar(self.toolbar)
# Host Widget for Editor and PDF Viewer
- self.WidtetCore = QtWidgets.QWidget(self)
- self.CoreLayout_v = QtWidgets.QVBoxLayout(self.WidtetCore)
+ self.WidgetCore = QtWidgets.QWidget(self)
+ self.CoreLayout_v = QtWidgets.QVBoxLayout(self.WidgetCore)
self.CoreLayout_v.setContentsMargins(4, 4, 4, 4)
self.CoreLayout_v.setSpacing(0)
- self.setCentralWidget(self.WidtetCore)
+ self.setCentralWidget(self.WidgetCore)
- self.splitter = QtWidgets.QSplitter(self.WidtetCore)
+ self.splitter = QtWidgets.QSplitter(self.WidgetCore)
self.splitter.setOrientation(QtCore.Qt.Vertical)
self.CoreLayout_v.addWidget(self.splitter)
@@ -139,7 +142,9 @@ def __init__(self):
self.actions.run.deactivated.connect(self.CompilerConnector.stop)
self.CompilerConnector.started.connect(lambda: self.actions.run.setChecked(True))
self.CompilerConnector.stopped.connect(lambda: self.actions.run.setChecked(False))
+ self.actions.open_settings.triggered.connect(self.open_settings)
self.actions.open_config.triggered.connect(self.open_config)
+ self.actions.about.triggered.connect(self.open_about)
self.FSExplorer.open_file.connect(self.editor.open_file)
state.working_directory.Signal.connect(self.FSExplorer.root_changed)
@@ -148,7 +153,7 @@ def __init__(self):
self.CompilerConnector.compilation_finished.connect(self.editor.clear_errors)
self.CompilerConnector.error_report.connect(self.editor.apply_errors)
state.main_file.Signal.connect(lambda s: self.CompilerConnector.stop())
- state.main_file.Signal.connect(lambda s: self.CompilerOptions.main_changed(s)) # noqa: PLW0108
+ state.main_file.Signal.connect(lambda s: self.CompilerOptions.main_changed(s))
state.main_file.Signal.connect(lambda s: self.PDFWidget.open(util.pdf_path(s)))
# For now only display errors
@@ -246,6 +251,20 @@ def set_compiler_output_visibility(self, visibility):
"""Set the visibility of the fs explplorer."""
self.CompilerOutputdock.setVisible(visibility)
+ def open_settings(self):
+ """Open settings dialog."""
+ dialog = settings_gui.SettingsDialog(self)
+ dialog.exec()
+
+ def open_about(self):
+ """Open about dialog."""
+ QtWidgets.QMessageBox.about(
+ self,
+ "About Typstwriter",
+ f"Typstwriter {__version__}\nAn integrated editor for the typst typesetting system\
+ \nhttps://github.com/Bzero/typstwriter\nLicensed under MIT ",
+ )
+
def open_config(self):
"""Open config file."""
config.write()
diff --git a/typstwriter/menubar.py b/typstwriter/menubar.py
index 1e57a02..bd993fb 100644
--- a/typstwriter/menubar.py
+++ b/typstwriter/menubar.py
@@ -79,7 +79,10 @@ def __init__(self, actions):
self.menuView.addSeparator()
self.menuView.addMenu(self.editor_zoom_menu)
+ self.menuSettings.addAction(actions.open_settings)
self.menuSettings.addAction(actions.open_config)
+ self.menuSettings.addSeparator()
+ self.menuSettings.addAction(actions.about)
class RecentFilesMenu(QtWidgets.QMenu):
diff --git a/typstwriter/settings_gui.py b/typstwriter/settings_gui.py
new file mode 100644
index 0000000..8828f85
--- /dev/null
+++ b/typstwriter/settings_gui.py
@@ -0,0 +1,214 @@
+from qtpy import QtWidgets
+
+import qt_themes
+
+from typstwriter import configuration
+from typstwriter import logging
+
+logger = logging.getLogger(__name__)
+config = configuration.Config
+
+
+class SettingsDialog(QtWidgets.QDialog):
+ """Settings Dialog."""
+
+ def __init__(self, parent=None):
+ """Init."""
+ super().__init__(parent)
+ self.setWindowTitle("Settings")
+ self.resize(600, 400)
+
+ self.main_layout = QtWidgets.QVBoxLayout(self)
+
+ self.tabs = QtWidgets.QTabWidget(self)
+ self.main_layout.addWidget(self.tabs)
+
+ self.init_general_tab()
+ self.init_compiler_tab()
+ self.init_editor_tab()
+ self.init_layout_tab()
+ self.init_internals_tab()
+
+ self.button_box = QtWidgets.QDialogButtonBox(QtWidgets.QDialogButtonBox.Save | QtWidgets.QDialogButtonBox.Cancel)
+ self.button_box.accepted.connect(self.save_settings)
+ self.button_box.rejected.connect(self.reject)
+ self.main_layout.addWidget(self.button_box)
+
+ def init_general_tab(self):
+ """Initialize the General tab."""
+ tab = QtWidgets.QWidget()
+ layout = QtWidgets.QFormLayout(tab)
+
+ self.general_working_directory = QtWidgets.QLineEdit(config.get("General", "working_directory"))
+ layout.addRow("Working Directory:", self.general_working_directory)
+
+ self.general_resume_session = QtWidgets.QCheckBox()
+ self.general_resume_session.setChecked(config.get("General", "resume_last_session", "bool"))
+ layout.addRow("Resume Last Session:", self.general_resume_session)
+
+ self.general_theme = QtWidgets.QComboBox()
+ theme_names = ["System Default", *sorted(qt_themes.get_themes().keys())]
+ for t in theme_names:
+ if t == "System Default":
+ self.general_theme.addItem(t, None)
+ else:
+ self.general_theme.addItem(t.title().replace("_", " "), t)
+ current_theme = config.get("General", "theme")
+ index = self.general_theme.findData(current_theme)
+ self.general_theme.setCurrentIndex(index if index >= 0 else 0)
+ layout.addRow("Theme:", self.general_theme)
+
+ hint_label = QtWidgets.QLabel()
+ hint_label.setWordWrap(True)
+ hint_label.setText(
+ "
Typstwriter looks for configuration files in the following locations, in reverse order:
"
+ "- /etc/typstwriter/typstwriter.ini
"
+ "- /usr/local/etc/typstwriter/typstwriter.ini
"
+ "- $XDG_CONFIG_HOME/typstwriter/typstwriter.ini (Linux/Unix Only)
"
+ "- %USERPROFILE%\\AppData\\Local\\typstwriter\\typstwriter.ini (Windows Only)
"
+ "- ~/.typstwriter.ini
"
+ "- ./typstwriter.ini (Working Directory)
"
+ "You can either edit the settings here in this GUI, or manually through the configuration file \
+ in the aforementioned locations."
+ )
+ layout.addRow(hint_label)
+ self.tabs.addTab(tab, "General")
+
+ def init_compiler_tab(self):
+ """Initialize the Compiler tab."""
+ tab = QtWidgets.QWidget()
+ layout = QtWidgets.QFormLayout(tab)
+
+ self.compiler_name = QtWidgets.QLineEdit(config.get("Compiler", "name"))
+ layout.addRow("Name of Typst executable:", self.compiler_name)
+
+ self.compiler_mode = QtWidgets.QComboBox()
+ self.compiler_mode.addItem("Live", "live")
+ self.compiler_mode.addItem("On Demand", "on_demand")
+ current_mode = config.get("Compiler", "mode")
+ index = self.compiler_mode.findData(current_mode)
+ if index >= 0:
+ self.compiler_mode.setCurrentIndex(index)
+ layout.addRow("Mode:", self.compiler_mode)
+
+ self.tabs.addTab(tab, "Compiler")
+
+ def init_editor_tab(self):
+ """Initialize the Editor tab."""
+ tab = QtWidgets.QWidget()
+ layout = QtWidgets.QFormLayout(tab)
+
+ self.editor_font_size = QtWidgets.QSpinBox()
+ self.editor_font_size.setRange(4, 72)
+ self.editor_font_size.setValue(config.get("Editor", "font_size", "int"))
+ layout.addRow("Font Size:", self.editor_font_size)
+
+ self.editor_save_at_run = QtWidgets.QCheckBox()
+ self.editor_save_at_run.setChecked(config.get("Editor", "save_at_run", "bool"))
+ layout.addRow("Save at run:", self.editor_save_at_run)
+
+ self.editor_highlighter_style = QtWidgets.QLineEdit(config.get("Editor", "highlighter_style"))
+ layout.addRow("Highlighter Style:", self.editor_highlighter_style)
+
+ hint_label = QtWidgets.QLabel()
+ hint_label.setText(
+ "See https://pygments.org/styles/\
+ for available options"
+ )
+ hint_label.setOpenExternalLinks(True)
+ layout.addRow("", hint_label)
+
+ self.editor_highlight_syntax = QtWidgets.QCheckBox()
+ self.editor_highlight_syntax.setChecked(config.get("Editor", "highlight_syntax", "bool"))
+ layout.addRow("Highlight Syntax:", self.editor_highlight_syntax)
+
+ self.editor_show_line_numbers = QtWidgets.QCheckBox()
+ self.editor_show_line_numbers.setChecked(config.get("Editor", "show_line_numbers", "bool"))
+ layout.addRow("Show Line Numbers:", self.editor_show_line_numbers)
+
+ self.editor_highlight_line = QtWidgets.QCheckBox()
+ self.editor_highlight_line.setChecked(config.get("Editor", "highlight_line", "bool"))
+ layout.addRow("Highlight Line:", self.editor_highlight_line)
+
+ self.editor_use_spaces = QtWidgets.QCheckBox()
+ self.editor_use_spaces.setChecked(config.get("Editor", "use_spaces", "bool"))
+ layout.addRow("Use Spaces:", self.editor_use_spaces)
+
+ self.tabs.addTab(tab, "Editor")
+
+ def init_layout_tab(self):
+ """Initialize the Layout tab."""
+ tab = QtWidgets.QWidget()
+ layout = QtWidgets.QFormLayout(tab)
+
+ self.layout_default_layout = QtWidgets.QComboBox()
+ self.layout_default_layout.addItem("Typewriter (Editor under Preview)", "typewriter")
+ self.layout_default_layout.addItem("Editor Right of Preview", "editor_right")
+ self.layout_default_layout.addItem("Editor Left of Preview", "editor_left")
+ current_layout = config.get("Layout", "default_layout")
+ index = self.layout_default_layout.findData(current_layout)
+ if index >= 0:
+ self.layout_default_layout.setCurrentIndex(index)
+
+ layout.addRow("Default Layout:", self.layout_default_layout)
+
+ self.layout_show_fs_explorer = QtWidgets.QCheckBox()
+ self.layout_show_fs_explorer.setChecked(config.get("Layout", "show_fs_explorer", "bool"))
+ layout.addRow("Show FS Explorer:", self.layout_show_fs_explorer)
+
+ self.layout_show_compiler_options = QtWidgets.QCheckBox()
+ self.layout_show_compiler_options.setChecked(config.get("Layout", "show_compiler_options", "bool"))
+ layout.addRow("Show Compiler Options:", self.layout_show_compiler_options)
+
+ self.layout_show_compiler_output = QtWidgets.QCheckBox()
+ self.layout_show_compiler_output.setChecked(config.get("Layout", "show_compiler_output", "bool"))
+ layout.addRow("Show Compiler Output:", self.layout_show_compiler_output)
+
+ self.tabs.addTab(tab, "Layout")
+
+ def init_internals_tab(self):
+ """Initialize the Internals tab."""
+ tab = QtWidgets.QWidget()
+ layout = QtWidgets.QFormLayout(tab)
+
+ self.internals_recent_files_path = QtWidgets.QLineEdit(config.get("Internals", "recent_files_path"))
+ layout.addRow("Recent Files Path:", self.internals_recent_files_path)
+
+ self.internals_recent_files_length = QtWidgets.QSpinBox()
+ self.internals_recent_files_length.setRange(1, 100)
+ self.internals_recent_files_length.setValue(config.get("Internals", "recent_files_length", "int"))
+ layout.addRow("Recent Files Length:", self.internals_recent_files_length)
+
+ self.internals_session_path = QtWidgets.QLineEdit(config.get("Internals", "session_path"))
+ layout.addRow("Session Path:", self.internals_session_path)
+
+ self.tabs.addTab(tab, "Internals")
+
+ def save_settings(self):
+ """Write the settings to the configuration file."""
+ config.set("General", "working_directory", self.general_working_directory.text())
+ config.set("General", "resume_last_session", self.general_resume_session.isChecked())
+ config.set("General", "theme", self.general_theme.currentData())
+
+ config.set("Compiler", "name", self.compiler_name.text())
+ config.set("Compiler", "mode", self.compiler_mode.currentData())
+
+ config.set("Editor", "font_size", self.editor_font_size.value())
+ config.set("Editor", "save_at_run", self.editor_save_at_run.isChecked())
+ config.set("Editor", "highlighter_style", self.editor_highlighter_style.text())
+ config.set("Editor", "highlight_syntax", self.editor_highlight_syntax.isChecked())
+ config.set("Editor", "show_line_numbers", self.editor_show_line_numbers.isChecked())
+ config.set("Editor", "highlight_line", self.editor_highlight_line.isChecked())
+ config.set("Editor", "use_spaces", self.editor_use_spaces.isChecked())
+
+ config.set("Layout", "default_layout", self.layout_default_layout.currentData())
+ config.set("Layout", "show_fs_explorer", self.layout_show_fs_explorer.isChecked())
+ config.set("Layout", "show_compiler_options", self.layout_show_compiler_options.isChecked())
+ config.set("Layout", "show_compiler_output", self.layout_show_compiler_output.isChecked())
+
+ config.set("Internals", "recent_files_path", self.internals_recent_files_path.text())
+ config.set("Internals", "recent_files_length", self.internals_recent_files_length.value())
+ config.set("Internals", "session_path", self.internals_session_path.text())
+
+ config.write()
+ self.accept()