clapp-pm 1.0.42__py3-none-any.whl → 1.0.44__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.
- {clapp_pm-1.0.42.data → clapp_pm-1.0.44.data}/data/version.json +1 -1
- {clapp_pm-1.0.42.dist-info → clapp_pm-1.0.44.dist-info}/METADATA +1 -1
- {clapp_pm-1.0.42.dist-info → clapp_pm-1.0.44.dist-info}/RECORD +30 -9
- packages/pycloudos/README.md +279 -0
- packages/pycloudos/core/appmon.py +0 -0
- packages/pycloudos/main.py +193 -0
- packages/pycloudos/manifest.json +15 -0
- packages/pycloudos/pycloud_fs/home/Desktop/test_desktop_file.txt +11 -0
- packages/pycloudos/pycloud_fs/home/default/Desktop/test_dosya.txt +1 -0
- packages/pycloudos/pycloud_fs/home/default/Desktop/test_script.py +11 -0
- packages/pycloudos/rain/__init__.py +7 -0
- packages/pycloudos/rain/contextmenu.py +1370 -0
- packages/pycloudos/rain/desktop.py +556 -0
- packages/pycloudos/rain/dock.py +722 -0
- packages/pycloudos/rain/flet_html_widgets.py +1448 -0
- packages/pycloudos/rain/theme.py +930 -0
- packages/pycloudos/rain/topbar.py +1421 -0
- packages/pycloudos/rain/ui.py +381 -0
- packages/pycloudos/rain/wallpaper.py +830 -0
- packages/pycloudos/rain/widgets.py +688 -0
- packages/pycloudos/rain/windowmanager.py +605 -0
- packages/pycloudos/requirements-dev.txt +43 -0
- packages/pycloudos/requirements.txt +43 -0
- packages/pycloudos/setup_deps.py +422 -0
- publish_command.py +45 -61
- version.py +1 -1
- {clapp_pm-1.0.42.dist-info → clapp_pm-1.0.44.dist-info}/WHEEL +0 -0
- {clapp_pm-1.0.42.dist-info → clapp_pm-1.0.44.dist-info}/entry_points.txt +0 -0
- {clapp_pm-1.0.42.dist-info → clapp_pm-1.0.44.dist-info}/licenses/LICENSE +0 -0
- {clapp_pm-1.0.42.dist-info → clapp_pm-1.0.44.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1370 @@
|
|
1
|
+
"""
|
2
|
+
PyCloud OS Rain Context Menu
|
3
|
+
Masaüstü, dosya yöneticisi ve uygulama ikonları için dinamik sağ tık menü sistemi
|
4
|
+
"""
|
5
|
+
|
6
|
+
import os
|
7
|
+
import logging
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import Dict, List, Optional, Any, Callable, Union
|
10
|
+
from dataclasses import dataclass
|
11
|
+
from enum import Enum
|
12
|
+
import json
|
13
|
+
|
14
|
+
try:
|
15
|
+
from PyQt6.QtWidgets import (QMenu, QAction, QWidget, QApplication,
|
16
|
+
QMessageBox, QInputDialog, QFileDialog)
|
17
|
+
from PyQt6.QtCore import Qt, QPoint, pyqtSignal, QObject
|
18
|
+
from PyQt6.QtGui import QIcon, QPixmap, QKeySequence
|
19
|
+
PYQT_AVAILABLE = True
|
20
|
+
except ImportError:
|
21
|
+
# Dummy classes for when PyQt6 is not available
|
22
|
+
class QMenu:
|
23
|
+
def __init__(self, *args, **kwargs): pass
|
24
|
+
def addAction(self, *args, **kwargs): return QAction()
|
25
|
+
def addSeparator(self): pass
|
26
|
+
def addMenu(self, *args, **kwargs): return QMenu()
|
27
|
+
def exec(self, *args, **kwargs): return None
|
28
|
+
def setStyleSheet(self, *args, **kwargs): pass
|
29
|
+
|
30
|
+
class QAction:
|
31
|
+
def __init__(self, *args, **kwargs): pass
|
32
|
+
def triggered(self): return pyqtSignal()
|
33
|
+
def setIcon(self, *args, **kwargs): pass
|
34
|
+
def setEnabled(self, *args, **kwargs): pass
|
35
|
+
|
36
|
+
class QWidget:
|
37
|
+
def __init__(self, *args, **kwargs): pass
|
38
|
+
|
39
|
+
class QObject:
|
40
|
+
def __init__(self, *args, **kwargs): pass
|
41
|
+
|
42
|
+
class pyqtSignal:
|
43
|
+
def __init__(self, *args, **kwargs): pass
|
44
|
+
def connect(self, *args, **kwargs): pass
|
45
|
+
def emit(self, *args, **kwargs): pass
|
46
|
+
|
47
|
+
class QIcon:
|
48
|
+
def __init__(self, *args, **kwargs): pass
|
49
|
+
|
50
|
+
class QPoint:
|
51
|
+
def __init__(self, *args, **kwargs): pass
|
52
|
+
|
53
|
+
PYQT_AVAILABLE = False
|
54
|
+
|
55
|
+
class MenuType(Enum):
|
56
|
+
"""Menü türleri"""
|
57
|
+
DESKTOP = "desktop"
|
58
|
+
FILE = "file"
|
59
|
+
FOLDER = "folder"
|
60
|
+
APPLICATION = "application"
|
61
|
+
WIDGET = "widget"
|
62
|
+
SELECTION = "selection" # Çoklu seçim
|
63
|
+
|
64
|
+
class MenuContext(Enum):
|
65
|
+
"""Menü bağlamları"""
|
66
|
+
DESKTOP_EMPTY = "desktop_empty"
|
67
|
+
DESKTOP_FILE = "desktop_file"
|
68
|
+
DESKTOP_FOLDER = "desktop_folder"
|
69
|
+
FILES_APP = "files_app"
|
70
|
+
DOCK_APP = "dock_app"
|
71
|
+
WIDGET_AREA = "widget_area"
|
72
|
+
APP_PACKAGE = "app_package"
|
73
|
+
|
74
|
+
@dataclass
|
75
|
+
class MenuAction:
|
76
|
+
"""Menü eylemi"""
|
77
|
+
id: str
|
78
|
+
text: str
|
79
|
+
icon: str = ""
|
80
|
+
shortcut: str = ""
|
81
|
+
enabled: bool = True
|
82
|
+
visible: bool = True
|
83
|
+
separator_after: bool = False
|
84
|
+
submenu: List['MenuAction'] = None
|
85
|
+
callback: Callable = None
|
86
|
+
|
87
|
+
def __post_init__(self):
|
88
|
+
if self.submenu is None:
|
89
|
+
self.submenu = []
|
90
|
+
|
91
|
+
@dataclass
|
92
|
+
class MenuRequest:
|
93
|
+
"""Menü isteği"""
|
94
|
+
menu_type: MenuType
|
95
|
+
context: MenuContext
|
96
|
+
target_path: str = ""
|
97
|
+
target_paths: List[str] = None
|
98
|
+
position: Optional[Any] = None
|
99
|
+
widget: Optional[Any] = None
|
100
|
+
extra_data: Dict = None
|
101
|
+
|
102
|
+
def __post_init__(self):
|
103
|
+
if self.target_paths is None:
|
104
|
+
self.target_paths = []
|
105
|
+
if self.extra_data is None:
|
106
|
+
self.extra_data = {}
|
107
|
+
|
108
|
+
class ContextMenuManager(QObject):
|
109
|
+
"""Bağlam menüsü yöneticisi"""
|
110
|
+
|
111
|
+
# Sinyaller
|
112
|
+
action_triggered = pyqtSignal(str, dict) # action_id, data
|
113
|
+
|
114
|
+
def __init__(self, kernel=None):
|
115
|
+
super().__init__()
|
116
|
+
self.kernel = kernel
|
117
|
+
self.logger = logging.getLogger("ContextMenuManager")
|
118
|
+
|
119
|
+
# Menü şablonları
|
120
|
+
self.menu_templates: Dict[MenuContext, List[MenuAction]] = {}
|
121
|
+
|
122
|
+
# Eylem callback'leri
|
123
|
+
self.action_callbacks: Dict[str, Callable] = {}
|
124
|
+
|
125
|
+
# Clipboard
|
126
|
+
self.clipboard_files: List[str] = []
|
127
|
+
self.clipboard_operation = "" # "copy" veya "cut"
|
128
|
+
|
129
|
+
# Başlangıç
|
130
|
+
self.create_default_templates()
|
131
|
+
self.register_default_actions()
|
132
|
+
|
133
|
+
def create_default_templates(self):
|
134
|
+
"""Varsayılan menü şablonları oluştur"""
|
135
|
+
|
136
|
+
# Masaüstü boş alan menüsü
|
137
|
+
self.menu_templates[MenuContext.DESKTOP_EMPTY] = [
|
138
|
+
MenuAction("new_file", "📄 Yeni", submenu=[
|
139
|
+
MenuAction("new_text", "📝 Metin Belgesi"),
|
140
|
+
MenuAction("new_python", "🐍 Python Dosyası"),
|
141
|
+
MenuAction("new_folder", "📁 Klasör"),
|
142
|
+
]),
|
143
|
+
MenuAction("paste", "📋 Yapıştır", shortcut="Ctrl+V", enabled=False),
|
144
|
+
MenuAction("", "", separator_after=True),
|
145
|
+
MenuAction("refresh", "🔄 Yenile", shortcut="F5"),
|
146
|
+
MenuAction("properties", "🎨 Masaüstü Ayarları"),
|
147
|
+
MenuAction("wallpaper", "🖼️ Duvar Kağıdı Değiştir"),
|
148
|
+
]
|
149
|
+
|
150
|
+
# Dosya menüsü
|
151
|
+
self.menu_templates[MenuContext.DESKTOP_FILE] = [
|
152
|
+
MenuAction("open", "📂 Aç", shortcut="Enter"),
|
153
|
+
MenuAction("open_with", "🔧 Aç ile...", submenu=[]),
|
154
|
+
MenuAction("", "", separator_after=True),
|
155
|
+
MenuAction("copy", "📋 Kopyala", shortcut="Ctrl+C"),
|
156
|
+
MenuAction("cut", "✂️ Kes", shortcut="Ctrl+X"),
|
157
|
+
MenuAction("", "", separator_after=True),
|
158
|
+
MenuAction("rename", "✏️ Yeniden Adlandır", shortcut="F2"),
|
159
|
+
MenuAction("delete", "🗑️ Sil", shortcut="Delete"),
|
160
|
+
MenuAction("", "", separator_after=True),
|
161
|
+
MenuAction("properties", "ℹ️ Özellikler", shortcut="Alt+Enter"),
|
162
|
+
]
|
163
|
+
|
164
|
+
# Klasör menüsü
|
165
|
+
self.menu_templates[MenuContext.DESKTOP_FOLDER] = [
|
166
|
+
MenuAction("open", "📂 Aç", shortcut="Enter"),
|
167
|
+
MenuAction("open_new_window", "🪟 Yeni Pencerede Aç"),
|
168
|
+
MenuAction("", "", separator_after=True),
|
169
|
+
MenuAction("copy", "📋 Kopyala", shortcut="Ctrl+C"),
|
170
|
+
MenuAction("cut", "✂️ Kes", shortcut="Ctrl+X"),
|
171
|
+
MenuAction("", "", separator_after=True),
|
172
|
+
MenuAction("rename", "✏️ Yeniden Adlandır", shortcut="F2"),
|
173
|
+
MenuAction("delete", "🗑️ Sil", shortcut="Delete"),
|
174
|
+
MenuAction("", "", separator_after=True),
|
175
|
+
MenuAction("properties", "ℹ️ Özellikler", shortcut="Alt+Enter"),
|
176
|
+
]
|
177
|
+
|
178
|
+
# Uygulama menüsü (Dock)
|
179
|
+
self.menu_templates[MenuContext.DOCK_APP] = [
|
180
|
+
MenuAction("launch", "🚀 Başlat"),
|
181
|
+
MenuAction("", "", separator_after=True),
|
182
|
+
MenuAction("pin", "📌 Dock'a Sabitle"),
|
183
|
+
MenuAction("unpin", "📌 Dock'tan Kaldır"),
|
184
|
+
MenuAction("", "", separator_after=True),
|
185
|
+
MenuAction("app_info", "ℹ️ Uygulama Bilgileri"),
|
186
|
+
MenuAction("app_settings", "⚙️ Uygulama Ayarları"),
|
187
|
+
]
|
188
|
+
|
189
|
+
# Dosya yöneticisi menüsü
|
190
|
+
self.menu_templates[MenuContext.FILES_APP] = [
|
191
|
+
MenuAction("open", "📂 Aç", shortcut="Enter"),
|
192
|
+
MenuAction("open_with", "🔧 Aç ile...", submenu=[]),
|
193
|
+
MenuAction("", "", separator_after=True),
|
194
|
+
MenuAction("copy", "📋 Kopyala", shortcut="Ctrl+C"),
|
195
|
+
MenuAction("cut", "✂️ Kes", shortcut="Ctrl+X"),
|
196
|
+
MenuAction("paste", "📄 Yapıştır", shortcut="Ctrl+V"),
|
197
|
+
MenuAction("", "", separator_after=True),
|
198
|
+
MenuAction("rename", "✏️ Yeniden Adlandır", shortcut="F2"),
|
199
|
+
MenuAction("delete", "🗑️ Sil", shortcut="Delete"),
|
200
|
+
MenuAction("", "", separator_after=True),
|
201
|
+
MenuAction("compress", "📦 Arşivle"),
|
202
|
+
MenuAction("properties", "ℹ️ Özellikler", shortcut="Alt+Enter"),
|
203
|
+
]
|
204
|
+
|
205
|
+
# Widget menüsü
|
206
|
+
self.menu_templates[MenuContext.WIDGET_AREA] = [
|
207
|
+
MenuAction("widget_settings", "⚙️ Widget Ayarları"),
|
208
|
+
MenuAction("widget_resize", "📏 Yeniden Boyutlandır"),
|
209
|
+
MenuAction("", "", separator_after=True),
|
210
|
+
MenuAction("widget_close", "✕ Kapat"),
|
211
|
+
]
|
212
|
+
|
213
|
+
# .app dosyası menüsü
|
214
|
+
self.menu_templates[MenuContext.APP_PACKAGE] = [
|
215
|
+
MenuAction("install_app", "📦 Uygulamayı Kur", shortcut="Enter"),
|
216
|
+
MenuAction("", "", separator_after=True),
|
217
|
+
MenuAction("copy", "📋 Kopyala", shortcut="Ctrl+C"),
|
218
|
+
MenuAction("cut", "✂️ Kes", shortcut="Ctrl+X"),
|
219
|
+
MenuAction("", "", separator_after=True),
|
220
|
+
MenuAction("rename", "✏️ Yeniden Adlandır", shortcut="F2"),
|
221
|
+
MenuAction("delete", "🗑️ Sil", shortcut="Delete"),
|
222
|
+
MenuAction("", "", separator_after=True),
|
223
|
+
MenuAction("app_package_info", "ℹ️ Paket Bilgileri"),
|
224
|
+
MenuAction("properties", "📋 Özellikler", shortcut="Alt+Enter"),
|
225
|
+
]
|
226
|
+
|
227
|
+
def register_default_actions(self):
|
228
|
+
"""Varsayılan eylem callback'lerini kaydet"""
|
229
|
+
self.action_callbacks.update({
|
230
|
+
# Dosya işlemleri
|
231
|
+
"new_text": self.create_new_text_file,
|
232
|
+
"new_python": self.create_new_python_file,
|
233
|
+
"new_folder": self.create_new_folder,
|
234
|
+
"open": self.open_item,
|
235
|
+
"copy": self.copy_items,
|
236
|
+
"cut": self.cut_items,
|
237
|
+
"paste": self.paste_items,
|
238
|
+
"rename": self.rename_item,
|
239
|
+
"delete": self.delete_items,
|
240
|
+
"properties": self.show_properties,
|
241
|
+
|
242
|
+
# Masaüstü işlemleri
|
243
|
+
"refresh": self.refresh_desktop,
|
244
|
+
"wallpaper": self.change_wallpaper,
|
245
|
+
|
246
|
+
# Uygulama işlemleri
|
247
|
+
"launch": self.launch_app,
|
248
|
+
"pin": self.pin_app,
|
249
|
+
"unpin": self.unpin_app,
|
250
|
+
"app_info": self.show_app_info,
|
251
|
+
"app_settings": self.show_app_settings,
|
252
|
+
|
253
|
+
# Widget işlemleri
|
254
|
+
"widget_settings": self.show_widget_settings,
|
255
|
+
"widget_resize": self.resize_widget,
|
256
|
+
"widget_close": self.close_widget,
|
257
|
+
|
258
|
+
# .app paketi işlemleri
|
259
|
+
"install_app": self.install_app_from_context,
|
260
|
+
"app_package_info": self.show_app_package_info,
|
261
|
+
})
|
262
|
+
|
263
|
+
def show_context_menu(self, request: MenuRequest) -> bool:
|
264
|
+
"""Bağlam menüsünü göster"""
|
265
|
+
try:
|
266
|
+
self.logger.info(f"show_context_menu called with context: {request.context}")
|
267
|
+
|
268
|
+
# Runtime PyQt6 kontrolü
|
269
|
+
pyqt_available = self._check_pyqt_availability()
|
270
|
+
self.logger.info(f"Runtime PYQT_AVAILABLE: {pyqt_available}, position: {request.position}")
|
271
|
+
|
272
|
+
if not pyqt_available or not request.position:
|
273
|
+
self.logger.warning(f"Cannot show menu: PYQT_AVAILABLE={pyqt_available}, position={request.position}")
|
274
|
+
return False
|
275
|
+
|
276
|
+
# Menü şablonunu al
|
277
|
+
template = self.menu_templates.get(request.context, [])
|
278
|
+
if not template:
|
279
|
+
self.logger.warning(f"No template found for context: {request.context}")
|
280
|
+
return False
|
281
|
+
|
282
|
+
self.logger.info(f"Found template with {len(template)} items")
|
283
|
+
|
284
|
+
# Menüyü oluştur
|
285
|
+
menu = self.create_menu(template, request)
|
286
|
+
if not menu:
|
287
|
+
self.logger.error("Failed to create menu")
|
288
|
+
return False
|
289
|
+
|
290
|
+
self.logger.info("Menu created successfully, showing...")
|
291
|
+
|
292
|
+
# Menüyü göster - artık lambda'lar action'ları handle ediyor
|
293
|
+
menu.exec(request.position)
|
294
|
+
|
295
|
+
self.logger.info("Menu execution completed")
|
296
|
+
return True
|
297
|
+
|
298
|
+
except Exception as e:
|
299
|
+
self.logger.error(f"Failed to show context menu: {e}")
|
300
|
+
import traceback
|
301
|
+
self.logger.error(traceback.format_exc())
|
302
|
+
return False
|
303
|
+
|
304
|
+
def _check_pyqt_availability(self) -> bool:
|
305
|
+
"""Runtime PyQt6 kontrolü"""
|
306
|
+
try:
|
307
|
+
from PyQt6.QtWidgets import QMenu
|
308
|
+
return True
|
309
|
+
except ImportError:
|
310
|
+
return False
|
311
|
+
|
312
|
+
def create_menu(self, template: List[MenuAction], request: MenuRequest) -> Optional[QMenu]:
|
313
|
+
"""Menü oluştur"""
|
314
|
+
try:
|
315
|
+
from PyQt6.QtWidgets import QMenu
|
316
|
+
from PyQt6.QtGui import QAction, QKeySequence
|
317
|
+
|
318
|
+
menu = QMenu()
|
319
|
+
menu.setStyleSheet(self.get_menu_stylesheet())
|
320
|
+
|
321
|
+
for action_def in template:
|
322
|
+
if not action_def.visible:
|
323
|
+
continue
|
324
|
+
|
325
|
+
if action_def.id == "": # Ayırıcı
|
326
|
+
menu.addSeparator()
|
327
|
+
continue
|
328
|
+
|
329
|
+
# Alt menü varsa
|
330
|
+
if action_def.submenu:
|
331
|
+
submenu = QMenu(action_def.text, menu)
|
332
|
+
submenu.setStyleSheet(self.get_menu_stylesheet())
|
333
|
+
|
334
|
+
for sub_action in action_def.submenu:
|
335
|
+
if sub_action.visible:
|
336
|
+
sub_qaction = QAction(sub_action.text, submenu)
|
337
|
+
# Lambda ile action_id'yi bağla
|
338
|
+
sub_qaction.triggered.connect(
|
339
|
+
lambda checked, aid=sub_action.id: self.execute_action(aid, request)
|
340
|
+
)
|
341
|
+
sub_qaction.setEnabled(sub_action.enabled)
|
342
|
+
submenu.addAction(sub_qaction)
|
343
|
+
|
344
|
+
menu.addMenu(submenu)
|
345
|
+
else:
|
346
|
+
# Normal eylem
|
347
|
+
qaction = QAction(action_def.text, menu)
|
348
|
+
# Lambda ile action_id'yi bağla
|
349
|
+
qaction.triggered.connect(
|
350
|
+
lambda checked, aid=action_def.id: self.execute_action(aid, request)
|
351
|
+
)
|
352
|
+
qaction.setEnabled(self.is_action_enabled(action_def.id, request))
|
353
|
+
|
354
|
+
# Shortcut ekle
|
355
|
+
if action_def.shortcut:
|
356
|
+
try:
|
357
|
+
qaction.setShortcut(QKeySequence(action_def.shortcut))
|
358
|
+
except Exception as e:
|
359
|
+
self.logger.warning(f"Failed to set shortcut {action_def.shortcut}: {e}")
|
360
|
+
|
361
|
+
menu.addAction(qaction)
|
362
|
+
|
363
|
+
if action_def.separator_after:
|
364
|
+
menu.addSeparator()
|
365
|
+
|
366
|
+
# Dinamik menü öğeleri ekle (paste action güncelleme)
|
367
|
+
self.update_paste_action_new(menu, request)
|
368
|
+
|
369
|
+
return menu
|
370
|
+
|
371
|
+
except Exception as e:
|
372
|
+
self.logger.error(f"Failed to create menu: {e}")
|
373
|
+
import traceback
|
374
|
+
self.logger.error(traceback.format_exc())
|
375
|
+
return None
|
376
|
+
|
377
|
+
def add_dynamic_items(self, menu: QMenu, request: MenuRequest):
|
378
|
+
"""Dinamik menü öğeleri ekle"""
|
379
|
+
try:
|
380
|
+
# "Aç ile" alt menüsünü doldur
|
381
|
+
if request.target_path and Path(request.target_path).is_file():
|
382
|
+
self.populate_open_with_menu(menu, request.target_path)
|
383
|
+
|
384
|
+
# Clipboard durumuna göre yapıştır'ı etkinleştir
|
385
|
+
self.update_paste_action(menu)
|
386
|
+
|
387
|
+
except Exception as e:
|
388
|
+
self.logger.error(f"Failed to add dynamic items: {e}")
|
389
|
+
|
390
|
+
def populate_open_with_menu(self, menu: QMenu, file_path: str):
|
391
|
+
"""Aç ile menüsünü doldur"""
|
392
|
+
try:
|
393
|
+
# Menüde "Aç ile" eylemini bul
|
394
|
+
for action in menu.actions():
|
395
|
+
if action.text().endswith("Aç ile..."):
|
396
|
+
submenu = action.menu()
|
397
|
+
if submenu:
|
398
|
+
submenu.clear()
|
399
|
+
|
400
|
+
# Dosya uzantısına göre uygulamaları öner
|
401
|
+
file_ext = Path(file_path).suffix.lower()
|
402
|
+
suggested_apps = self.get_suggested_apps(file_ext)
|
403
|
+
|
404
|
+
for app_info in suggested_apps:
|
405
|
+
app_action = QAction(f"{app_info['icon']} {app_info['name']}", submenu)
|
406
|
+
app_action.setData(f"open_with:{app_info['id']}")
|
407
|
+
submenu.addAction(app_action)
|
408
|
+
|
409
|
+
if suggested_apps:
|
410
|
+
submenu.addSeparator()
|
411
|
+
|
412
|
+
# Diğer uygulama seç
|
413
|
+
choose_action = QAction("🔍 Diğer Uygulama Seç...", submenu)
|
414
|
+
choose_action.setData("open_with:choose")
|
415
|
+
submenu.addAction(choose_action)
|
416
|
+
break
|
417
|
+
|
418
|
+
except Exception as e:
|
419
|
+
self.logger.error(f"Failed to populate open with menu: {e}")
|
420
|
+
|
421
|
+
def get_suggested_apps(self, file_ext: str) -> List[Dict]:
|
422
|
+
"""Dosya uzantısına göre önerilen uygulamalar"""
|
423
|
+
app_suggestions = {
|
424
|
+
".txt": [{"id": "cloud.notepad", "name": "Notepad", "icon": "📝"}],
|
425
|
+
".py": [{"id": "cloud.pyide", "name": "Python IDE", "icon": "🐍"}],
|
426
|
+
".md": [{"id": "cloud.notepad", "name": "Notepad", "icon": "📝"}],
|
427
|
+
".json": [{"id": "cloud.notepad", "name": "Notepad", "icon": "📝"}],
|
428
|
+
".log": [{"id": "cloud.notepad", "name": "Notepad", "icon": "📝"}],
|
429
|
+
".html": [{"id": "cloud.browser", "name": "Browser", "icon": "🌐"}],
|
430
|
+
".pdf": [{"id": "cloud.browser", "name": "Browser", "icon": "🌐"}],
|
431
|
+
".app": [{"id": "install_app", "name": "Uygulamayı Kur", "icon": "📦"}],
|
432
|
+
}
|
433
|
+
|
434
|
+
return app_suggestions.get(file_ext, [])
|
435
|
+
|
436
|
+
def update_paste_action(self, menu: QMenu):
|
437
|
+
"""Yapıştır eylemini güncelle"""
|
438
|
+
try:
|
439
|
+
for action in menu.actions():
|
440
|
+
if action.data() == "paste":
|
441
|
+
action.setEnabled(len(self.clipboard_files) > 0)
|
442
|
+
break
|
443
|
+
except Exception as e:
|
444
|
+
self.logger.error(f"Failed to update paste action: {e}")
|
445
|
+
|
446
|
+
def is_action_enabled(self, action_id: str, request: MenuRequest) -> bool:
|
447
|
+
"""Eylemin etkin olup olmadığını kontrol et"""
|
448
|
+
try:
|
449
|
+
# Genel kontroller
|
450
|
+
if action_id == "paste":
|
451
|
+
return len(self.clipboard_files) > 0
|
452
|
+
|
453
|
+
if action_id in ["copy", "cut", "delete", "rename", "properties"]:
|
454
|
+
return bool(request.target_path or request.target_paths)
|
455
|
+
|
456
|
+
if action_id == "open":
|
457
|
+
return bool(request.target_path)
|
458
|
+
|
459
|
+
# Uygulama kontrolleri
|
460
|
+
if action_id in ["launch", "pin", "unpin"]:
|
461
|
+
return bool(request.extra_data.get("app_id"))
|
462
|
+
|
463
|
+
return True
|
464
|
+
|
465
|
+
except Exception as e:
|
466
|
+
self.logger.error(f"Failed to check action enabled: {e}")
|
467
|
+
return False
|
468
|
+
|
469
|
+
def execute_action(self, action_id: str, request: MenuRequest):
|
470
|
+
"""Eylemi çalıştır"""
|
471
|
+
try:
|
472
|
+
self.logger.info(f"Executing action: {action_id}")
|
473
|
+
|
474
|
+
# Özel eylemler (open_with gibi)
|
475
|
+
if ":" in action_id:
|
476
|
+
action_type, action_data = action_id.split(":", 1)
|
477
|
+
if action_type == "open_with":
|
478
|
+
self.open_with_app(request.target_path, action_data)
|
479
|
+
return
|
480
|
+
|
481
|
+
# Normal eylemler
|
482
|
+
callback = self.action_callbacks.get(action_id)
|
483
|
+
if callback:
|
484
|
+
self.logger.info(f"Found callback for action: {action_id}")
|
485
|
+
callback(request)
|
486
|
+
else:
|
487
|
+
self.logger.warning(f"No callback found for action: {action_id}")
|
488
|
+
|
489
|
+
# Sinyal yayınla
|
490
|
+
self.action_triggered.emit(action_id, {
|
491
|
+
"target_path": request.target_path,
|
492
|
+
"target_paths": request.target_paths,
|
493
|
+
"context": request.context.value,
|
494
|
+
"extra_data": request.extra_data
|
495
|
+
})
|
496
|
+
|
497
|
+
except Exception as e:
|
498
|
+
self.logger.error(f"Failed to execute action {action_id}: {e}")
|
499
|
+
import traceback
|
500
|
+
self.logger.error(traceback.format_exc())
|
501
|
+
|
502
|
+
def get_menu_stylesheet(self) -> str:
|
503
|
+
"""Menü stil sayfası"""
|
504
|
+
return """
|
505
|
+
QMenu {
|
506
|
+
background-color: rgba(45, 45, 45, 0.95);
|
507
|
+
border: 1px solid rgba(80, 80, 80, 0.8);
|
508
|
+
border-radius: 8px;
|
509
|
+
padding: 6px;
|
510
|
+
color: #ffffff;
|
511
|
+
}
|
512
|
+
|
513
|
+
QMenu::item {
|
514
|
+
background-color: transparent;
|
515
|
+
padding: 10px 18px;
|
516
|
+
border-radius: 6px;
|
517
|
+
margin: 1px;
|
518
|
+
color: #ffffff;
|
519
|
+
}
|
520
|
+
|
521
|
+
QMenu::item:selected {
|
522
|
+
background-color: rgba(70, 70, 70, 0.8);
|
523
|
+
color: #ffffff;
|
524
|
+
}
|
525
|
+
|
526
|
+
QMenu::item:disabled {
|
527
|
+
color: #888888;
|
528
|
+
background-color: transparent;
|
529
|
+
}
|
530
|
+
|
531
|
+
QMenu::separator {
|
532
|
+
height: 1px;
|
533
|
+
background-color: rgba(100, 100, 100, 0.6);
|
534
|
+
margin: 6px 12px;
|
535
|
+
}
|
536
|
+
|
537
|
+
QMenu::indicator {
|
538
|
+
width: 16px;
|
539
|
+
height: 16px;
|
540
|
+
}
|
541
|
+
"""
|
542
|
+
|
543
|
+
# Eylem callback'leri
|
544
|
+
def create_new_text_file(self, request: MenuRequest):
|
545
|
+
"""Yeni metin dosyası oluştur"""
|
546
|
+
try:
|
547
|
+
from PyQt6.QtWidgets import QInputDialog
|
548
|
+
|
549
|
+
name, ok = QInputDialog.getText(None, "Yeni Dosya", "Dosya adı:", text="Yeni Metin Belgesi.txt")
|
550
|
+
if ok and name:
|
551
|
+
if not name.endswith('.txt'):
|
552
|
+
name += '.txt'
|
553
|
+
|
554
|
+
# Masaüstüne dosya oluştur
|
555
|
+
desktop_path = Path("users/default/Desktop")
|
556
|
+
desktop_path.mkdir(parents=True, exist_ok=True)
|
557
|
+
file_path = desktop_path / name
|
558
|
+
|
559
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
560
|
+
f.write("")
|
561
|
+
|
562
|
+
self.logger.info(f"Created text file: {file_path}")
|
563
|
+
|
564
|
+
# Desktop'u yenile
|
565
|
+
self.refresh_desktop(request)
|
566
|
+
|
567
|
+
except Exception as e:
|
568
|
+
self.logger.error(f"Failed to create text file: {e}")
|
569
|
+
import traceback
|
570
|
+
self.logger.error(traceback.format_exc())
|
571
|
+
|
572
|
+
def create_new_python_file(self, request: MenuRequest):
|
573
|
+
"""Yeni Python dosyası oluştur"""
|
574
|
+
try:
|
575
|
+
from PyQt6.QtWidgets import QInputDialog
|
576
|
+
|
577
|
+
name, ok = QInputDialog.getText(None, "Yeni Python Dosyası", "Dosya adı:", text="yeni_dosya.py")
|
578
|
+
if ok and name:
|
579
|
+
if not name.endswith('.py'):
|
580
|
+
name += '.py'
|
581
|
+
|
582
|
+
# Masaüstüne dosya oluştur
|
583
|
+
desktop_path = Path("users/default/Desktop")
|
584
|
+
desktop_path.mkdir(parents=True, exist_ok=True)
|
585
|
+
file_path = desktop_path / name
|
586
|
+
|
587
|
+
with open(file_path, 'w', encoding='utf-8') as f:
|
588
|
+
f.write('#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\n')
|
589
|
+
|
590
|
+
self.logger.info(f"Created Python file: {file_path}")
|
591
|
+
|
592
|
+
# Desktop'u yenile
|
593
|
+
self.refresh_desktop(request)
|
594
|
+
|
595
|
+
except Exception as e:
|
596
|
+
self.logger.error(f"Failed to create Python file: {e}")
|
597
|
+
import traceback
|
598
|
+
self.logger.error(traceback.format_exc())
|
599
|
+
|
600
|
+
def create_new_folder(self, request: MenuRequest):
|
601
|
+
"""Yeni klasör oluştur"""
|
602
|
+
try:
|
603
|
+
from PyQt6.QtWidgets import QInputDialog
|
604
|
+
|
605
|
+
name, ok = QInputDialog.getText(None, "Yeni Klasör", "Klasör adı:", text="Yeni Klasör")
|
606
|
+
if ok and name:
|
607
|
+
# Masaüstüne klasör oluştur
|
608
|
+
desktop_path = Path("users/default/Desktop")
|
609
|
+
desktop_path.mkdir(parents=True, exist_ok=True)
|
610
|
+
folder_path = desktop_path / name
|
611
|
+
folder_path.mkdir(exist_ok=True)
|
612
|
+
|
613
|
+
self.logger.info(f"Created folder: {folder_path}")
|
614
|
+
|
615
|
+
# Desktop'u yenile
|
616
|
+
self.refresh_desktop(request)
|
617
|
+
|
618
|
+
except Exception as e:
|
619
|
+
self.logger.error(f"Failed to create folder: {e}")
|
620
|
+
import traceback
|
621
|
+
self.logger.error(traceback.format_exc())
|
622
|
+
|
623
|
+
def open_item(self, request: MenuRequest):
|
624
|
+
"""Öğeyi aç"""
|
625
|
+
try:
|
626
|
+
if not request.target_path:
|
627
|
+
return
|
628
|
+
|
629
|
+
path = Path(request.target_path)
|
630
|
+
|
631
|
+
if path.is_dir():
|
632
|
+
# Klasörü dosya yöneticisi ile aç
|
633
|
+
self.open_folder_in_files(request.target_path)
|
634
|
+
else:
|
635
|
+
# Dosyayı varsayılan uygulama ile aç
|
636
|
+
self.open_file_with_default(request.target_path)
|
637
|
+
|
638
|
+
except Exception as e:
|
639
|
+
self.logger.error(f"Failed to open item: {e}")
|
640
|
+
|
641
|
+
def open_folder_in_files(self, folder_path: str):
|
642
|
+
"""Klasörü dosya yöneticisinde aç"""
|
643
|
+
try:
|
644
|
+
if self.kernel:
|
645
|
+
launcher = self.kernel.get_module("launcher")
|
646
|
+
if launcher:
|
647
|
+
# PyCloud OS sanal dosya sistemi yolunu kullan
|
648
|
+
pycloud_path = folder_path
|
649
|
+
if not folder_path.startswith(("users/", "apps/", "system/", "temp/")):
|
650
|
+
# Eğer gerçek dosya sistemi yolu ise, PyCloud OS yoluna çevir
|
651
|
+
pycloud_path = str(Path("users") / "default" / "Desktop")
|
652
|
+
|
653
|
+
launcher.launch_app("cloud_files", {"open_path": pycloud_path})
|
654
|
+
|
655
|
+
except Exception as e:
|
656
|
+
self.logger.error(f"Failed to open folder in files: {e}")
|
657
|
+
|
658
|
+
def open_file_with_default(self, file_path: str):
|
659
|
+
"""Dosyayı varsayılan uygulama ile aç"""
|
660
|
+
try:
|
661
|
+
file_ext = Path(file_path).suffix.lower()
|
662
|
+
file_name = Path(file_path).name.lower()
|
663
|
+
|
664
|
+
# .app dosyası mı kontrol et
|
665
|
+
if file_name.endswith('.app') or file_path.endswith('.app'):
|
666
|
+
# .app dosyası - kurulum işlemi başlat
|
667
|
+
self.install_app_package(file_path)
|
668
|
+
return
|
669
|
+
|
670
|
+
# Varsayılan uygulamalar - .py dosyaları için PyIDE eklendi
|
671
|
+
default_apps = {
|
672
|
+
".txt": "cloud_notepad",
|
673
|
+
".py": "cloud_pyide", # Python dosyaları PyIDE ile açılsın
|
674
|
+
".js": "cloud_pyide", # JavaScript dosyaları da PyIDE ile
|
675
|
+
".html": "cloud_browser",
|
676
|
+
".css": "cloud_pyide", # CSS dosyaları PyIDE ile
|
677
|
+
".md": "cloud_notepad",
|
678
|
+
".json": "cloud_pyide", # JSON dosyaları PyIDE ile
|
679
|
+
".log": "cloud_notepad",
|
680
|
+
".pdf": "cloud_browser",
|
681
|
+
}
|
682
|
+
|
683
|
+
app_id = default_apps.get(file_ext, "cloud_notepad")
|
684
|
+
|
685
|
+
if self.kernel:
|
686
|
+
launcher = self.kernel.get_module("launcher")
|
687
|
+
if launcher:
|
688
|
+
# ÇÖZÜM: Absolute path kullan - boş açılma sorununu çözer
|
689
|
+
absolute_path = str(Path(file_path).resolve().absolute())
|
690
|
+
|
691
|
+
self.logger.info(f"🚀 Masaüstünden dosya açılıyor: {app_id} -> {absolute_path}")
|
692
|
+
|
693
|
+
# Launcher API ile dosyayı aç
|
694
|
+
launch_success = launcher.launch_app(app_id, open_file=absolute_path)
|
695
|
+
|
696
|
+
if launch_success:
|
697
|
+
self.logger.info(f"✅ Dosya başarıyla açıldı: {absolute_path}")
|
698
|
+
else:
|
699
|
+
self.logger.warning(f"⚠️ Dosya açma başarısız")
|
700
|
+
# Fallback olarak sistem uygulamasını dene
|
701
|
+
self._open_with_system_app(absolute_path)
|
702
|
+
else:
|
703
|
+
self.logger.warning("⚠️ Launcher modülü bulunamadı")
|
704
|
+
self._open_with_system_app(str(Path(file_path).absolute()))
|
705
|
+
else:
|
706
|
+
self.logger.warning("⚠️ Kernel bulunamadı")
|
707
|
+
self._open_with_system_app(str(Path(file_path).absolute()))
|
708
|
+
|
709
|
+
except Exception as e:
|
710
|
+
self.logger.error(f"Failed to open file with default app: {e}")
|
711
|
+
import traceback
|
712
|
+
self.logger.error(traceback.format_exc())
|
713
|
+
|
714
|
+
def _open_with_system_app(self, absolute_path: str):
|
715
|
+
"""Sistem uygulamasıyla dosyayı aç"""
|
716
|
+
try:
|
717
|
+
import subprocess
|
718
|
+
import platform
|
719
|
+
|
720
|
+
system = platform.system()
|
721
|
+
self.logger.info(f"🔄 Sistem uygulamasıyla açılıyor: {absolute_path}")
|
722
|
+
|
723
|
+
if system == "Darwin": # macOS
|
724
|
+
subprocess.run(["open", absolute_path], check=True)
|
725
|
+
elif system == "Windows":
|
726
|
+
subprocess.run(["start", absolute_path], shell=True, check=True)
|
727
|
+
else: # Linux
|
728
|
+
subprocess.run(["xdg-open", absolute_path], check=True)
|
729
|
+
|
730
|
+
self.logger.info(f"✅ Sistem uygulamasıyla açıldı: {absolute_path}")
|
731
|
+
|
732
|
+
except Exception as e:
|
733
|
+
self.logger.error(f"❌ Sistem uygulamasıyla açma hatası: {e}")
|
734
|
+
|
735
|
+
def install_app_package(self, app_path: str):
|
736
|
+
"""Uygulama paketini kur"""
|
737
|
+
try:
|
738
|
+
from PyQt6.QtWidgets import QMessageBox
|
739
|
+
|
740
|
+
app_name = Path(app_path).stem
|
741
|
+
|
742
|
+
# Onay dialogu
|
743
|
+
reply = QMessageBox.question(None, "Uygulama Kurulumu",
|
744
|
+
f"'{app_name}' uygulamasını kurmak istediğinizden emin misiniz?",
|
745
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
746
|
+
|
747
|
+
if reply != QMessageBox.StandardButton.Yes:
|
748
|
+
return
|
749
|
+
|
750
|
+
# AppKit ile kurulum yap
|
751
|
+
if self.kernel:
|
752
|
+
appkit = self.kernel.get_module("appkit")
|
753
|
+
if appkit:
|
754
|
+
# İlerleme mesajı göster
|
755
|
+
progress_msg = QMessageBox(None)
|
756
|
+
progress_msg.setWindowTitle("Kurulum")
|
757
|
+
progress_msg.setText(f"'{app_name}' kuruluyor...")
|
758
|
+
progress_msg.setStandardButtons(QMessageBox.StandardButton.NoButton)
|
759
|
+
progress_msg.show()
|
760
|
+
|
761
|
+
# Kurulum işlemi
|
762
|
+
result, message = appkit.install_app(app_path, force=False)
|
763
|
+
|
764
|
+
progress_msg.close()
|
765
|
+
|
766
|
+
# Sonuç mesajı
|
767
|
+
if result.name == "SUCCESS":
|
768
|
+
QMessageBox.information(None, "Başarılı", f"✅ {message}")
|
769
|
+
|
770
|
+
# AppExplorer'ı yenile
|
771
|
+
app_explorer = self.kernel.get_module("appexplorer")
|
772
|
+
if app_explorer:
|
773
|
+
app_explorer.force_discovery()
|
774
|
+
|
775
|
+
# Desktop'u yenile
|
776
|
+
self.refresh_desktop(None)
|
777
|
+
|
778
|
+
elif result.name == "ALREADY_INSTALLED":
|
779
|
+
reply = QMessageBox.question(None, "Zaten Kurulu",
|
780
|
+
f"'{app_name}' zaten kurulu. Güncellemek istediğinizden emin misiniz?",
|
781
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
782
|
+
|
783
|
+
if reply == QMessageBox.StandardButton.Yes:
|
784
|
+
# Zorla kurulum (güncelleme)
|
785
|
+
result, message = appkit.install_app(app_path, force=True)
|
786
|
+
if result.name == "SUCCESS":
|
787
|
+
QMessageBox.information(None, "Güncellendi", f"✅ {message}")
|
788
|
+
else:
|
789
|
+
QMessageBox.critical(None, "Hata", f"❌ {message}")
|
790
|
+
else:
|
791
|
+
QMessageBox.critical(None, "Kurulum Hatası", f"❌ {message}")
|
792
|
+
else:
|
793
|
+
QMessageBox.critical(None, "Hata", "AppKit modülü bulunamadı!")
|
794
|
+
else:
|
795
|
+
QMessageBox.critical(None, "Hata", "Sistem çekirdeği bulunamadı!")
|
796
|
+
|
797
|
+
except Exception as e:
|
798
|
+
self.logger.error(f"Failed to install app package: {e}")
|
799
|
+
from PyQt6.QtWidgets import QMessageBox
|
800
|
+
QMessageBox.critical(None, "Hata", f"Kurulum sırasında hata oluştu: {str(e)}")
|
801
|
+
|
802
|
+
def open_with_app(self, file_path: str, app_id: str):
|
803
|
+
"""Dosyayı belirtilen uygulama ile aç"""
|
804
|
+
try:
|
805
|
+
if app_id == "choose":
|
806
|
+
# Uygulama seçim dialogu
|
807
|
+
self.show_app_chooser(file_path)
|
808
|
+
return
|
809
|
+
|
810
|
+
if self.kernel:
|
811
|
+
launcher = self.kernel.get_module("launcher")
|
812
|
+
if launcher:
|
813
|
+
launcher.launch_app(app_id, {"open_file": file_path})
|
814
|
+
|
815
|
+
except Exception as e:
|
816
|
+
self.logger.error(f"Failed to open file with app {app_id}: {e}")
|
817
|
+
|
818
|
+
def show_app_chooser(self, file_path: str):
|
819
|
+
"""Uygulama seçici dialogu göster"""
|
820
|
+
# TODO: Uygulama seçici dialog implementasyonu
|
821
|
+
self.logger.info(f"Show app chooser for: {file_path}")
|
822
|
+
|
823
|
+
def copy_items(self, request: MenuRequest):
|
824
|
+
"""Öğeleri kopyala"""
|
825
|
+
try:
|
826
|
+
self.clipboard_files = request.target_paths if request.target_paths else [request.target_path]
|
827
|
+
self.clipboard_operation = "copy"
|
828
|
+
|
829
|
+
self.logger.info(f"Copied {len(self.clipboard_files)} items to clipboard")
|
830
|
+
|
831
|
+
except Exception as e:
|
832
|
+
self.logger.error(f"Failed to copy items: {e}")
|
833
|
+
|
834
|
+
def cut_items(self, request: MenuRequest):
|
835
|
+
"""Öğeleri kes"""
|
836
|
+
try:
|
837
|
+
self.clipboard_files = request.target_paths if request.target_paths else [request.target_path]
|
838
|
+
self.clipboard_operation = "cut"
|
839
|
+
|
840
|
+
self.logger.info(f"Cut {len(self.clipboard_files)} items to clipboard")
|
841
|
+
|
842
|
+
except Exception as e:
|
843
|
+
self.logger.error(f"Failed to cut items: {e}")
|
844
|
+
|
845
|
+
def paste_items(self, request: MenuRequest):
|
846
|
+
"""Öğeleri yapıştır"""
|
847
|
+
try:
|
848
|
+
if not self.clipboard_files:
|
849
|
+
return
|
850
|
+
|
851
|
+
target_dir = request.extra_data.get("current_dir", "users/demo/Desktop")
|
852
|
+
|
853
|
+
for file_path in self.clipboard_files:
|
854
|
+
source = Path(file_path)
|
855
|
+
target = Path(target_dir) / source.name
|
856
|
+
|
857
|
+
if self.clipboard_operation == "copy":
|
858
|
+
if source.is_dir():
|
859
|
+
import shutil
|
860
|
+
shutil.copytree(source, target, dirs_exist_ok=True)
|
861
|
+
else:
|
862
|
+
import shutil
|
863
|
+
shutil.copy2(source, target)
|
864
|
+
elif self.clipboard_operation == "cut":
|
865
|
+
source.rename(target)
|
866
|
+
|
867
|
+
if self.clipboard_operation == "cut":
|
868
|
+
self.clipboard_files.clear()
|
869
|
+
|
870
|
+
self.logger.info(f"Pasted {len(self.clipboard_files)} items")
|
871
|
+
|
872
|
+
except Exception as e:
|
873
|
+
self.logger.error(f"Failed to paste items: {e}")
|
874
|
+
|
875
|
+
def rename_item(self, request: MenuRequest):
|
876
|
+
"""Öğeyi yeniden adlandır"""
|
877
|
+
try:
|
878
|
+
from PyQt6.QtWidgets import QInputDialog
|
879
|
+
|
880
|
+
if not request.target_path:
|
881
|
+
self.logger.warning("No target path for rename")
|
882
|
+
return
|
883
|
+
|
884
|
+
path = Path(request.target_path)
|
885
|
+
if not path.exists():
|
886
|
+
self.logger.warning(f"Target path does not exist: {path}")
|
887
|
+
return
|
888
|
+
|
889
|
+
current_name = path.name
|
890
|
+
|
891
|
+
new_name, ok = QInputDialog.getText(None, "Yeniden Adlandır",
|
892
|
+
"Yeni ad:", text=current_name)
|
893
|
+
if ok and new_name and new_name != current_name:
|
894
|
+
new_path = path.parent / new_name
|
895
|
+
path.rename(new_path)
|
896
|
+
|
897
|
+
self.logger.info(f"Renamed {current_name} to {new_name}")
|
898
|
+
|
899
|
+
# Desktop'u yenile
|
900
|
+
self.refresh_desktop(request)
|
901
|
+
|
902
|
+
except Exception as e:
|
903
|
+
self.logger.error(f"Failed to rename item: {e}")
|
904
|
+
import traceback
|
905
|
+
self.logger.error(traceback.format_exc())
|
906
|
+
|
907
|
+
def delete_items(self, request: MenuRequest):
|
908
|
+
"""Öğeleri sil"""
|
909
|
+
try:
|
910
|
+
from PyQt6.QtWidgets import QMessageBox
|
911
|
+
|
912
|
+
items = request.target_paths if request.target_paths else [request.target_path]
|
913
|
+
if not items or not items[0]:
|
914
|
+
self.logger.warning("No items to delete")
|
915
|
+
return
|
916
|
+
|
917
|
+
# Onay dialogu
|
918
|
+
reply = QMessageBox.question(None, "Silme Onayı",
|
919
|
+
f"{len(items)} öğe silinecek. Emin misiniz?",
|
920
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
|
921
|
+
|
922
|
+
if reply == QMessageBox.StandardButton.Yes:
|
923
|
+
for item_path in items:
|
924
|
+
if not item_path:
|
925
|
+
continue
|
926
|
+
|
927
|
+
path = Path(item_path)
|
928
|
+
if not path.exists():
|
929
|
+
self.logger.warning(f"Item does not exist: {path}")
|
930
|
+
continue
|
931
|
+
|
932
|
+
if path.is_dir():
|
933
|
+
import shutil
|
934
|
+
shutil.rmtree(path)
|
935
|
+
else:
|
936
|
+
path.unlink()
|
937
|
+
|
938
|
+
self.logger.info(f"Deleted {len(items)} items")
|
939
|
+
|
940
|
+
# Desktop'u yenile
|
941
|
+
self.refresh_desktop(request)
|
942
|
+
|
943
|
+
except Exception as e:
|
944
|
+
self.logger.error(f"Failed to delete items: {e}")
|
945
|
+
import traceback
|
946
|
+
self.logger.error(traceback.format_exc())
|
947
|
+
|
948
|
+
def show_properties(self, request: MenuRequest):
|
949
|
+
"""Özellikler dialogu göster"""
|
950
|
+
# TODO: Özellikler dialog implementasyonu
|
951
|
+
self.logger.info(f"Show properties for: {request.target_path}")
|
952
|
+
|
953
|
+
def refresh_desktop(self, request: MenuRequest):
|
954
|
+
"""Masaüstünü yenile"""
|
955
|
+
try:
|
956
|
+
self.logger.info("Refreshing desktop...")
|
957
|
+
|
958
|
+
if self.kernel:
|
959
|
+
# rain_ui modülünü dene
|
960
|
+
ui = self.kernel.get_module("rain_ui")
|
961
|
+
if ui and hasattr(ui, 'desktop'):
|
962
|
+
if hasattr(ui.desktop, 'refresh_desktop'):
|
963
|
+
ui.desktop.refresh_desktop()
|
964
|
+
self.logger.info("Desktop refreshed via rain_ui module")
|
965
|
+
return
|
966
|
+
elif hasattr(ui.desktop, 'load_desktop_items'):
|
967
|
+
ui.desktop.load_desktop_items()
|
968
|
+
self.logger.info("Desktop refreshed via load_desktop_items")
|
969
|
+
return
|
970
|
+
else:
|
971
|
+
self.logger.warning("No refresh method found on desktop")
|
972
|
+
|
973
|
+
# Alternatif: QApplication üzerinden tüm widget'ları yenile
|
974
|
+
try:
|
975
|
+
from PyQt6.QtWidgets import QApplication
|
976
|
+
app = QApplication.instance()
|
977
|
+
if app:
|
978
|
+
# Tüm widget'ları yenile
|
979
|
+
for widget in app.allWidgets():
|
980
|
+
if hasattr(widget, 'load_desktop_items'):
|
981
|
+
widget.load_desktop_items()
|
982
|
+
self.logger.info("Desktop refreshed via QApplication widget search")
|
983
|
+
return
|
984
|
+
elif hasattr(widget, 'refresh_desktop'):
|
985
|
+
widget.refresh_desktop()
|
986
|
+
self.logger.info("Desktop refreshed via QApplication widget search")
|
987
|
+
return
|
988
|
+
except Exception as e:
|
989
|
+
self.logger.warning(f"QApplication refresh failed: {e}")
|
990
|
+
|
991
|
+
self.logger.warning("No UI module or desktop found for refresh")
|
992
|
+
else:
|
993
|
+
self.logger.warning("No kernel available for refresh")
|
994
|
+
|
995
|
+
except Exception as e:
|
996
|
+
self.logger.error(f"Failed to refresh desktop: {e}")
|
997
|
+
|
998
|
+
def change_wallpaper(self, request: MenuRequest):
|
999
|
+
"""Duvar kağıdı değiştir - PyCloud OS dosya sistemi kullan"""
|
1000
|
+
try:
|
1001
|
+
# PyCloud OS wallpaper manager'ı kullan
|
1002
|
+
if self.kernel:
|
1003
|
+
wallpaper_manager = self.kernel.get_module("wallpaper")
|
1004
|
+
if wallpaper_manager:
|
1005
|
+
# Wallpaper dialog'unu göster
|
1006
|
+
success = wallpaper_manager.show_wallpaper_dialog()
|
1007
|
+
if success:
|
1008
|
+
self.logger.info("Wallpaper changed via wallpaper manager")
|
1009
|
+
return
|
1010
|
+
else:
|
1011
|
+
self.logger.info("Wallpaper dialog cancelled")
|
1012
|
+
return
|
1013
|
+
|
1014
|
+
# Fallback - PyCloud OS dosya sistemi ile manuel seçim
|
1015
|
+
from PyQt6.QtWidgets import QFileDialog, QMessageBox
|
1016
|
+
|
1017
|
+
# PyCloud OS wallpaper dizinleri
|
1018
|
+
system_wallpapers = Path("system/wallpapers")
|
1019
|
+
user_wallpapers = Path("users/default/wallpapers")
|
1020
|
+
|
1021
|
+
# Dizinleri oluştur
|
1022
|
+
system_wallpapers.mkdir(parents=True, exist_ok=True)
|
1023
|
+
user_wallpapers.mkdir(parents=True, exist_ok=True)
|
1024
|
+
|
1025
|
+
# Mevcut duvar kağıtlarını kontrol et
|
1026
|
+
available_wallpapers = []
|
1027
|
+
|
1028
|
+
# Sistem duvar kağıtları
|
1029
|
+
for wallpaper in system_wallpapers.glob("*"):
|
1030
|
+
if wallpaper.suffix.lower() in ['.png', '.jpg', '.jpeg', '.bmp', '.webp']:
|
1031
|
+
available_wallpapers.append(str(wallpaper))
|
1032
|
+
|
1033
|
+
# Kullanıcı duvar kağıtları
|
1034
|
+
for wallpaper in user_wallpapers.glob("*"):
|
1035
|
+
if wallpaper.suffix.lower() in ['.png', '.jpg', '.jpeg', '.bmp', '.webp']:
|
1036
|
+
available_wallpapers.append(str(wallpaper))
|
1037
|
+
|
1038
|
+
if available_wallpapers:
|
1039
|
+
# Mevcut duvar kağıtlarından seç
|
1040
|
+
from PyQt6.QtWidgets import QInputDialog
|
1041
|
+
|
1042
|
+
wallpaper_names = [Path(wp).name for wp in available_wallpapers]
|
1043
|
+
|
1044
|
+
selected_name, ok = QInputDialog.getItem(
|
1045
|
+
None,
|
1046
|
+
"Duvar Kağıdı Seç",
|
1047
|
+
"Mevcut duvar kağıtlarından birini seçin:",
|
1048
|
+
wallpaper_names,
|
1049
|
+
0,
|
1050
|
+
False
|
1051
|
+
)
|
1052
|
+
|
1053
|
+
if ok and selected_name:
|
1054
|
+
# Seçilen duvar kağıdının tam yolunu bul
|
1055
|
+
selected_path = None
|
1056
|
+
for wp in available_wallpapers:
|
1057
|
+
if Path(wp).name == selected_name:
|
1058
|
+
selected_path = wp
|
1059
|
+
break
|
1060
|
+
|
1061
|
+
if selected_path:
|
1062
|
+
# Wallpaper manager ile ayarla
|
1063
|
+
if wallpaper_manager:
|
1064
|
+
wallpaper_manager.set_wallpaper(selected_path)
|
1065
|
+
self.logger.info(f"Wallpaper set to: {selected_path}")
|
1066
|
+
else:
|
1067
|
+
# Rain UI'dan wallpaper değiştir
|
1068
|
+
ui = self.kernel.get_module("ui")
|
1069
|
+
if ui and hasattr(ui, 'set_wallpaper'):
|
1070
|
+
ui.set_wallpaper(selected_path)
|
1071
|
+
self.logger.info("Wallpaper changed via rain_ui")
|
1072
|
+
|
1073
|
+
QMessageBox.information(
|
1074
|
+
None,
|
1075
|
+
"Duvar Kağıdı Değiştirildi",
|
1076
|
+
f"Duvar kağıdı '{selected_name}' olarak ayarlandı."
|
1077
|
+
)
|
1078
|
+
return
|
1079
|
+
|
1080
|
+
# Yeni duvar kağıdı ekleme seçeneği
|
1081
|
+
reply = QMessageBox.question(
|
1082
|
+
None,
|
1083
|
+
"Duvar Kağıdı Bulunamadı",
|
1084
|
+
"Sistem duvar kağıdı bulunamadı. Yeni bir duvar kağıdı eklemek ister misiniz?",
|
1085
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
1086
|
+
)
|
1087
|
+
|
1088
|
+
if reply == QMessageBox.StandardButton.Yes:
|
1089
|
+
# PyCloud OS dosya sistemi içinden seç
|
1090
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
1091
|
+
None,
|
1092
|
+
"Duvar Kağıdı Seç - PyCloud OS",
|
1093
|
+
str(user_wallpapers), # PyCloud OS kullanıcı wallpaper dizini
|
1094
|
+
"Resim Dosyaları (*.png *.jpg *.jpeg *.bmp *.webp);;Tüm Dosyalar (*)"
|
1095
|
+
)
|
1096
|
+
|
1097
|
+
if file_path:
|
1098
|
+
# Eğer PyCloud OS dışından seçildiyse, kopyala
|
1099
|
+
file_path_obj = Path(file_path)
|
1100
|
+
|
1101
|
+
if not str(file_path).startswith(str(Path.cwd())):
|
1102
|
+
# Dosyayı PyCloud OS'a kopyala
|
1103
|
+
dest_path = user_wallpapers / file_path_obj.name
|
1104
|
+
|
1105
|
+
try:
|
1106
|
+
import shutil
|
1107
|
+
shutil.copy2(file_path_obj, dest_path)
|
1108
|
+
file_path = str(dest_path)
|
1109
|
+
|
1110
|
+
QMessageBox.information(
|
1111
|
+
None,
|
1112
|
+
"Duvar Kağıdı Kopyalandı",
|
1113
|
+
f"Duvar kağıdı PyCloud OS'a kopyalandı:\n{dest_path}"
|
1114
|
+
)
|
1115
|
+
except Exception as e:
|
1116
|
+
QMessageBox.critical(
|
1117
|
+
None,
|
1118
|
+
"Kopyalama Hatası",
|
1119
|
+
f"Duvar kağıdı kopyalanamadı:\n{e}"
|
1120
|
+
)
|
1121
|
+
return
|
1122
|
+
|
1123
|
+
# Wallpaper'ı ayarla
|
1124
|
+
if wallpaper_manager:
|
1125
|
+
wallpaper_manager.set_wallpaper(file_path)
|
1126
|
+
self.logger.info(f"New wallpaper set: {file_path}")
|
1127
|
+
else:
|
1128
|
+
# Rain UI'dan wallpaper değiştir
|
1129
|
+
ui = self.kernel.get_module("ui")
|
1130
|
+
if ui and hasattr(ui, 'set_wallpaper'):
|
1131
|
+
ui.set_wallpaper(file_path)
|
1132
|
+
self.logger.info("New wallpaper changed via rain_ui")
|
1133
|
+
|
1134
|
+
QMessageBox.information(
|
1135
|
+
None,
|
1136
|
+
"Duvar Kağıdı Ayarlandı",
|
1137
|
+
f"Yeni duvar kağıdı başarıyla ayarlandı!"
|
1138
|
+
)
|
1139
|
+
|
1140
|
+
except Exception as e:
|
1141
|
+
self.logger.error(f"Failed to change wallpaper: {e}")
|
1142
|
+
import traceback
|
1143
|
+
self.logger.error(traceback.format_exc())
|
1144
|
+
|
1145
|
+
# Hata mesajı göster
|
1146
|
+
try:
|
1147
|
+
from PyQt6.QtWidgets import QMessageBox
|
1148
|
+
QMessageBox.critical(
|
1149
|
+
None,
|
1150
|
+
"Duvar Kağıdı Hatası",
|
1151
|
+
f"Duvar kağıdı değiştirilemedi:\n{e}"
|
1152
|
+
)
|
1153
|
+
except:
|
1154
|
+
pass
|
1155
|
+
|
1156
|
+
def launch_app(self, request: MenuRequest):
|
1157
|
+
"""Uygulamayı başlat"""
|
1158
|
+
try:
|
1159
|
+
app_id = request.extra_data.get("app_id")
|
1160
|
+
if app_id and self.kernel:
|
1161
|
+
launcher = self.kernel.get_module("launcher")
|
1162
|
+
if launcher:
|
1163
|
+
launcher.launch_app(app_id)
|
1164
|
+
|
1165
|
+
except Exception as e:
|
1166
|
+
self.logger.error(f"Failed to launch app: {e}")
|
1167
|
+
|
1168
|
+
def pin_app(self, request: MenuRequest):
|
1169
|
+
"""Uygulamayı dock'a sabitle"""
|
1170
|
+
try:
|
1171
|
+
app_id = request.extra_data.get("app_id")
|
1172
|
+
if app_id and self.kernel:
|
1173
|
+
ui = self.kernel.get_module("ui")
|
1174
|
+
if ui and hasattr(ui, 'dock'):
|
1175
|
+
ui.dock.pin_app(app_id)
|
1176
|
+
|
1177
|
+
except Exception as e:
|
1178
|
+
self.logger.error(f"Failed to pin app: {e}")
|
1179
|
+
|
1180
|
+
def unpin_app(self, request: MenuRequest):
|
1181
|
+
"""Uygulamayı dock'tan kaldır"""
|
1182
|
+
try:
|
1183
|
+
app_id = request.extra_data.get("app_id")
|
1184
|
+
if app_id and self.kernel:
|
1185
|
+
ui = self.kernel.get_module("ui")
|
1186
|
+
if ui and hasattr(ui, 'dock'):
|
1187
|
+
ui.dock.unpin_app(app_id)
|
1188
|
+
|
1189
|
+
except Exception as e:
|
1190
|
+
self.logger.error(f"Failed to unpin app: {e}")
|
1191
|
+
|
1192
|
+
def show_app_info(self, request: MenuRequest):
|
1193
|
+
"""Uygulama bilgilerini göster"""
|
1194
|
+
# TODO: Uygulama bilgi dialogu implementasyonu
|
1195
|
+
app_id = request.extra_data.get("app_id")
|
1196
|
+
self.logger.info(f"Show app info for: {app_id}")
|
1197
|
+
|
1198
|
+
def show_app_settings(self, request: MenuRequest):
|
1199
|
+
"""Uygulama ayarlarını göster"""
|
1200
|
+
# TODO: Uygulama ayar dialogu implementasyonu
|
1201
|
+
app_id = request.extra_data.get("app_id")
|
1202
|
+
self.logger.info(f"Show app settings for: {app_id}")
|
1203
|
+
|
1204
|
+
def show_widget_settings(self, request: MenuRequest):
|
1205
|
+
"""Widget ayarlarını göster"""
|
1206
|
+
try:
|
1207
|
+
widget_id = request.extra_data.get("widget_id")
|
1208
|
+
if widget_id and self.kernel:
|
1209
|
+
widget_manager = self.kernel.get_module("widgets")
|
1210
|
+
if widget_manager:
|
1211
|
+
widget_manager.show_widget_settings(widget_id)
|
1212
|
+
|
1213
|
+
except Exception as e:
|
1214
|
+
self.logger.error(f"Failed to show widget settings: {e}")
|
1215
|
+
|
1216
|
+
def resize_widget(self, request: MenuRequest):
|
1217
|
+
"""Widget'ı yeniden boyutlandır"""
|
1218
|
+
# TODO: Widget boyutlandırma implementasyonu
|
1219
|
+
widget_id = request.extra_data.get("widget_id")
|
1220
|
+
self.logger.info(f"Resize widget: {widget_id}")
|
1221
|
+
|
1222
|
+
def close_widget(self, request: MenuRequest):
|
1223
|
+
"""Widget'ı kapat"""
|
1224
|
+
try:
|
1225
|
+
widget_id = request.extra_data.get("widget_id")
|
1226
|
+
if widget_id and self.kernel:
|
1227
|
+
widget_manager = self.kernel.get_module("widgets")
|
1228
|
+
if widget_manager:
|
1229
|
+
widget_manager.remove_widget(widget_id)
|
1230
|
+
|
1231
|
+
except Exception as e:
|
1232
|
+
self.logger.error(f"Failed to close widget: {e}")
|
1233
|
+
|
1234
|
+
def register_action(self, action_id: str, callback: Callable):
|
1235
|
+
"""Özel eylem kaydet"""
|
1236
|
+
self.action_callbacks[action_id] = callback
|
1237
|
+
|
1238
|
+
def add_menu_template(self, context: MenuContext, template: List[MenuAction]):
|
1239
|
+
"""Menü şablonu ekle"""
|
1240
|
+
self.menu_templates[context] = template
|
1241
|
+
|
1242
|
+
def shutdown(self):
|
1243
|
+
"""Context menu manager'ı kapat"""
|
1244
|
+
try:
|
1245
|
+
self.clipboard_files.clear()
|
1246
|
+
self.action_callbacks.clear()
|
1247
|
+
self.menu_templates.clear()
|
1248
|
+
|
1249
|
+
self.logger.info("Context menu manager shutdown completed")
|
1250
|
+
|
1251
|
+
except Exception as e:
|
1252
|
+
self.logger.error(f"Context menu manager shutdown failed: {e}")
|
1253
|
+
|
1254
|
+
def update_paste_action_new(self, menu, request):
|
1255
|
+
"""Yapıştır eylemini güncelle (PyQt6 uyumlu)"""
|
1256
|
+
try:
|
1257
|
+
# Menu'daki tüm action'ları kontrol et
|
1258
|
+
for action in menu.actions():
|
1259
|
+
if action.text() == "📋 Yapıştır":
|
1260
|
+
action.setEnabled(len(self.clipboard_files) > 0)
|
1261
|
+
break
|
1262
|
+
|
1263
|
+
# Alt menüleri de kontrol et
|
1264
|
+
for action in menu.actions():
|
1265
|
+
if action.menu():
|
1266
|
+
self.update_paste_action_new(action.menu(), request)
|
1267
|
+
|
1268
|
+
except Exception as e:
|
1269
|
+
self.logger.warning(f"Failed to update paste action: {e}")
|
1270
|
+
|
1271
|
+
def install_app_from_context(self, request: MenuRequest):
|
1272
|
+
"""Context menu'den uygulama kur"""
|
1273
|
+
if request.target_path:
|
1274
|
+
self.install_app_package(request.target_path)
|
1275
|
+
|
1276
|
+
def show_app_package_info(self, request: MenuRequest):
|
1277
|
+
"""Uygulama paketi bilgilerini göster"""
|
1278
|
+
try:
|
1279
|
+
from PyQt6.QtWidgets import QMessageBox
|
1280
|
+
|
1281
|
+
if not request.target_path:
|
1282
|
+
return
|
1283
|
+
|
1284
|
+
app_path = Path(request.target_path)
|
1285
|
+
app_json_path = app_path / "app.json"
|
1286
|
+
|
1287
|
+
if not app_json_path.exists():
|
1288
|
+
QMessageBox.warning(None, "Hata", "app.json dosyası bulunamadı!")
|
1289
|
+
return
|
1290
|
+
|
1291
|
+
try:
|
1292
|
+
with open(app_json_path, 'r', encoding='utf-8') as f:
|
1293
|
+
app_data = json.load(f)
|
1294
|
+
|
1295
|
+
# Bilgi mesajı oluştur
|
1296
|
+
info_text = f"""
|
1297
|
+
📦 Uygulama Paketi Bilgileri
|
1298
|
+
|
1299
|
+
🆔 ID: {app_data.get('id', 'Bilinmiyor')}
|
1300
|
+
📝 Ad: {app_data.get('name', 'Bilinmiyor')}
|
1301
|
+
🔢 Sürüm: {app_data.get('version', 'Bilinmiyor')}
|
1302
|
+
👤 Geliştirici: {app_data.get('developer', 'Bilinmiyor')}
|
1303
|
+
📋 Açıklama: {app_data.get('description', 'Açıklama yok')}
|
1304
|
+
🏷️ Kategori: {app_data.get('category', 'Bilinmiyor')}
|
1305
|
+
📄 Giriş Dosyası: {app_data.get('entry', 'main.py')}
|
1306
|
+
🏷️ Etiketler: {', '.join(app_data.get('tags', []))}
|
1307
|
+
📦 Boyut: {self._get_directory_size(app_path):.2f} MB
|
1308
|
+
""".strip()
|
1309
|
+
|
1310
|
+
QMessageBox.information(None, "Paket Bilgileri", info_text)
|
1311
|
+
|
1312
|
+
except json.JSONDecodeError:
|
1313
|
+
QMessageBox.critical(None, "Hata", "app.json dosyası geçersiz!")
|
1314
|
+
except Exception as e:
|
1315
|
+
QMessageBox.critical(None, "Hata", f"Bilgi okunamadı: {str(e)}")
|
1316
|
+
|
1317
|
+
except Exception as e:
|
1318
|
+
self.logger.error(f"Failed to show app package info: {e}")
|
1319
|
+
|
1320
|
+
def _get_directory_size(self, directory: Path) -> float:
|
1321
|
+
"""Dizin boyutunu MB cinsinden hesapla"""
|
1322
|
+
try:
|
1323
|
+
total_size = 0
|
1324
|
+
for file_path in directory.rglob("*"):
|
1325
|
+
if file_path.is_file():
|
1326
|
+
total_size += file_path.stat().st_size
|
1327
|
+
return total_size / 1024 / 1024
|
1328
|
+
except Exception:
|
1329
|
+
return 0.0
|
1330
|
+
|
1331
|
+
# Kolaylık fonksiyonları
|
1332
|
+
_context_menu_manager = None
|
1333
|
+
|
1334
|
+
def init_context_menu_manager(kernel=None) -> ContextMenuManager:
|
1335
|
+
"""Context menu manager'ı başlat"""
|
1336
|
+
global _context_menu_manager
|
1337
|
+
|
1338
|
+
# Debug: PyQt6 durumunu kontrol et
|
1339
|
+
logger = logging.getLogger("ContextMenuInit")
|
1340
|
+
logger.info(f"Initializing context menu manager, PYQT_AVAILABLE: {PYQT_AVAILABLE}")
|
1341
|
+
|
1342
|
+
try:
|
1343
|
+
# PyQt6 import'unu tekrar test et
|
1344
|
+
from PyQt6.QtWidgets import QMenu
|
1345
|
+
logger.info("PyQt6 import test successful")
|
1346
|
+
except ImportError as e:
|
1347
|
+
logger.error(f"PyQt6 import test failed: {e}")
|
1348
|
+
|
1349
|
+
_context_menu_manager = ContextMenuManager(kernel)
|
1350
|
+
logger.info("Context menu manager created successfully")
|
1351
|
+
return _context_menu_manager
|
1352
|
+
|
1353
|
+
def get_context_menu_manager() -> Optional[ContextMenuManager]:
|
1354
|
+
"""Context menu manager'ı al"""
|
1355
|
+
return _context_menu_manager
|
1356
|
+
|
1357
|
+
def show_context_menu(menu_type: MenuType, context: MenuContext,
|
1358
|
+
position: QPoint, target_path: str = "",
|
1359
|
+
**kwargs) -> bool:
|
1360
|
+
"""Bağlam menüsü göster (kısayol)"""
|
1361
|
+
if _context_menu_manager:
|
1362
|
+
request = MenuRequest(
|
1363
|
+
menu_type=menu_type,
|
1364
|
+
context=context,
|
1365
|
+
target_path=target_path,
|
1366
|
+
position=position,
|
1367
|
+
**kwargs
|
1368
|
+
)
|
1369
|
+
return _context_menu_manager.show_context_menu(request)
|
1370
|
+
return False
|