pyloid 0.13.1__py3-none-any.whl → 0.14.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
pyloid/pyloid.py CHANGED
@@ -2,40 +2,28 @@ import sys
2
2
  import os
3
3
  from PySide6.QtWidgets import (
4
4
  QApplication,
5
- QMainWindow,
6
5
  QSystemTrayIcon,
7
6
  QMenu,
8
7
  )
9
- from PySide6.QtWebEngineWidgets import QWebEngineView
10
- from PySide6.QtWebChannel import QWebChannel
11
8
  from PySide6.QtGui import (
12
9
  QIcon,
13
- QKeySequence,
14
- QShortcut,
15
10
  QClipboard,
16
11
  QImage,
17
12
  QAction,
18
- QCursor,
19
13
  )
20
- from PySide6.QtCore import Qt, Signal, QPoint, QUrl, QObject, QTimer, QSize, QEvent
14
+ from PySide6.QtCore import Qt, Signal, QObject, QTimer
21
15
  from PySide6.QtNetwork import QLocalServer, QLocalSocket
22
- from PySide6.QtWebEngineCore import QWebEnginePage, QWebEngineSettings
23
- from .api import PyloidAPI, Bridge
24
- import uuid
25
- from typing import List, Optional, Dict, Callable, Union, Any
16
+ from .api import PyloidAPI
17
+ from typing import List, Optional, Dict, Callable, Union
26
18
  from PySide6.QtCore import qInstallMessageHandler
27
19
  import signal
28
20
  from .utils import is_production
29
21
  from .monitor import Monitor
30
- import json
31
22
  from .autostart import AutoStart
32
23
  from .filewatcher import FileWatcher
33
24
  import logging
34
- from PySide6.QtWidgets import (
35
- QWidget,
36
- QVBoxLayout,
37
- )
38
- from .custom.titlebar import CustomTitleBar
25
+ from .browser_window import BrowserWindow
26
+ from .tray import TrayEvent
39
27
 
40
28
  # for linux debug
41
29
  os.environ["QTWEBENGINE_DICTIONARIES_PATH"] = "/"
@@ -67,809 +55,6 @@ def custom_message_handler(mode, context, message):
67
55
 
68
56
  qInstallMessageHandler(custom_message_handler)
69
57
 
70
-
71
- class WindowAPI(PyloidAPI):
72
- def __init__(self, window_id: str, app):
73
- super().__init__()
74
- self.window_id: str = window_id
75
- self.app: Pyloid = app
76
-
77
- @Bridge(result=str)
78
- def getWindowId(self):
79
- """Returns the current window ID."""
80
- return self.window_id
81
-
82
- @Bridge(result=dict)
83
- def getWindowProperties(self):
84
- """Returns the properties of the window."""
85
- window = self.app.get_window_by_id(self.window_id)
86
- window_properties = window.get_window_properties()
87
- return window_properties
88
-
89
- @Bridge()
90
- def close(self):
91
- """Closes the window."""
92
- window = self.app.get_window_by_id(self.window_id)
93
- if window:
94
- window.close()
95
-
96
- @Bridge()
97
- def hide(self):
98
- """Hides the window."""
99
- window = self.app.get_window_by_id(self.window_id)
100
- if window:
101
- window.hide()
102
-
103
- @Bridge()
104
- def show(self):
105
- """Shows and focuses the window."""
106
- window = self.app.get_window_by_id(self.window_id)
107
- if window:
108
- window.show()
109
-
110
- @Bridge()
111
- def toggleFullscreen(self):
112
- """Toggles fullscreen mode for the window."""
113
- window = self.app.get_window_by_id(self.window_id)
114
- if window:
115
- window.toggle_fullscreen()
116
-
117
- @Bridge()
118
- def minimize(self):
119
- """Minimizes the window."""
120
- window = self.app.get_window_by_id(self.window_id)
121
- if window:
122
- window.minimize()
123
-
124
- @Bridge()
125
- def maximize(self):
126
- """Maximizes the window."""
127
- window = self.app.get_window_by_id(self.window_id)
128
- if window:
129
- window.maximize()
130
-
131
- @Bridge()
132
- def unmaximize(self):
133
- """Restores the window to its normal state."""
134
- window = self.app.get_window_by_id(self.window_id)
135
- if window:
136
- window.unmaximize()
137
-
138
- @Bridge(str)
139
- def setTitle(self, title: str):
140
- """Sets the title of the window."""
141
- window = self.app.get_window_by_id(self.window_id)
142
- if window:
143
- window.set_title(title)
144
-
145
- @Bridge(int, int)
146
- def setSize(self, width: int, height: int):
147
- """Sets the size of the window."""
148
- window = self.app.get_window_by_id(self.window_id)
149
- if window:
150
- window.set_size(width, height)
151
-
152
- @Bridge(int, int)
153
- def setPosition(self, x: int, y: int):
154
- """Sets the position of the window."""
155
- window = self.app.get_window_by_id(self.window_id)
156
- if window:
157
- window.set_position(x, y)
158
-
159
- @Bridge(bool)
160
- def setFrame(self, frame: bool):
161
- """Sets the frame of the window."""
162
- window = self.app.get_window_by_id(self.window_id)
163
- if window:
164
- window.set_frame(frame)
165
-
166
- @Bridge(bool)
167
- def setContextMenu(self, context_menu: bool):
168
- """Sets the context menu of the window."""
169
- window = self.app.get_window_by_id(self.window_id)
170
- if window:
171
- window.set_context_menu(context_menu)
172
-
173
- @Bridge(bool)
174
- def setDevTools(self, enable: bool):
175
- """Sets the developer tools of the window."""
176
- window = self.app.get_window_by_id(self.window_id)
177
- if window:
178
- window.set_dev_tools(enable)
179
-
180
- @Bridge(str, result=Optional[str])
181
- def capture(self, save_path: str) -> Optional[str]:
182
- """Captures the current window."""
183
- window = self.app.get_window_by_id(self.window_id)
184
- if window:
185
- return window.capture(save_path)
186
- return None
187
-
188
- @Bridge(result=bool)
189
- def getFrame(self):
190
- """Returns whether the window has a frame."""
191
- window = self.app.get_window_by_id(self.window_id)
192
- return window.frame if window else False
193
-
194
- @Bridge(result=bool)
195
- def getContextMenu(self):
196
- """Returns whether the window has a context menu."""
197
- window = self.app.get_window_by_id(self.window_id)
198
- return window.context_menu if window else False
199
-
200
- @Bridge(result=bool)
201
- def getDevTools(self):
202
- """Returns whether the window has developer tools."""
203
- window = self.app.get_window_by_id(self.window_id)
204
- return window.dev_tools if window else False
205
-
206
- @Bridge(result=str)
207
- def getTitle(self):
208
- """Returns the title of the window."""
209
- window = self.app.get_window_by_id(self.window_id)
210
- return window.title if window else ""
211
-
212
- @Bridge(result=dict)
213
- def getSize(self):
214
- """Returns the size of the window."""
215
- window = self.app.get_window_by_id(self.window_id)
216
- return (
217
- {"width": window.width, "height": window.height}
218
- if window
219
- else {"width": 0, "height": 0}
220
- )
221
-
222
- @Bridge(result=dict)
223
- def getPosition(self):
224
- """Returns the position of the window."""
225
- window = self.app.get_window_by_id(self.window_id)
226
- return {"x": window.x, "y": window.y} if window else {"x": 0, "y": 0}
227
-
228
- @Bridge()
229
- def startSystemDrag(self):
230
- """Starts the system drag."""
231
- window = self.app.get_window_by_id(self.window_id)
232
- if window:
233
- window.web_view.start_system_drag()
234
-
235
-
236
- # class EventAPI(PylonAPI):
237
- # def __init__(self, window_id: str, app):
238
- # super().__init__()
239
- # self.window_id: str = window_id
240
- # self.app: PylonApp = app
241
- # self.subscribers = {}
242
-
243
- # @Bridge(str, Callable)
244
- # def on(self, event_name: str, callback: Callable):
245
- # """특정 이벤트를 구독합니다."""
246
- # if event_name not in self.subscribers:
247
- # self.subscribers[event_name] = []
248
- # self.subscribers[event_name].append(callback)
249
-
250
- # @Bridge(str, result=Optional[str])
251
- # def emit(self, event_name: str, *args, **kwargs):
252
- # """다른 윈도우로 특정 이벤트를 보냅니다."""
253
- # if event_name in self.subscribers:
254
- # for callback in self.subscribers[event_name]:
255
- # callback(*args, **kwargs)
256
-
257
-
258
- # 어차피 load 부분에만 쓰이니까 나중에 분리해서 load 위에서 선언하자.
259
- class CustomWebEngineView(QWebEngineView):
260
- def __init__(self, parent=None):
261
- super().__init__(parent._window)
262
- self.parent = parent
263
- self.drag_relative_position = None
264
- self.is_dragging = False
265
- self.is_resizing = False
266
- self.resize_start_pos = None
267
- self.resize_direction = None
268
- self.screen_geometry = self.screen().availableGeometry()
269
- self.is_resizing_enabled = True
270
-
271
- def mouse_press_event(self, event):
272
- if event.button() == Qt.LeftButton:
273
- self.drag_relative_position = event.pos()
274
- if not self.parent.frame and self.is_resizing_enabled:
275
- self.resize_direction = self.get_resize_direction(event.pos())
276
- if self.resize_direction:
277
- self.is_resizing = True
278
- self.resize_start_pos = event.globalPos()
279
-
280
- def start_system_drag(self):
281
- self.is_dragging = True
282
-
283
- def mouse_move_event(self, event):
284
- if self.is_resizing and self.is_resizing_enabled:
285
- self.resize_window(event.globalPos())
286
- elif not self.parent.frame and self.is_dragging:
287
- # 현재 마우스 위치를 전역 좌표로 가져옵니다
288
- current_global_pos = event.globalPos()
289
-
290
- # 화면 경계를 계산합니다
291
- left_boundary = self.screen_geometry.left()
292
- right_boundary = self.screen_geometry.right()
293
- top_boundary = self.screen_geometry.top()
294
- bottom_boundary = self.screen_geometry.bottom()
295
-
296
- # 마우스 커서 위치를 제한합니다
297
- new_cursor_pos = QPoint(
298
- max(left_boundary, min(current_global_pos.x(), right_boundary)),
299
- max(top_boundary, min(current_global_pos.y(), bottom_boundary)),
300
- )
301
-
302
- # 마우스 커서를 새 위치로 이동합니다
303
- QCursor.setPos(new_cursor_pos)
304
-
305
- # 창의 새 위치를 계산합니다
306
- new_window_pos = new_cursor_pos - self.drag_relative_position
307
-
308
- # 창을 새 위치로 이동합니다
309
- self.parent._window.move(new_window_pos)
310
- else:
311
- # Change cursor based on resize direction
312
- resize_direction = self.get_resize_direction(event.pos())
313
- if resize_direction and self.is_resizing_enabled:
314
- self.set_cursor_for_resize_direction(resize_direction)
315
- else:
316
- self.unsetCursor()
317
-
318
- def mouse_release_event(self, event):
319
- if event.button() == Qt.LeftButton:
320
- self.is_dragging = False
321
- self.is_resizing = False
322
- self.resize_direction = None
323
- self.unsetCursor()
324
-
325
- def eventFilter(self, source, event):
326
- if self.focusProxy() is source:
327
- if event.type() == QEvent.MouseButtonPress:
328
- self.mouse_press_event(event)
329
- elif event.type() == QEvent.MouseMove:
330
- self.mouse_move_event(event)
331
- elif event.type() == QEvent.MouseButtonRelease:
332
- self.mouse_release_event(event)
333
- return super().eventFilter(source, event)
334
-
335
- def get_resize_direction(self, pos):
336
- if not self.parent.frame and self.is_resizing_enabled: # Check if frame is not present and resizing is enabled
337
- margin = 5 # Margin in pixels to detect edge
338
- rect = self.rect()
339
- direction = None
340
-
341
- if pos.x() <= margin:
342
- direction = 'left'
343
- elif pos.x() >= rect.width() - margin:
344
- direction = 'right'
345
-
346
- if pos.y() <= margin:
347
- direction = 'top' if direction is None else direction + '-top'
348
- elif pos.y() >= rect.height() - margin:
349
- direction = 'bottom' if direction is None else direction + '-bottom'
350
-
351
- return direction
352
- return None
353
-
354
- def set_cursor_for_resize_direction(self, direction):
355
- if not self.parent.frame and direction and self.is_resizing_enabled: # Check if frame is not present and resizing is enabled
356
- if direction in ['left', 'right']:
357
- self.setCursor(Qt.SizeHorCursor)
358
- elif direction in ['top', 'bottom']:
359
- self.setCursor(Qt.SizeVerCursor)
360
- elif direction in ['left-top', 'right-bottom']:
361
- self.setCursor(Qt.SizeFDiagCursor)
362
- elif direction in ['right-top', 'left-bottom']:
363
- self.setCursor(Qt.SizeBDiagCursor)
364
-
365
- def resize_window(self, global_pos):
366
- if not self.parent.frame and self.resize_start_pos and self.resize_direction and self.is_resizing_enabled: # Check if frame is not present and resizing is enabled
367
- delta = global_pos - self.resize_start_pos
368
- new_geometry = self.parent._window.geometry()
369
-
370
- if 'left' in self.resize_direction:
371
- new_geometry.setLeft(new_geometry.left() + delta.x())
372
- if 'right' in self.resize_direction:
373
- new_geometry.setRight(new_geometry.right() + delta.x())
374
- if 'top' in self.resize_direction:
375
- new_geometry.setTop(new_geometry.top() + delta.y())
376
- if 'bottom' in self.resize_direction:
377
- new_geometry.setBottom(new_geometry.bottom() + delta.y())
378
-
379
- self.parent._window.setGeometry(new_geometry)
380
- self.resize_start_pos = global_pos
381
-
382
-
383
- class BrowserWindow:
384
- def __init__(
385
- self,
386
- app,
387
- title: str = "pyloid app",
388
- width: int = 800,
389
- height: int = 600,
390
- x: int = 200,
391
- y: int = 200,
392
- frame: bool = True,
393
- context_menu: bool = False,
394
- dev_tools: bool = False,
395
- js_apis: List[PyloidAPI] = [],
396
- ):
397
- ###########################################################################################
398
- self.id = str(uuid.uuid4()) # Generate unique ID
399
- self._window = QMainWindow()
400
- self.web_view = CustomWebEngineView(self)
401
-
402
- self._window.closeEvent = self.closeEvent # Override closeEvent method
403
- ###########################################################################################
404
- self.app = app
405
- self.title = title
406
- self.width = width
407
- self.height = height
408
- self.x = x
409
- self.y = y
410
- self.frame = frame
411
- self.context_menu = context_menu
412
- self.dev_tools = dev_tools
413
- self.js_apis = [WindowAPI(self.id, self.app)]
414
- for js_api in js_apis:
415
- self.js_apis.append(js_api)
416
- self.shortcuts = {}
417
- ###########################################################################################
418
-
419
- def _set_custom_frame(
420
- self,
421
- use_custom: bool,
422
- title: str = "Custom Title",
423
- bg_color: str = "darkblue",
424
- text_color: str = "white",
425
- icon_path: str = None,
426
- ):
427
- """Sets or removes the custom frame."""
428
- if use_custom:
429
- self._window.setWindowFlags(Qt.FramelessWindowHint)
430
- self.custom_title_bar = CustomTitleBar(self._window)
431
- self.custom_title_bar.set_style(bg_color, text_color)
432
- self.custom_title_bar.set_title(title)
433
-
434
- if icon_path:
435
- self.custom_title_bar.set_icon(icon_path)
436
-
437
- layout = QVBoxLayout()
438
- layout.setContentsMargins(0, 0, 0, 0)
439
- layout.setSpacing(0)
440
- layout.addWidget(self.custom_title_bar)
441
- layout.addWidget(self.web_view)
442
-
443
- central_widget = QWidget()
444
- central_widget.setLayout(layout)
445
- self._window.setCentralWidget(central_widget)
446
-
447
- # Add properties for window movement
448
- self._window.moving = False
449
- self._window.offset = QPoint()
450
- else:
451
- self._window.setWindowFlags(Qt.Window)
452
- self._window.setCentralWidget(self.web_view)
453
- self.custom_title_bar = None
454
-
455
- self._window.show()
456
-
457
- def _load(self):
458
- self.set_title(self.title)
459
-
460
- self.set_size(self.width, self.height)
461
- self.set_position(self.x, self.y)
462
-
463
- # allow local file access to remote urls
464
- self.web_view.settings().setAttribute(
465
- QWebEngineSettings.LocalContentCanAccessRemoteUrls, True
466
- )
467
-
468
- # Set icon
469
- if self.app.icon:
470
- self._window.setWindowIcon(self.app.icon)
471
- else:
472
- print("Icon is not set.")
473
-
474
- # Set Windows taskbar icon
475
- if sys.platform == "win32":
476
- import ctypes
477
-
478
- myappid = "mycompany.myproduct.subproduct.version"
479
- ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
480
-
481
- # Remove title bar and borders (if needed)
482
- if not self.frame:
483
- self._window.setWindowFlags(Qt.FramelessWindowHint)
484
-
485
- # Disable default context menu
486
- if not self.context_menu:
487
- self.web_view.setContextMenuPolicy(Qt.NoContextMenu)
488
-
489
- # Set up QWebChannel
490
- self.channel = QWebChannel()
491
-
492
- # Register additional JS APIs
493
- if self.js_apis:
494
- for js_api in self.js_apis:
495
- self.channel.registerObject(js_api.__class__.__name__, js_api)
496
-
497
- self.web_view.page().setWebChannel(self.channel)
498
-
499
- # Connect pylonjs bridge
500
- self.web_view.loadFinished.connect(self._on_load_finished)
501
-
502
- # Add QWebEngineView to main window
503
- self._window.setCentralWidget(self.web_view)
504
-
505
- # Set F12 shortcut
506
- self.set_dev_tools(self.dev_tools)
507
-
508
- def _on_load_finished(self, ok):
509
- """Handles the event when the web page finishes loading."""
510
- if ok and self.js_apis:
511
- js_code = """
512
- if (typeof QWebChannel !== 'undefined') {
513
- new QWebChannel(qt.webChannelTransport, function (channel) {
514
- window.pyloid = {
515
- EventAPI: {
516
- listen: function(eventName, callback) {
517
- document.addEventListener(eventName, function(event) {
518
- let eventData;
519
- try {
520
- eventData = JSON.parse(event.detail);
521
- } catch (e) {
522
- eventData = event.detail;
523
- }
524
- callback(eventData);
525
- });
526
- },
527
- unlisten: function(eventName) {
528
- document.removeEventListener(eventName);
529
- }
530
- }
531
- };
532
- console.log('pyloid.EventAPI object initialized:', window.pyloid.EventAPI);
533
-
534
- %s
535
-
536
- document.addEventListener('mousedown', function (e) {
537
- if (e.target.hasAttribute('data-pyloid-drag-region')) {
538
- window.pyloid.WindowAPI.startSystemDrag();
539
- }
540
- });
541
-
542
- // Dispatch a custom event to signal that the initialization is ready
543
- const event = new CustomEvent('pyloidReady');
544
- document.dispatchEvent(event);
545
- });
546
- } else {
547
- console.error('QWebChannel is not defined.');
548
- }
549
- """
550
- js_api_init = "\n".join(
551
- [
552
- f"window.pyloid['{js_api.__class__.__name__}'] = channel.objects['{js_api.__class__.__name__}'];\n"
553
- f"console.log('pyloid.{js_api.__class__.__name__} object initialized:', window.pyloid['{js_api.__class__.__name__}']);"
554
- for js_api in self.js_apis
555
- ]
556
- )
557
- self.web_view.page().runJavaScript(js_code % js_api_init)
558
- else:
559
- pass
560
-
561
- ###########################################################################################
562
- # Load
563
- ###########################################################################################
564
- def load_file(self, file_path):
565
- """Loads a local HTML file into the web view."""
566
- self._load()
567
- file_path = os.path.abspath(file_path) # absolute path
568
- self.web_view.setUrl(QUrl.fromLocalFile(file_path))
569
- self.web_view.focusProxy().installEventFilter(self.web_view)
570
-
571
- def load_url(self, url):
572
- """Sets the URL of the window."""
573
- self._load()
574
- self.web_view.setUrl(QUrl(url))
575
- self.web_view.focusProxy().installEventFilter(self.web_view)
576
-
577
- ###########################################################################################
578
- # Set Parameters
579
- ###########################################################################################
580
- def set_title(self, title: str):
581
- """Sets the title of the window."""
582
- self.title = title
583
- self._window.setWindowTitle(self.title)
584
-
585
- def set_size(self, width: int, height: int):
586
- """Sets the size of the window."""
587
- self.width = width
588
- self.height = height
589
- self._window.setGeometry(self.x, self.y, self.width, self.height)
590
-
591
- def set_position(self, x: int, y: int):
592
- """Sets the position of the window."""
593
- self.x = x
594
- self.y = y
595
- self._window.setGeometry(self.x, self.y, self.width, self.height)
596
-
597
- def set_frame(self, frame: bool):
598
- """Sets the frame of the window."""
599
- self.frame = frame
600
- was_visible = self._window.isVisible()
601
- if self.frame:
602
- self._window.setWindowFlags(Qt.Window)
603
- else:
604
- self._window.setWindowFlags(Qt.FramelessWindowHint)
605
- if was_visible:
606
- self._window.show()
607
-
608
- def set_context_menu(self, context_menu: bool):
609
- """Sets the context menu of the window."""
610
- self.context_menu = context_menu
611
- if self.context_menu:
612
- self.web_view.setContextMenuPolicy(Qt.NoContextMenu)
613
- else:
614
- self.web_view.setContextMenuPolicy(Qt.DefaultContextMenu)
615
-
616
- def set_dev_tools(self, enable: bool):
617
- """Sets the developer tools of the window.
618
-
619
- If enabled, the developer tools can be opened using the F12 key.
620
- """
621
- self.dev_tools = enable
622
- if self.dev_tools:
623
- self.add_shortcut("F12", self.open_dev_tools)
624
- else:
625
- self.remove_shortcut("F12")
626
-
627
- def open_dev_tools(self):
628
- """Opens the developer tools window."""
629
- self.web_view.page().setDevToolsPage(QWebEnginePage(self.web_view.page()))
630
- self.dev_tools_window = QMainWindow(self._window)
631
- dev_tools_view = QWebEngineView(self.dev_tools_window)
632
- dev_tools_view.setPage(self.web_view.page().devToolsPage())
633
- self.dev_tools_window.setCentralWidget(dev_tools_view)
634
- self.dev_tools_window.resize(800, 600)
635
- self.dev_tools_window.show()
636
-
637
- # Add this line to handle dev tools window closure
638
- self.dev_tools_window.closeEvent = lambda event: setattr(
639
- self, "dev_tools_window", None
640
- )
641
-
642
- def closeEvent(self, event):
643
- """Handles the event when the window is closed."""
644
- # Close developer tools if open
645
- if hasattr(self, "dev_tools_window") and self.dev_tools_window:
646
- self.dev_tools_window.close()
647
- self.dev_tools_window = None
648
-
649
- # Solve memory leak issue with web view engine
650
- self.web_view.page().deleteLater()
651
- self.web_view.deleteLater()
652
- self._remove_from_app_windows()
653
- event.accept() # Accept the event (allow the window to close)
654
-
655
- def _remove_from_app_windows(self):
656
- """Removes the window from the app's window list."""
657
- if self in self.app.windows:
658
- self.app.windows.remove(self)
659
- if not self.app.windows:
660
- self.app.quit() # Quit the app if all windows are closed
661
-
662
- ###########################################################################################
663
- # Window management (no ID required)
664
- ###########################################################################################
665
- def hide(self):
666
- """Hides the window."""
667
- self._window.hide()
668
-
669
- def show(self):
670
- """Shows the window."""
671
- self._window.show()
672
-
673
- def focus(self):
674
- """Focuses the window."""
675
- self._window.activateWindow()
676
- self._window.raise_()
677
- self._window.setWindowState(
678
- self._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive
679
- )
680
-
681
- def show_and_focus(self):
682
- """Shows and focuses the window."""
683
- self._window.show()
684
- self._window.activateWindow()
685
- self._window.raise_()
686
- self._window.setWindowState(
687
- self._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive
688
- )
689
-
690
- def close(self):
691
- """Closes the window."""
692
- self._window.close()
693
-
694
- def toggle_fullscreen(self):
695
- """Toggles fullscreen mode for the window."""
696
- if self._window.isFullScreen():
697
- self._window.showNormal()
698
- else:
699
- self._window.showFullScreen()
700
-
701
- def minimize(self):
702
- """Minimizes the window."""
703
- self._window.showMinimized()
704
-
705
- def maximize(self):
706
- """Maximizes the window."""
707
- self._window.showMaximized()
708
-
709
- def unmaximize(self):
710
- """Unmaximizes the window."""
711
- self._window.showNormal()
712
-
713
- def capture(self, save_path: str) -> Optional[str]:
714
- """
715
- Captures the current window.
716
-
717
- :param save_path: Path to save the captured image. If not specified, it will be saved in the current directory.
718
- :return: Path of the saved image
719
- """
720
- try:
721
- # Capture window
722
- screenshot = self._window.grab()
723
-
724
- # Save image
725
- screenshot.save(save_path)
726
- return save_path
727
- except Exception as e:
728
- print(f"Error occurred while capturing the window: {e}")
729
- return None
730
-
731
- ###########################################################################################
732
- # Shortcut
733
- ###########################################################################################
734
- def add_shortcut(self, key_sequence: str, callback: Callable):
735
- """
736
- Adds a keyboard shortcut to the window if it does not already exist.
737
-
738
- :param key_sequence: Shortcut sequence (e.g., "Ctrl+C")
739
- :param callback: Function to be executed when the shortcut is pressed
740
- :return: Created QShortcut object or None if the shortcut already exists
741
- """
742
- if key_sequence in self.shortcuts:
743
- # print(f"Shortcut {key_sequence} already exists.")
744
- return None
745
-
746
- shortcut = QShortcut(QKeySequence(key_sequence), self._window)
747
- shortcut.activated.connect(callback)
748
- self.shortcuts[key_sequence] = shortcut
749
- return shortcut
750
-
751
- def remove_shortcut(self, key_sequence: str):
752
- """
753
- Removes a keyboard shortcut from the window.
754
-
755
- :param key_sequence: Shortcut sequence to be removed
756
- """
757
- if key_sequence in self.shortcuts:
758
- shortcut = self.shortcuts.pop(key_sequence)
759
- shortcut.setEnabled(False)
760
- shortcut.deleteLater()
761
-
762
- def get_all_shortcuts(self):
763
- """
764
- Returns all registered shortcuts in the window.
765
-
766
- :return: Dictionary of shortcut sequences and QShortcut objects
767
- """
768
- return self.shortcuts
769
-
770
- ###########################################################################################
771
- # Event (Calling the JS from Python)
772
- ###########################################################################################
773
- def emit(self, event_name, data: Optional[Dict] = None):
774
- """
775
- Emits an event to the JavaScript side.
776
-
777
- :param event_name: Name of the event
778
- :param data: Data to be sent with the event (optional)
779
- """
780
- script = f"""
781
- (function() {{
782
- const eventData = {json.dumps(data)};
783
- const customEvent = new CustomEvent('{event_name}', {{ detail: eventData }});
784
- document.dispatchEvent(customEvent);
785
- }})();
786
- """
787
- self.web_view.page().runJavaScript(script)
788
-
789
- ###########################################################################################
790
- # Get Properties
791
- ###########################################################################################
792
- def get_window_properties(self):
793
- """Returns the properties of the window."""
794
- return {
795
- "id": self.id,
796
- "title": self.title,
797
- "width": self.width,
798
- "height": self.height,
799
- "x": self.x,
800
- "y": self.y,
801
- "frame": self.frame,
802
- "context_menu": self.context_menu,
803
- "dev_tools": self.dev_tools,
804
- }
805
-
806
- def get_id(self):
807
- """Returns the ID of the window."""
808
- return self.id
809
-
810
- def get_size(self) -> Dict[str, int]:
811
- """Returns the size of the window."""
812
- return {"width": self.width, "height": self.height}
813
-
814
- def get_position(self) -> Dict[str, int]:
815
- """Returns the position of the window."""
816
- return {"x": self.x, "y": self.y}
817
-
818
- def get_title(self) -> str:
819
- """Returns the title of the window."""
820
- return self.title
821
-
822
- def get_url(self) -> str:
823
- """Returns the URL of the window."""
824
- return self.web_view.url().toString()
825
-
826
- def get_visible(self) -> bool:
827
- """Returns the visibility of the window."""
828
- return self._window.isVisible()
829
-
830
- def get_frame(self) -> bool:
831
- """Returns the frame enabled state of the window."""
832
- return self.frame
833
-
834
- ###########################################################################################
835
- # Resize
836
- ###########################################################################################
837
- def set_resizable(self, resizable: bool):
838
- """Sets the resizability of the window."""
839
- self.resizable = resizable
840
- if self.frame:
841
- flags = self._window.windowFlags() | Qt.WindowCloseButtonHint
842
- if resizable:
843
- pass
844
- else:
845
- flags |= Qt.MSWindowsFixedSizeDialogHint
846
- self._window.setWindowFlags(flags)
847
- else:
848
- # 프레임이 없는 경우 커스텀 리사이징 로직을 설정합니다.
849
- self.web_view.is_resizing_enabled = resizable
850
-
851
- self._window.show() # 변경사항을 적용하기 위해 창을 다시 표시합니다.
852
-
853
- def set_minimum_size(self, min_width: int, min_height: int):
854
- """Sets the minimum size of the window."""
855
- self._window.setMinimumSize(min_width, min_height)
856
-
857
- def set_maximum_size(self, max_width: int, max_height: int):
858
- """Sets the maximum size of the window."""
859
- self._window.setMaximumSize(max_width, max_height)
860
-
861
- def get_minimum_size(self):
862
- """Returns the minimum size of the window."""
863
- return {'width': self._window.minimumWidth(), 'height': self._window.minimumHeight()}
864
-
865
- def get_maximum_size(self):
866
- """Returns the maximum size of the window."""
867
- return {'width': self._window.maximumWidth(), 'height': self._window.maximumHeight()}
868
-
869
- def get_resizable(self):
870
- """Returns the resizability of the window."""
871
- return self.resizable
872
-
873
58
  class _WindowController(QObject):
874
59
  create_window_signal = Signal(
875
60
  QApplication, str, int, int, int, int, bool, bool, bool, list
@@ -882,6 +67,27 @@ class Pyloid(QApplication):
882
67
  app_name,
883
68
  single_instance=True,
884
69
  ):
70
+ """
71
+ Initializes the Pyloid application.
72
+
73
+ Parameters
74
+ ----------
75
+ app_name : str, required
76
+ The name of the application
77
+ single_instance : bool, optional
78
+ Whether to run the application as a single instance (default is True)
79
+
80
+ Examples
81
+ --------
82
+ ```python
83
+ app = Pyloid(app_name="Pyloid-App")
84
+
85
+ window = app.create_window(title="New Window", width=1024, height=768)
86
+ window.show()
87
+
88
+ app.run()
89
+ ```
90
+ """
885
91
  super().__init__(sys.argv)
886
92
 
887
93
  self.windows = []
@@ -920,20 +126,28 @@ class Pyloid(QApplication):
920
126
  """
921
127
  Dynamically sets the application's icon.
922
128
 
923
- :param icon_path: Path to the new icon file
924
-
925
129
  This method can be called while the application is running.
926
- The icon can be changed at any time and is applied immediately.
130
+ The icon can be changed at any time and will be applied immediately.
131
+
132
+ Parameters
133
+ ----------
134
+ icon_path : str
135
+ Path to the new icon file
136
+
137
+ Examples
138
+ --------
139
+ >>> app = Pyloid(app_name="Pyloid-App")
140
+ >>> app.set_icon("icons/icon.png")
927
141
  """
928
142
  self.icon = QIcon(icon_path)
929
143
 
930
- # Immediately update the icon for all open windows
144
+ # Immediately update the icon for all open windows.
931
145
  for window in self.windows:
932
146
  window._window.setWindowIcon(self.icon)
933
147
 
934
148
  def create_window(
935
149
  self,
936
- title: str = "pyloid app",
150
+ title: str,
937
151
  width: int = 800,
938
152
  height: int = 600,
939
153
  x: int = 200,
@@ -943,7 +157,41 @@ class Pyloid(QApplication):
943
157
  dev_tools: bool = False,
944
158
  js_apis: List[PyloidAPI] = [],
945
159
  ) -> BrowserWindow:
946
- """Creates a new browser window."""
160
+ """
161
+ Creates a new browser window.
162
+
163
+ Parameters
164
+ ----------
165
+ title : str, required
166
+ Title of the window
167
+ width : int, optional
168
+ Width of the window (default is 800)
169
+ height : int, optional
170
+ Height of the window (default is 600)
171
+ x : int, optional
172
+ X coordinate of the window (default is 200)
173
+ y : int, optional
174
+ Y coordinate of the window (default is 200)
175
+ frame : bool, optional
176
+ Whether the window has a frame (default is True)
177
+ context_menu : bool, optional
178
+ Whether to use the context menu (default is False)
179
+ dev_tools : bool, optional
180
+ Whether to use developer tools (default is False)
181
+ js_apis : list of PyloidAPI, optional
182
+ List of JavaScript APIs to add to the window (default is an empty list)
183
+
184
+ Returns
185
+ -------
186
+ BrowserWindow
187
+ The created browser window object
188
+
189
+ Examples
190
+ --------
191
+ >>> app = Pyloid(app_name="Pyloid-App")
192
+ >>> window = app.create_window(title="New Window", width=1024, height=768)
193
+ >>> window.show()
194
+ """
947
195
  self.controller.create_window_signal.emit(
948
196
  self,
949
197
  title,
@@ -988,7 +236,20 @@ class Pyloid(QApplication):
988
236
  return window
989
237
 
990
238
  def run(self):
991
- """Runs the application event loop."""
239
+ """
240
+ Runs the application event loop.
241
+
242
+ This method starts the application's event loop, allowing the application to run.
243
+
244
+ This code should be written at the very end of the file.
245
+
246
+ Examples
247
+ --------
248
+ ```python
249
+ app = Pyloid(app_name="Pyloid-App")
250
+ app.run()
251
+ ```
252
+ """
992
253
  if is_production():
993
254
  sys.exit(self.exec())
994
255
  else:
@@ -1016,17 +277,51 @@ class Pyloid(QApplication):
1016
277
  # App window
1017
278
  ###########################################################################################
1018
279
  def get_windows(self) -> List[BrowserWindow]:
1019
- """Returns a list of all browser windows."""
280
+ """
281
+ Returns a list of all browser windows.
282
+
283
+ Returns
284
+ -------
285
+ List[BrowserWindow]
286
+ List of all browser windows
287
+
288
+ Examples
289
+ --------
290
+ ```python
291
+ app = Pyloid(app_name="Pyloid-App")
292
+ windows = app.get_windows()
293
+ for window in windows:
294
+ print(window.get_id())
295
+ ```
296
+ """
1020
297
  return self.windows
1021
298
 
1022
299
  def show_main_window(self):
1023
- """Shows and focuses the first window."""
300
+ """
301
+ Shows and focuses the first window.
302
+
303
+ Examples
304
+ --------
305
+ ```python
306
+ app = Pyloid(app_name="Pyloid-App")
307
+ app.show_main_window()
308
+ ```
309
+ """
1024
310
  if self.windows:
1025
311
  main_window = self.windows[0]
1026
312
  main_window._window.show()
1027
313
 
1028
314
  def focus_main_window(self):
1029
- """Focuses the first window."""
315
+ """
316
+ Focuses the first window.
317
+
318
+ Examples
319
+ --------
320
+ ```python
321
+ app = Pyloid(app_name="Pyloid-App")
322
+ app.focus_main_window()
323
+ ```
324
+ """
1030
325
  if self.windows:
1031
326
  main_window = self.windows[0]
1032
327
  main_window._window.activateWindow()
@@ -1037,7 +332,16 @@ class Pyloid(QApplication):
1037
332
  )
1038
333
 
1039
334
  def show_and_focus_main_window(self):
1040
- """Shows and focuses the first window."""
335
+ """
336
+ Shows and focuses the first window.
337
+
338
+ Examples
339
+ --------
340
+ ```python
341
+ app = Pyloid(app_name="Pyloid-App")
342
+ app.show_and_focus_main_window()
343
+ ```
344
+ """
1041
345
  if self.windows:
1042
346
  main_window = self.windows[0]
1043
347
  main_window._window.show()
@@ -1049,12 +353,30 @@ class Pyloid(QApplication):
1049
353
  )
1050
354
 
1051
355
  def close_all_windows(self):
1052
- """Closes all windows."""
356
+ """
357
+ Closes all windows.
358
+
359
+ Examples
360
+ --------
361
+ ```python
362
+ app = Pyloid(app_name="Pyloid-App")
363
+ app.close_all_windows()
364
+ ```
365
+ """
1053
366
  for window in self.windows:
1054
367
  window._window.close()
1055
368
 
1056
369
  def quit(self):
1057
- """애플리케이션을 종료합니다."""
370
+ """
371
+ Quits the application.
372
+
373
+ Examples
374
+ --------
375
+ ```python
376
+ app = Pyloid(app_name="Pyloid-App")
377
+ app.quit()
378
+ ```
379
+ """
1058
380
  for window in self.windows:
1059
381
  window._window.close()
1060
382
  window.web_page.deleteLater()
@@ -1065,20 +387,77 @@ class Pyloid(QApplication):
1065
387
  # Window management in the app (ID required)
1066
388
  ###########################################################################################
1067
389
  def get_window_by_id(self, window_id: str) -> Optional[BrowserWindow]:
1068
- """Returns the window with the given ID."""
390
+ """
391
+ Returns the window with the given ID.
392
+
393
+ Parameters
394
+ ----------
395
+ window_id : str
396
+ The ID of the window to find
397
+
398
+ Returns
399
+ -------
400
+ Optional[BrowserWindow]
401
+ The window object with the given ID. Returns None if the window is not found.
402
+
403
+ Examples
404
+ --------
405
+ ```python
406
+ app = Pyloid(app_name="Pyloid-App")
407
+
408
+ window = app.get_window_by_id("123e4567-e89b-12d3-a456-426614174000")
409
+
410
+ if window:
411
+ print("Window found:", window)
412
+ ```
413
+ """
1069
414
  for window in self.windows:
1070
415
  if window.id == window_id:
1071
416
  return window
1072
417
  return None
1073
418
 
1074
419
  def hide_window_by_id(self, window_id: str):
1075
- """Hides the window with the given ID."""
420
+ """
421
+ Hides the window with the given ID.
422
+
423
+ Parameters
424
+ ----------
425
+ window_id : str
426
+ The ID of the window to hide
427
+
428
+ Examples
429
+ --------
430
+ ```python
431
+ app = Pyloid(app_name="Pyloid-App")
432
+
433
+ window = app.create_window(title="pyloid-window")
434
+
435
+ app.hide_window_by_id(window.id)
436
+ ```
437
+ """
1076
438
  window = self.get_window_by_id(window_id)
1077
439
  if window:
1078
440
  window.hide()
1079
441
 
1080
442
  def show_window_by_id(self, window_id: str):
1081
- """Shows and focuses the window with the given ID."""
443
+ """
444
+ Shows and focuses the window with the given ID.
445
+
446
+ Parameters
447
+ ----------
448
+ window_id : str
449
+ The ID of the window to show
450
+
451
+ Examples
452
+ --------
453
+ ```python
454
+ app = Pyloid(app_name="Pyloid-App")
455
+
456
+ window = app.create_window(title="pyloid-window")
457
+
458
+ app.show_window_by_id(window.id)
459
+ ```
460
+ """
1082
461
  window = self.get_window_by_id(window_id)
1083
462
  if window:
1084
463
  window._window.show()
@@ -1089,41 +468,147 @@ class Pyloid(QApplication):
1089
468
  )
1090
469
 
1091
470
  def close_window_by_id(self, window_id: str):
1092
- """Closes the window with the given ID."""
471
+ """
472
+ Closes the window with the given ID.
473
+
474
+ Parameters
475
+ ----------
476
+ window_id : str
477
+ The ID of the window to close
478
+
479
+ Examples
480
+ --------
481
+ ```python
482
+ app = Pyloid(app_name="Pyloid-App")
483
+
484
+ window = app.create_window(title="pyloid-window")
485
+
486
+ app.close_window_by_id(window.id)
487
+ ```
488
+ """
1093
489
  window = self.get_window_by_id(window_id)
1094
490
  if window:
1095
491
  window._window.close()
1096
492
 
1097
493
  def toggle_fullscreen_by_id(self, window_id: str):
1098
- """Toggles fullscreen mode for the window with the given ID."""
494
+ """
495
+ Toggles fullscreen mode for the window with the given ID.
496
+
497
+ Parameters
498
+ ----------
499
+ window_id : str
500
+ The ID of the window to toggle fullscreen mode
501
+
502
+ Examples
503
+ --------
504
+ ```python
505
+ app = Pyloid(app_name="Pyloid-App")
506
+
507
+ window = app.create_window(title="pyloid-window")
508
+
509
+ app.toggle_fullscreen_by_id(window.id)
510
+ ```
511
+ """
1099
512
  window = self.get_window_by_id(window_id)
1100
513
  window.toggle_fullscreen()
1101
514
 
1102
515
  def minimize_window_by_id(self, window_id: str):
1103
- """Minimizes the window with the given ID."""
516
+ """
517
+ Minimizes the window with the given ID.
518
+
519
+ Parameters
520
+ ----------
521
+ window_id : str
522
+ The ID of the window to minimize
523
+
524
+ Examples
525
+ --------
526
+ ```python
527
+ app = Pyloid(app_name="Pyloid-App")
528
+
529
+ window = app.create_window(title="pyloid-window")
530
+
531
+ app.minimize_window_by_id(window.id)
532
+ ```
533
+ """
1104
534
  window = self.get_window_by_id(window_id)
1105
535
  if window:
1106
536
  window.minimize()
1107
537
 
1108
538
  def maximize_window_by_id(self, window_id: str):
1109
- """Maximizes the window with the given ID."""
539
+ """
540
+ Maximizes the window with the given ID.
541
+
542
+ Parameters
543
+ ----------
544
+ window_id : str
545
+ The ID of the window to maximize
546
+
547
+ Examples
548
+ --------
549
+ ```python
550
+ app = Pyloid(app_name="Pyloid-App")
551
+
552
+ window = app.create_window(title="pyloid-window")
553
+
554
+ app.maximize_window_by_id(window.id)
555
+ ```
556
+ """
1110
557
  window = self.get_window_by_id(window_id)
1111
558
  if window:
1112
559
  window.maximize()
1113
560
 
1114
561
  def unmaximize_window_by_id(self, window_id: str):
1115
- """Unmaximizes the window with the given ID."""
562
+ """
563
+ Unmaximizes the window with the given ID.
564
+
565
+ Parameters
566
+ ----------
567
+ window_id : str
568
+ The ID of the window to unmaximize
569
+
570
+ Examples
571
+ --------
572
+ ```python
573
+ app = Pyloid(app_name="Pyloid-App")
574
+
575
+ window = app.create_window(title="pyloid-window")
576
+
577
+ app.unmaximize_window_by_id(window.id)
578
+ ```
579
+ """
1116
580
  window = self.get_window_by_id(window_id)
1117
581
  if window:
1118
582
  window.unmaximize()
1119
583
 
1120
584
  def capture_window_by_id(self, window_id: str, save_path: str) -> Optional[str]:
1121
585
  """
1122
- Captures a specific window.
586
+ Captures the specified window.
1123
587
 
1124
- :param window_id: ID of the window to capture
1125
- :param save_path: Path to save the captured image. If not specified, it will be saved in the current directory.
1126
- :return: Path of the saved image
588
+ Parameters
589
+ ----------
590
+ window_id : str
591
+ The ID of the window to capture
592
+ save_path : str
593
+ The path to save the captured image. If not specified, it will be saved in the current directory.
594
+
595
+ Returns
596
+ -------
597
+ Optional[str]
598
+ The path of the saved image. Returns None if the window is not found or an error occurs.
599
+
600
+ Examples
601
+ --------
602
+ ```python
603
+ app = Pyloid(app_name="Pyloid-App")
604
+
605
+ window = app.create_window(title="pyloid-window")
606
+
607
+ image_path = app.capture_window_by_id(window.id, "save/image.png")
608
+
609
+ if image_path:
610
+ print("Image saved at:", image_path)
611
+ ```
1127
612
  """
1128
613
  try:
1129
614
  window = self.get_window_by_id(window_id)
@@ -1149,7 +634,15 @@ class Pyloid(QApplication):
1149
634
  Dynamically sets the tray icon.
1150
635
  Can be called while the application is running, and changes are applied immediately.
1151
636
 
1152
- :param tray_icon_path: Path to the new tray icon file
637
+ Parameters
638
+ ----------
639
+ tray_icon_path : str
640
+ The path of the new tray icon file
641
+
642
+ Examples
643
+ --------
644
+ >>> app = Pyloid(app_name="Pyloid-App")
645
+ >>> app.set_tray_icon("icons/icon.png")
1153
646
  """
1154
647
  # Stop and remove existing animation timer if present
1155
648
  if hasattr(self, "animation_timer") and self.animation_timer is not None:
@@ -1176,7 +669,19 @@ class Pyloid(QApplication):
1176
669
  Dynamically sets the tray menu items.
1177
670
  Can be called while the application is running, and changes are applied immediately.
1178
671
 
1179
- :param tray_menu_items: List of new tray menu items
672
+ Parameters
673
+ ----------
674
+ tray_menu_items : List[Dict[str, Union[str, Callable]]]
675
+ The list of new tray menu items
676
+
677
+ Examples
678
+ --------
679
+ >>> app = Pyloid(app_name="Pyloid-App")
680
+ >>> menu_items = [
681
+ >>> {"label": "Open", "callback": lambda: print("Open clicked")},
682
+ >>> {"label": "Exit", "callback": app.quit}
683
+ >>> ]
684
+ >>> app.set_tray_menu_items(menu_items)
1180
685
  """
1181
686
  self.tray_menu_items = tray_menu_items
1182
687
  if not hasattr(self, "tray"):
@@ -1212,12 +717,27 @@ class Pyloid(QApplication):
1212
717
  if reason_enum in self.tray_actions:
1213
718
  self.tray_actions[reason_enum]()
1214
719
 
1215
- def set_tray_actions(self, actions):
720
+ def set_tray_actions(self, actions: Dict[TrayEvent, Callable]):
1216
721
  """
1217
- Dynamically sets actions for tray icon activation.
722
+ Dynamically sets the actions for tray icon activation.
1218
723
  Can be called while the application is running, and changes are applied immediately.
1219
724
 
1220
- :param actions: Dictionary with TrayEvent enum values as keys and corresponding callback functions as values
725
+ Parameters
726
+ ----------
727
+ actions: Dict[TrayEvent, Callable]
728
+ Dictionary with TrayEvent enum values as keys and corresponding callback functions as values
729
+
730
+ Examples
731
+ --------
732
+ >>> app = Pyloid(app_name="Pyloid-App")
733
+ >>> app.set_tray_actions(
734
+ >>> {
735
+ >>> TrayEvent.DoubleClick: lambda: print("Tray icon was double-clicked."),
736
+ >>> TrayEvent.MiddleClick: lambda: print("Tray icon was middle-clicked."),
737
+ >>> TrayEvent.RightClick: lambda: print("Tray icon was right-clicked."),
738
+ >>> TrayEvent.LeftClick: lambda: print("Tray icon was left-clicked."),
739
+ >>> }
740
+ >>> )
1221
741
  """
1222
742
  if self.tray_actions:
1223
743
  self.tray.activated.disconnect() # Disconnect existing connections
@@ -1233,8 +753,17 @@ class Pyloid(QApplication):
1233
753
  Displays a notification in the system tray.
1234
754
  Can be called while the application is running, and the notification is displayed immediately.
1235
755
 
1236
- :param title: Notification title
1237
- :param message: Notification content
756
+ Parameters
757
+ ----------
758
+ title : str
759
+ Notification title
760
+ message : str
761
+ Notification message
762
+
763
+ Examples
764
+ --------
765
+ >>> app = Pyloid(app_name="Pyloid-App")
766
+ >>> app.show_notification("Update Available", "A new update is available for download.")
1238
767
  """
1239
768
  if not hasattr(self, "tray"):
1240
769
  self._init_tray() # Ensure the tray is initialized
@@ -1242,18 +771,30 @@ class Pyloid(QApplication):
1242
771
  self.tray.showMessage(title, message, QIcon(self.icon), 5000)
1243
772
 
1244
773
  def _update_tray_icon(self):
1245
- """Updates the animation frames."""
774
+ """
775
+ Updates the animation frame.
776
+ """
1246
777
  if hasattr(self, "tray") and self.icon_frames:
1247
778
  self.tray.setIcon(self.icon_frames[self.current_frame])
1248
779
  self.current_frame = (self.current_frame + 1) % len(self.icon_frames)
1249
780
 
1250
781
  def set_tray_icon_animation(self, icon_frames: List[str], interval: int = 200):
1251
782
  """
1252
- Dynamically sets and starts an animation for the tray icon.
783
+ Dynamically sets and starts the animation for the tray icon.
1253
784
  Can be called while the application is running, and changes are applied immediately.
1254
785
 
1255
- :param icon_frames: List of paths to animation frame images
1256
- :param interval: Interval between frames (milliseconds)
786
+ Parameters
787
+ ----------
788
+ icon_frames : list of str
789
+ List of animation frame image paths
790
+ interval : int, optional
791
+ Frame interval in milliseconds, default is 200
792
+
793
+ Examples
794
+ --------
795
+ >>> app = Pyloid(app_name="Pyloid-App")
796
+ >>> icon_frames = ["frame1.png", "frame2.png", "frame3.png"]
797
+ >>> app.set_tray_icon_animation(icon_frames, 100)
1257
798
  """
1258
799
  if not hasattr(self, "tray"):
1259
800
  self._init_tray()
@@ -1270,10 +811,12 @@ class Pyloid(QApplication):
1270
811
 
1271
812
  self.icon_frames = [QIcon(frame) for frame in icon_frames]
1272
813
  self.animation_interval = interval
1273
- self.start_tray_icon_animation()
814
+ self._start_tray_icon_animation()
1274
815
 
1275
- def start_tray_icon_animation(self):
1276
- """Starts the tray icon animation."""
816
+ def _start_tray_icon_animation(self):
817
+ """
818
+ Starts the tray icon animation.
819
+ """
1277
820
  if self.icon_frames:
1278
821
  if self.animation_timer is None:
1279
822
  self.animation_timer = QTimer(self)
@@ -1286,7 +829,15 @@ class Pyloid(QApplication):
1286
829
  Dynamically sets the tooltip for the tray icon.
1287
830
  Can be called while the application is running, and changes are applied immediately.
1288
831
 
1289
- :param message: New tooltip message
832
+ Parameters
833
+ ----------
834
+ message : str
835
+ New tooltip message
836
+
837
+ Examples
838
+ --------
839
+ >>> app = Pyloid(app_name="Pyloid-App")
840
+ >>> app.set_tray_tooltip("Pyloid is running")
1290
841
  """
1291
842
  if not hasattr(self, "tray"):
1292
843
  self._init_tray()
@@ -1296,7 +847,17 @@ class Pyloid(QApplication):
1296
847
  """
1297
848
  Sets the callback function to be called when a notification is clicked.
1298
849
 
1299
- :param callback: Callback function to be called when a notification is clicked
850
+ Parameters
851
+ ----------
852
+ callback : function
853
+ Callback function to be called when a notification is clicked
854
+
855
+ Examples
856
+ --------
857
+ >>> app = Pyloid(app_name="Pyloid-App")
858
+ >>> def on_notification_click():
859
+ >>> print("Notification clicked")
860
+ >>> app.set_notification_callback(on_notification_click)
1300
861
  """
1301
862
  if not hasattr(self, "tray"):
1302
863
  self._init_tray()
@@ -1307,9 +868,19 @@ class Pyloid(QApplication):
1307
868
  ###########################################################################################
1308
869
  def get_all_monitors(self) -> List[Monitor]:
1309
870
  """
1310
- Returns a list of information for all connected monitors.
871
+ Returns information about all connected monitors.
872
+
873
+ Returns
874
+ -------
875
+ list of Monitor
876
+ List containing monitor information
1311
877
 
1312
- :return: List containing monitor information
878
+ Examples
879
+ --------
880
+ >>> app = Pyloid(app_name="Pyloid-App")
881
+ >>> monitors = app.get_all_monitors()
882
+ >>> for monitor in monitors:
883
+ >>> print(monitor.info())
1313
884
  """
1314
885
  monitors = [
1315
886
  Monitor(index, screen) for index, screen in enumerate(self.screens())
@@ -1318,9 +889,18 @@ class Pyloid(QApplication):
1318
889
 
1319
890
  def get_primary_monitor(self) -> Monitor:
1320
891
  """
1321
- Returns information for the primary monitor.
892
+ Returns information about the primary monitor.
1322
893
 
1323
- :return: Primary monitor information
894
+ Returns
895
+ -------
896
+ Monitor
897
+ Primary monitor information
898
+
899
+ Examples
900
+ --------
901
+ >>> app = Pyloid(app_name="Pyloid-App")
902
+ >>> primary_monitor = app.get_primary_monitor()
903
+ >>> print(primary_monitor.info())
1324
904
  """
1325
905
  primary_monitor = self.screens()[0]
1326
906
  return Monitor(0, primary_monitor)
@@ -1328,11 +908,21 @@ class Pyloid(QApplication):
1328
908
  ###########################################################################################
1329
909
  # Clipboard
1330
910
  ###########################################################################################
1331
- def copy_to_clipboard(self, text):
911
+ def set_clipboard_text(self, text):
1332
912
  """
1333
913
  Copies text to the clipboard.
1334
914
 
1335
- :param text: Text to be copied
915
+ This function copies the given text to the clipboard. The text copied to the clipboard can be pasted into other applications.
916
+
917
+ Parameters
918
+ ----------
919
+ text : str
920
+ Text to copy to the clipboard
921
+
922
+ Examples
923
+ --------
924
+ >>> app = Pyloid(app_name="Pyloid-App")
925
+ >>> app.set_clipboard_text("Hello, World!")
1336
926
  """
1337
927
  self.clipboard_class.setText(text, QClipboard.Clipboard)
1338
928
 
@@ -1340,7 +930,19 @@ class Pyloid(QApplication):
1340
930
  """
1341
931
  Retrieves text from the clipboard.
1342
932
 
1343
- :return: Text from the clipboard
933
+ This function returns the text stored in the clipboard. If there is no text in the clipboard, it may return an empty string.
934
+
935
+ Returns
936
+ -------
937
+ str
938
+ Text stored in the clipboard
939
+
940
+ Examples
941
+ --------
942
+ >>> app = Pyloid(app_name="Pyloid-App")
943
+ >>> text = app.get_clipboard_text()
944
+ >>> print(text)
945
+ Hello, World!
1344
946
  """
1345
947
  return self.clipboard_class.text()
1346
948
 
@@ -1348,7 +950,17 @@ class Pyloid(QApplication):
1348
950
  """
1349
951
  Copies an image to the clipboard.
1350
952
 
1351
- :param image: Path to the image to be copied
953
+ This function copies the given image file to the clipboard. The image copied to the clipboard can be pasted into other applications.
954
+
955
+ Parameters
956
+ ----------
957
+ image : Union[str, bytes, os.PathLike]
958
+ Path to the image file to copy to the clipboard
959
+
960
+ Examples
961
+ --------
962
+ >>> app = Pyloid(app_name="Pyloid-App")
963
+ >>> app.set_clipboard_image("/path/to/image.png")
1352
964
  """
1353
965
  self.clipboard_class.setImage(QImage(image), QClipboard.Clipboard)
1354
966
 
@@ -1356,20 +968,46 @@ class Pyloid(QApplication):
1356
968
  """
1357
969
  Retrieves an image from the clipboard.
1358
970
 
1359
- :return: QImage object from the clipboard (None if no image)
971
+ This function returns the image stored in the clipboard. If there is no image in the clipboard, it may return None.
972
+
973
+ Returns
974
+ -------
975
+ QImage
976
+ QImage object stored in the clipboard (None if no image)
977
+
978
+ Examples
979
+ --------
980
+ >>> app = Pyloid(app_name="Pyloid-App")
981
+ >>> image = app.get_clipboard_image()
982
+ >>> if image is not None:
983
+ >>> image.save("/path/to/save/image.png")
1360
984
  """
1361
985
  return self.clipboard_class.image()
1362
986
 
1363
987
  ###########################################################################################
1364
- # Atostart
988
+ # Autostart
1365
989
  ###########################################################################################
1366
990
  def set_auto_start(self, enable: bool):
1367
991
  """
1368
- Sets the application to start automatically with the system. (set_auto_start(True) only works in production)
1369
- True only works in production.
1370
- False works in both environments.
992
+ Sets the application to start automatically at system startup. (set_auto_start(True) only works in production environment)
993
+ True only works in production environment.
994
+ False works in all environments.
995
+
996
+ Parameters
997
+ ----------
998
+ enable : bool
999
+ True to enable auto start, False to disable
1000
+
1001
+ Returns
1002
+ -------
1003
+ bool or None
1004
+ True if auto start is successfully set, False if disabled, None if trying to enable in non-production environment
1371
1005
 
1372
- :param enable: True to enable auto-start, False to disable
1006
+ Examples
1007
+ --------
1008
+ >>> app = Pyloid(app_name="Pyloid-App")
1009
+ >>> app.set_auto_start(True)
1010
+ True
1373
1011
  """
1374
1012
  if not enable:
1375
1013
  self.auto_start.set_auto_start(False)
@@ -1387,11 +1025,20 @@ class Pyloid(QApplication):
1387
1025
 
1388
1026
  def is_auto_start(self):
1389
1027
  """
1390
- Checks if the application is set to start automatically with the system.
1028
+ Checks if the application is set to start automatically at system startup.
1391
1029
 
1392
- :return: True if auto-start is enabled, False otherwise
1393
- """
1030
+ Returns
1031
+ -------
1032
+ bool
1033
+ True if auto start is enabled, False otherwise
1394
1034
 
1035
+ Examples
1036
+ --------
1037
+ >>> app = Pyloid(app_name="Pyloid-App")
1038
+ >>> auto_start_enabled = app.is_auto_start()
1039
+ >>> print(auto_start_enabled)
1040
+ True
1041
+ """
1395
1042
  return self.auto_start.is_auto_start()
1396
1043
 
1397
1044
  ###########################################################################################
@@ -1401,8 +1048,23 @@ class Pyloid(QApplication):
1401
1048
  """
1402
1049
  Adds a file to the watch list.
1403
1050
 
1404
- :param file_path: Path of the file to watch
1405
- :return: True if the file was successfully added to the watch list, False otherwise
1051
+ This function adds the specified file to the watch list. When the file is changed, the set callback function is called.
1052
+
1053
+ Parameters
1054
+ ----------
1055
+ file_path : str
1056
+ Path to the file to watch
1057
+
1058
+ Returns
1059
+ -------
1060
+ bool
1061
+ True if the file is successfully added to the watch list, False otherwise
1062
+
1063
+ Examples
1064
+ --------
1065
+ >>> app = Pyloid(app_name="Pyloid-App")
1066
+ >>> app.watch_file("/path/to/file.txt")
1067
+ True
1406
1068
  """
1407
1069
  return self.file_watcher.add_path(file_path)
1408
1070
 
@@ -1410,8 +1072,23 @@ class Pyloid(QApplication):
1410
1072
  """
1411
1073
  Adds a directory to the watch list.
1412
1074
 
1413
- :param dir_path: Path of the directory to watch
1414
- :return: True if the directory was successfully added to the watch list, False otherwise
1075
+ This function adds the specified directory to the watch list. When a file in the directory is changed, the set callback function is called.
1076
+
1077
+ Parameters
1078
+ ----------
1079
+ dir_path : str
1080
+ Path to the directory to watch
1081
+
1082
+ Returns
1083
+ -------
1084
+ bool
1085
+ True if the directory is successfully added to the watch list, False otherwise
1086
+
1087
+ Examples
1088
+ --------
1089
+ >>> app = Pyloid(app_name="Pyloid-App")
1090
+ >>> app.watch_directory("/path/to/directory")
1091
+ True
1415
1092
  """
1416
1093
  return self.file_watcher.add_path(dir_path)
1417
1094
 
@@ -1419,8 +1096,23 @@ class Pyloid(QApplication):
1419
1096
  """
1420
1097
  Removes a file or directory from the watch list.
1421
1098
 
1422
- :param path: Path of the file or directory to stop watching
1423
- :return: True if the path was successfully removed from the watch list, False otherwise
1099
+ This function removes the specified file or directory from the watch list.
1100
+
1101
+ Parameters
1102
+ ----------
1103
+ path : str
1104
+ Path to the file or directory to stop watching
1105
+
1106
+ Returns
1107
+ -------
1108
+ bool
1109
+ True if the path is successfully removed from the watch list, False otherwise
1110
+
1111
+ Examples
1112
+ --------
1113
+ >>> app = Pyloid(app_name="Pyloid-App")
1114
+ >>> app.stop_watching("/path/to/file_or_directory")
1115
+ True
1424
1116
  """
1425
1117
  return self.file_watcher.remove_path(path)
1426
1118
 
@@ -1428,7 +1120,18 @@ class Pyloid(QApplication):
1428
1120
  """
1429
1121
  Returns all currently watched paths.
1430
1122
 
1431
- :return: List of all watched paths
1123
+ This function returns the paths of all files and directories currently being watched.
1124
+
1125
+ Returns
1126
+ -------
1127
+ List[str]
1128
+ List of all watched paths
1129
+
1130
+ Examples
1131
+ --------
1132
+ >>> app = Pyloid(app_name="Pyloid-App")
1133
+ >>> app.get_watched_paths()
1134
+ ['/path/to/file1.txt', '/path/to/directory']
1432
1135
  """
1433
1136
  return self.file_watcher.get_watched_paths()
1434
1137
 
@@ -1436,7 +1139,18 @@ class Pyloid(QApplication):
1436
1139
  """
1437
1140
  Returns all currently watched files.
1438
1141
 
1439
- :return: List of all watched files
1142
+ This function returns the paths of all files currently being watched.
1143
+
1144
+ Returns
1145
+ -------
1146
+ List[str]
1147
+ List of all watched files
1148
+
1149
+ Examples
1150
+ --------
1151
+ >>> app = Pyloid(app_name="Pyloid-App")
1152
+ >>> app.get_watched_files()
1153
+ ['/path/to/file1.txt', '/path/to/file2.txt']
1440
1154
  """
1441
1155
  return self.file_watcher.get_watched_files()
1442
1156
 
@@ -1444,7 +1158,18 @@ class Pyloid(QApplication):
1444
1158
  """
1445
1159
  Returns all currently watched directories.
1446
1160
 
1447
- :return: List of all watched directories
1161
+ This function returns the paths of all directories currently being watched.
1162
+
1163
+ Returns
1164
+ -------
1165
+ List[str]
1166
+ List of all watched directories
1167
+
1168
+ Examples
1169
+ --------
1170
+ >>> app = Pyloid(app_name="Pyloid-App")
1171
+ >>> app.get_watched_directories()
1172
+ ['/path/to/directory1', '/path/to/directory2']
1448
1173
  """
1449
1174
  return self.file_watcher.get_watched_directories()
1450
1175
 
@@ -1452,22 +1177,66 @@ class Pyloid(QApplication):
1452
1177
  """
1453
1178
  Removes all paths from the watch list.
1454
1179
 
1455
- :return: None
1180
+ This function removes the paths of all files and directories from the watch list.
1181
+
1182
+ Returns
1183
+ -------
1184
+ None
1185
+
1186
+ Examples
1187
+ --------
1188
+ >>> app = Pyloid(app_name="Pyloid-App")
1189
+ >>> app.remove_all_watched_paths()
1456
1190
  """
1457
1191
  self.file_watcher.remove_all_paths()
1458
1192
 
1459
1193
  def set_file_change_callback(self, callback: Callable[[str], None]) -> None:
1460
1194
  """
1461
- Sets the callback function to be called when a file changes.
1195
+ Sets the callback function to be called when a file is changed.
1196
+
1197
+ This function sets the callback function to be called when a file is changed.
1198
+
1199
+ Parameters
1200
+ ----------
1201
+ callback : Callable[[str], None]
1202
+ Function to be called when a file is changed
1203
+
1204
+ Returns
1205
+ -------
1206
+ None
1462
1207
 
1463
- :param callback: Function to be called when a file changes
1208
+ Examples
1209
+ --------
1210
+ >>> def on_file_change(file_path):
1211
+ >>> print(f"File changed: {file_path}")
1212
+ >>>
1213
+ >>> app = Pyloid(app_name="Pyloid-App")
1214
+ >>> app.set_file_change_callback(on_file_change)
1464
1215
  """
1465
1216
  self.file_watcher.file_changed.connect(callback)
1466
1217
 
1467
1218
  def set_directory_change_callback(self, callback: Callable[[str], None]) -> None:
1468
1219
  """
1469
- Sets the callback function to be called when a directory changes.
1220
+ Sets the callback function to be called when a directory is changed.
1470
1221
 
1471
- :param callback: Function to be called when a directory changes
1222
+ This function sets the callback function to be called when a directory is changed.
1223
+
1224
+ Parameters
1225
+ ----------
1226
+ callback : Callable[[str], None]
1227
+ Function to be called when a directory is changed
1228
+
1229
+ Returns
1230
+ -------
1231
+ None
1232
+
1233
+ Examples
1234
+ --------
1235
+ >>> def on_directory_change(dir_path):
1236
+ >>> print(f"Directory changed: {dir_path}")
1237
+ >>>
1238
+ >>> app = Pyloid(app_name="Pyloid-App")
1239
+ >>> app.set_directory_change_callback(on_directory_change)
1472
1240
  """
1473
1241
  self.file_watcher.directory_changed.connect(callback)
1242
+