Skip to content

Commit 7f7def6

Browse files
author
Jan Kostulski
committed
better gui
1 parent e73496f commit 7f7def6

9 files changed

+256
-1
lines changed

.gitignore

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@
22
.vscode/**
33
.env
44
.history/**
5-
.venv2/**
5+
.venv2/**
6+
.venv_linux/**
7+
build/**
8+
dist/**
9+
.gitignore

build_win.bat

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pyinstaller custom_tkinter.py

custom_tkinter.py

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import customtkinter as ctk
2+
3+
# Initialize the main window
4+
root = ctk.CTk()
5+
root.title("CustomTkinter Demo")
6+
root.geometry("600x400")
7+
8+
# Function to update label
9+
def update_label():
10+
label.config(text="Button Clicked!")
11+
12+
# # Create a label
13+
# label = ctk.CTkLabel(root, text="Welcome to the CustomTkinter GUI!", text_font=("Arial", 16))
14+
# label.pack(pady=20)
15+
16+
# Create a button
17+
button = ctk.CTkButton(root, text="Click Me!", command=update_label)
18+
button.pack(pady=10)
19+
20+
# Create an entry widget
21+
entry = ctk.CTkEntry(root, width=200, placeholder_text="Type here")
22+
entry.pack(pady=10)
23+
24+
# Create a checkbox
25+
checkbox = ctk.CTkCheckBox(root, text="Check Me!")
26+
checkbox.pack(pady=10)
27+
28+
# Create a switch
29+
switch = ctk.CTkSwitch(root, text="Toggle Switch")
30+
switch.pack(pady=10)
31+
32+
# Create a dropdown menu
33+
options = ["Option A", "Option B", "Option C"]
34+
dropdown = ctk.CTkOptionMenu(root, values=options)
35+
dropdown.pack(pady=10)
36+
37+
# Start the GUI loop
38+
root.mainloop()

json-schemas/schema1.json

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"$id": "https://example.com/employee.schema.json",
4+
"title": "Record of employee",
5+
"description": "This document records the details of an employee",
6+
"type": "object",
7+
"additionalProperties": false,
8+
"properties": {
9+
"id": {
10+
"description": "A unique identifier for an employee",
11+
"type": "integer"
12+
},
13+
"name": {
14+
"description": "Full name of the employee",
15+
"type": "string"
16+
},
17+
"age": {
18+
"description": "Age of the employee",
19+
"type": "number"
20+
},
21+
"hobbies": {
22+
"description": "Hobbies of the employee",
23+
"type": "object",
24+
"properties": {
25+
"indoor": {
26+
"description": "List of indoor hobbies",
27+
"type": "string"
28+
},
29+
"outdoor": {
30+
"description": "List of outdoor hobbies",
31+
"type": "string"
32+
}
33+
}
34+
}
35+
}
36+
}

json-schemas/schema2.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"$schema": "http://json-schema.org/draft-04/schema#",
3+
"$id": "https://example.com/employee.schema.json",
4+
"title": "small json",
5+
"description": "This document records the details of an employee",
6+
"type": "object",
7+
"additionalProperties": false,
8+
"properties": {
9+
"namebla": {
10+
"description": "the name",
11+
"type": "string"
12+
},
13+
"lastname": {
14+
"description": "the lastname",
15+
"type": "string"
16+
}
17+
}
18+
}

requirements-dev.txt

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
python-dotenv==1.0.0
2+
customtkinter==5.2.1
3+
GitPython==3.1.40
4+
jsonschema==3.2.0
5+
packaging==23.2
6+
pyinstaller==6.2.0

requirements.txt

+3
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
python-dotenv==1.0.0
2+
customtkinter==5.2.1
23
GitPython==3.1.40
4+
jsonschema==3.2.0
5+
packaging==23.2

simple-git-editor.py

+133
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
import tkinter as tk
33
from tkinter import messagebox, ttk
44
import tkinter.filedialog as filedialog
5+
import tkinter.messagebox as messagebox
56
import os
67
import subprocess
78

89
from dotenv import load_dotenv
910
from git import Repo
1011
import socket
12+
import json
13+
from jsonschema import validate, ValidationError
1114

1215
load_dotenv()
1316

@@ -17,12 +20,16 @@
1720
file_extensions = os.getenv("file_extensions")
1821
search_folders = os.getenv("search_folders")
1922
repo_path = './repo'
23+
json_schema_path = "./json-schemas"
24+
2025

2126
class GitGUIApp:
2227
def __init__(self, root):
2328
self.root = root
2429
self.root.title(app_name)
2530

31+
self.json_schemas = self.load_json_schemas(json_schema_path)
32+
2633
# Frames
2734
left_frame = tk.Frame(root)
2835
left_frame.pack(side=tk.TOP, padx=10, pady=10, fill=tk.X)
@@ -82,6 +89,15 @@ def get_files(self):
8289

8390
self.populate_list(repo_path)
8491

92+
def load_json_schemas(self, schema_dir):
93+
schemas = {}
94+
# todo: check if folder exist
95+
for file in os.listdir(schema_dir):
96+
if file.endswith('.json'):
97+
with open(os.path.join(schema_dir, file)) as schema_file:
98+
schemas[file] = json.load(schema_file)
99+
return schemas
100+
85101
def populate_list(self, repo_path):
86102
extensions = os.environ.get('file_extensions', '')
87103
valid_extensions = extensions.split(';')
@@ -129,6 +145,17 @@ def on_file_click(self, event):
129145
filename = self.file_list.item(item, 'values')[1]
130146
file_path = os.path.join(repo_path, filename)
131147

148+
if filename.endswith('.json'):
149+
with open(file_path) as json_file:
150+
json_data = json.load(json_file)
151+
for _, schema in self.json_schemas.items():
152+
try:
153+
validate(instance=json_data, schema=schema)
154+
self.show_json_dialog(json_data, schema, file_path)
155+
return
156+
except ValidationError:
157+
pass
158+
132159
if os.path.isfile(file_path):
133160
if platform.system() == "Windows":
134161
os.startfile(file_path)
@@ -137,6 +164,112 @@ def on_file_click(self, event):
137164
else: # Linux
138165
subprocess.call(["xdg-open", file_path])
139166

167+
def show_json_dialog(self, json_data, schema, file_path):
168+
dialog = tk.Toplevel(self.root)
169+
dialog.title(schema.get('title', 'JSON Data'))
170+
171+
# Create a canvas and a scrollbar
172+
canvas = tk.Canvas(dialog)
173+
scrollbar = tk.Scrollbar(dialog, orient="vertical", command=canvas.yview)
174+
scrollable_frame = tk.Frame(canvas)
175+
176+
# Configure canvas
177+
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
178+
canvas.configure(yscrollcommand=scrollbar.set)
179+
180+
def on_configure(event):
181+
canvas.configure(scrollregion=canvas.bbox("all"))
182+
183+
scrollable_frame.bind("<Configure>", on_configure)
184+
185+
self.widget_references = {}
186+
187+
row = 0
188+
for prop, details in schema.get('properties', {}).items():
189+
label = tk.Label(scrollable_frame, text=prop)
190+
label.grid(row=row, column=0, sticky='ew', padx=10, pady=5)
191+
192+
value = json_data.get(prop, '')
193+
194+
if isinstance(value, dict):
195+
nested_row = 0
196+
frame = tk.Frame(scrollable_frame)
197+
frame.grid(row=row, column=1, padx=10, pady=5, sticky='ew')
198+
for nested_prop, nested_details in details['properties'].items():
199+
nested_label = tk.Label(frame, text=nested_prop)
200+
nested_label.grid(row=nested_row, column=0, sticky='w', padx=10, pady=2)
201+
nested_text = tk.Text(frame, height=3, wrap='word')
202+
nested_text.insert('end', str(value.get(nested_prop, '')))
203+
nested_text.grid(row=nested_row, column=1, padx=10, pady=2, sticky='ew')
204+
frame.grid_columnconfigure(1, weight=1)
205+
nested_row += 1
206+
self.widget_references[prop] = nested_text
207+
else:
208+
text = tk.Text(scrollable_frame, height=3, wrap='word')
209+
text.insert('end', str(value))
210+
text.grid(row=row, column=1, padx=10, pady=5, sticky='ew')
211+
scrollable_frame.grid_columnconfigure(1, weight=1)
212+
self.widget_references[prop] = text
213+
214+
row += 1
215+
216+
# Save button
217+
save_button = tk.Button(dialog, text="Save", command=lambda: self.save_json_data(json_data, schema, file_path))
218+
save_button.pack()
219+
220+
canvas.pack(side="left", fill="both", expand=True)
221+
scrollbar.pack(side="right", fill="y")
222+
223+
def save_json_data(self, original_data, schema, file_path):
224+
def parse_input(data, schema_properties, parent=None):
225+
for prop, details in schema_properties.items():
226+
if details.get('type') == 'object':
227+
# Handle nested object
228+
nested_data = data.get(prop, {})
229+
parse_input(nested_data, details['properties'], prop)
230+
data[prop] = nested_data
231+
else:
232+
widget_key = f"{parent}.{prop}" if parent else prop
233+
widget = self.widget_references.get(widget_key)
234+
235+
if widget:
236+
input_value = widget.get("1.0", "end-1c") # Get text from Text widget
237+
238+
# Convert input value to the correct type based on the schema
239+
if details.get('type') == 'number':
240+
try:
241+
input_value = float(input_value)
242+
except ValueError:
243+
messagebox.showerror("Validation Error", f"Value for '{prop}' should be a number.")
244+
return False
245+
elif details.get('type') == 'integer':
246+
try:
247+
input_value = int(input_value)
248+
except ValueError:
249+
messagebox.showerror("Validation Error", f"Value for '{prop}' should be a integer.")
250+
return False
251+
# Add other type conversions as needed
252+
253+
data[prop] = input_value
254+
return True
255+
256+
# Create a copy of the original data to modify
257+
updated_data = original_data.copy()
258+
259+
if not parse_input(updated_data, schema.get('properties', {})):
260+
return # Return early if validation fails
261+
262+
# Validate updated data
263+
try:
264+
validate(instance=updated_data, schema=schema)
265+
# Write back to file if validation passes
266+
with open(file_path, 'w') as json_file:
267+
json.dump(updated_data, json_file, indent=4)
268+
messagebox.showinfo("Success", "Data saved successfully.")
269+
except ValidationError as e:
270+
messagebox.showerror("Validation Error", str(e))
271+
272+
140273
def save_files(self):
141274
if not os.path.exists(repo_path):
142275
messagebox.showerror("Error", "Repository not cloned.")

test1.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import customtkinter
2+
3+
customtkinter.set_appearance_mode("System") # Modes: system (default), light, dark
4+
customtkinter.set_default_color_theme("blue") # Themes: blue (default), dark-blue, green
5+
6+
app = customtkinter.CTk() # create CTk window like you do with the Tk window
7+
app.geometry("400x240")
8+
9+
def button_function():
10+
print("button pressed")
11+
12+
# Use CTkButton instead of tkinter Button
13+
button = customtkinter.CTkButton(master=app, text="CTkButton", command=button_function)
14+
button.place(relx=0.5, rely=0.5, anchor=customtkinter.CENTER)
15+
16+
app.mainloop()

0 commit comments

Comments
 (0)