toga-winforms 0.5.3__py3-none-win_amd64.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.
Files changed (60) hide show
  1. toga_winforms/__init__.py +37 -0
  2. toga_winforms/app.py +274 -0
  3. toga_winforms/colors.py +23 -0
  4. toga_winforms/command.py +121 -0
  5. toga_winforms/container.py +70 -0
  6. toga_winforms/dialogs.py +328 -0
  7. toga_winforms/factory.py +88 -0
  8. toga_winforms/fonts.py +123 -0
  9. toga_winforms/hardware/__init__.py +0 -0
  10. toga_winforms/icons.py +26 -0
  11. toga_winforms/images.py +62 -0
  12. toga_winforms/keys.py +173 -0
  13. toga_winforms/libs/WebView2/Microsoft.Web.WebView2.Core.dll +0 -0
  14. toga_winforms/libs/WebView2/Microsoft.Web.WebView2.WinForms.dll +0 -0
  15. toga_winforms/libs/WebView2/README.md +12 -0
  16. toga_winforms/libs/WebView2/runtimes/win-x64/native/WebView2Loader.dll +0 -0
  17. toga_winforms/libs/__init__.py +0 -0
  18. toga_winforms/libs/extensions.py +37 -0
  19. toga_winforms/libs/fonts.py +23 -0
  20. toga_winforms/libs/proactor.py +165 -0
  21. toga_winforms/libs/shcore.py +5 -0
  22. toga_winforms/libs/user32.py +36 -0
  23. toga_winforms/paths.py +30 -0
  24. toga_winforms/resources/spinner.gif +0 -0
  25. toga_winforms/resources/toga.ico +0 -0
  26. toga_winforms/screens.py +76 -0
  27. toga_winforms/statusicons.py +102 -0
  28. toga_winforms/widgets/__init__.py +0 -0
  29. toga_winforms/widgets/activityindicator.py +127 -0
  30. toga_winforms/widgets/base.py +203 -0
  31. toga_winforms/widgets/box.py +11 -0
  32. toga_winforms/widgets/button.py +58 -0
  33. toga_winforms/widgets/canvas.py +388 -0
  34. toga_winforms/widgets/dateinput.py +49 -0
  35. toga_winforms/widgets/detailedlist.py +57 -0
  36. toga_winforms/widgets/divider.py +45 -0
  37. toga_winforms/widgets/imageview.py +44 -0
  38. toga_winforms/widgets/label.py +33 -0
  39. toga_winforms/widgets/mapview.py +226 -0
  40. toga_winforms/widgets/multilinetextinput.py +114 -0
  41. toga_winforms/widgets/numberinput.py +74 -0
  42. toga_winforms/widgets/optioncontainer.py +79 -0
  43. toga_winforms/widgets/passwordinput.py +7 -0
  44. toga_winforms/widgets/progressbar.py +84 -0
  45. toga_winforms/widgets/scrollcontainer.py +134 -0
  46. toga_winforms/widgets/selection.py +81 -0
  47. toga_winforms/widgets/slider.py +63 -0
  48. toga_winforms/widgets/splitcontainer.py +83 -0
  49. toga_winforms/widgets/switch.py +47 -0
  50. toga_winforms/widgets/table.py +291 -0
  51. toga_winforms/widgets/textinput.py +99 -0
  52. toga_winforms/widgets/timeinput.py +53 -0
  53. toga_winforms/widgets/webview.py +252 -0
  54. toga_winforms/window.py +490 -0
  55. toga_winforms-0.5.3.dist-info/METADATA +65 -0
  56. toga_winforms-0.5.3.dist-info/RECORD +60 -0
  57. toga_winforms-0.5.3.dist-info/WHEEL +5 -0
  58. toga_winforms-0.5.3.dist-info/entry_points.txt +2 -0
  59. toga_winforms-0.5.3.dist-info/licenses/LICENSE +27 -0
  60. toga_winforms-0.5.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,37 @@
1
+ import clr
2
+ import travertino
3
+
4
+ from .libs.user32 import (
5
+ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2,
6
+ SetProcessDpiAwarenessContext,
7
+ )
8
+
9
+ # Add a reference to the Winforms assembly
10
+ clr.AddReference("System.Windows.Forms")
11
+
12
+ # Add a reference to the WindowsBase assembly. This is needed to access
13
+ # System.Windows.Threading.Dispatcher.
14
+ #
15
+ # This assembly isn't exposed as a simple dot-path name; we have to extract it from the
16
+ # Global Assembly Cache (GAC). The version number and public key doesn't appear to
17
+ # change with Windows version or the underlying .NET, and has been available since
18
+ # Windows 7.
19
+ clr.AddReference(
20
+ "WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
21
+ )
22
+
23
+
24
+ # Enable DPI awareness. This must be done before calling any other UI-related code
25
+ # (https://learn.microsoft.com/en-us/dotnet/desktop/winforms/high-dpi-support-in-windows-forms).
26
+ import System.Windows.Forms as WinForms # noqa: E402
27
+
28
+ WinForms.Application.EnableVisualStyles()
29
+ WinForms.Application.SetCompatibleTextRenderingDefault(False)
30
+
31
+ if SetProcessDpiAwarenessContext is not None:
32
+ if not SetProcessDpiAwarenessContext(
33
+ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2
34
+ ): # pragma: no cover
35
+ print("WARNING: Failed to set the DPI Awareness mode for the app.")
36
+
37
+ __version__ = travertino._package_version(__file__, __name__)
toga_winforms/app.py ADDED
@@ -0,0 +1,274 @@
1
+ import asyncio
2
+ import re
3
+ import sys
4
+ import threading
5
+
6
+ import System.Windows.Forms as WinForms
7
+ from Microsoft.Win32 import SystemEvents
8
+ from System import Threading
9
+ from System.Media import SystemSounds
10
+ from System.Net import SecurityProtocolType, ServicePointManager
11
+ from System.Windows.Threading import Dispatcher
12
+
13
+ from toga.dialogs import InfoDialog
14
+ from toga.handlers import WeakrefCallable
15
+
16
+ from .libs.proactor import WinformsProactorEventLoop
17
+ from .screens import Screen as ScreenImpl
18
+
19
+
20
+ def winforms_thread_exception(sender, winforms_exc): # pragma: no cover
21
+ # The PythonException returned by Winforms doesn't give us
22
+ # easy access to the underlying Python stacktrace; so we
23
+ # reconstruct it from the string message.
24
+ # The Python message is helpfully included in square brackets,
25
+ # as the context for the first line in the .net stack trace.
26
+ # So, look for the closing bracket and the start of the Python.net
27
+ # stack trace. Then, reconstruct the line breaks internal to the
28
+ # remaining string.
29
+ print("Traceback (most recent call last):")
30
+ py_exc = winforms_exc.get_Exception()
31
+ full_stack_trace = py_exc.StackTrace
32
+ regex = re.compile(
33
+ r"^\[(?:'(.*?)', )*(?:'(.*?)')\] (?:.*?) Python\.Runtime",
34
+ re.DOTALL | re.UNICODE,
35
+ )
36
+
37
+ def print_stack_trace(stack_trace_line): # pragma: no cover
38
+ for level in stack_trace_line.split("', '"):
39
+ for line in level.split("\\n"):
40
+ if line:
41
+ print(line)
42
+
43
+ stacktrace_relevant_lines = regex.findall(full_stack_trace)
44
+ if len(stacktrace_relevant_lines) == 0:
45
+ print_stack_trace(full_stack_trace)
46
+ else:
47
+ for lines in stacktrace_relevant_lines:
48
+ for line in lines:
49
+ print_stack_trace(line)
50
+
51
+ print(py_exc.Message)
52
+
53
+
54
+ class App:
55
+ # Winforms apps exit when the last window is closed
56
+ CLOSE_ON_LAST_WINDOW = True
57
+ # Winforms apps use default command line handling
58
+ HANDLES_COMMAND_LINE = False
59
+
60
+ def __init__(self, interface):
61
+ self.interface = interface
62
+ self.interface._impl = self
63
+
64
+ # Track whether the app is exiting. This is used to stop the event loop,
65
+ # and shortcut close handling on any open windows when the app exits.
66
+ self._is_exiting = False
67
+
68
+ # Winforms cursor visibility is a stack; If you call hide N times, you
69
+ # need to call Show N times to make the cursor re-appear. Store a local
70
+ # boolean to allow us to avoid building a deep stack.
71
+ self._cursor_visible = True
72
+
73
+ self.loop = WinformsProactorEventLoop()
74
+ asyncio.set_event_loop(self.loop)
75
+
76
+ def create(self):
77
+ self.native = WinForms.Application
78
+ self.app_context = WinForms.ApplicationContext()
79
+ self.app_dispatcher = Dispatcher.CurrentDispatcher
80
+
81
+ # We would prefer to detect DPI changes directly, using the DpiChanged,
82
+ # DpiChangedBeforeParent or DpiChangedAfterParent events on the window. But none
83
+ # of these events ever fire, possibly because we're missing some app metadata
84
+ # (https://github.com/beeware/toga/pull/2155#issuecomment-2460374101). So
85
+ # instead we need to listen to all events which could cause a DPI change:
86
+ # * DisplaySettingsChanged
87
+ # * Form.LocationChanged and Form.Resize, since a window's DPI is determined
88
+ # by which screen most of its area is on.
89
+ SystemEvents.DisplaySettingsChanged += WeakrefCallable(
90
+ self.winforms_DisplaySettingsChanged
91
+ )
92
+
93
+ # Ensure that TLS1.2 and TLS1.3 are enabled for HTTPS connections.
94
+ # For some reason, some Windows installs have these protocols
95
+ # turned off by default. SSL3, TLS1.0 and TLS1.1 are *not* enabled
96
+ # as they are deprecated protocols and their use should *not* be
97
+ # encouraged.
98
+ try:
99
+ ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12
100
+ except AttributeError: # pragma: no cover
101
+ print(
102
+ "WARNING: Your Windows .NET install does not support TLS1.2. "
103
+ "You may experience difficulties accessing some web server content."
104
+ )
105
+ try:
106
+ ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls13
107
+ except AttributeError: # pragma: no cover
108
+ print(
109
+ "WARNING: Your Windows .NET install does not support TLS1.3. "
110
+ "You may experience difficulties accessing some web server content."
111
+ )
112
+
113
+ # Populate the main window as soon as the event loop is running.
114
+ self.loop.call_soon_threadsafe(self.interface._startup)
115
+
116
+ ######################################################################
117
+ # Native event handlers
118
+ ######################################################################
119
+
120
+ def winforms_DisplaySettingsChanged(self, sender, event):
121
+ # This event is NOT called on the UI thread, so it's not safe for it to access
122
+ # the UI directly.
123
+ self.interface.loop.call_soon_threadsafe(self.update_dpi)
124
+
125
+ def update_dpi(self):
126
+ for window in self.interface.windows:
127
+ window._impl.update_dpi()
128
+
129
+ ######################################################################
130
+ # Commands and menus
131
+ ######################################################################
132
+
133
+ def create_standard_commands(self):
134
+ pass
135
+
136
+ def create_menus(self):
137
+ # Winforms menus are created on the Window.
138
+ for window in self.interface.windows:
139
+ # It's difficult to trigger this on a simple window, because we can't easily
140
+ # modify the set of app-level commands that are registered, and a simple
141
+ # window doesn't exist when the app starts up. Therefore, no-branch the else
142
+ # case.
143
+ if hasattr(window._impl, "create_menus"): # pragma: no branch
144
+ window._impl.create_menus()
145
+
146
+ ######################################################################
147
+ # App lifecycle
148
+ ######################################################################
149
+
150
+ def exit(self): # pragma: no cover
151
+ self._is_exiting = True
152
+ self.native.Exit()
153
+
154
+ def _run_app(self): # pragma: no cover
155
+ # Enable coverage tracing on this non-Python-created thread
156
+ # (https://github.com/nedbat/coveragepy/issues/686).
157
+ if threading._trace_hook:
158
+ sys.settrace(threading._trace_hook)
159
+
160
+ try:
161
+ self.create()
162
+
163
+ # This catches errors in handlers, and prints them
164
+ # in a usable form.
165
+ self.native.ThreadException += WeakrefCallable(winforms_thread_exception)
166
+
167
+ self.loop.run_forever(self)
168
+ except Exception as e:
169
+ # In case of an unhandled error at the level of the app,
170
+ # preserve the Python stacktrace
171
+ self._exception = e
172
+ else:
173
+ # Ensure the event loop is fully closed.
174
+ self.loop.close()
175
+ self._exception = None
176
+
177
+ def main_loop(self):
178
+ thread = Threading.Thread(Threading.ThreadStart(self._run_app))
179
+ thread.SetApartmentState(Threading.ApartmentState.STA)
180
+ thread.Start()
181
+ thread.Join()
182
+
183
+ # If the thread has exited, the _exception attribute will exist.
184
+ # If it's non-None, raise it, as it indicates the underlying
185
+ # app thread had a problem; this is effectibely a re-raise over
186
+ # a thread boundary.
187
+ if self._exception: # pragma: no cover
188
+ raise self._exception
189
+
190
+ def set_icon(self, icon):
191
+ for window in self.interface.windows:
192
+ window._impl.native.Icon = icon._impl.native
193
+
194
+ def set_main_window(self, window):
195
+ pass
196
+
197
+ ######################################################################
198
+ # App resources
199
+ ######################################################################
200
+
201
+ def get_primary_screen(self):
202
+ return ScreenImpl(WinForms.Screen.PrimaryScreen)
203
+
204
+ def get_screens(self):
205
+ primary_screen = self.get_primary_screen()
206
+ screen_list = [primary_screen] + [
207
+ ScreenImpl(native=screen)
208
+ for screen in WinForms.Screen.AllScreens
209
+ if screen != primary_screen.native
210
+ ]
211
+ return screen_list
212
+
213
+ ######################################################################
214
+ # App state
215
+ ######################################################################
216
+
217
+ def get_dark_mode_state(self):
218
+ self.interface.factory.not_implemented("dark mode state")
219
+ return None
220
+
221
+ ######################################################################
222
+ # App capabilities
223
+ ######################################################################
224
+
225
+ def beep(self):
226
+ SystemSounds.Beep.Play()
227
+
228
+ def show_about_dialog(self):
229
+ message_parts = []
230
+ if self.interface.version is not None:
231
+ message_parts.append(
232
+ f"{self.interface.formal_name} v{self.interface.version}"
233
+ )
234
+ else:
235
+ message_parts.append(self.interface.formal_name)
236
+
237
+ if self.interface.author is not None:
238
+ message_parts.append(f"Author: {self.interface.author}")
239
+ if self.interface.description is not None:
240
+ message_parts.append(f"\n{self.interface.description}")
241
+ asyncio.create_task(
242
+ self.interface.dialog(
243
+ InfoDialog(
244
+ f"About {self.interface.formal_name}", "\n".join(message_parts)
245
+ )
246
+ )
247
+ )
248
+
249
+ ######################################################################
250
+ # Cursor control
251
+ ######################################################################
252
+
253
+ def hide_cursor(self):
254
+ if self._cursor_visible:
255
+ WinForms.Cursor.Hide()
256
+ self._cursor_visible = False
257
+
258
+ def show_cursor(self):
259
+ if not self._cursor_visible:
260
+ WinForms.Cursor.Show()
261
+ self._cursor_visible = True
262
+
263
+ ######################################################################
264
+ # Window control
265
+ ######################################################################
266
+
267
+ def get_current_window(self):
268
+ for window in self.interface.windows:
269
+ if WinForms.Form.ActiveForm == window._impl.native:
270
+ return window._impl
271
+ return None
272
+
273
+ def set_current_window(self, window):
274
+ window._impl.native.Activate()
@@ -0,0 +1,23 @@
1
+ from System.Drawing import Color
2
+ from travertino.colors import TRANSPARENT, rgba
3
+
4
+ CACHE = {TRANSPARENT: Color.Transparent}
5
+
6
+
7
+ def native_color(c):
8
+ try:
9
+ color = CACHE[c]
10
+ except KeyError:
11
+ color = Color.FromArgb(
12
+ int(c.rgba.a * 255),
13
+ int(c.rgba.r),
14
+ int(c.rgba.g),
15
+ int(c.rgba.b),
16
+ )
17
+ CACHE[c] = color
18
+
19
+ return color
20
+
21
+
22
+ def toga_color(c):
23
+ return rgba(c.R, c.G, c.B, c.A / 255)
@@ -0,0 +1,121 @@
1
+ import sys
2
+
3
+ from System.ComponentModel import InvalidEnumArgumentException
4
+
5
+ from toga import Command as StandardCommand, Group, Key
6
+ from toga.handlers import WeakrefCallable
7
+ from toga_winforms.keys import toga_to_winforms_key, toga_to_winforms_shortcut
8
+
9
+
10
+ class Command:
11
+ def __init__(self, interface):
12
+ self.interface = interface
13
+ self.native = []
14
+
15
+ @classmethod
16
+ def standard(self, app, id):
17
+ # ---- File menu -----------------------------------
18
+ if id == StandardCommand.NEW:
19
+ return {
20
+ "text": "New",
21
+ "shortcut": Key.MOD_1 + "n",
22
+ "group": Group.FILE,
23
+ "section": 0,
24
+ "order": 0,
25
+ }
26
+ elif id == StandardCommand.OPEN:
27
+ return {
28
+ "text": "Open...",
29
+ "shortcut": Key.MOD_1 + "o",
30
+ "group": Group.FILE,
31
+ "section": 0,
32
+ "order": 10,
33
+ }
34
+ elif id == StandardCommand.SAVE:
35
+ return {
36
+ "text": "Save",
37
+ "shortcut": Key.MOD_1 + "s",
38
+ "group": Group.FILE,
39
+ "section": 0,
40
+ "order": 20,
41
+ }
42
+ elif id == StandardCommand.SAVE_AS:
43
+ return {
44
+ "text": "Save As...",
45
+ "shortcut": Key.MOD_1 + "S",
46
+ "group": Group.FILE,
47
+ "section": 0,
48
+ "order": 21,
49
+ }
50
+ elif id == StandardCommand.SAVE_ALL:
51
+ return {
52
+ "text": "Save All",
53
+ "shortcut": Key.MOD_1 + Key.MOD_2 + "s",
54
+ "group": Group.FILE,
55
+ "section": 0,
56
+ "order": 22,
57
+ }
58
+ elif id == StandardCommand.PREFERENCES:
59
+ # Preferences should be towards the end of the File menu.
60
+ return {
61
+ "text": "Preferences",
62
+ "group": Group.FILE,
63
+ "section": sys.maxsize - 1,
64
+ }
65
+ elif id == StandardCommand.EXIT:
66
+ # Quit should always be the last item, in a section on its own.
67
+ return {
68
+ "text": "Exit",
69
+ "group": Group.FILE,
70
+ "section": sys.maxsize,
71
+ }
72
+ # ---- Help menu -----------------------------------
73
+ elif id == StandardCommand.VISIT_HOMEPAGE:
74
+ return {
75
+ "text": "Visit homepage",
76
+ "enabled": app.home_page is not None,
77
+ "group": Group.HELP,
78
+ }
79
+ elif id == StandardCommand.ABOUT:
80
+ return {
81
+ "text": f"About {app.formal_name}",
82
+ "group": Group.HELP,
83
+ "section": sys.maxsize,
84
+ }
85
+
86
+ raise ValueError(f"Unknown standard command {id!r}")
87
+
88
+ def winforms_Click(self, sender, event):
89
+ return self.interface.action()
90
+
91
+ def set_enabled(self, value):
92
+ if self.native:
93
+ for widget in self.native:
94
+ widget.Enabled = self.interface.enabled
95
+
96
+ def create_menu_item(self, WinformsClass):
97
+ item = WinformsClass(self.interface.text)
98
+
99
+ item.Click += WeakrefCallable(self.winforms_Click)
100
+ if self.interface.shortcut is not None:
101
+ try:
102
+ item.ShortcutKeys = toga_to_winforms_key(self.interface.shortcut)
103
+ # The Winforms key enum is... daft. The "oem" key
104
+ # values render as "Oem" or "Oemcomma", so we need to
105
+ # *manually* set the display text for the key shortcut.
106
+ item.ShortcutKeyDisplayString = toga_to_winforms_shortcut(
107
+ self.interface.shortcut
108
+ )
109
+ except (
110
+ ValueError,
111
+ InvalidEnumArgumentException,
112
+ ) as e: # pragma: no cover
113
+ # Make this a non-fatal warning, because different backends may
114
+ # accept different shortcuts.
115
+ print(f"WARNING: invalid shortcut {self.interface.shortcut!r}: {e}")
116
+
117
+ item.Enabled = self.interface.enabled
118
+
119
+ self.native.append(item)
120
+
121
+ return item
@@ -0,0 +1,70 @@
1
+ import System.Windows.Forms as WinForms
2
+ from System.Drawing import Size
3
+
4
+ from .widgets.base import Scalable
5
+
6
+
7
+ class Container(Scalable):
8
+ def __init__(self, native_parent):
9
+ self.native_parent = native_parent
10
+ self.native_width = self.native_height = 0
11
+ self.content = None
12
+
13
+ self.native_content = WinForms.Panel()
14
+ native_parent.Controls.Add(self.native_content)
15
+
16
+ # See comment in Widget.__init__.
17
+ self.native_content.CreateGraphics().Dispose()
18
+
19
+ @property
20
+ def dpi_scale(self):
21
+ window = self.content.interface.window if self.content else None
22
+ if window:
23
+ return window._impl.dpi_scale
24
+ else:
25
+ return 1
26
+
27
+ @property
28
+ def width(self):
29
+ return self.scale_out(self.native_width)
30
+
31
+ @property
32
+ def height(self):
33
+ return self.scale_out(self.native_height)
34
+
35
+ def set_content(self, widget):
36
+ self.clear_content()
37
+ if widget:
38
+ widget.container = self
39
+ self.content = widget
40
+
41
+ def clear_content(self):
42
+ if self.content:
43
+ self.content.container = None
44
+ self.content = None
45
+
46
+ def resize_content(self, width, height, *, force_refresh=False):
47
+ if (self.native_width, self.native_height) != (width, height):
48
+ self.native_width, self.native_height = (width, height)
49
+ force_refresh = True
50
+
51
+ if force_refresh and self.content:
52
+ self.content.interface.refresh()
53
+
54
+ def refreshed(self):
55
+ layout = self.content.interface.layout
56
+ self.apply_layout(layout.width, layout.height)
57
+
58
+ def apply_layout(self, layout_width, layout_height):
59
+ self.native_content.Size = Size(
60
+ self.scale_in(max(self.width, layout_width)),
61
+ self.scale_in(max(self.height, layout_height)),
62
+ )
63
+
64
+ def add_content(self, widget):
65
+ # The default is to add new controls to the back of the Z-order.
66
+ self.native_content.Controls.Add(widget.native)
67
+ widget.native.BringToFront()
68
+
69
+ def remove_content(self, widget):
70
+ self.native_content.Controls.Remove(widget.native)