pyloid 0.13.0__py3-none-any.whl → 0.14.0__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.
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
+