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

Sign up to get free protection for your applications and to get access to all the features.
@@ -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