robotframework-dialogsplus 0.1.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,5 @@
1
+ # src/DialogsPlus/__init__.py
2
+
3
+ from .dialogsplus import DialogsPlus
4
+
5
+ __all__ = ["DialogsPlus"]
@@ -0,0 +1,90 @@
1
+ from robot.api.deco import keyword
2
+ import os
3
+ from DialogsPlus.utils.config import DialogConfig
4
+ from DialogsPlus.widgets.wrappers import ( GetValueFromUserDialog,
5
+ ExecuteManualStepDialog,
6
+ CountdownDialogRunner,
7
+ GetConfirmationFromUser,
8
+ MultiValueInput,
9
+ ChooseFromFileDialog,
10
+ ChooseFolderDialog,
11
+ ConfirmWithCheckbox,
12
+ SelectOptionsWithCheckboxes)
13
+
14
+
15
+ ROBOT_LIBRARY_SCOPE = 'SUITE'
16
+
17
+ class DialogsPlus:
18
+
19
+ def __init__(self, config=None):
20
+
21
+ if config and os.path.exists(config):
22
+ self.config = DialogConfig.from_yaml(config)
23
+ else:
24
+ self.config = DialogConfig() # use defaults
25
+
26
+
27
+ @keyword
28
+ def get_value_from_user(self, prompt="Enter value:", default=""):
29
+ return GetValueFromUserDialog.show(prompt,default,config=self.config)
30
+
31
+ @keyword
32
+ def run_manual_steps(self, steps):
33
+ ExecuteManualStepDialog.run_steps(steps, config=self.config)
34
+
35
+ @keyword
36
+ def count_down(self, seconds):
37
+ CountdownDialogRunner.show(int(seconds), config=self.config)
38
+
39
+ @keyword
40
+ def get_confirmation(self, message):
41
+ return GetConfirmationFromUser.show(message=message,config=self.config)
42
+
43
+ @keyword
44
+ def get_multi_value(self, fields, default=None):
45
+ fields_list = fields if isinstance(fields, list) else [fields]
46
+ calculated_height = 150 + (len(fields_list) * 40) + 60
47
+ #max_field_length = max(len(field) for field in fields_list)
48
+ max_field_length = 20
49
+ calculated_width = 300 + (max_field_length * 8)
50
+ self.config.height = calculated_height
51
+ self.config.width = calculated_width
52
+ return MultiValueInput.run_multival(fields=fields,defaults=default,config=self.config)
53
+
54
+ @keyword
55
+ def choose_file(self, message="", filetypes=None, multiple=False):
56
+ return ChooseFromFileDialog.show( message, filetypes, multiple, self.config)
57
+
58
+ @keyword
59
+ def choose_folder(self, message):
60
+ return ChooseFolderDialog.show(message, self.config)
61
+
62
+
63
+ @keyword
64
+ def confirm_with_checkbox(self, message, checkbox_text="I agree"):
65
+ return ConfirmWithCheckbox.show(message, checkbox_text, self.config)
66
+
67
+
68
+ @keyword
69
+ def select_options_with_checkboxes(self, message, options, defaults=None):
70
+ """Show multiple checkboxes and return selected options as dictionary."""
71
+
72
+ options_list = options if isinstance(options, list) else options.split('|')
73
+
74
+ # Base sizing
75
+ num_options = len(options_list)
76
+ max_option_length = max(len(opt) for opt in options_list)
77
+ message_length = len(message)
78
+
79
+ # Height: base + checkboxes + some buffer
80
+ calculated_height = 180 + (num_options * 40)
81
+
82
+ # Width: consider both message and longest option
83
+ width_from_message = min(600, max(300, message_length * 7))
84
+ width_from_options = max(300, 200 + (max_option_length * 8))
85
+ calculated_width = max(width_from_message, width_from_options)
86
+
87
+ self.config.height = calculated_height
88
+ self.config.width = calculated_width
89
+
90
+ return SelectOptionsWithCheckboxes.show(message, options, defaults, self.config)
DialogsPlus/py.typed ADDED
File without changes
@@ -0,0 +1,83 @@
1
+ # dialog_config.py
2
+
3
+ import yaml
4
+
5
+ class DialogConfig:
6
+ def __init__(
7
+ self,
8
+ title="Robot Framework",
9
+ width=400,
10
+ height=150,
11
+ theme="blue",
12
+ appearance_mode="system",
13
+
14
+ button_width=120,
15
+ button_height=32,
16
+ label_font=("Courier New", 16, "bold"),
17
+ entry_width= 200,
18
+ entry_height=28,
19
+ spacing=10,
20
+ button_fg_color="#06bdb1",
21
+ button_font=("Courier New", 16, "bold"),
22
+ label_text_color="white",
23
+ button_text_color="black",
24
+ button_hover_color="#57b7b0",
25
+ entry_font=("Courier New", 14),
26
+ entry_text_color="white",
27
+ entry_fg_color="#212121",
28
+ entry_border_color="#46fff4",
29
+ frame_fg_color = "transparent",
30
+ progress_bar_width = 300,
31
+ progress_bar_height = 12,
32
+ progress_bar_color = "#00c0b5",
33
+
34
+
35
+ ):
36
+ self.title = title
37
+ self.width = width
38
+ self.height = height
39
+ self.theme = theme
40
+ self.appearance_mode = appearance_mode
41
+
42
+ self.button_width = button_width
43
+ self.button_height = button_height
44
+ self.label_font = label_font
45
+ self.entry_width = entry_width
46
+ self.entry_height = entry_height
47
+ self.spacing = spacing
48
+ self.button_fg_color=button_fg_color
49
+ self.button_font = button_font
50
+ self.label_text_color = label_text_color
51
+ self.button_text_color = button_text_color
52
+ self.button_hover_color = button_hover_color
53
+ self.entry_font = entry_font
54
+ self.entry_text_color = entry_text_color
55
+ self.entry_fg_color = entry_fg_color
56
+ self.entry_border_color = entry_border_color
57
+ self.frame_fg_color = frame_fg_color
58
+ self.progress_bar_width = progress_bar_width
59
+ self.progress_bar_height = progress_bar_height
60
+ self.progress_bar_color = progress_bar_color
61
+
62
+
63
+ @classmethod
64
+ def from_yaml(cls, path: str):
65
+ print(f"[DEBUG] Received config path: {path}")
66
+ with open(path, "r", encoding="utf-8") as f:
67
+ data = yaml.safe_load(f)
68
+
69
+ return cls(
70
+ title=data.get("title", "Dialog"),
71
+ width=data.get("width", 300),
72
+ height=data.get("height", 150),
73
+ theme=data.get("theme", "blue"),
74
+ appearance_mode=data.get("appearance_mode", "system"),
75
+
76
+ button_width=data.get("button_width", 120),
77
+ button_height=data.get("button_height", 32),
78
+ label_font=tuple(data.get("label_font", ("Arial", 12))),
79
+ entry_height=data.get("entry_height", 28),
80
+ entry_width=data.get("entry_width",60),
81
+ spacing=data.get("spacing", 10),
82
+ button_fg_color=data.get("button_fg_color", "#06bdb1")
83
+ )
Binary file
@@ -0,0 +1,169 @@
1
+ import customtkinter as ctk
2
+ from customtkinter import BooleanVar, IntVar
3
+ from DialogsPlus.utils.config import DialogConfig
4
+ from tkinter import filedialog
5
+ import os
6
+
7
+
8
+ # Export for use in other modules
9
+ __all__ = [
10
+ 'BaseDialogRunner',
11
+ 'BaseDialog',
12
+ 'ctk',
13
+ 'BooleanVar',
14
+ 'filedialog',
15
+ 'IntVar'
16
+ ]
17
+
18
+ class BaseDialogRunner:
19
+ _theme_initialized = False
20
+
21
+ @staticmethod
22
+ def _center_window(app, config):
23
+ app.update_idletasks()
24
+
25
+ screen_width = app.winfo_screenwidth()
26
+ screen_height = app.winfo_screenheight()
27
+
28
+ x = (screen_width - config.width) // 2
29
+ y = (screen_height - config.height) // 2
30
+ app.geometry(f"{config.width}x{config.height}+{x}+{y}")
31
+
32
+ @staticmethod
33
+ def create_app(config: DialogConfig):
34
+ # Initialize theme once on first dialog creation
35
+ if not BaseDialogRunner._theme_initialized:
36
+ ctk.set_appearance_mode(config.appearance_mode)
37
+ ctk.set_default_color_theme(config.theme)
38
+ BaseDialogRunner._theme_initialized = True
39
+
40
+ app = ctk.CTk()
41
+ app.title(config.title)
42
+
43
+ icon_path = os.path.join(os.path.dirname(__file__), "assets", "robot.ico")
44
+ try:
45
+ app.iconbitmap(icon_path)
46
+ except (FileNotFoundError, Exception):
47
+ pass
48
+
49
+ BaseDialogRunner._center_window(app, config)
50
+ return app
51
+
52
+ @staticmethod
53
+ def run_dialog(ui_builder_func, config: DialogConfig):
54
+ app = BaseDialogRunner.create_app(config)
55
+ ui_builder_func(app)
56
+ app.mainloop()
57
+
58
+ app.withdraw()
59
+
60
+ try:
61
+ after_ids = app.tk.call('after', 'info')
62
+ for after_id in after_ids:
63
+ try:
64
+ app.after_cancel(after_id)
65
+ except:
66
+ pass
67
+ except:
68
+ pass
69
+
70
+ app.update_idletasks()
71
+
72
+ try:
73
+ app.quit()
74
+ except:
75
+ pass
76
+
77
+ try:
78
+ app.destroy()
79
+ except:
80
+ pass
81
+
82
+
83
+ class BaseDialog:
84
+
85
+ def __init__(self, config=None):
86
+ self.config = config if config else DialogConfig()
87
+ self.result = {}
88
+
89
+ def create_button(self, parent, text, command, **kwargs):
90
+ """Create a button with config styling"""
91
+ return ctk.CTkButton(
92
+ parent,
93
+ text=text,
94
+ command=command,
95
+ font=self.config.button_font,
96
+ fg_color=self.config.button_fg_color,
97
+ text_color=self.config.button_text_color,
98
+ hover_color=self.config.button_hover_color,
99
+ width=kwargs.get('width', self.config.button_width),
100
+ height=kwargs.get('height', self.config.button_height),
101
+ **{k: v for k, v in kwargs.items() if k not in ['width', 'height']}
102
+ )
103
+
104
+ def create_label(self, parent, text, **kwargs):
105
+ """Create a label with config styling"""
106
+ return ctk.CTkLabel(
107
+ parent,
108
+ text=text,
109
+ font=self.config.label_font,
110
+ text_color=self.config.label_text_color,
111
+ anchor=kwargs.get('anchor', "center"),
112
+ justify=kwargs.get('justify', "left"),
113
+ wraplength=kwargs.get('wraplength', 0),
114
+ **{k: v for k, v in kwargs.items() if k not in ['anchor', 'justify', 'wraplength']}
115
+ )
116
+
117
+ def create_entry(self, parent, **kwargs):
118
+ """Create an entry with consistent styling from config"""
119
+ return ctk.CTkEntry(
120
+ parent,
121
+ width=kwargs.get('width', self.config.entry_width),
122
+ height=kwargs.get('height', self.config.entry_height),
123
+ font=kwargs.get('font', self.config.entry_font),
124
+ text_color=kwargs.get('text_color', self.config.entry_text_color),
125
+ fg_color=kwargs.get('fg_color', self.config.entry_fg_color),
126
+ border_color=kwargs.get('border_color', self.config.entry_border_color),
127
+ **{k: v for k, v in kwargs.items() if k not in ['width', 'height', 'font', 'text_color', 'fg_color', 'border_color']}
128
+ )
129
+
130
+ def create_frame(self, parent, **kwargs):
131
+ return ctk.CTkFrame(
132
+ parent,
133
+ fg_color=kwargs.get('fg_color', self.config.frame_fg_color),
134
+ **{k: v for k, v in kwargs.items() if k not in ['fg_color']} # ← Add this
135
+ )
136
+
137
+ def create_progress_bar(self, parent, **kwargs):
138
+ return ctk.CTkProgressBar(
139
+ parent,
140
+ width=kwargs.get('width', self.config.progress_bar_width),
141
+ height=kwargs.get('height', self.config.progress_bar_height),
142
+ progress_color=kwargs.get('progress_color', self.config.progress_bar_color),
143
+ **{k: v for k, v in kwargs.items() if k not in ['width', 'height', 'progress_color']}
144
+ )
145
+
146
+ def create_checkbox(self, parent, text="", **kwargs):
147
+ """Create a checkbox with consistent styling from config"""
148
+ return ctk.CTkCheckBox(
149
+ parent,
150
+ text=text,
151
+ font=kwargs.get('font', self.config.label_font),
152
+ text_color=kwargs.get('text_color', self.config.label_text_color),
153
+ fg_color=kwargs.get('fg_color', self.config.button_fg_color),
154
+ hover_color=kwargs.get('hover_color', self.config.button_hover_color),
155
+ **{k: v for k, v in kwargs.items() if k not in ['font', 'text_color', 'fg_color', 'hover_color']}
156
+ )
157
+
158
+
159
+ # testing this shit
160
+
161
+ def show(self):
162
+ def ui(app):
163
+ self.build_ui(app)
164
+
165
+ BaseDialogRunner.run_dialog(ui, self.config)
166
+ return self.result
167
+
168
+ def build_ui(self, app):
169
+ raise NotImplementedError
@@ -0,0 +1,337 @@
1
+ from DialogsPlus.widgets.base import BaseDialog, filedialog, BooleanVar, IntVar
2
+ import time
3
+
4
+
5
+
6
+ class InputDialog(BaseDialog):
7
+
8
+ def __init__(self, prompt, default="", config=None, is_error=False):
9
+ super().__init__(config)
10
+ self.prompt = prompt
11
+ self.default = default
12
+ self.is_error = is_error
13
+
14
+ def build_ui(self, app):
15
+ frame = self.create_frame(app)
16
+ frame.pack(pady=8)
17
+
18
+ label = self.create_label(frame, text=self.prompt)
19
+ label.pack()
20
+
21
+ entry_frame = self.create_frame(app)
22
+ entry_frame.pack(pady=8)
23
+ entry = self.create_entry(entry_frame)
24
+ entry.insert(0, self.default)
25
+ entry.pack()
26
+
27
+ def on_submit():
28
+ self.result['value'] = entry.get()
29
+ app.quit()
30
+
31
+ app.protocol("WM_DELETE_WINDOW", app.quit)
32
+ app.bind('<Return>', lambda e: on_submit())
33
+ app.bind('<Escape>', lambda e: app.quit())
34
+
35
+ entry.focus_set()
36
+
37
+ button_frame = self.create_frame(app)
38
+ button_frame.pack(pady=10)
39
+ self.create_button(button_frame, text="Submit", command=on_submit).pack()
40
+
41
+
42
+ class ManualStepDialog(BaseDialog):
43
+
44
+ def __init__(self, message, config=None):
45
+ super().__init__(config)
46
+ self.message = message
47
+
48
+ def build_ui(self, app):
49
+ def on_pass():
50
+ self.result["status"] = "pass"
51
+ app.quit()
52
+
53
+ def on_fail():
54
+ self.result["status"] = "fail"
55
+ app.quit()
56
+
57
+ app.protocol("WM_DELETE_WINDOW", app.quit)
58
+ app.bind('<Escape>', lambda e: app.quit())
59
+
60
+ self.create_label(app, text=self.message).pack(pady=25)
61
+
62
+ button_frame = self.create_frame(app)
63
+ button_frame.pack(pady=(10, self.config.spacing), expand=True)
64
+
65
+ self.create_button(
66
+ button_frame,
67
+ text="PASS",
68
+ command=on_pass).pack(side="left", padx=10)
69
+
70
+ self.create_button(
71
+ button_frame,
72
+ text="FAIL",
73
+ command=on_fail).pack(side="left", padx=10)
74
+
75
+
76
+ class CountdownDialog(BaseDialog):
77
+
78
+ def __init__(self, seconds, message="Please wait...", config=None):
79
+ super().__init__(config)
80
+ self.seconds = seconds
81
+ self.message = message
82
+
83
+ def build_ui(self, app):
84
+
85
+ label = self.create_label(app, text="")
86
+ label.place(relx=0.5, rely=0.4, anchor="center")
87
+
88
+ progress = self.create_progress_bar(app)
89
+ progress.place(relx=0.5, rely=0.8, anchor="center")
90
+ progress.set(0)
91
+
92
+ start_time = time.perf_counter()
93
+
94
+ def update():
95
+ elapsed = time.perf_counter() - start_time
96
+ remaining = self.seconds - elapsed
97
+
98
+ if remaining > 0:
99
+ mins, secs = divmod(int(remaining), 60)
100
+ label.configure(text=f"{self.message}\n{mins:02}:{secs:02}")
101
+ progress.set(min(elapsed / self.seconds, 1))
102
+ app.after(100, update)
103
+ else:
104
+ progress.set(1)
105
+ label.configure(text=f"{self.message}\n00:00")
106
+ app.quit()
107
+
108
+ update()
109
+
110
+
111
+ class ConfirmationDialog(BaseDialog):
112
+ def __init__(self, message, default="Yes", config=None):
113
+ super().__init__(config)
114
+ self.message = message
115
+ self.default = default
116
+
117
+ def build_ui(self, app):
118
+
119
+ def on_yes():
120
+ self.result["status"] = "yes"
121
+ app.quit()
122
+
123
+ def on_no():
124
+ self.result["status"] = "no"
125
+ app.quit()
126
+
127
+ def on_cancel():
128
+ self.result["status"] = "cancel"
129
+ app.quit()
130
+
131
+ app.protocol("WM_DELETE_WINDOW", app.quit)
132
+ app.bind('<Escape>', lambda e: app.quit())
133
+
134
+ self.create_label(app, text=self.message).pack(pady=25)
135
+
136
+ button_frame = self.create_frame(app)
137
+ button_frame.pack(pady=(10, self.config.spacing), expand=True)
138
+
139
+ self.create_button(
140
+ button_frame,
141
+ text="Yes",
142
+ command=on_yes).pack(side="left", padx=5)
143
+
144
+ self.create_button(
145
+ button_frame,
146
+ text="No",
147
+ command=on_no).pack(side="left", padx=5)
148
+
149
+ self.create_button(
150
+ button_frame,
151
+ text="Cancel",
152
+ command=on_cancel).pack(side="left", padx=5)
153
+
154
+
155
+
156
+
157
+ class MultiValueInputDialog(BaseDialog):
158
+ def __init__(self, fields, defaults=None, config=None):
159
+ super().__init__(config)
160
+
161
+ # Normalize string input to list
162
+ self.fields = fields if isinstance(fields, list) else [fields]
163
+ self.defaults = defaults or {}
164
+ self.entries = {}
165
+
166
+ def build_ui(self, app):
167
+ def on_submit():
168
+ self.result = {field: self.entries[field].get() for field in self.fields}
169
+ self.result["status"] = "pass"
170
+ app.quit()
171
+
172
+ def on_cancel():
173
+ self.result["status"] = "fail"
174
+ app.quit()
175
+
176
+ app.protocol("WM_DELETE_WINDOW", on_cancel)
177
+ app.bind('<Escape>', lambda e: on_cancel())
178
+ app.bind('<Return>', lambda e: on_submit())
179
+
180
+ main_frame = self.create_frame(app)
181
+ main_frame.pack(fill="both", expand=True, padx=10, pady=10)
182
+
183
+ title = self.create_label(main_frame, text="Enter values")
184
+ title.pack(pady=25)
185
+
186
+ fields_frame = self.create_frame(main_frame)
187
+ fields_frame.pack(fill="both", expand=True)
188
+
189
+ for field in self.fields:
190
+ row_frame = self.create_frame(fields_frame)
191
+ row_frame.pack(fill="x", pady=5)
192
+
193
+ label = self.create_label(row_frame, text=field)
194
+ label.pack(side="left", padx=(0, 10))
195
+
196
+ entry = self.create_entry(row_frame)
197
+ entry.insert(0, self.defaults.get(field, ""))
198
+ entry.pack(side="left", fill="x", expand=True)
199
+
200
+ self.entries[field] = entry
201
+
202
+ # Buttons
203
+ button_frame = self.create_frame(app)
204
+ button_frame.place(relx=0.5, rely=0.8, anchor="center")
205
+
206
+ submit_btn = self.create_button(button_frame, text="Submit", command=on_submit)
207
+ submit_btn.pack(side="left", padx=5)
208
+
209
+ cancel_btn = self.create_button(button_frame, text="Cancel", command=on_cancel)
210
+ cancel_btn.pack(side="left", padx=5)
211
+
212
+
213
+
214
+
215
+
216
+ class FileDialog(BaseDialog):
217
+ def __init__(self, message="", filetypes=None, multiple=False, config=None):
218
+ super().__init__(config)
219
+ self.title = self.config.title
220
+ self.message = message
221
+ self.filetypes = filetypes or [("All files", "*.*")]
222
+ self.multiple = multiple
223
+
224
+ def build_ui(self, app):
225
+ # app.withdraw() # Hide the main window
226
+
227
+ def on_browse():
228
+ if self.multiple:
229
+ files = filedialog.askopenfilenames(title=self.title, filetypes=self.filetypes)
230
+ self.result['files'] = list(files) if files else None
231
+
232
+ else:
233
+ file = filedialog.askopenfilename(title=self.title, filetypes=self.filetypes)
234
+ self.result['file'] = file if file else None
235
+
236
+ app.quit()
237
+
238
+ app.protocol("WM_DELETE_WINDOW", app.quit)
239
+ app.bind('<Escape>', lambda e: app.quit())
240
+
241
+ self.create_label(app, text=self.message).pack(pady=25)
242
+
243
+ button_frame = self.create_frame(app)
244
+ button_frame.pack(pady=(10, self.config.spacing), expand=True)
245
+
246
+
247
+ self.create_button(
248
+ button_frame,
249
+ text="Browse",
250
+ command=on_browse).pack(side="bottom", padx=10)
251
+
252
+
253
+ class FolderDialog(BaseDialog):
254
+ def __init__(self, message, config=None):
255
+ super().__init__(config)
256
+ self.title = self.config.title
257
+ self.message = message
258
+
259
+ def build_ui(self, app):
260
+ #app.withdraw() # Hide the main window
261
+
262
+ def on_browser_folder():
263
+ folder = filedialog.askdirectory(title=self.title)
264
+ self.result['folder'] = folder if folder else None
265
+ app.quit()
266
+
267
+ app.protocol("WM_DELETE_WINDOW", app.quit)
268
+ app.bind('<Escape>', lambda e: app.quit())
269
+
270
+ self.create_label(app, text=self.message).pack(pady=25)
271
+
272
+ button_frame = self.create_frame(app)
273
+ button_frame.pack(pady=(10, self.config.spacing), expand=True)
274
+
275
+
276
+ self.create_button(
277
+ button_frame,
278
+ text="Browse",
279
+ command=on_browser_folder).pack(side="bottom", padx=10)
280
+
281
+
282
+ class CheckboxConfirmationDialog(BaseDialog):
283
+ def __init__(self, message, checkbox_text="I agree", config=None):
284
+ super().__init__(config)
285
+ self.message = message
286
+ self.checkbox_text = checkbox_text
287
+
288
+ def build_ui(self, app):
289
+ checkbox_var = BooleanVar(value=False)
290
+
291
+ def on_submit():
292
+ self.result['confirmed'] = checkbox_var.get()
293
+ app.quit()
294
+
295
+ app.protocol("WM_DELETE_WINDOW", app.quit)
296
+ app.bind('<Escape>', lambda e: app.quit())
297
+ app.bind('<Return>', lambda e: on_submit())
298
+
299
+ self.create_label(app, text=self.message).pack(pady=10)
300
+
301
+ self.create_checkbox(app, text=self.checkbox_text, variable=checkbox_var).pack(pady=10)
302
+
303
+ self.create_button(app, text="Submit", command=on_submit).pack(pady=10)
304
+
305
+
306
+ class MultiCheckboxDialog(BaseDialog):
307
+ def __init__(self, message, options, defaults=None, config=None):
308
+ super().__init__(config)
309
+ self.message = message
310
+ self.options = options if isinstance(options, list) else options.split('|')
311
+ self.defaults = defaults or []
312
+ self.checkbox_vars = {}
313
+
314
+ def build_ui(self, app):
315
+ def on_submit():
316
+ self.result = {option: var.get() for option, var in self.checkbox_vars.items()}
317
+ app.quit()
318
+
319
+ app.protocol("WM_DELETE_WINDOW", app.quit)
320
+ app.bind('<Escape>', lambda e: app.quit())
321
+ app.bind('<Return>', lambda e: on_submit())
322
+
323
+ self.create_label(app, text=self.message).pack(pady=20)
324
+
325
+ # Checkboxes frame
326
+ checkbox_frame = self.create_frame(app)
327
+ checkbox_frame.pack(pady=10, padx=20, fill="both", expand=True)
328
+
329
+ for option in self.options:
330
+ var = BooleanVar(value=(option in self.defaults))
331
+ self.checkbox_vars[option] = var
332
+
333
+ self.create_checkbox(checkbox_frame, text=option, variable=var).pack(
334
+ anchor="w", pady=5, padx=10
335
+ )
336
+
337
+ self.create_button(app, text="Submit", command=on_submit).pack(pady=20)
@@ -0,0 +1,118 @@
1
+ from DialogsPlus.widgets.styling import ( InputDialog,
2
+ ManualStepDialog,
3
+ CountdownDialog,
4
+ ConfirmationDialog,
5
+ MultiValueInputDialog,
6
+ FileDialog,
7
+ FolderDialog,
8
+ CheckboxConfirmationDialog,
9
+ MultiCheckboxDialog)
10
+ from robot.api import logger
11
+ from robot.errors import ExecutionFailed
12
+
13
+
14
+ class GetValueFromUserDialog:
15
+ @staticmethod
16
+ def show(prompt="Enter value:", default="", config=None):
17
+ dialog = InputDialog(prompt, default, config)
18
+ return dialog.show().get('value')
19
+
20
+
21
+ class ExecuteManualStepDialog:
22
+ @staticmethod
23
+ def show(message="Please perform the step and confirm.", config=None):
24
+ logger.info(message)
25
+
26
+ dialog = ManualStepDialog(message, config)
27
+ result = dialog.show()
28
+
29
+ if result.get("status") == "pass":
30
+ return
31
+ else:
32
+ failure_dialog = InputDialog("Test Failed - Reason:", "", config, is_error=True)
33
+ reason = failure_dialog.show().get('value', 'No reason provided')
34
+ logger.error(f"{message} | Reason: {reason}")
35
+ raise ExecutionFailed(reason)
36
+
37
+ @staticmethod
38
+ def run_steps(steps, config=None):
39
+ if isinstance(steps, str):
40
+ ExecuteManualStepDialog.show(steps, config)
41
+ elif isinstance(steps, list):
42
+ for step in steps:
43
+ ExecuteManualStepDialog.show(step, config)
44
+ else:
45
+ raise ExecutionFailed("Invalid input: must be a string or a list of strings.")
46
+
47
+
48
+ class CountdownDialogRunner:
49
+ @staticmethod
50
+ def show(seconds=10, message="Please wait...", config=None):
51
+ logger.info(f"Starting countdown for {seconds} seconds...")
52
+ dialog = CountdownDialog(seconds, message, config)
53
+ dialog.show()
54
+
55
+
56
+ class GetConfirmationFromUser:
57
+ @staticmethod
58
+ def show(message="Are you sure?", default="Yes", config=None):
59
+ logger.info(message)
60
+ dialog = ConfirmationDialog(message, default, config)
61
+ dialog.config.width = 450
62
+ result = dialog.show().get("status")
63
+
64
+ if result == "yes":
65
+ return True
66
+ elif result == "no":
67
+ return False
68
+ else: # cancel
69
+ return None
70
+
71
+
72
+ class MultiValueInput:
73
+ @staticmethod
74
+ def show(fields, defaults=None, config=None):
75
+ logger.info(f"Showing input dialog for: {fields}")
76
+ dialog = MultiValueInputDialog(fields, defaults=defaults, config=config)
77
+ result = dialog.show()
78
+ if result.get("status") == "pass":
79
+ return result
80
+ else:
81
+ return None
82
+
83
+ @staticmethod
84
+ def run_multival(fields, config=None, defaults=None):
85
+ return MultiValueInput.show(fields, defaults=defaults, config=config)
86
+
87
+ class ChooseFromFileDialog:
88
+ @staticmethod
89
+ def show(message="", filetypes=None, multiple=False, config=None):
90
+ logger.info(f"Opening file picker: {message}")
91
+ dialog = FileDialog(message, filetypes, multiple, config)
92
+ result = dialog.show()
93
+ return result.get('files' if multiple else 'file')
94
+
95
+
96
+ class ChooseFolderDialog:
97
+ @staticmethod
98
+ def show(message, config=None):
99
+ logger.info(f"Opening folder picker: {message}")
100
+ dialog = FolderDialog(message, config)
101
+ return dialog.show().get('folder')
102
+
103
+
104
+ class ConfirmWithCheckbox:
105
+ @staticmethod
106
+ def show(message, checkbox_text="I agree", config=None):
107
+ logger.info(f"Showing checkbox confirmation: {message}")
108
+ dialog = CheckboxConfirmationDialog(message, checkbox_text, config)
109
+ result = dialog.show()
110
+ return result.get('confirmed', False)
111
+
112
+
113
+ class SelectOptionsWithCheckboxes:
114
+ @staticmethod
115
+ def show(message, options, defaults=None, config=None):
116
+ logger.info(f"Showing multi-checkbox dialog: {message}")
117
+ dialog = MultiCheckboxDialog(message, options, defaults, config)
118
+ return dialog.show()
@@ -0,0 +1,37 @@
1
+ Metadata-Version: 2.4
2
+ Name: robotframework-dialogsplus
3
+ Version: 0.1.0
4
+ Summary: A drop-in enhancement for Robot Framework's Dialogs library with modern UI and extended user interaction keywords.
5
+ Author-email: Alpha-Centauri-00 <Alpha@Centauri.c0m>
6
+ License-Expression: MIT
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: customtkinter>=5.2.2
9
+ Requires-Dist: pyyaml>=6.0.3
10
+ Requires-Dist: robotframework>=7.3.2
11
+ Description-Content-Type: text/markdown
12
+
13
+ # DialogsPlus
14
+ A drop-in enhancement for Robot Framework's Dialogs library with modern UI and extended user interaction keywords.
15
+
16
+ ### ⚠️ Known Limitations
17
+
18
+ - Not supported in headless environments such as CI/CD pipelines (e.g., Jenkins, GitHub Actions)
19
+
20
+
21
+ **DialogsPlus** is a user-friendly and fully customizable dialog library for [Robot Framework](https://robotframework.org/), built on top of [`customtkinter`](https://github.com/TomSchimansky/CustomTkinter). It extends the built-in dialog functionality with rich GUI dialogs that are **stylish**, **modern**, and **configurable** via a simple `config.yaml` file.
22
+
23
+ ---
24
+
25
+ ## ✨ Features
26
+
27
+ - Easy-to-use dialogs for user interaction during test runs
28
+ - Full GUI-based interface using `customtkinter`
29
+ - Customizable look and feel (colors, fonts, sizes, etc.) via `config.yaml`
30
+ - Drop-in replacement for standard Robot Framework dialogs
31
+ - Supports dynamic sizing based on input fields or options
32
+
33
+ ---
34
+
35
+
36
+
37
+ Pull requests are welcome! More info coming soon.
@@ -0,0 +1,11 @@
1
+ DialogsPlus/__init__.py,sha256=bj2en4W8oOadu0BgP-BMmhOB1woGUFfNYRyQIffTCWI,102
2
+ DialogsPlus/dialogsplus.py,sha256=EEZpMYvu8Evu3V_Xy-0hNl5UsaCMa4VWJUycm59wZG0,3630
3
+ DialogsPlus/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ DialogsPlus/utils/config.py,sha256=aPoNgC5RPOtp6yEZiNQkj-qhyLTo391eEscqjL2w9_M,2864
5
+ DialogsPlus/widgets/base.py,sha256=lyTnRtnE1aI0rCrqG6a17kIjcFNcacTZr3rmQ0zmCm0,5929
6
+ DialogsPlus/widgets/styling.py,sha256=LL-O-gPUw3I_QIqn05BK02SEE4qnVt9beNV_nQjmMXk,10981
7
+ DialogsPlus/widgets/wrappers.py,sha256=oVa-ScnDNr8IdGv2aR_HKflL5DhCeqVlO-Ix5C5snS8,4352
8
+ DialogsPlus/widgets/assets/robot.ico,sha256=Ikez3We-xWB3jvB7WMXB5mpgxUx57ebZbR1hcmBkoZk,7744
9
+ robotframework_dialogsplus-0.1.0.dist-info/METADATA,sha256=ZfSv2NzmNoYgbrl71LrEOKpsDSlEBn9uQi5yE6F-mlA,1443
10
+ robotframework_dialogsplus-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
11
+ robotframework_dialogsplus-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any