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.
@@ -0,0 +1,1190 @@
1
+ import sys
2
+ import os
3
+ from PySide6.QtWidgets import (
4
+ QMainWindow,
5
+ )
6
+ from PySide6.QtWebEngineWidgets import QWebEngineView
7
+ from PySide6.QtWebChannel import QWebChannel
8
+ from PySide6.QtGui import (
9
+ QKeySequence,
10
+ QShortcut,
11
+ QCursor,
12
+ )
13
+ from PySide6.QtCore import Qt, QPoint, QUrl, QEvent
14
+ from PySide6.QtWebEngineCore import QWebEnginePage, QWebEngineSettings
15
+ from .api import PyloidAPI
16
+ import uuid
17
+ from typing import List, Optional, Dict, Callable
18
+ import json
19
+ from PySide6.QtWidgets import (
20
+ QWidget,
21
+ QVBoxLayout,
22
+ )
23
+ from .custom.titlebar import CustomTitleBar
24
+ from .js_api.window_api import WindowAPI
25
+
26
+
27
+
28
+ # 어차피 load 부분에만 쓰이니까 나중에 분리해서 load 위에서 선언하자.
29
+ class CustomWebEngineView(QWebEngineView):
30
+ def __init__(self, parent=None):
31
+ super().__init__(parent._window)
32
+ self.parent = parent
33
+ self.drag_relative_position = None
34
+ self.is_dragging = False
35
+ self.is_resizing = False
36
+ self.resize_start_pos = None
37
+ self.resize_direction = None
38
+ self.screen_geometry = self.screen().availableGeometry()
39
+ self.is_resizing_enabled = True
40
+
41
+ def mouse_press_event(self, event):
42
+ if event.button() == Qt.LeftButton:
43
+ self.drag_relative_position = event.pos()
44
+ if not self.parent.frame and self.is_resizing_enabled:
45
+ self.resize_direction = self.get_resize_direction(event.pos())
46
+ if self.resize_direction:
47
+ self.is_resizing = True
48
+ self.resize_start_pos = event.globalPos()
49
+
50
+ def start_system_drag(self):
51
+ self.is_dragging = True
52
+
53
+ def mouse_move_event(self, event):
54
+ if self.is_resizing and self.is_resizing_enabled:
55
+ self.resize_window(event.globalPos())
56
+ elif not self.parent.frame and self.is_dragging:
57
+ # 현재 마우스 위치를 전역 좌표로 가져옵니다
58
+ current_global_pos = event.globalPos()
59
+
60
+ # 화면 경계를 계산합니다
61
+ left_boundary = self.screen_geometry.left()
62
+ right_boundary = self.screen_geometry.right()
63
+ top_boundary = self.screen_geometry.top()
64
+ bottom_boundary = self.screen_geometry.bottom()
65
+
66
+ # 마우스 커서 위치를 제한합니다
67
+ new_cursor_pos = QPoint(
68
+ max(left_boundary, min(current_global_pos.x(), right_boundary)),
69
+ max(top_boundary, min(current_global_pos.y(), bottom_boundary)),
70
+ )
71
+
72
+ # 마우스 커서를 새 위치로 이동합니다
73
+ QCursor.setPos(new_cursor_pos)
74
+
75
+ # 창의 새 위치를 계산합니다
76
+ new_window_pos = new_cursor_pos - self.drag_relative_position
77
+
78
+ # 창을 새 위치로 이동합니다
79
+ self.parent._window.move(new_window_pos)
80
+ else:
81
+ # Change cursor based on resize direction
82
+ resize_direction = self.get_resize_direction(event.pos())
83
+ if resize_direction and self.is_resizing_enabled:
84
+ self.set_cursor_for_resize_direction(resize_direction)
85
+ else:
86
+ self.unsetCursor()
87
+
88
+ def mouse_release_event(self, event):
89
+ if event.button() == Qt.LeftButton:
90
+ self.is_dragging = False
91
+ self.is_resizing = False
92
+ self.resize_direction = None
93
+ self.unsetCursor()
94
+
95
+ def eventFilter(self, source, event):
96
+ if self.focusProxy() is source:
97
+ if event.type() == QEvent.MouseButtonPress:
98
+ self.mouse_press_event(event)
99
+ elif event.type() == QEvent.MouseMove:
100
+ self.mouse_move_event(event)
101
+ elif event.type() == QEvent.MouseButtonRelease:
102
+ self.mouse_release_event(event)
103
+ return super().eventFilter(source, event)
104
+
105
+ def get_resize_direction(self, pos):
106
+ if not self.parent.frame and self.is_resizing_enabled: # Check if frame is not present and resizing is enabled
107
+ margin = 5 # Margin in pixels to detect edge
108
+ rect = self.rect()
109
+ direction = None
110
+
111
+ if pos.x() <= margin:
112
+ direction = 'left'
113
+ elif pos.x() >= rect.width() - margin:
114
+ direction = 'right'
115
+
116
+ if pos.y() <= margin:
117
+ direction = 'top' if direction is None else direction + '-top'
118
+ elif pos.y() >= rect.height() - margin:
119
+ direction = 'bottom' if direction is None else direction + '-bottom'
120
+
121
+ return direction
122
+ return None
123
+
124
+ def set_cursor_for_resize_direction(self, direction):
125
+ if not self.parent.frame and direction and self.is_resizing_enabled: # Check if frame is not present and resizing is enabled
126
+ if direction in ['left', 'right']:
127
+ self.setCursor(Qt.SizeHorCursor)
128
+ elif direction in ['top', 'bottom']:
129
+ self.setCursor(Qt.SizeVerCursor)
130
+ elif direction in ['left-top', 'right-bottom']:
131
+ self.setCursor(Qt.SizeFDiagCursor)
132
+ elif direction in ['right-top', 'left-bottom']:
133
+ self.setCursor(Qt.SizeBDiagCursor)
134
+
135
+ def resize_window(self, global_pos):
136
+ 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
137
+ delta = global_pos - self.resize_start_pos
138
+ new_geometry = self.parent._window.geometry()
139
+
140
+ if 'left' in self.resize_direction:
141
+ new_geometry.setLeft(new_geometry.left() + delta.x())
142
+ if 'right' in self.resize_direction:
143
+ new_geometry.setRight(new_geometry.right() + delta.x())
144
+ if 'top' in self.resize_direction:
145
+ new_geometry.setTop(new_geometry.top() + delta.y())
146
+ if 'bottom' in self.resize_direction:
147
+ new_geometry.setBottom(new_geometry.bottom() + delta.y())
148
+
149
+ self.parent._window.setGeometry(new_geometry)
150
+ self.resize_start_pos = global_pos
151
+
152
+
153
+ class BrowserWindow:
154
+ def __init__(
155
+ self,
156
+ app,
157
+ title: str = "pyloid app",
158
+ width: int = 800,
159
+ height: int = 600,
160
+ x: int = 200,
161
+ y: int = 200,
162
+ frame: bool = True,
163
+ context_menu: bool = False,
164
+ dev_tools: bool = False,
165
+ js_apis: List[PyloidAPI] = [],
166
+ ):
167
+ ###########################################################################################
168
+ self.id = str(uuid.uuid4()) # Generate unique ID
169
+ self._window = QMainWindow()
170
+ self.web_view = CustomWebEngineView(self)
171
+
172
+ self._window.closeEvent = self.closeEvent # Override closeEvent method
173
+ ###########################################################################################
174
+ self.app = app
175
+ self.title = title
176
+ self.width = width
177
+ self.height = height
178
+ self.x = x
179
+ self.y = y
180
+ self.frame = frame
181
+ self.context_menu = context_menu
182
+ self.dev_tools = dev_tools
183
+ self.js_apis = [WindowAPI(self.id, self.app)]
184
+ for js_api in js_apis:
185
+ self.js_apis.append(js_api)
186
+ self.shortcuts = {}
187
+ ###########################################################################################
188
+
189
+ def _set_custom_frame(
190
+ self,
191
+ use_custom: bool,
192
+ title: str = "Custom Title",
193
+ bg_color: str = "darkblue",
194
+ text_color: str = "white",
195
+ icon_path: str = None,
196
+ ):
197
+ """Sets or removes the custom frame."""
198
+ if use_custom:
199
+ self._window.setWindowFlags(Qt.FramelessWindowHint)
200
+ self.custom_title_bar = CustomTitleBar(self._window)
201
+ self.custom_title_bar.set_style(bg_color, text_color)
202
+ self.custom_title_bar.set_title(title)
203
+
204
+ if icon_path:
205
+ self.custom_title_bar.set_icon(icon_path)
206
+
207
+ layout = QVBoxLayout()
208
+ layout.setContentsMargins(0, 0, 0, 0)
209
+ layout.setSpacing(0)
210
+ layout.addWidget(self.custom_title_bar)
211
+ layout.addWidget(self.web_view)
212
+
213
+ central_widget = QWidget()
214
+ central_widget.setLayout(layout)
215
+ self._window.setCentralWidget(central_widget)
216
+
217
+ # Add properties for window movement
218
+ self._window.moving = False
219
+ self._window.offset = QPoint()
220
+ else:
221
+ self._window.setWindowFlags(Qt.Window)
222
+ self._window.setCentralWidget(self.web_view)
223
+ self.custom_title_bar = None
224
+
225
+ self._window.show()
226
+
227
+ def _load(self):
228
+ self.set_title(self.title)
229
+
230
+ self.set_size(self.width, self.height)
231
+ self.set_position(self.x, self.y)
232
+
233
+ # allow local file access to remote urls
234
+ self.web_view.settings().setAttribute(
235
+ QWebEngineSettings.LocalContentCanAccessRemoteUrls, True
236
+ )
237
+
238
+ # Set icon
239
+ if self.app.icon:
240
+ self._window.setWindowIcon(self.app.icon)
241
+ else:
242
+ print("Icon is not set.")
243
+
244
+ # Set Windows taskbar icon
245
+ if sys.platform == "win32":
246
+ import ctypes
247
+
248
+ myappid = "mycompany.myproduct.subproduct.version"
249
+ ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
250
+
251
+ # Remove title bar and borders (if needed)
252
+ if not self.frame:
253
+ self._window.setWindowFlags(Qt.FramelessWindowHint)
254
+
255
+ # Disable default context menu
256
+ if not self.context_menu:
257
+ self.web_view.setContextMenuPolicy(Qt.NoContextMenu)
258
+
259
+ # Set up QWebChannel
260
+ self.channel = QWebChannel()
261
+
262
+ # Register additional JS APIs
263
+ if self.js_apis:
264
+ for js_api in self.js_apis:
265
+ self.channel.registerObject(js_api.__class__.__name__, js_api)
266
+
267
+ self.web_view.page().setWebChannel(self.channel)
268
+
269
+ # Connect pylonjs bridge
270
+ self.web_view.loadFinished.connect(self._on_load_finished)
271
+
272
+ # Add QWebEngineView to main window
273
+ self._window.setCentralWidget(self.web_view)
274
+
275
+ # Set F12 shortcut
276
+ self.set_dev_tools(self.dev_tools)
277
+
278
+ def _on_load_finished(self, ok):
279
+ """Handles the event when the web page finishes loading."""
280
+ if ok and self.js_apis:
281
+ js_code = """
282
+ if (typeof QWebChannel !== 'undefined') {
283
+ new QWebChannel(qt.webChannelTransport, function (channel) {
284
+ window.pyloid = {
285
+ EventAPI: {
286
+ listen: function(eventName, callback) {
287
+ document.addEventListener(eventName, function(event) {
288
+ let eventData;
289
+ try {
290
+ eventData = JSON.parse(event.detail);
291
+ } catch (e) {
292
+ eventData = event.detail;
293
+ }
294
+ callback(eventData);
295
+ });
296
+ },
297
+ unlisten: function(eventName) {
298
+ document.removeEventListener(eventName);
299
+ }
300
+ }
301
+ };
302
+ console.log('pyloid.EventAPI object initialized:', window.pyloid.EventAPI);
303
+
304
+ %s
305
+
306
+ document.addEventListener('mousedown', function (e) {
307
+ if (e.target.hasAttribute('data-pyloid-drag-region')) {
308
+ window.pyloid.WindowAPI.startSystemDrag();
309
+ }
310
+ });
311
+
312
+ // Dispatch a custom event to signal that the initialization is ready
313
+ const event = new CustomEvent('pyloidReady');
314
+ document.dispatchEvent(event);
315
+ });
316
+ } else {
317
+ console.error('QWebChannel is not defined.');
318
+ }
319
+ """
320
+ js_api_init = "\n".join(
321
+ [
322
+ f"window.pyloid['{js_api.__class__.__name__}'] = channel.objects['{js_api.__class__.__name__}'];\n"
323
+ f"console.log('pyloid.{js_api.__class__.__name__} object initialized:', window.pyloid['{js_api.__class__.__name__}']);"
324
+ for js_api in self.js_apis
325
+ ]
326
+ )
327
+ self.web_view.page().runJavaScript(js_code % js_api_init)
328
+ else:
329
+ pass
330
+
331
+ ###########################################################################################
332
+ # Load
333
+ ###########################################################################################
334
+ def load_file(self, file_path):
335
+ """
336
+ Loads a local HTML file into the web view.
337
+
338
+ Parameters
339
+ ----------
340
+ file_path : str
341
+ The path to the local HTML file to be loaded.
342
+
343
+ Examples
344
+ --------
345
+ >>> app = Pyloid(app_name="Pyloid-App")
346
+ >>> window = app.create_window("pyloid-window")
347
+ >>> window.load_file('/path/to/local/file.html')
348
+ >>> window.show()
349
+ """
350
+ self._load()
351
+ file_path = os.path.abspath(file_path) # absolute path
352
+ self.web_view.setUrl(QUrl.fromLocalFile(file_path))
353
+ self.web_view.focusProxy().installEventFilter(self.web_view)
354
+
355
+ def load_url(self, url):
356
+ """
357
+ Sets the URL of the window.
358
+
359
+ Parameters
360
+ ----------
361
+ url : str
362
+ The URL to be loaded in the web view.
363
+
364
+ Examples
365
+ --------
366
+ >>> app = Pyloid(app_name="Pyloid-App")
367
+ >>> window = app.create_window("pyloid-window")
368
+ >>> window.load_url('https://www.example.com')
369
+ >>> window.show()
370
+ """
371
+ self._load()
372
+ self.web_view.setUrl(QUrl(url))
373
+ self.web_view.focusProxy().installEventFilter(self.web_view)
374
+
375
+ ###########################################################################################
376
+ # Set Parameters
377
+ ###########################################################################################
378
+ def set_title(self, title: str):
379
+ """
380
+ Sets the title of the window.
381
+
382
+ Parameters
383
+ ----------
384
+ title : str
385
+ The title to be set for the window.
386
+
387
+ Examples
388
+ --------
389
+ >>> app = Pyloid(app_name="Pyloid-App")
390
+ >>> window = app.create_window("pyloid-window")
391
+ >>> window.set_title('My Window Title')
392
+ """
393
+ self.title = title
394
+ self._window.setWindowTitle(self.title)
395
+
396
+ def set_size(self, width: int, height: int):
397
+ """
398
+ Sets the size of the window.
399
+
400
+ Parameters
401
+ ----------
402
+ width : int
403
+ The width of the window.
404
+ height : int
405
+ The height of the window.
406
+
407
+ Examples
408
+ --------
409
+ >>> app = Pyloid(app_name="Pyloid-App")
410
+ >>> window = app.create_window("pyloid-window")
411
+ >>> window.set_size(800, 600)
412
+ """
413
+ self.width = width
414
+ self.height = height
415
+ self._window.setGeometry(self.x, self.y, self.width, self.height)
416
+
417
+ def set_position(self, x: int, y: int):
418
+ """
419
+ Sets the position of the window.
420
+
421
+ Parameters
422
+ ----------
423
+ x : int
424
+ The x-coordinate of the window's position.
425
+ y : int
426
+ The y-coordinate of the window's position.
427
+
428
+ Examples
429
+ --------
430
+ >>> app = Pyloid(app_name="Pyloid-App")
431
+ >>> window = app.create_window("pyloid-window")
432
+ >>> window.set_position(100, 100)
433
+ """
434
+ self.x = x
435
+ self.y = y
436
+ self._window.setGeometry(self.x, self.y, self.width, self.height)
437
+
438
+ def set_frame(self, frame: bool):
439
+ """
440
+ Sets the frame of the window.
441
+
442
+ Parameters
443
+ ----------
444
+ frame : bool
445
+ If True, the window will have a frame. If False, the window will be frameless.
446
+
447
+ Examples
448
+ --------
449
+ >>> app = Pyloid(app_name="Pyloid-App")
450
+ >>> window = app.create_window("pyloid-window")
451
+ >>> window.set_frame(True)
452
+ >>> window.set_frame(False)
453
+ """
454
+ self.frame = frame
455
+ was_visible = self._window.isVisible()
456
+ if self.frame:
457
+ self._window.setWindowFlags(Qt.Window)
458
+ else:
459
+ self._window.setWindowFlags(Qt.FramelessWindowHint)
460
+ if was_visible:
461
+ self._window.show()
462
+
463
+ def set_context_menu(self, context_menu: bool):
464
+ """
465
+ Sets the context menu of the window.
466
+
467
+ Parameters
468
+ ----------
469
+ context_menu : bool
470
+ If True, the context menu will be disabled. If False, the default context menu will be enabled.
471
+
472
+ Examples
473
+ --------
474
+ >>> app = Pyloid(app_name="Pyloid-App")
475
+ >>> window = app.create_window("pyloid-window")
476
+ >>> window.set_context_menu(True)
477
+ >>> window.set_context_menu(False)
478
+ """
479
+ self.context_menu = context_menu
480
+ if self.context_menu:
481
+ self.web_view.setContextMenuPolicy(Qt.NoContextMenu)
482
+ else:
483
+ self.web_view.setContextMenuPolicy(Qt.DefaultContextMenu)
484
+
485
+ def set_dev_tools(self, enable: bool):
486
+ """
487
+ Sets the developer tools of the window.
488
+
489
+ If enabled, the developer tools can be opened using the F12 key.
490
+
491
+ Parameters
492
+ ----------
493
+ enable : bool
494
+ If True, the developer tools will be enabled. If False, the developer tools will be disabled.
495
+
496
+ Examples
497
+ --------
498
+ >>> app = Pyloid(app_name="Pyloid-App")
499
+ >>> window = app.create_window("pyloid-window")
500
+ >>> window.set_dev_tools(True)
501
+ >>> window.set_dev_tools(False)
502
+ """
503
+ self.dev_tools = enable
504
+ if self.dev_tools:
505
+ self.add_shortcut("F12", self.open_dev_tools)
506
+ else:
507
+ self.remove_shortcut("F12")
508
+
509
+ def open_dev_tools(self):
510
+ """
511
+ Opens the developer tools window.
512
+
513
+ Examples
514
+ --------
515
+ >>> app = Pyloid(app_name="Pyloid-App")
516
+ >>> window = app.create_window("pyloid-window")
517
+ >>> window.open_dev_tools()
518
+ """
519
+ self.web_view.page().setDevToolsPage(QWebEnginePage(self.web_view.page()))
520
+ self.dev_tools_window = QMainWindow(self._window)
521
+ dev_tools_view = QWebEngineView(self.dev_tools_window)
522
+ dev_tools_view.setPage(self.web_view.page().devToolsPage())
523
+ self.dev_tools_window.setCentralWidget(dev_tools_view)
524
+ self.dev_tools_window.resize(800, 600)
525
+ self.dev_tools_window.show()
526
+
527
+ # Add this line to handle dev tools window closure
528
+ self.dev_tools_window.closeEvent = lambda event: setattr(
529
+ self, "dev_tools_window", None
530
+ )
531
+
532
+ def closeEvent(self, event):
533
+ """Handles the event when the window is closed."""
534
+ # Close developer tools if open
535
+ if hasattr(self, "dev_tools_window") and self.dev_tools_window:
536
+ self.dev_tools_window.close()
537
+ self.dev_tools_window = None
538
+
539
+ # Solve memory leak issue with web view engine
540
+ self.web_view.page().deleteLater()
541
+ self.web_view.deleteLater()
542
+ self._remove_from_app_windows()
543
+ event.accept() # Accept the event (allow the window to close)
544
+
545
+ def _remove_from_app_windows(self):
546
+ """Removes the window from the app's window list."""
547
+ if self in self.app.windows:
548
+ self.app.windows.remove(self)
549
+ if not self.app.windows:
550
+ self.app.quit() # Quit the app if all windows are closed
551
+
552
+ ###########################################################################################
553
+ # Window management (no ID required)
554
+ ###########################################################################################
555
+ def hide(self):
556
+ """
557
+ Hides the window.
558
+
559
+ Examples
560
+ --------
561
+ >>> app = Pyloid(app_name="Pyloid-App")
562
+ >>> window = app.create_window("pyloid-window")
563
+ >>> window.hide()
564
+ """
565
+ self._window.hide()
566
+
567
+ def show(self):
568
+ """
569
+ Shows the window.
570
+
571
+ Examples
572
+ --------
573
+ >>> app = Pyloid(app_name="Pyloid-App")
574
+ >>> window = app.create_window("pyloid-window")
575
+ >>> window.show()
576
+ """
577
+ self._window.show()
578
+
579
+ def focus(self):
580
+ """
581
+ Focuses the window.
582
+
583
+ Examples
584
+ --------
585
+ >>> app = Pyloid(app_name="Pyloid-App")
586
+ >>> window = app.create_window("pyloid-window")
587
+ >>> window.focus()
588
+ """
589
+ self._window.activateWindow()
590
+ self._window.raise_()
591
+ self._window.setWindowState(
592
+ self._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive
593
+ )
594
+
595
+ def show_and_focus(self):
596
+ """
597
+ Shows and focuses the window.
598
+
599
+ Examples
600
+ --------
601
+ >>> app = Pyloid(app_name="Pyloid-App")
602
+ >>> window = app.create_window("pyloid-window")
603
+ >>> window.show_and_focus()
604
+ """
605
+ self._window.show()
606
+ self._window.activateWindow()
607
+ self._window.raise_()
608
+ self._window.setWindowState(
609
+ self._window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive
610
+ )
611
+
612
+ def close(self):
613
+ """
614
+ Closes the window.
615
+
616
+ Examples
617
+ --------
618
+ >>> app = Pyloid(app_name="Pyloid-App")
619
+ >>> window = app.create_window("pyloid-window")
620
+ >>> window.close()
621
+ """
622
+ self._window.close()
623
+
624
+ def toggle_fullscreen(self):
625
+ """
626
+ Toggles the fullscreen mode of the window.
627
+
628
+ Examples
629
+ --------
630
+ >>> app = Pyloid(app_name="Pyloid-App")
631
+ >>> window = app.create_window("pyloid-window")
632
+ >>> window.toggle_fullscreen()
633
+ """
634
+ if self._window.isFullScreen():
635
+ self._window.showNormal()
636
+ else:
637
+ self._window.showFullScreen()
638
+
639
+ def minimize(self):
640
+ """
641
+ Minimizes the window.
642
+
643
+ Examples
644
+ --------
645
+ >>> app = Pyloid(app_name="Pyloid-App")
646
+ >>> window = app.create_window("pyloid-window")
647
+ >>> window.minimize()
648
+ """
649
+ self._window.showMinimized()
650
+
651
+ def maximize(self):
652
+ """
653
+ Maximizes the window.
654
+
655
+ Examples
656
+ --------
657
+ >>> app = Pyloid(app_name="Pyloid-App")
658
+ >>> window = app.create_window("pyloid-window")
659
+ >>> window.maximize()
660
+ """
661
+ self._window.showMaximized()
662
+
663
+ def unmaximize(self):
664
+ """
665
+ Restores the window from maximized state.
666
+
667
+ Examples
668
+ --------
669
+ >>> app = Pyloid(app_name="Pyloid-App")
670
+ >>> window = app.create_window("pyloid-window")
671
+ >>> window.unmaximize()
672
+ """
673
+ self._window.showNormal()
674
+
675
+ def capture(self, save_path: str) -> Optional[str]:
676
+ """
677
+ Captures the current window.
678
+
679
+ Parameters
680
+ ----------
681
+ save_path : str
682
+ Path to save the captured image. If not specified, it will be saved in the current directory.
683
+
684
+ Returns
685
+ -------
686
+ Optional[str]
687
+ Returns the path of the saved image.
688
+
689
+ Examples
690
+ --------
691
+ >>> app = Pyloid(app_name="Pyloid-App")
692
+ >>> window = app.create_window("pyloid-window")
693
+ >>> save_path = window.capture("screenshot.png")
694
+ >>> print(f"Image saved at: {save_path}")
695
+ Image saved at: screenshot.png
696
+ """
697
+ try:
698
+ # Capture the window
699
+ screenshot = self._window.grab()
700
+
701
+ # Save the image
702
+ screenshot.save(save_path)
703
+ return save_path
704
+ except Exception as e:
705
+ print(f"An error occurred while capturing the window: {e}")
706
+ return None
707
+
708
+ ###########################################################################################
709
+ # Shortcut
710
+ ###########################################################################################
711
+ def add_shortcut(self, key_sequence: str, callback: Callable):
712
+ """
713
+ Adds a keyboard shortcut to the window if it does not already exist.
714
+
715
+ Parameters
716
+ ----------
717
+ key_sequence : str
718
+ Shortcut sequence (e.g., "Ctrl+C")
719
+ callback : Callable
720
+ Function to be executed when the shortcut is pressed
721
+
722
+ Returns
723
+ -------
724
+ QShortcut or None
725
+ Created QShortcut object or None if the shortcut already exists
726
+
727
+ Examples
728
+ --------
729
+ ```python
730
+ app = Pyloid(app_name="Pyloid-App")
731
+
732
+ window = app.create_window("pyloid-window")
733
+
734
+ def on_shortcut():
735
+ print("Shortcut activated!")
736
+ window.add_shortcut("Ctrl+C", on_shortcut)
737
+
738
+ app.run()
739
+ ```
740
+ """
741
+ if key_sequence in self.shortcuts:
742
+ # print(f"Shortcut {key_sequence} already exists.")
743
+ return None
744
+
745
+ shortcut = QShortcut(QKeySequence(key_sequence), self._window)
746
+ shortcut.activated.connect(callback)
747
+ self.shortcuts[key_sequence] = shortcut
748
+ return shortcut
749
+
750
+ def remove_shortcut(self, key_sequence: str):
751
+ """
752
+ Removes a keyboard shortcut from the window.
753
+
754
+ Parameters
755
+ ----------
756
+ key_sequence : str
757
+ Shortcut sequence to be removed
758
+
759
+ Examples
760
+ --------
761
+ ```python
762
+ app = Pyloid(app_name="Pyloid-App")
763
+
764
+ window = app.create_window("pyloid-window")
765
+ window.remove_shortcut("Ctrl+C")
766
+
767
+ app.run()
768
+ ```
769
+ """
770
+ if key_sequence in self.shortcuts:
771
+ shortcut = self.shortcuts.pop(key_sequence)
772
+ shortcut.setEnabled(False)
773
+ shortcut.deleteLater()
774
+
775
+ def get_all_shortcuts(self):
776
+ """
777
+ Returns all registered shortcuts in the window.
778
+
779
+ Returns
780
+ -------
781
+ dict
782
+ Dictionary of shortcut sequences and QShortcut objects
783
+
784
+ Examples
785
+ --------
786
+ ```python
787
+ app = Pyloid(app_name="Pyloid-App")
788
+
789
+ window = app.create_window("pyloid-window")
790
+ shortcuts = window.get_all_shortcuts()
791
+ print(shortcuts)
792
+
793
+ app.run()
794
+ ```
795
+ """
796
+ return self.shortcuts
797
+
798
+ ###########################################################################################
799
+ # Event (Calling the JS from Python)
800
+ ###########################################################################################
801
+ def emit(self, event_name, data: Optional[Dict] = None):
802
+ """
803
+ Emits an event to the JavaScript side.
804
+
805
+ Parameters
806
+ ----------
807
+ event_name : str
808
+ Name of the event
809
+ data : dict, optional
810
+ Data to be sent with the event (default is None)
811
+
812
+ Examples
813
+ --------
814
+ (Python)
815
+ ```python
816
+ app = Pyloid(app_name="Pyloid-App")
817
+
818
+ window = app.create_window("pyloid-window")
819
+ window.emit("customEvent", {"message": "Hello, Pyloid!"})
820
+
821
+ app.run()
822
+ ```
823
+ ---
824
+
825
+ (JavaScript)
826
+ ```javascript
827
+ document.addEventListener('customEvent', (data) => {
828
+ console.log(data.message);
829
+ });
830
+ ```
831
+ """
832
+ script = f"""
833
+ (function() {{
834
+ const eventData = {json.dumps(data)};
835
+ const customEvent = new CustomEvent('{event_name}', {{ detail: eventData }});
836
+ document.dispatchEvent(customEvent);
837
+ }})();
838
+ """
839
+ self.web_view.page().runJavaScript(script)
840
+
841
+ ###########################################################################################
842
+ # Get Properties
843
+ ###########################################################################################
844
+ def get_window_properties(self):
845
+ """
846
+ Returns the properties of the window.
847
+
848
+ Returns
849
+ -------
850
+ dict
851
+ Dictionary containing the properties of the window
852
+
853
+ Examples
854
+ --------
855
+ ```python
856
+ app = Pyloid(app_name="Pyloid-App")
857
+
858
+ window = app.create_window("pyloid-window")
859
+ properties = window.get_window_properties()
860
+ print(properties)
861
+
862
+ app.run()
863
+ ```
864
+ """
865
+ return {
866
+ "id": self.id,
867
+ "title": self.title,
868
+ "width": self.width,
869
+ "height": self.height,
870
+ "x": self.x,
871
+ "y": self.y,
872
+ "frame": self.frame,
873
+ "context_menu": self.context_menu,
874
+ "dev_tools": self.dev_tools,
875
+ }
876
+
877
+ def get_id(self):
878
+ """
879
+ Returns the ID of the window.
880
+
881
+ Returns
882
+ -------
883
+ str
884
+ ID of the window
885
+
886
+ Examples
887
+ --------
888
+ ```python
889
+ app = Pyloid(app_name="Pyloid-App")
890
+
891
+ window = app.create_window("pyloid-window")
892
+ window_id = window.get_id()
893
+ print(window_id)
894
+
895
+ app.run()
896
+ ```
897
+ """
898
+ return self.id
899
+
900
+ def get_size(self) -> Dict[str, int]:
901
+ """
902
+ Returns the size of the window.
903
+
904
+ Returns
905
+ -------
906
+ dict
907
+ Dictionary containing the width and height of the window
908
+
909
+ Examples
910
+ --------
911
+ ```python
912
+ app = Pyloid(app_name="Pyloid-App")
913
+
914
+ window = app.create_window("pyloid-window")
915
+ size = window.get_size()
916
+ print(size)
917
+
918
+ app.run()
919
+ ```
920
+ """
921
+ return {"width": self.width, "height": self.height}
922
+
923
+ def get_position(self) -> Dict[str, int]:
924
+ """
925
+ Returns the position of the window.
926
+
927
+ Returns
928
+ -------
929
+ dict
930
+ Dictionary containing the x and y coordinates of the window
931
+
932
+ Examples
933
+ --------
934
+ ```python
935
+ app = Pyloid(app_name="Pyloid-App")
936
+
937
+ window = app.create_window("pyloid-window")
938
+ position = window.get_position()
939
+ print(position)
940
+
941
+ app.run()
942
+ ```
943
+ """
944
+ return {"x": self.x, "y": self.y}
945
+
946
+ def get_title(self) -> str:
947
+ """
948
+ Returns the title of the window.
949
+
950
+ Returns
951
+ -------
952
+ str
953
+ Title of the window
954
+
955
+ Examples
956
+ --------
957
+ ```python
958
+ app = Pyloid(app_name="Pyloid-App")
959
+
960
+ window = app.create_window("pyloid-window")
961
+ title = window.get_title()
962
+ print(title)
963
+
964
+ app.run()
965
+ ```
966
+ """
967
+ return self.title
968
+
969
+ def get_url(self) -> str:
970
+ """
971
+ Returns the URL of the window.
972
+
973
+ Returns
974
+ -------
975
+ str
976
+ URL of the window
977
+
978
+ Examples
979
+ --------
980
+ ```python
981
+ app = Pyloid(app_name="Pyloid-App")
982
+
983
+ window = app.create_window("pyloid-window")
984
+ url = window.get_url()
985
+ print(url)
986
+
987
+ app.run()
988
+ ```
989
+ """
990
+ return self.web_view.url().toString()
991
+
992
+ def get_visible(self) -> bool:
993
+ """
994
+ Returns the visibility of the window.
995
+
996
+ Returns
997
+ -------
998
+ bool
999
+ True if the window is visible, False otherwise
1000
+
1001
+ Examples
1002
+ --------
1003
+ ```python
1004
+ app = Pyloid(app_name="Pyloid-App")
1005
+
1006
+ window = app.create_window("pyloid-window")
1007
+ visible = window.get_visible()
1008
+ print(visible)
1009
+
1010
+ app.run()
1011
+ ```
1012
+ """
1013
+ return self._window.isVisible()
1014
+
1015
+ def get_frame(self) -> bool:
1016
+ """
1017
+ Returns the frame enabled state of the window.
1018
+
1019
+ Returns
1020
+ -------
1021
+ bool
1022
+ True if the frame is enabled, False otherwise
1023
+
1024
+ Examples
1025
+ --------
1026
+ ```python
1027
+ app = Pyloid(app_name="Pyloid-App")
1028
+
1029
+ window = app.create_window("pyloid-window")
1030
+ frame = window.get_frame()
1031
+ print(frame)
1032
+
1033
+ app.run()
1034
+ ```
1035
+ """
1036
+ return self.frame
1037
+
1038
+ ###########################################################################################
1039
+ # Resize
1040
+ ###########################################################################################
1041
+ def set_resizable(self, resizable: bool):
1042
+ """
1043
+ Sets the resizability of the window.
1044
+
1045
+ Parameters
1046
+ ----------
1047
+ resizable : bool
1048
+ True to make the window resizable, False to make it fixed size
1049
+
1050
+ Examples
1051
+ --------
1052
+ ```python
1053
+ app = Pyloid(app_name="Pyloid-App")
1054
+
1055
+ window = app.create_window("pyloid-window")
1056
+ window.set_resizable(True)
1057
+
1058
+ app.run()
1059
+ ```
1060
+ """
1061
+ self.resizable = resizable
1062
+ if self.frame:
1063
+ flags = self._window.windowFlags() | Qt.WindowCloseButtonHint
1064
+ if resizable:
1065
+ pass
1066
+ else:
1067
+ flags |= Qt.MSWindowsFixedSizeDialogHint
1068
+ self._window.setWindowFlags(flags)
1069
+ else:
1070
+ # 프레임이 없는 경우 커스텀 리사이징 로직을 설정합니다.
1071
+ self.web_view.is_resizing_enabled = resizable
1072
+
1073
+ self._window.show() # 변경사항을 적용하기 위해 창을 다시 표시합니다.
1074
+
1075
+ def set_minimum_size(self, min_width: int, min_height: int):
1076
+ """
1077
+ Sets the minimum size of the window.
1078
+
1079
+ Parameters
1080
+ ----------
1081
+ min_width : int
1082
+ Minimum width of the window
1083
+ min_height : int
1084
+ Minimum height of the window
1085
+
1086
+ Examples
1087
+ --------
1088
+ ```python
1089
+ app = Pyloid(app_name="Pyloid-App")
1090
+
1091
+ window = app.create_window("pyloid-window")
1092
+ window.set_minimum_size(400, 300)
1093
+
1094
+ app.run()
1095
+ ```
1096
+ """
1097
+ self._window.setMinimumSize(min_width, min_height)
1098
+
1099
+ def set_maximum_size(self, max_width: int, max_height: int):
1100
+ """
1101
+ Sets the maximum size of the window.
1102
+
1103
+ Parameters
1104
+ ----------
1105
+ max_width : int
1106
+ Maximum width of the window
1107
+ max_height : int
1108
+ Maximum height of the window
1109
+
1110
+ Examples
1111
+ --------
1112
+ ```python
1113
+ app = Pyloid(app_name="Pyloid-App")
1114
+
1115
+ window = app.create_window("pyloid-window")
1116
+ window.set_maximum_size(1024, 768)
1117
+
1118
+ app.run()
1119
+ ```
1120
+ """
1121
+ self._window.setMaximumSize(max_width, max_height)
1122
+
1123
+ def get_minimum_size(self) -> Dict[str, int]:
1124
+ """
1125
+ Returns the minimum size of the window.
1126
+
1127
+ Returns
1128
+ -------
1129
+ dict
1130
+ Dictionary containing the minimum width and height of the window
1131
+
1132
+ Examples
1133
+ --------
1134
+ ```python
1135
+ app = Pyloid(app_name="Pyloid-App")
1136
+
1137
+ window = app.create_window("pyloid-window")
1138
+ min_size = window.get_minimum_size()
1139
+ print(min_size)
1140
+
1141
+ app.run()
1142
+ ```
1143
+ """
1144
+ return {'width': self._window.minimumWidth(), 'height': self._window.minimumHeight()}
1145
+
1146
+ def get_maximum_size(self) -> Dict[str, int]:
1147
+ """
1148
+ Returns the maximum size of the window.
1149
+
1150
+ Returns
1151
+ -------
1152
+ dict
1153
+ Dictionary containing the maximum width and height of the window
1154
+
1155
+ Examples
1156
+ --------
1157
+ ```python
1158
+ app = Pyloid(app_name="Pyloid-App")
1159
+
1160
+ window = app.create_window("pyloid-window")
1161
+ max_size = window.get_maximum_size()
1162
+ print(max_size)
1163
+
1164
+ app.run()
1165
+ ```
1166
+ """
1167
+ return {'width': self._window.maximumWidth(), 'height': self._window.maximumHeight()}
1168
+
1169
+ def get_resizable(self) -> bool:
1170
+ """
1171
+ Returns the resizability of the window.
1172
+
1173
+ Returns
1174
+ -------
1175
+ bool
1176
+ True if the window is resizable, False otherwise
1177
+
1178
+ Examples
1179
+ --------
1180
+ ```python
1181
+ app = Pyloid(app_name="Pyloid-App")
1182
+
1183
+ window = app.create_window("pyloid-window")
1184
+ resizable = window.get_resizable()
1185
+ print(resizable)
1186
+
1187
+ app.run()
1188
+ ```
1189
+ """
1190
+ return self.resizable