Skip to content

Commit

Permalink
date entry integration
Browse files Browse the repository at this point in the history
  • Loading branch information
e3rd committed Nov 27, 2024
1 parent 7ef7386 commit afe239c
Show file tree
Hide file tree
Showing 11 changed files with 184 additions and 104 deletions.
Binary file added asset/datetimetag_date_calendar.avif
Binary file not shown.
Binary file added asset/datetimetag_datetime.avif
Binary file not shown.
1 change: 1 addition & 0 deletions docs/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 0.7.1
* GUI scrollbars if window is bigger than the screen
* [non-interactive][mininterface.Mininterface.__enter__] session support
* [datetime](Types/#mininterface.types.DatetimeTag) support

## 0.7.0 (2024-11-08)
* hidden [`--integrate-to-system`](Overview.md#bash-completion) argument
Expand Down
8 changes: 4 additions & 4 deletions mininterface/cli_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
import yaml
from tyro import cli
from tyro._argparse_formatter import TyroArgumentParser
from tyro._singleton import NonpropagatingMissingType
from tyro._fields import NonpropagatingMissingType
# NOTE in the future versions of tyro, include that way:
# from tyro._singleton import NonpropagatingMissingType
from tyro.extras import get_parser

from .form_dict import MissingTagValue

from .auxiliary import yield_annotations, yield_defaults
from .form_dict import EnvClass
from .form_dict import EnvClass, MissingTagValue
from .tag import Tag
from .tag_factory import tag_factory
from .validators import not_empty
Expand Down
10 changes: 5 additions & 5 deletions mininterface/tag_factory.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from copy import copy
from datetime import datetime
from datetime import date, datetime, time
from pathlib import Path
from typing import Type, get_type_hints

from .auxiliary import matches_annotation

from .tag import Tag
from .type_stubs import TagCallback
from .types import CallbackTag, DateTag, PathTag
from .types import CallbackTag, DatetimeTag, PathTag


def _get_annotation_from_class_hierarchy(cls, key):
Expand All @@ -28,8 +28,8 @@ def get_type_hint_from_class_hierarchy(cls, key):
def _get_tag_type(tag: Tag) -> Type[Tag]:
if tag._is_subclass(Path):
return PathTag
if tag._is_subclass(datetime):
return DateTag
if tag._is_subclass(date) or tag._is_subclass(time):
return DatetimeTag
return Tag


Expand All @@ -39,7 +39,7 @@ def tag_fetch(tag: Tag, ref: dict | None):

def tag_assure_type(tag: Tag):
""" morph to correct class `Tag("", annotation=Path)` -> `PathTag("", annotation=Path)` """
if (type_ := _get_tag_type(tag)) is not Tag:
if (type_ := _get_tag_type(tag)) is not Tag and not isinstance(tag, type_):
return type_(annotation=tag.annotation)._fetch_from(tag)
return tag

Expand Down
132 changes: 58 additions & 74 deletions mininterface/tk_interface/date_entry.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,66 @@
import tkinter as tk
import re
from datetime import datetime
from typing import TYPE_CHECKING

try:
from tkcalendar import Calendar
except ImportError:
Calendar = None

class DateEntry(tk.Frame):
def __init__(self, master=None, **kwargs):
from ..types import DatetimeTag
if TYPE_CHECKING:
from tk_window import TkWindow


class DateEntryFrame(tk.Frame):
def __init__(self, master, tk_app: "TkWindow", tag: DatetimeTag, variable: tk.Variable, **kwargs):
super().__init__(master, **kwargs)
self.create_widgets()
self.pack(expand=True, fill=tk.BOTH)

self.tk_app = tk_app
self.tag = tag

# Date entry
self.spinbox = self.create_spinbox(variable)

# Frame holding the calendar
self.frame = tk.Frame(self)

# The calendar widget
if Calendar:
# Toggle calendar button
tk.Button(self, text="…", command=self.toggle_calendar).grid(row=0, column=1)

# Add a calendar widget
self.calendar = Calendar(self.frame, selectmode='day', date_pattern='yyyy-mm-dd')
# Bind date selection event
self.calendar.bind("<<CalendarSelected>>", self.on_date_select)
self.calendar.grid()
# Initialize calendar with the current date
self.update_calendar(self.spinbox.get(), '%Y-%m-%d %H:%M:%S.%f')
else:
self.calendar = None

self.bind_all_events()

def create_widgets(self):
self.spinbox = tk.Spinbox(self, font=("Arial", 16), width=30, wrap=True)
self.spinbox.pack(padx=20, pady=20)
self.spinbox.insert(0, datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-4])
self.spinbox.focus_set()
self.spinbox.icursor(8)
def create_spinbox(self, variable: tk.Variable):
spinbox = tk.Spinbox(self, font=("Arial", 16), width=30, wrap=True, textvariable=variable)
spinbox.grid()
if not variable.get():
spinbox.insert(0, datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-4])
spinbox.focus_set()
spinbox.icursor(8)

# Bind up/down arrow keys
self.spinbox.bind("<Up>", self.increment_value)
self.spinbox.bind("<Down>", self.decrement_value)
spinbox.bind("<Up>", self.increment_value)
spinbox.bind("<Down>", self.decrement_value)

# Bind mouse click on spinbox arrows
self.spinbox.bind("<ButtonRelease-1>", self.on_spinbox_click)
spinbox.bind("<ButtonRelease-1>", self.on_spinbox_click)

# Bind key release event to update calendar when user changes the input field
self.spinbox.bind("<KeyRelease>", self.on_spinbox_change)

# Toggle calendar button
self.toggle_button = tk.Button(self, text="Show/Hide Calendar", command=self.toggle_calendar)
self.toggle_button.pack(pady=10)

if Calendar:
self.create_calendar()
spinbox.bind("<KeyRelease>", self.on_spinbox_change)
return spinbox

def bind_all_events(self):
# Copy to clipboard with ctrl+c
Expand All @@ -51,27 +75,15 @@ def bind_all_events(self):
# Toggle calendar widget with ctrl+shift+c
self.bind_all("<Control-Shift-C>", lambda event: self.toggle_calendar())

def create_calendar(self):
# Create a frame to hold the calendar
self.frame = tk.Frame(self)
self.frame.pack(padx=20, pady=20, expand=True, fill=tk.BOTH)

# Add a calendar widget
self.calendar = Calendar(self.frame, selectmode='day', date_pattern='yyyy-mm-dd')
self.calendar.place(relwidth=0.7, relheight=0.8, anchor='n', relx=0.5)

# Bind date selection event
self.calendar.bind("<<CalendarSelected>>", self.on_date_select)

# Initialize calendar with the current date
self.update_calendar(self.spinbox.get(), '%Y-%m-%d %H:%M:%S.%f')

def toggle_calendar(self, event=None):
if Calendar:
if hasattr(self, 'frame') and self.frame.winfo_ismapped():
self.frame.pack_forget()
else:
self.frame.pack(padx=20, pady=20, expand=True, fill=tk.BOTH)
if not self.calendar:
return
if self.calendar.winfo_ismapped():
self.frame.grid_forget()
else:
self.frame.grid(row=1, column=0)
self.tk_app._refresh_size()
return

def increment_value(self, event=None):
self.change_date(1)
Expand Down Expand Up @@ -112,7 +124,8 @@ def change_date(self, delta):
split_input[part_index] = str(new_number).zfill(len(split_input[part_index]))

if time:
new_value_str = f"{split_input[0]}-{split_input[1]}-{split_input[2]} {split_input[3]}:{split_input[4]}:{split_input[5]}.{split_input[6][:2]}"
new_value_str = f"{split_input[0]}-{split_input[1]}-{split_input[2]} "\
f"{split_input[3]}:{split_input[4]}:{split_input[5]}.{split_input[6][:2]}"
string_format = '%Y-%m-%d %H:%M:%S.%f'
else:
new_value_str = f"{split_input[0]}-{split_input[1]}-{split_input[2]}"
Expand All @@ -139,9 +152,9 @@ def get_part_index(self, caret_pos, split_length):
elif split_length > 3:
if caret_pos < 14: # hour
return 3
elif caret_pos < 17: # minute
elif caret_pos < 17: # minute
return 4
elif caret_pos < 20: # second
elif caret_pos < 20: # second
return 5
else: # millisecond
return 6
Expand Down Expand Up @@ -187,7 +200,7 @@ def show_popup(self, message):
# Position the popup window in the top-left corner of the widget
x = self.winfo_rootx()
y = self.winfo_rooty()

# Position of the popup window has to be "inside" the main window or it will be focused on popup
popup.geometry(f"400x100+{x+200}+{y-150}")

Expand All @@ -197,7 +210,6 @@ def show_popup(self, message):
# Keep focus on the spinbox
self.spinbox.focus_force()


def select_all(self, event=None):
self.spinbox.selection_range(0, tk.END)
self.spinbox.focus_set()
Expand All @@ -207,31 +219,3 @@ def select_all(self, event=None):
def paste_from_clipboard(self, event=None):
self.spinbox.delete(0, tk.END)
self.spinbox.insert(0, self.clipboard_get())

if __name__ == "__main__":
root = tk.Tk()
# Get the screen width and height
# This is calculating the position of the TOTAL dimentions of all screens combined
# How to calculate the position of the window on the current screen?
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()

print(screen_width, screen_height)

# Calculate the position to center the window
x = (screen_width // 2) - 400
y = (screen_height // 2) - 600

print(x, y)

# Set the position of the window
root.geometry(f"800x600+{x}+{y}")
# keep the main widget on top all the time
root.wm_attributes("-topmost", False)
root.wm_attributes("-topmost", True)
root.title("Date Editor")

date_entry = DateEntry(root)
date_entry.pack(expand=True, fill=tk.BOTH)
root.mainloop()

23 changes: 11 additions & 12 deletions mininterface/tk_interface/utils.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
from typing import TYPE_CHECKING
from autocombobox import AutoCombobox
from pathlib import Path, PosixPath
from tkinter import Button, Entry, Label, TclError, Variable, Widget
from tkinter import Button, Entry, Label, TclError, Variable, Widget, Spinbox
from tkinter.filedialog import askopenfilename, askopenfilenames
from tkinter.ttk import Checkbutton, Combobox, Frame, Radiobutton, Widget
from typing import TYPE_CHECKING

from autocombobox import AutoCombobox

from ..types import DateTag, PathTag
from ..auxiliary import flatten, flatten_keys
from ..experimental import MININTERFACE_CONFIG, FacetCallback, SubmitButton
from ..form_dict import TagDict
from ..tag import Tag
from ..types import DatetimeTag, PathTag
from .date_entry import DateEntryFrame

if TYPE_CHECKING:
from tk_window import TkWindow
Expand Down Expand Up @@ -132,14 +133,12 @@ def _fetch(variable):
widget2 = Button(master, text='…', command=choose_file_handler(variable, tag))
widget2.grid(row=grid_info['row'], column=grid_info['column']+1)

# TODO
# Calendar
# elif isinstance(tag, DateTag):
# grid_info = widget.grid_info()
# nested_frame = Frame(master)
# nested_frame.grid(row=grid_info['row'], column=grid_info['column'])
# widget = DateEntry(nested_frame)
# widget.pack()
elif isinstance(tag, DatetimeTag):
grid_info = widget.grid_info()
nested_frame = DateEntryFrame(master, tk_app, tag, variable)
nested_frame.grid(row=grid_info['row'], column=grid_info['column'])
widget = nested_frame.spinbox

# Special type: Submit button
elif tag.annotation is SubmitButton: # NOTE EXPERIMENTAL
Expand All @@ -162,7 +161,7 @@ def inner(tag: Tag):
h = on_change_handler(variable, tag)
if isinstance(w, Combobox):
w.bind("<<ComboboxSelected>>", h)
elif isinstance(w, Entry):
elif isinstance(w, (Entry, Spinbox)):
w.bind("<FocusOut>", h)
elif isinstance(w, Checkbutton):
w.configure(command=h)
Expand Down
Loading

0 comments on commit afe239c

Please sign in to comment.