clapp-pm 1.0.42__py3-none-any.whl → 1.0.43__py3-none-any.whl

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.
@@ -0,0 +1,930 @@
1
+ """
2
+ PyCloud OS Rain Theme
3
+ Arayüz bileşenleri için görsel temalar ve ikon paketleri yönetimi
4
+ """
5
+
6
+ import os
7
+ import json
8
+ import logging
9
+ from pathlib import Path
10
+ from typing import Dict, List, Optional, Any, Callable
11
+ from dataclasses import dataclass, asdict
12
+ from enum import Enum
13
+
14
+ try:
15
+ from PyQt6.QtGui import QColor, QPalette, QFont, QPixmap, QIcon
16
+ from PyQt6.QtCore import Qt
17
+ from PyQt6.QtWidgets import QApplication
18
+ PYQT_AVAILABLE = True
19
+ except ImportError:
20
+ PYQT_AVAILABLE = False
21
+
22
+ class ThemeMode(Enum):
23
+ """Tema modları"""
24
+ LIGHT = "light"
25
+ DARK = "dark"
26
+ AUTO = "auto"
27
+
28
+ class IconTheme(Enum):
29
+ """İkon temaları"""
30
+ SYSTEM = "system"
31
+ FLAT = "flat"
32
+ MODERN = "modern"
33
+ CLASSIC = "classic"
34
+
35
+ @dataclass
36
+ class ColorScheme:
37
+ """Renk şeması"""
38
+ primary: str = "#2196F3"
39
+ secondary: str = "#FFC107"
40
+ success: str = "#4CAF50"
41
+ warning: str = "#FF9800"
42
+ error: str = "#F44336"
43
+ info: str = "#00BCD4"
44
+
45
+ # Arka plan renkleri
46
+ background: str = "#FFFFFF"
47
+ surface: str = "#F5F5F5"
48
+ card: str = "#FFFFFF"
49
+
50
+ # Metin renkleri
51
+ text_primary: str = "#212121"
52
+ text_secondary: str = "#757575"
53
+ text_disabled: str = "#BDBDBD"
54
+
55
+ # Sınır ve ayırıcı renkleri
56
+ border: str = "#E0E0E0"
57
+ divider: str = "#E0E0E0"
58
+
59
+ def to_dict(self) -> Dict:
60
+ """Dict'e çevir"""
61
+ return asdict(self)
62
+
63
+ @classmethod
64
+ def from_dict(cls, data: Dict) -> 'ColorScheme':
65
+ """Dict'ten oluştur"""
66
+ return cls(**data)
67
+
68
+ @dataclass
69
+ class ThemeConfig:
70
+ """Tema yapılandırması"""
71
+ name: str
72
+ display_name: str
73
+ mode: ThemeMode
74
+ colors: ColorScheme
75
+ fonts: Dict[str, str] = None
76
+ borders: Dict[str, str] = None
77
+ shadows: Dict[str, str] = None
78
+ animations: Dict[str, Any] = None
79
+
80
+ def __post_init__(self):
81
+ if self.fonts is None:
82
+ self.fonts = {
83
+ "default": "Arial",
84
+ "heading": "Arial",
85
+ "monospace": "Courier New",
86
+ "size_small": "9",
87
+ "size_normal": "10",
88
+ "size_large": "12",
89
+ "size_heading": "14"
90
+ }
91
+
92
+ if self.borders is None:
93
+ self.borders = {
94
+ "radius": "4px",
95
+ "width": "1px",
96
+ "style": "solid"
97
+ }
98
+
99
+ if self.shadows is None:
100
+ self.shadows = {
101
+ "small": "0 1px 3px rgba(0,0,0,0.12)",
102
+ "medium": "0 4px 6px rgba(0,0,0,0.15)",
103
+ "large": "0 10px 20px rgba(0,0,0,0.19)"
104
+ }
105
+
106
+ if self.animations is None:
107
+ self.animations = {
108
+ "duration_fast": 150,
109
+ "duration_normal": 250,
110
+ "duration_slow": 400,
111
+ "easing": "ease-in-out"
112
+ }
113
+
114
+ def to_dict(self) -> Dict:
115
+ """Dict'e çevir"""
116
+ data = asdict(self)
117
+ data['mode'] = self.mode.value
118
+ data['colors'] = self.colors.to_dict()
119
+ return data
120
+
121
+ @classmethod
122
+ def from_dict(cls, data: Dict) -> 'ThemeConfig':
123
+ """Dict'ten oluştur"""
124
+ data['mode'] = ThemeMode(data.get('mode', 'auto'))
125
+ data['colors'] = ColorScheme.from_dict(data.get('colors', {}))
126
+ return cls(**data)
127
+
128
+ class IconManager:
129
+ """İkon yöneticisi"""
130
+
131
+ def __init__(self, theme_manager=None):
132
+ self.theme_manager = theme_manager
133
+ self.logger = logging.getLogger("IconManager")
134
+
135
+ # İkon dizinleri
136
+ self.system_icons_dir = Path("system/icons")
137
+ self.user_icons_dir = Path("users/icons")
138
+ self.system_icons_dir.mkdir(parents=True, exist_ok=True)
139
+ self.user_icons_dir.mkdir(parents=True, exist_ok=True)
140
+
141
+ # İkon cache
142
+ self.icon_cache: Dict[str, Any] = {}
143
+ self.icon_mappings: Dict[str, str] = {}
144
+
145
+ # Mevcut tema
146
+ self.current_theme = IconTheme.SYSTEM
147
+
148
+ # Başlangıç
149
+ self.load_icon_mappings()
150
+ self.create_default_icons()
151
+
152
+ def load_icon_mappings(self):
153
+ """İkon eşleştirmelerini yükle"""
154
+ try:
155
+ mappings_file = self.system_icons_dir / "mappings.json"
156
+
157
+ if mappings_file.exists():
158
+ with open(mappings_file, 'r', encoding='utf-8') as f:
159
+ self.icon_mappings = json.load(f)
160
+ else:
161
+ self.create_default_mappings()
162
+
163
+ except Exception as e:
164
+ self.logger.error(f"Failed to load icon mappings: {e}")
165
+
166
+ def create_default_mappings(self):
167
+ """Varsayılan ikon eşleştirmeleri oluştur"""
168
+ default_mappings = {
169
+ # Dosya türleri
170
+ "file.txt": "📄",
171
+ "file.py": "🐍",
172
+ "file.js": "📜",
173
+ "file.html": "🌐",
174
+ "file.css": "🎨",
175
+ "file.json": "📋",
176
+ "file.md": "📝",
177
+ "file.pdf": "📕",
178
+ "file.doc": "📘",
179
+ "file.xls": "📊",
180
+ "file.zip": "📦",
181
+ "file.rar": "📦",
182
+ "file.exe": "⚙️",
183
+ "file.app": "📱",
184
+ "file.dmg": "💿",
185
+ "file.iso": "💿",
186
+
187
+ # Klasörler
188
+ "folder": "📁",
189
+ "folder.open": "📂",
190
+ "folder.home": "🏠",
191
+ "folder.documents": "📑",
192
+ "folder.downloads": "⬇️",
193
+ "folder.pictures": "🖼️",
194
+ "folder.music": "🎵",
195
+ "folder.videos": "🎬",
196
+ "folder.desktop": "🖥️",
197
+ "folder.trash": "🗑️",
198
+
199
+ # Uygulamalar
200
+ "app.files": "📁",
201
+ "app.terminal": "💻",
202
+ "app.settings": "⚙️",
203
+ "app.browser": "🌐",
204
+ "app.editor": "📝",
205
+ "app.calculator": "🔢",
206
+ "app.calendar": "📅",
207
+ "app.clock": "🕐",
208
+ "app.notes": "📓",
209
+ "app.weather": "🌤️",
210
+
211
+ # Sistem
212
+ "system.warning": "⚠️",
213
+ "system.error": "❌",
214
+ "system.info": "ℹ️",
215
+ "system.success": "✅",
216
+ "system.loading": "⏳",
217
+ "system.refresh": "🔄",
218
+ "system.search": "🔍",
219
+ "system.menu": "☰",
220
+ "system.close": "✕",
221
+ "system.minimize": "−",
222
+ "system.maximize": "□",
223
+
224
+ # Eylemler
225
+ "action.copy": "📋",
226
+ "action.cut": "✂️",
227
+ "action.paste": "📄",
228
+ "action.delete": "🗑️",
229
+ "action.rename": "✏️",
230
+ "action.new": "📄",
231
+ "action.save": "💾",
232
+ "action.open": "📂",
233
+ "action.print": "🖨️",
234
+ "action.share": "📤",
235
+
236
+ # Navigasyon
237
+ "nav.back": "⬅️",
238
+ "nav.forward": "➡️",
239
+ "nav.up": "⬆️",
240
+ "nav.down": "⬇️",
241
+ "nav.home": "🏠",
242
+ "nav.refresh": "🔄",
243
+ }
244
+
245
+ try:
246
+ mappings_file = self.system_icons_dir / "mappings.json"
247
+ with open(mappings_file, 'w', encoding='utf-8') as f:
248
+ json.dump(default_mappings, f, indent=2, ensure_ascii=False)
249
+
250
+ self.icon_mappings = default_mappings
251
+ self.logger.info("Default icon mappings created")
252
+
253
+ except Exception as e:
254
+ self.logger.error(f"Failed to create default mappings: {e}")
255
+
256
+ def create_default_icons(self):
257
+ """Varsayılan ikonları oluştur (emoji tabanlı)"""
258
+ # Emoji tabanlı ikonlar zaten mappings'te var
259
+ # Bu metod gelecekte gerçek ikon dosyaları için kullanılabilir
260
+ pass
261
+
262
+ def get_icon(self, icon_id: str, size: int = 16) -> Any:
263
+ """İkon al"""
264
+ try:
265
+ # Cache'de var mı?
266
+ cache_key = f"{icon_id}_{size}_{self.current_theme.value}"
267
+ if cache_key in self.icon_cache:
268
+ return self.icon_cache[cache_key]
269
+
270
+ # Emoji ikon
271
+ if icon_id in self.icon_mappings:
272
+ emoji = self.icon_mappings[icon_id]
273
+
274
+ if PYQT_AVAILABLE:
275
+ # PyQt6 için emoji'yi QIcon'a çevir
276
+ pixmap = QPixmap(size, size)
277
+ pixmap.fill(Qt.GlobalColor.transparent)
278
+
279
+ # Bu basit bir implementasyon, gerçek uygulamada
280
+ # emoji'yi bitmap'e çeviren bir kütüphane kullanılmalı
281
+ icon = QIcon()
282
+ self.icon_cache[cache_key] = icon
283
+ return icon
284
+ else:
285
+ # PyQt yok, emoji string döndür
286
+ self.icon_cache[cache_key] = emoji
287
+ return emoji
288
+
289
+ # Dosya ikonu ara
290
+ icon_file = self.get_icon_file(icon_id, size)
291
+ if icon_file and icon_file.exists():
292
+ if PYQT_AVAILABLE:
293
+ icon = QIcon(str(icon_file))
294
+ self.icon_cache[cache_key] = icon
295
+ return icon
296
+ else:
297
+ self.icon_cache[cache_key] = str(icon_file)
298
+ return str(icon_file)
299
+
300
+ # Varsayılan ikon
301
+ default_icon = self.icon_mappings.get("file", "📄")
302
+ self.icon_cache[cache_key] = default_icon
303
+ return default_icon
304
+
305
+ except Exception as e:
306
+ self.logger.error(f"Failed to get icon {icon_id}: {e}")
307
+ return "📄" # Varsayılan
308
+
309
+ def get_icon_file(self, icon_id: str, size: int = 16) -> Optional[Path]:
310
+ """İkon dosya yolunu al"""
311
+ # Theme klasörlerinde ara
312
+ theme_dirs = [
313
+ self.system_icons_dir / self.current_theme.value,
314
+ self.system_icons_dir / "default",
315
+ self.user_icons_dir
316
+ ]
317
+
318
+ for theme_dir in theme_dirs:
319
+ if not theme_dir.exists():
320
+ continue
321
+
322
+ # Farklı dosya formatlarını dene
323
+ for ext in ['.png', '.svg', '.ico', '.jpg']:
324
+ icon_file = theme_dir / f"{icon_id}_{size}{ext}"
325
+ if icon_file.exists():
326
+ return icon_file
327
+
328
+ # Boyut belirtilmemiş dosya
329
+ icon_file = theme_dir / f"{icon_id}{ext}"
330
+ if icon_file.exists():
331
+ return icon_file
332
+
333
+ return None
334
+
335
+ def get_file_icon(self, file_path: str) -> Any:
336
+ """Dosya tipine göre ikon al"""
337
+ path = Path(file_path)
338
+
339
+ if path.is_dir():
340
+ # Özel klasör isimleri
341
+ folder_names = {
342
+ "Desktop": "folder.desktop",
343
+ "Documents": "folder.documents",
344
+ "Downloads": "folder.downloads",
345
+ "Pictures": "folder.pictures",
346
+ "Music": "folder.music",
347
+ "Videos": "folder.videos",
348
+ "Trash": "folder.trash"
349
+ }
350
+
351
+ folder_icon = folder_names.get(path.name, "folder")
352
+ return self.get_icon(folder_icon)
353
+ else:
354
+ # Dosya uzantısına göre
355
+ extension = path.suffix.lower()
356
+ icon_id = f"file{extension}" if extension else "file"
357
+
358
+ return self.get_icon(icon_id)
359
+
360
+ def set_icon_theme(self, theme: IconTheme):
361
+ """İkon temasını değiştir"""
362
+ if theme != self.current_theme:
363
+ self.current_theme = theme
364
+ self.icon_cache.clear() # Cache'i temizle
365
+ self.logger.info(f"Icon theme changed to: {theme.value}")
366
+
367
+ def add_custom_icon(self, icon_id: str, icon_path: str) -> bool:
368
+ """Özel ikon ekle"""
369
+ try:
370
+ source_path = Path(icon_path)
371
+ if not source_path.exists():
372
+ return False
373
+
374
+ # Kullanıcı ikon dizinine kopyala
375
+ target_path = self.user_icons_dir / f"{icon_id}{source_path.suffix}"
376
+
377
+ import shutil
378
+ shutil.copy2(source_path, target_path)
379
+
380
+ # Cache'i temizle
381
+ cache_keys_to_remove = [key for key in self.icon_cache.keys()
382
+ if key.startswith(icon_id)]
383
+ for key in cache_keys_to_remove:
384
+ del self.icon_cache[key]
385
+
386
+ self.logger.info(f"Custom icon added: {icon_id}")
387
+ return True
388
+
389
+ except Exception as e:
390
+ self.logger.error(f"Failed to add custom icon {icon_id}: {e}")
391
+ return False
392
+
393
+ class ThemeManager:
394
+ """Ana tema yöneticisi"""
395
+
396
+ def __init__(self, kernel=None):
397
+ self.kernel = kernel
398
+ self.logger = logging.getLogger("ThemeManager")
399
+
400
+ # Tema dizinleri
401
+ self.themes_dir = Path("system/themes")
402
+ self.user_themes_dir = Path("users/themes")
403
+ self.themes_dir.mkdir(parents=True, exist_ok=True)
404
+ self.user_themes_dir.mkdir(parents=True, exist_ok=True)
405
+
406
+ # Tema verileri
407
+ self.themes: Dict[str, ThemeConfig] = {}
408
+ self.current_theme_name = "dark"
409
+ self.current_mode = ThemeMode.AUTO
410
+
411
+ # İkon yöneticisi
412
+ self.icon_manager = IconManager(self)
413
+
414
+ # Tema değişim callback'leri
415
+ self.theme_callbacks: List[Callable] = []
416
+
417
+ # Başlangıç
418
+ self.create_default_themes()
419
+ self.load_themes()
420
+ self.load_current_theme()
421
+
422
+ def create_default_themes(self):
423
+ """Varsayılan temaları oluştur"""
424
+ # Koyu tema
425
+ dark_colors = ColorScheme(
426
+ primary="#2196F3",
427
+ secondary="#FFC107",
428
+ success="#4CAF50",
429
+ warning="#FF9800",
430
+ error="#F44336",
431
+ info="#00BCD4",
432
+ background="#1e1e1e",
433
+ surface="#2d2d2d",
434
+ card="#3c3c3c",
435
+ text_primary="#ffffff",
436
+ text_secondary="#b0b0b0",
437
+ text_disabled="#666666",
438
+ border="#555555",
439
+ divider="#444444"
440
+ )
441
+
442
+ dark_theme = ThemeConfig(
443
+ name="dark",
444
+ display_name="Koyu Tema",
445
+ mode=ThemeMode.DARK,
446
+ colors=dark_colors
447
+ )
448
+
449
+ # Açık tema
450
+ light_colors = ColorScheme(
451
+ primary="#2196F3",
452
+ secondary="#FFC107",
453
+ success="#4CAF50",
454
+ warning="#FF9800",
455
+ error="#F44336",
456
+ info="#00BCD4",
457
+ background="#ffffff",
458
+ surface="#f5f5f5",
459
+ card="#ffffff",
460
+ text_primary="#212121",
461
+ text_secondary="#757575",
462
+ text_disabled="#bdbdbd",
463
+ border="#e0e0e0",
464
+ divider="#e0e0e0"
465
+ )
466
+
467
+ light_theme = ThemeConfig(
468
+ name="light",
469
+ display_name="Açık Tema",
470
+ mode=ThemeMode.LIGHT,
471
+ colors=light_colors
472
+ )
473
+
474
+ # Temaları kaydet
475
+ themes = {"dark": dark_theme, "light": light_theme}
476
+
477
+ for theme_name, theme in themes.items():
478
+ theme_file = self.themes_dir / f"{theme_name}.json"
479
+ try:
480
+ with open(theme_file, 'w', encoding='utf-8') as f:
481
+ json.dump(theme.to_dict(), f, indent=2, ensure_ascii=False)
482
+ except Exception as e:
483
+ self.logger.error(f"Failed to save theme {theme_name}: {e}")
484
+
485
+ def load_themes(self):
486
+ """Temaları yükle"""
487
+ try:
488
+ # Sistem temaları
489
+ for theme_file in self.themes_dir.glob("*.json"):
490
+ try:
491
+ with open(theme_file, 'r', encoding='utf-8') as f:
492
+ theme_data = json.load(f)
493
+
494
+ theme = ThemeConfig.from_dict(theme_data)
495
+ self.themes[theme.name] = theme
496
+
497
+ except Exception as e:
498
+ self.logger.error(f"Failed to load theme {theme_file}: {e}")
499
+
500
+ # Kullanıcı temaları
501
+ for theme_file in self.user_themes_dir.glob("*.json"):
502
+ try:
503
+ with open(theme_file, 'r', encoding='utf-8') as f:
504
+ theme_data = json.load(f)
505
+
506
+ theme = ThemeConfig.from_dict(theme_data)
507
+ self.themes[theme.name] = theme
508
+
509
+ except Exception as e:
510
+ self.logger.error(f"Failed to load user theme {theme_file}: {e}")
511
+
512
+ self.logger.info(f"Loaded {len(self.themes)} themes")
513
+
514
+ except Exception as e:
515
+ self.logger.error(f"Failed to load themes: {e}")
516
+
517
+ def load_current_theme(self):
518
+ """Mevcut temayı yükle"""
519
+ try:
520
+ if self.kernel:
521
+ config_manager = self.kernel.get_module("config")
522
+ if config_manager:
523
+ self.current_theme_name = config_manager.get("ui.theme", "dark")
524
+ mode_str = config_manager.get("ui.theme_mode", "auto")
525
+ self.current_mode = ThemeMode(mode_str)
526
+
527
+ # Otomatik mod kontrolü
528
+ if self.current_mode == ThemeMode.AUTO:
529
+ # Sistem saatine göre koyu/açık tema seç
530
+ from datetime import datetime
531
+ hour = datetime.now().hour
532
+ if 6 <= hour <= 18: # Gündüz
533
+ auto_theme = "light"
534
+ else: # Gece
535
+ auto_theme = "dark"
536
+
537
+ if auto_theme in self.themes:
538
+ self.current_theme_name = auto_theme
539
+
540
+ self.logger.info(f"Current theme: {self.current_theme_name} (mode: {self.current_mode.value})")
541
+
542
+ except Exception as e:
543
+ self.logger.error(f"Failed to load current theme: {e}")
544
+
545
+ def get_current_theme(self) -> Optional[ThemeConfig]:
546
+ """Mevcut temayı al"""
547
+ return self.themes.get(self.current_theme_name)
548
+
549
+ def set_theme(self, theme_name: str, save: bool = True) -> bool:
550
+ """Tema değiştir"""
551
+ try:
552
+ if theme_name not in self.themes:
553
+ self.logger.warning(f"Theme not found: {theme_name}")
554
+ return False
555
+
556
+ old_theme = self.current_theme_name
557
+ self.current_theme_name = theme_name
558
+
559
+ # Config'e kaydet
560
+ if save and self.kernel:
561
+ config_manager = self.kernel.get_module("config")
562
+ if config_manager:
563
+ config_manager.set("ui.theme", theme_name)
564
+
565
+ # Tema uygula
566
+ self.apply_current_theme()
567
+
568
+ # Callback'leri çağır
569
+ self.call_theme_callbacks(old_theme, theme_name)
570
+
571
+ self.logger.info(f"Theme changed from {old_theme} to {theme_name}")
572
+ return True
573
+
574
+ except Exception as e:
575
+ self.logger.error(f"Failed to set theme {theme_name}: {e}")
576
+ return False
577
+
578
+ def set_theme_mode(self, mode: ThemeMode, save: bool = True) -> bool:
579
+ """Tema modunu değiştir"""
580
+ try:
581
+ old_mode = self.current_mode
582
+ self.current_mode = mode
583
+
584
+ # Config'e kaydet
585
+ if save and self.kernel:
586
+ config_manager = self.kernel.get_module("config")
587
+ if config_manager:
588
+ config_manager.set("ui.theme_mode", mode.value)
589
+
590
+ # Mevcut temayı yeniden yükle
591
+ self.load_current_theme()
592
+ self.apply_current_theme()
593
+
594
+ self.logger.info(f"Theme mode changed from {old_mode.value} to {mode.value}")
595
+ return True
596
+
597
+ except Exception as e:
598
+ self.logger.error(f"Failed to set theme mode {mode.value}: {e}")
599
+ return False
600
+
601
+ def apply_current_theme(self):
602
+ """Mevcut temayı uygula"""
603
+ try:
604
+ theme = self.get_current_theme()
605
+ if not theme or not PYQT_AVAILABLE:
606
+ return
607
+
608
+ app = QApplication.instance()
609
+ if not app:
610
+ return
611
+
612
+ # Renk paleti oluştur
613
+ palette = QPalette()
614
+
615
+ # Ana renkler
616
+ palette.setColor(QPalette.ColorRole.Window, QColor(theme.colors.background))
617
+ palette.setColor(QPalette.ColorRole.WindowText, QColor(theme.colors.text_primary))
618
+ palette.setColor(QPalette.ColorRole.Base, QColor(theme.colors.surface))
619
+ palette.setColor(QPalette.ColorRole.AlternateBase, QColor(theme.colors.card))
620
+ palette.setColor(QPalette.ColorRole.Text, QColor(theme.colors.text_primary))
621
+ palette.setColor(QPalette.ColorRole.Button, QColor(theme.colors.surface))
622
+ palette.setColor(QPalette.ColorRole.ButtonText, QColor(theme.colors.text_primary))
623
+ palette.setColor(QPalette.ColorRole.Highlight, QColor(theme.colors.primary))
624
+ palette.setColor(QPalette.ColorRole.HighlightedText, QColor("#ffffff"))
625
+
626
+ # Devre dışı renkler
627
+ palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, QColor(theme.colors.text_disabled))
628
+ palette.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, QColor(theme.colors.text_disabled))
629
+
630
+ # Paleti uygula
631
+ app.setPalette(palette)
632
+
633
+ # Global stylesheet
634
+ stylesheet = self.generate_stylesheet(theme)
635
+ app.setStyleSheet(stylesheet)
636
+
637
+ self.logger.debug(f"Theme applied: {theme.name}")
638
+
639
+ except Exception as e:
640
+ self.logger.error(f"Failed to apply theme: {e}")
641
+
642
+ def generate_stylesheet(self, theme: ThemeConfig) -> str:
643
+ """Tema için stylesheet oluştur"""
644
+ colors = theme.colors
645
+ fonts = theme.fonts
646
+ borders = theme.borders
647
+
648
+ stylesheet = f"""
649
+ /* Genel stil */
650
+ QWidget {{
651
+ background-color: {colors.background};
652
+ color: {colors.text_primary};
653
+ font-family: '{fonts['default']}';
654
+ font-size: {fonts['size_normal']}pt;
655
+ }}
656
+
657
+ /* Butonlar */
658
+ QPushButton {{
659
+ background-color: {colors.surface};
660
+ border: {borders['width']} {borders['style']} {colors.border};
661
+ border-radius: {borders['radius']};
662
+ padding: 6px 12px;
663
+ min-height: 20px;
664
+ }}
665
+
666
+ QPushButton:hover {{
667
+ background-color: {colors.primary};
668
+ color: white;
669
+ }}
670
+
671
+ QPushButton:pressed {{
672
+ background-color: {colors.primary};
673
+ border: {borders['width']} {borders['style']} {colors.primary};
674
+ }}
675
+
676
+ QPushButton:disabled {{
677
+ background-color: {colors.surface};
678
+ color: {colors.text_disabled};
679
+ border-color: {colors.text_disabled};
680
+ }}
681
+
682
+ /* Metin kutuları */
683
+ QLineEdit, QTextEdit, QPlainTextEdit {{
684
+ background-color: {colors.card};
685
+ border: {borders['width']} {borders['style']} {colors.border};
686
+ border-radius: {borders['radius']};
687
+ padding: 4px 8px;
688
+ selection-background-color: {colors.primary};
689
+ }}
690
+
691
+ QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus {{
692
+ border-color: {colors.primary};
693
+ }}
694
+
695
+ /* Listeler */
696
+ QListWidget, QTreeWidget {{
697
+ background-color: {colors.card};
698
+ border: {borders['width']} {borders['style']} {colors.border};
699
+ border-radius: {borders['radius']};
700
+ alternate-background-color: {colors.surface};
701
+ }}
702
+
703
+ QListWidget::item, QTreeWidget::item {{
704
+ padding: 4px;
705
+ border-bottom: 1px solid {colors.divider};
706
+ }}
707
+
708
+ QListWidget::item:selected, QTreeWidget::item:selected {{
709
+ background-color: {colors.primary};
710
+ color: white;
711
+ }}
712
+
713
+ QListWidget::item:hover, QTreeWidget::item:hover {{
714
+ background-color: {colors.surface};
715
+ }}
716
+
717
+ /* Menüler */
718
+ QMenuBar {{
719
+ background-color: {colors.surface};
720
+ border-bottom: 1px solid {colors.border};
721
+ }}
722
+
723
+ QMenuBar::item {{
724
+ background-color: transparent;
725
+ padding: 4px 8px;
726
+ }}
727
+
728
+ QMenuBar::item:selected {{
729
+ background-color: {colors.primary};
730
+ color: white;
731
+ }}
732
+
733
+ QMenu {{
734
+ background-color: {colors.card};
735
+ border: {borders['width']} {borders['style']} {colors.border};
736
+ border-radius: {borders['radius']};
737
+ }}
738
+
739
+ QMenu::item {{
740
+ padding: 6px 12px;
741
+ }}
742
+
743
+ QMenu::item:selected {{
744
+ background-color: {colors.primary};
745
+ color: white;
746
+ }}
747
+
748
+ /* Scroll barlar */
749
+ QScrollBar:vertical {{
750
+ background-color: {colors.surface};
751
+ width: 12px;
752
+ border-radius: 6px;
753
+ }}
754
+
755
+ QScrollBar::handle:vertical {{
756
+ background-color: {colors.text_secondary};
757
+ border-radius: 6px;
758
+ min-height: 20px;
759
+ }}
760
+
761
+ QScrollBar::handle:vertical:hover {{
762
+ background-color: {colors.primary};
763
+ }}
764
+
765
+ /* Tab widget */
766
+ QTabWidget::pane {{
767
+ border: {borders['width']} {borders['style']} {colors.border};
768
+ border-radius: {borders['radius']};
769
+ background-color: {colors.card};
770
+ }}
771
+
772
+ QTabBar::tab {{
773
+ background-color: {colors.surface};
774
+ border: {borders['width']} {borders['style']} {colors.border};
775
+ padding: 6px 12px;
776
+ margin-right: 2px;
777
+ }}
778
+
779
+ QTabBar::tab:selected {{
780
+ background-color: {colors.primary};
781
+ color: white;
782
+ }}
783
+
784
+ QTabBar::tab:hover {{
785
+ background-color: {colors.text_secondary};
786
+ }}
787
+
788
+ /* Başlık çubukları */
789
+ QFrame[class="title-bar"] {{
790
+ background-color: {colors.surface};
791
+ border-bottom: 1px solid {colors.border};
792
+ }}
793
+
794
+ /* Dock */
795
+ QFrame[class="dock"] {{
796
+ background-color: {colors.surface};
797
+ border: {borders['width']} {borders['style']} {colors.border};
798
+ border-radius: 8px;
799
+ }}
800
+
801
+ /* Topbar */
802
+ QFrame[class="topbar"] {{
803
+ background-color: {colors.surface};
804
+ border-bottom: 1px solid {colors.border};
805
+ }}
806
+ """
807
+
808
+ return stylesheet
809
+
810
+ def get_available_themes(self) -> List[Dict]:
811
+ """Mevcut temaları al"""
812
+ themes_list = []
813
+ for theme_name, theme in self.themes.items():
814
+ themes_list.append({
815
+ "name": theme.name,
816
+ "display_name": theme.display_name,
817
+ "mode": theme.mode.value,
818
+ "is_current": theme_name == self.current_theme_name
819
+ })
820
+
821
+ return themes_list
822
+
823
+ def add_theme_callback(self, callback: Callable):
824
+ """Tema değişim callback'i ekle"""
825
+ self.theme_callbacks.append(callback)
826
+
827
+ def remove_theme_callback(self, callback: Callable):
828
+ """Tema değişim callback'ini kaldır"""
829
+ if callback in self.theme_callbacks:
830
+ self.theme_callbacks.remove(callback)
831
+
832
+ def call_theme_callbacks(self, old_theme: str, new_theme: str):
833
+ """Tema callback'lerini çağır"""
834
+ for callback in self.theme_callbacks:
835
+ try:
836
+ callback(old_theme, new_theme)
837
+ except Exception as e:
838
+ self.logger.error(f"Theme callback failed: {e}")
839
+
840
+ def export_theme(self, theme_name: str, export_path: str) -> bool:
841
+ """Temayı dışa aktar"""
842
+ try:
843
+ if theme_name not in self.themes:
844
+ return False
845
+
846
+ theme = self.themes[theme_name]
847
+ export_file = Path(export_path)
848
+
849
+ with open(export_file, 'w', encoding='utf-8') as f:
850
+ json.dump(theme.to_dict(), f, indent=2, ensure_ascii=False)
851
+
852
+ self.logger.info(f"Theme exported: {theme_name} to {export_path}")
853
+ return True
854
+
855
+ except Exception as e:
856
+ self.logger.error(f"Failed to export theme {theme_name}: {e}")
857
+ return False
858
+
859
+ def import_theme(self, import_path: str) -> bool:
860
+ """Tema içe aktar"""
861
+ try:
862
+ import_file = Path(import_path)
863
+ if not import_file.exists():
864
+ return False
865
+
866
+ with open(import_file, 'r', encoding='utf-8') as f:
867
+ theme_data = json.load(f)
868
+
869
+ theme = ThemeConfig.from_dict(theme_data)
870
+
871
+ # Kullanıcı tema dizinine kaydet
872
+ theme_file = self.user_themes_dir / f"{theme.name}.json"
873
+ with open(theme_file, 'w', encoding='utf-8') as f:
874
+ json.dump(theme.to_dict(), f, indent=2, ensure_ascii=False)
875
+
876
+ # Tema listesine ekle
877
+ self.themes[theme.name] = theme
878
+
879
+ self.logger.info(f"Theme imported: {theme.name}")
880
+ return True
881
+
882
+ except Exception as e:
883
+ self.logger.error(f"Failed to import theme: {e}")
884
+ return False
885
+
886
+ def shutdown(self):
887
+ """Theme manager'ı kapat"""
888
+ try:
889
+ # Mevcut ayarları kaydet
890
+ if self.kernel:
891
+ config_manager = self.kernel.get_module("config")
892
+ if config_manager:
893
+ config_manager.set("ui.theme", self.current_theme_name)
894
+ config_manager.set("ui.theme_mode", self.current_mode.value)
895
+
896
+ self.logger.info("Theme manager shutdown completed")
897
+
898
+ except Exception as e:
899
+ self.logger.error(f"Theme manager shutdown failed: {e}")
900
+
901
+ # Kolaylık fonksiyonları
902
+ _theme_manager = None
903
+
904
+ def init_theme_manager(kernel=None) -> ThemeManager:
905
+ """Theme manager'ı başlat"""
906
+ global _theme_manager
907
+ _theme_manager = ThemeManager(kernel)
908
+ return _theme_manager
909
+
910
+ def get_theme_manager() -> Optional[ThemeManager]:
911
+ """Theme manager'ı al"""
912
+ return _theme_manager
913
+
914
+ def get_icon(icon_id: str, size: int = 16) -> Any:
915
+ """İkon al (kısayol)"""
916
+ if _theme_manager:
917
+ return _theme_manager.icon_manager.get_icon(icon_id, size)
918
+ return "📄"
919
+
920
+ def get_file_icon(file_path: str) -> Any:
921
+ """Dosya ikonu al (kısayol)"""
922
+ if _theme_manager:
923
+ return _theme_manager.icon_manager.get_file_icon(file_path)
924
+ return "📄"
925
+
926
+ def set_theme(theme_name: str) -> bool:
927
+ """Tema değiştir (kısayol)"""
928
+ if _theme_manager:
929
+ return _theme_manager.set_theme(theme_name)
930
+ return False