robotframework-dialogsplus 0.1.0__tar.gz
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.
- robotframework_dialogsplus-0.1.0/.gitignore +78 -0
- robotframework_dialogsplus-0.1.0/.python-version +1 -0
- robotframework_dialogsplus-0.1.0/PKG-INFO +37 -0
- robotframework_dialogsplus-0.1.0/README.md +25 -0
- robotframework_dialogsplus-0.1.0/atests/config.yaml +14 -0
- robotframework_dialogsplus-0.1.0/atests/testing.robot +87 -0
- robotframework_dialogsplus-0.1.0/pyproject.toml +31 -0
- robotframework_dialogsplus-0.1.0/src/DialogsPlus/__init__.py +5 -0
- robotframework_dialogsplus-0.1.0/src/DialogsPlus/dialogsplus.py +90 -0
- robotframework_dialogsplus-0.1.0/src/DialogsPlus/py.typed +0 -0
- robotframework_dialogsplus-0.1.0/src/DialogsPlus/utils/config.py +83 -0
- robotframework_dialogsplus-0.1.0/src/DialogsPlus/widgets/assets/robot.ico +0 -0
- robotframework_dialogsplus-0.1.0/src/DialogsPlus/widgets/base.py +169 -0
- robotframework_dialogsplus-0.1.0/src/DialogsPlus/widgets/styling.py +337 -0
- robotframework_dialogsplus-0.1.0/src/DialogsPlus/widgets/wrappers.py +118 -0
- robotframework_dialogsplus-0.1.0/uv.lock +291 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
### Python ###
|
|
2
|
+
# Byte-compiled / optimized / DLL files
|
|
3
|
+
__pycache__/
|
|
4
|
+
*.py[cod]
|
|
5
|
+
*$py.class
|
|
6
|
+
|
|
7
|
+
# Distribution / packaging
|
|
8
|
+
.Python
|
|
9
|
+
build/
|
|
10
|
+
develop-eggs/
|
|
11
|
+
dist/
|
|
12
|
+
downloads/
|
|
13
|
+
eggs/
|
|
14
|
+
.eggs/
|
|
15
|
+
lib/
|
|
16
|
+
lib64/
|
|
17
|
+
parts/
|
|
18
|
+
sdist/
|
|
19
|
+
var/
|
|
20
|
+
wheels/
|
|
21
|
+
share/python-wheels/
|
|
22
|
+
*.egg-info/
|
|
23
|
+
.installed.cfg
|
|
24
|
+
*.egg
|
|
25
|
+
MANIFEST
|
|
26
|
+
|
|
27
|
+
# PyInstaller
|
|
28
|
+
# Usually these files are written by a python script from a template
|
|
29
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
30
|
+
*.manifest
|
|
31
|
+
*.spec
|
|
32
|
+
|
|
33
|
+
# Installer logs
|
|
34
|
+
pip-log.txt
|
|
35
|
+
pip-delete-this-directory.txt
|
|
36
|
+
|
|
37
|
+
# Unit test / coverage reports
|
|
38
|
+
utests/.pytest_cache/
|
|
39
|
+
utests/.coverage
|
|
40
|
+
utests/coverage.xml
|
|
41
|
+
utests/htmlcov/
|
|
42
|
+
|
|
43
|
+
# Translations
|
|
44
|
+
*.mo
|
|
45
|
+
*.pot
|
|
46
|
+
|
|
47
|
+
# IPython
|
|
48
|
+
profile_default/
|
|
49
|
+
ipython_config.py
|
|
50
|
+
|
|
51
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
52
|
+
__pypackages__/
|
|
53
|
+
|
|
54
|
+
# Environments
|
|
55
|
+
.venv
|
|
56
|
+
|
|
57
|
+
# mypy
|
|
58
|
+
.mypy_cache/
|
|
59
|
+
.dmypy.json
|
|
60
|
+
dmypy.json
|
|
61
|
+
|
|
62
|
+
# Pyre type checker
|
|
63
|
+
.pyre/
|
|
64
|
+
|
|
65
|
+
# pytype static type analyzer
|
|
66
|
+
.pytype/
|
|
67
|
+
|
|
68
|
+
# Cython debug symbols
|
|
69
|
+
cython_debug/
|
|
70
|
+
|
|
71
|
+
# ruff
|
|
72
|
+
.ruff_cache/
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Robot Framework output files in atests/
|
|
77
|
+
results
|
|
78
|
+
.vscode
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -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,25 @@
|
|
|
1
|
+
# DialogsPlus
|
|
2
|
+
A drop-in enhancement for Robot Framework's Dialogs library with modern UI and extended user interaction keywords.
|
|
3
|
+
|
|
4
|
+
### ⚠️ Known Limitations
|
|
5
|
+
|
|
6
|
+
- Not supported in headless environments such as CI/CD pipelines (e.g., Jenkins, GitHub Actions)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
**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.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## ✨ Features
|
|
14
|
+
|
|
15
|
+
- Easy-to-use dialogs for user interaction during test runs
|
|
16
|
+
- Full GUI-based interface using `customtkinter`
|
|
17
|
+
- Customizable look and feel (colors, fonts, sizes, etc.) via `config.yaml`
|
|
18
|
+
- Drop-in replacement for standard Robot Framework dialogs
|
|
19
|
+
- Supports dynamic sizing based on input fields or options
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
Pull requests are welcome! More info coming soon.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
*** Settings ***
|
|
2
|
+
Library DialogsPlus #config=D:/robotframework-dialogsplus/atests/config.yaml
|
|
3
|
+
Library OperatingSystem
|
|
4
|
+
|
|
5
|
+
Suite Setup Set Log Level level=TRACE
|
|
6
|
+
Test Setup Log Test Case Name
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
*** Keywords ***
|
|
10
|
+
Log Test Case Name
|
|
11
|
+
[Tags] robot:flatten
|
|
12
|
+
Log <p style="background-color: #06bdb1; font-weight: bold; display: inline-block; padding: 4px;">*** Running Test: ${TEST NAME} ***</p> html=${True}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
*** Variables ***
|
|
16
|
+
|
|
17
|
+
@{fields_val} username password email phone
|
|
18
|
+
@{fields} username password
|
|
19
|
+
&{default_val} username=admin password=P@55 phone=1234567
|
|
20
|
+
&{default} username=user1 password=P@55
|
|
21
|
+
|
|
22
|
+
*** Test Cases ***
|
|
23
|
+
|
|
24
|
+
Get Value From User Default
|
|
25
|
+
${result} Get Value From User prompt=Enter your name: default=Robot framework
|
|
26
|
+
Should Be Equal ${result} Robot framework
|
|
27
|
+
|
|
28
|
+
Run Manual Steps Executes
|
|
29
|
+
${steps} Create List Open the app Click Start button Verify status
|
|
30
|
+
Run Manual Steps ${steps}
|
|
31
|
+
Log Manual steps executed successfully
|
|
32
|
+
|
|
33
|
+
Count Down Runs
|
|
34
|
+
Count Down 3
|
|
35
|
+
Log Countdown executed for 3 seconds
|
|
36
|
+
|
|
37
|
+
Get Confirmation Returns Boolean
|
|
38
|
+
${result} Get Confirmation Are you sure?
|
|
39
|
+
Should Be True isinstance(${result}, bool)
|
|
40
|
+
|
|
41
|
+
Get Multi Value
|
|
42
|
+
${result} Get Multi Value ${fields} default=${default}
|
|
43
|
+
Should Be Equal ${result}[username] user1
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
Get Multi Value Multiple Fields
|
|
47
|
+
${result} Get Multi Value ${fields_val} default=${default_val}
|
|
48
|
+
Should Be Equal ${result}[password] P@55
|
|
49
|
+
Should Be Equal ${result}[phone] 1234567
|
|
50
|
+
|
|
51
|
+
Choose Single XML File
|
|
52
|
+
${XML_FILETYPES} Evaluate [("xml files", "*.xml")]
|
|
53
|
+
${result}= Choose File message=Select Single XML File filetypes=${XML_FILETYPES}
|
|
54
|
+
Should Contain ${result} .xml
|
|
55
|
+
|
|
56
|
+
Choose Multiple HTML Files
|
|
57
|
+
${HTML_FILETYPES} Evaluate [("HTML", "*.html")]
|
|
58
|
+
${result}= Choose File message=Select Multiple HTML Files filetypes=${HTML_FILETYPES} multiple=True
|
|
59
|
+
Should Contain ${result}[0] .html
|
|
60
|
+
|
|
61
|
+
Choose Folder Test
|
|
62
|
+
${result}= Choose Folder message=Select Any Directory
|
|
63
|
+
Directory Should Exist ${result}
|
|
64
|
+
|
|
65
|
+
Single Ceckbox Test
|
|
66
|
+
${r} Confirm With Checkbox message=Do you accept the terms? checkbox_text=I accept, no matter what!
|
|
67
|
+
Should Be True ${r}
|
|
68
|
+
|
|
69
|
+
Select Many Checkbox Test
|
|
70
|
+
${r} Select Options With Checkboxes message=Select as much as you want options=${fields_val}
|
|
71
|
+
Should Not Be True ${r}[username]
|
|
72
|
+
Should Not Be True ${r}[password]
|
|
73
|
+
Should Not Be True ${r}[email]
|
|
74
|
+
Should Not Be True ${r}[phone]
|
|
75
|
+
|
|
76
|
+
Select Many Checkbox With Defaults Test
|
|
77
|
+
@{Contacts} Create List Email SMS Phone Slack Discord
|
|
78
|
+
${selected}= Select Options With Checkboxes
|
|
79
|
+
... message=Choose your preferences
|
|
80
|
+
... options=${Contacts}
|
|
81
|
+
... defaults=Email,SMS # Default Selected!
|
|
82
|
+
|
|
83
|
+
Should Be True ${selected}[Email]
|
|
84
|
+
Should Be True ${selected}[SMS]
|
|
85
|
+
Should Not Be True ${selected}[Phone]
|
|
86
|
+
Should Not Be True ${selected}[Slack]
|
|
87
|
+
Should Not Be True ${selected}[Discord]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "robotframework-dialogsplus"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "A drop-in enhancement for Robot Framework's Dialogs library with modern UI and extended user interaction keywords."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Alpha-Centauri-00", email = "Alpha@Centauri.c0m" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
|
|
12
|
+
dependencies = [
|
|
13
|
+
"customtkinter>=5.2.2",
|
|
14
|
+
"pyyaml>=6.0.3",
|
|
15
|
+
"robotframework>=7.3.2",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[build-system]
|
|
19
|
+
requires = ["hatchling"]
|
|
20
|
+
build-backend = "hatchling.build"
|
|
21
|
+
|
|
22
|
+
[tool.hatch.build.targets.wheel]
|
|
23
|
+
packages = ["src/DialogsPlus"]
|
|
24
|
+
|
|
25
|
+
[dependency-groups]
|
|
26
|
+
dev = [
|
|
27
|
+
"robotframework-tidy>=4.18.0",
|
|
28
|
+
]
|
|
29
|
+
[tool.ruff]
|
|
30
|
+
ignore = ["E722"]
|
|
31
|
+
|
|
@@ -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)
|
|
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
|
+
)
|
|
@@ -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
|