Skip to content

Commit 7d65eb5

Browse files
author
Lukas Geiger
committed
fix: apply editor settings immediately
1 parent 30f4828 commit 7d65eb5

5 files changed

Lines changed: 211 additions & 15 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Format basiert auf [Keep a Changelog](https://keepachangelog.com/de/1.1.0/).
1717
### Behoben / Fixed
1818
- Persistenz unbekannter Einstellungsschluessel abgesichert, damit UI-/Legacy-Aliase beim Speichern nicht verloren gehen.
1919
- Fehlende `chardet`-Abhaengigkeit fuer frische CI-/Installationsumgebungen ergaenzt.
20+
- Editor-Einstellungen werden nach dem Speichern auf offene Tabs angewendet; der Dialog persistiert jetzt auch „Aktuelle Zeile hervorheben“ und aktualisiert Schrift, Tab-Breite, Zeilennummern und Cursor-Markierung unmittelbar.
2021

2122
## [1.0.0] - YYYY-MM-DD
2223

src/gui/dialogs/settings_dialog.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ def _load_settings(self):
382382
self.line_numbers.setChecked(self.settings.get('editor.line_numbers', True))
383383
self.auto_complete.setChecked(self.settings.get('editor.auto_complete', True))
384384
self.auto_save.setChecked(self.settings.get('editor.auto_save', False))
385+
self.highlight_line.setChecked(self.settings.get('editor.highlight_current_line', True))
385386

386387
# Build
387388
self.pyinstaller_path.setText(self.settings.get('build.pyinstaller_path', ''))
@@ -411,6 +412,7 @@ def _save_settings(self):
411412
self.settings.set('editor.line_numbers', self.line_numbers.isChecked())
412413
self.settings.set('editor.auto_complete', self.auto_complete.isChecked())
413414
self.settings.set('editor.auto_save', self.auto_save.isChecked())
415+
self.settings.set('editor.highlight_current_line', self.highlight_line.isChecked())
414416

415417
# Build
416418
self.settings.set('build.pyinstaller_path', self.pyinstaller_path.text())

src/gui/main_window.py

Lines changed: 83 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
from PySide6.QtWidgets import (
1313
QMainWindow, QApplication, QWidget, QVBoxLayout, QHBoxLayout,
1414
QTabWidget, QMenuBar, QMenu, QToolBar, QStatusBar, QSplitter,
15-
QLabel, QPushButton, QMessageBox, QFileDialog, QDockWidget
15+
QLabel, QPushButton, QMessageBox, QFileDialog, QDockWidget, QInputDialog
1616
)
1717
from PySide6.QtCore import Qt, QTimer, QSize, Signal
18-
from PySide6.QtGui import QAction, QFont, QKeySequence, QIcon
18+
from PySide6.QtGui import QAction, QFont, QKeySequence, QIcon, QTextCursor
1919

2020
# Lokale Imports
2121
sys.path.insert(0, str(Path(__file__).parent.parent))
@@ -70,6 +70,7 @@ def __init__(self):
7070
# Aktives Projekt
7171
self.current_project: Optional[ProjectConfig] = None
7272
self.open_files: Dict[str, CodeEditor] = {}
73+
self._search_term = ""
7374

7475
# UI Setup
7576
self.setWindowTitle("DevCenter")
@@ -546,13 +547,11 @@ def _setup_connections(self):
546547

547548
def _restore_state(self):
548549
"""Stellt den Fensterzustand wieder her"""
549-
state = self.settings.get('window.state', {})
550-
551-
if 'geometry' in state:
552-
pass # TODO: Geometrie wiederherstellen
553-
554-
if 'maximized' in state and state['maximized']:
555-
self.showMaximized()
550+
geometry, state = self.settings.restore_window_state()
551+
if geometry:
552+
self.restoreGeometry(geometry)
553+
if state:
554+
self.restoreState(state)
556555

557556
def _show_welcome(self):
558557
"""Zeigt Welcome-Screen oder öffnet letztes Projekt"""
@@ -605,6 +604,7 @@ def _load_project(self, project: ProjectConfig):
605604
def _new_file(self):
606605
"""Erstellt eine neue leere Datei"""
607606
editor = CodeEditor()
607+
self._apply_editor_settings(editor)
608608
self.editor_tabs.addTab(editor, "Unbenannt")
609609
self.editor_tabs.setCurrentWidget(editor)
610610
self._connect_editor(editor)
@@ -628,6 +628,7 @@ def _open_file_path(self, path: str):
628628

629629
# Neue Datei öffnen
630630
editor = CodeEditor()
631+
self._apply_editor_settings(editor)
631632
if editor.load_file(path):
632633
self.open_files[path] = editor
633634
name = os.path.basename(path)
@@ -641,6 +642,21 @@ def _connect_editor(self, editor: CodeEditor):
641642
"""Verbindet Editor-Signale"""
642643
editor.file_modified.connect(self._on_file_modified)
643644
editor.cursor_position_changed.connect(self._on_cursor_changed)
645+
646+
def _apply_editor_settings(self, editor: CodeEditor):
647+
"""Wendet die aktuellen Editor-Einstellungen auf einen Editor an."""
648+
line_numbers = self.settings.get('editor.line_numbers', None)
649+
if line_numbers is None:
650+
line_numbers = self.settings.get('editor.show_line_numbers', True)
651+
652+
editor.apply_settings(
653+
font_family=self.settings.get('editor.font_family', 'Consolas'),
654+
font_size=self.settings.get('editor.font_size', 11),
655+
tab_size=self.settings.get('editor.tab_size', 4),
656+
show_line_numbers=bool(line_numbers),
657+
auto_complete=self.settings.get('editor.auto_complete', True),
658+
highlight_current_line=self.settings.get('editor.highlight_current_line', True),
659+
)
644660

645661
def _save_file(self):
646662
"""Speichert die aktuelle Datei"""
@@ -760,12 +776,62 @@ def _paste(self):
760776
editor.paste()
761777

762778
def _find(self):
763-
"""TODO: Such-Dialog"""
764-
pass
779+
"""Öffnet einfachen Suchdialog."""
780+
editor = self._get_current_editor()
781+
if not editor:
782+
self.statusbar.showMessage("Kein aktiver Editor vorhanden", 2000)
783+
return
784+
785+
default = editor.textCursor().selectedText() or self._search_term
786+
pattern, ok = QInputDialog.getText(
787+
self, "Suchen", "Suchbegriff:", text=default
788+
)
789+
if not ok or not pattern:
790+
return
791+
792+
self._search_term = pattern
793+
794+
if not editor.find(pattern):
795+
cursor = editor.textCursor()
796+
cursor.movePosition(QTextCursor.MoveOperation.Start)
797+
editor.setTextCursor(cursor)
798+
if not editor.find(pattern):
799+
self.statusbar.showMessage(f"\"{pattern}\" nicht gefunden", 2500)
800+
return
801+
802+
self.statusbar.showMessage(f"Gefunden: {pattern}", 2500)
765803

766804
def _replace(self):
767-
"""TODO: Ersetzen-Dialog"""
768-
pass
805+
"""Öffnet einfachen Ersetzen-Dialog."""
806+
editor = self._get_current_editor()
807+
if not editor:
808+
self.statusbar.showMessage("Kein aktiver Editor vorhanden", 2000)
809+
return
810+
811+
search_text, ok = QInputDialog.getText(
812+
self, "Ersetzen", "Zu ersetzen:", text=editor.textCursor().selectedText()
813+
)
814+
if not ok or not search_text:
815+
return
816+
817+
replace_text, ok = QInputDialog.getText(
818+
self, "Ersetzen", "Durch diesen Text ersetzen:"
819+
)
820+
if not ok:
821+
return
822+
823+
text = editor.toPlainText()
824+
matches = text.count(search_text)
825+
if matches == 0:
826+
self.statusbar.showMessage(f'"{search_text}" nicht gefunden', 2500)
827+
return
828+
829+
cursor_pos = editor.textCursor().position()
830+
editor.setPlainText(text.replace(search_text, replace_text))
831+
cursor = editor.textCursor()
832+
cursor.setPosition(min(cursor_pos, len(editor.toPlainText())))
833+
editor.setTextCursor(cursor)
834+
self.statusbar.showMessage(f"{matches} Ersetzung(en) durchgeführt", 2500)
769835

770836
# === Ansicht-Operationen ===
771837

@@ -920,7 +986,10 @@ def _apply_settings(self):
920986
self.ai_service.set_api_key(self.settings.get('ai.api_key', ''))
921987

922988
# Editor-Einstellungen auf alle offenen Editoren anwenden
923-
# TODO: Implementieren
989+
for index in range(self.editor_tabs.count()):
990+
widget = self.editor_tabs.widget(index)
991+
if isinstance(widget, CodeEditor):
992+
self._apply_editor_settings(widget)
924993

925994
def _show_about(self):
926995
"""Zeigt den Über-Dialog"""

src/modules/editor/code_editor.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ def __init__(self, parent=None):
203203

204204
# Tab-Einstellungen
205205
self.tab_size = 4
206+
self.show_line_numbers = True
207+
self.highlight_current_line_enabled = True
206208
tab_width = QFontMetrics(font).horizontalAdvance(' ') * self.tab_size
207209
self.setTabStopDistance(tab_width)
208210

@@ -214,6 +216,7 @@ def __init__(self, parent=None):
214216

215217
# Line Number Area
216218
self.line_number_area = LineNumberArea(self)
219+
self.line_number_area.setVisible(self.show_line_numbers)
217220

218221
# Connections
219222
self.blockCountChanged.connect(self._update_line_number_area_width)
@@ -254,6 +257,9 @@ def _setup_appearance(self):
254257

255258
def line_number_area_width(self) -> int:
256259
"""Berechnet die Breite des Zeilennummern-Bereichs"""
260+
if not self.show_line_numbers:
261+
return 0
262+
257263
digits = 1
258264
max_num = max(1, self.blockCount())
259265
while max_num >= 10:
@@ -266,6 +272,7 @@ def line_number_area_width(self) -> int:
266272
def _update_line_number_area_width(self, new_block_count):
267273
"""Aktualisiert die Breite des Zeilennummern-Bereichs"""
268274
self.setViewportMargins(self.line_number_area_width(), 0, 0, 0)
275+
self.line_number_area.setVisible(self.show_line_numbers)
269276

270277
def _update_line_number_area(self, rect, dy):
271278
"""Aktualisiert den Zeilennummern-Bereich beim Scrollen"""
@@ -289,6 +296,9 @@ def resizeEvent(self, event):
289296

290297
def line_number_area_paint_event(self, event):
291298
"""Zeichnet die Zeilennummern"""
299+
if not self.show_line_numbers:
300+
return
301+
292302
painter = QPainter(self.line_number_area)
293303
painter.fillRect(event.rect(), QColor("#1e1e1e"))
294304

@@ -322,7 +332,7 @@ def _highlight_current_line(self):
322332
"""Hebt die aktuelle Zeile hervor"""
323333
extra_selections = []
324334

325-
if not self.isReadOnly():
335+
if self.highlight_current_line_enabled and not self.isReadOnly():
326336
selection = QTextEdit.ExtraSelection()
327337
line_color = QColor("#282828")
328338
selection.format.setBackground(line_color)
@@ -332,6 +342,44 @@ def _highlight_current_line(self):
332342
extra_selections.append(selection)
333343

334344
self.setExtraSelections(extra_selections)
345+
346+
def apply_settings(
347+
self,
348+
font_family: Optional[str] = None,
349+
font_size: Optional[int] = None,
350+
tab_size: Optional[int] = None,
351+
show_line_numbers: Optional[bool] = None,
352+
auto_complete: Optional[bool] = None,
353+
highlight_current_line: Optional[bool] = None,
354+
):
355+
"""Wendet Editor-Einstellungen auf diese Instanz an."""
356+
font = QFont(self.font())
357+
358+
if font_family is not None:
359+
font.setFamily(font_family)
360+
if font_size is not None:
361+
font.setPointSize(font_size)
362+
363+
self.setFont(font)
364+
365+
if tab_size is not None:
366+
self.tab_size = max(1, int(tab_size))
367+
368+
tab_width = QFontMetrics(self.font()).horizontalAdvance(' ') * self.tab_size
369+
self.setTabStopDistance(tab_width)
370+
371+
if show_line_numbers is not None:
372+
self.show_line_numbers = bool(show_line_numbers)
373+
374+
if auto_complete is not None:
375+
self.autocomplete_enabled = bool(auto_complete)
376+
377+
if highlight_current_line is not None:
378+
self.highlight_current_line_enabled = bool(highlight_current_line)
379+
380+
self._update_line_number_area_width(0)
381+
self.line_number_area.update()
382+
self._highlight_current_line()
335383

336384
def _emit_cursor_position(self):
337385
"""Sendet die aktuelle Cursor-Position"""

tests/test_settings_application.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# -*- coding: utf-8 -*-
2+
"""Regressionstests für DevCenter-Einstellungen."""
3+
4+
import os
5+
import sys
6+
import tempfile
7+
import unittest
8+
from pathlib import Path
9+
from unittest.mock import MagicMock
10+
11+
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
12+
13+
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
14+
15+
from PySide6.QtWidgets import QApplication, QTabWidget
16+
17+
from core.settings_manager import SettingsManager
18+
from gui.dialogs.settings_dialog import SettingsDialog
19+
from gui.main_window import MainWindow
20+
from modules.editor.code_editor import CodeEditor
21+
22+
23+
class DevCenterSettingsTests(unittest.TestCase):
24+
"""Tests für die Anwendung von Editor-Einstellungen."""
25+
26+
@classmethod
27+
def setUpClass(cls):
28+
cls.app = QApplication.instance() or QApplication([])
29+
30+
def _temp_settings(self):
31+
temp_dir = tempfile.TemporaryDirectory()
32+
self.addCleanup(temp_dir.cleanup)
33+
settings_path = Path(temp_dir.name) / "settings.json"
34+
return SettingsManager(str(settings_path))
35+
36+
def test_settings_dialog_saves_highlight_current_line(self):
37+
settings = self._temp_settings()
38+
dialog = SettingsDialog(settings)
39+
40+
dialog.highlight_line.setChecked(False)
41+
dialog._save_settings()
42+
43+
self.assertFalse(settings.get("editor.highlight_current_line"))
44+
45+
def test_main_window_applies_editor_settings_to_open_tabs(self):
46+
settings = self._temp_settings()
47+
settings.set("editor.font_family", "Courier New")
48+
settings.set("editor.font_size", 14)
49+
settings.set("editor.tab_size", 2)
50+
settings.set("editor.line_numbers", False)
51+
settings.set("editor.auto_complete", False)
52+
settings.set("editor.highlight_current_line", False)
53+
settings.set("ai.api_key", "secret-token")
54+
55+
window = MainWindow.__new__(MainWindow)
56+
window.settings = settings
57+
window.ai_service = MagicMock()
58+
window.editor_tabs = QTabWidget()
59+
60+
editor = CodeEditor()
61+
window.editor_tabs.addTab(editor, "scratch.py")
62+
63+
window._apply_settings()
64+
65+
window.ai_service.set_api_key.assert_called_once_with("secret-token")
66+
self.assertEqual(editor.font().family(), "Courier New")
67+
self.assertEqual(editor.font().pointSize(), 14)
68+
self.assertEqual(editor.tab_size, 2)
69+
self.assertEqual(editor.line_number_area_width(), 0)
70+
self.assertFalse(editor.autocomplete_enabled)
71+
self.assertFalse(editor.highlight_current_line_enabled)
72+
self.assertEqual(editor.extraSelections(), [])
73+
74+
75+
if __name__ == "__main__":
76+
unittest.main(verbosity=2)

0 commit comments

Comments
 (0)