Left column: fixed width preference, auto scrollbars, pane floors
The tree and palette column took a share of every extra pixel the window gained. It is now pinned to a width set in Preferences, defaulting to 230 px, so widening the window only ever feeds the wiresheet. That fixed width brought two existing weaknesses into view: - Long paths and type names had nowhere to go. Both lists now size their tree column to the widest visible row - ttk derives the horizontal scroll range from the column width, so a column left stretching can never scroll - and carry vertical and horizontal scrollbars that leave the grid when they have nothing to scroll. - A short window squeezed the navigation tree away entirely. Both lists asked for ten rows, so the palette requested the most while the tree, carrying the larger weight, gave up its height first. The requested rows are now 6 and 5, a 300 px minimum window height keeps the shortfall shallow, and clamp_left_pane_split holds a dragged sash between the two floors. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+20
-4
@@ -19,6 +19,13 @@ DEFAULT_BOX_WIDTH = 100
|
|||||||
MIN_ALLOWED_BOX_WIDTH = 60
|
MIN_ALLOWED_BOX_WIDTH = 60
|
||||||
MAX_ALLOWED_BOX_WIDTH = 400
|
MAX_ALLOWED_BOX_WIDTH = 400
|
||||||
|
|
||||||
|
# The tree and palette column is held at this width whatever the window does, so a wider
|
||||||
|
# window only ever gives the wiresheet more room. The sash can still be dragged; this is
|
||||||
|
# the width the column is set to at startup and whenever the preference changes.
|
||||||
|
DEFAULT_LEFT_PANE_WIDTH = 230
|
||||||
|
MIN_ALLOWED_LEFT_PANE_WIDTH = 120
|
||||||
|
MAX_ALLOWED_LEFT_PANE_WIDTH = 600
|
||||||
|
|
||||||
# Widget handles, filled in by main once the UI exists
|
# Widget handles, filled in by main once the UI exists
|
||||||
window = None
|
window = None
|
||||||
canvas = None
|
canvas = None
|
||||||
@@ -70,6 +77,7 @@ def load_preferences():
|
|||||||
grid_size = DEFAULT_GRID_SIZE
|
grid_size = DEFAULT_GRID_SIZE
|
||||||
add_timestamp = DEFAULT_ADD_TIMESTAMP
|
add_timestamp = DEFAULT_ADD_TIMESTAMP
|
||||||
box_width = DEFAULT_BOX_WIDTH
|
box_width = DEFAULT_BOX_WIDTH
|
||||||
|
left_pane_width = DEFAULT_LEFT_PANE_WIDTH
|
||||||
sedona_home = ""
|
sedona_home = ""
|
||||||
|
|
||||||
if os.path.exists(PROPERTIES_FILE):
|
if os.path.exists(PROPERTIES_FILE):
|
||||||
@@ -78,6 +86,9 @@ def load_preferences():
|
|||||||
grid_size = config.getint("Editor", "grid_size", fallback=DEFAULT_GRID_SIZE)
|
grid_size = config.getint("Editor", "grid_size", fallback=DEFAULT_GRID_SIZE)
|
||||||
add_timestamp = config.getboolean("Editor", "add_timestamp", fallback=DEFAULT_ADD_TIMESTAMP)
|
add_timestamp = config.getboolean("Editor", "add_timestamp", fallback=DEFAULT_ADD_TIMESTAMP)
|
||||||
box_width = config.getint("Editor", "box_width", fallback=DEFAULT_BOX_WIDTH)
|
box_width = config.getint("Editor", "box_width", fallback=DEFAULT_BOX_WIDTH)
|
||||||
|
left_pane_width = config.getint(
|
||||||
|
"Editor", "left_pane_width", fallback=DEFAULT_LEFT_PANE_WIDTH
|
||||||
|
)
|
||||||
sedona_home = config.get("Editor", "sedona.home", fallback="").strip()
|
sedona_home = config.get("Editor", "sedona.home", fallback="").strip()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -86,7 +97,8 @@ def load_preferences():
|
|||||||
"sedona.home": "",
|
"sedona.home": "",
|
||||||
"grid_size": str(DEFAULT_GRID_SIZE),
|
"grid_size": str(DEFAULT_GRID_SIZE),
|
||||||
"add_timestamp": str(DEFAULT_ADD_TIMESTAMP),
|
"add_timestamp": str(DEFAULT_ADD_TIMESTAMP),
|
||||||
"box_width": str(DEFAULT_BOX_WIDTH)
|
"box_width": str(DEFAULT_BOX_WIDTH),
|
||||||
|
"left_pane_width": str(DEFAULT_LEFT_PANE_WIDTH)
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
with open(PROPERTIES_FILE, "w") as configfile:
|
with open(PROPERTIES_FILE, "w") as configfile:
|
||||||
@@ -95,7 +107,10 @@ def load_preferences():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
box_width = max(MIN_ALLOWED_BOX_WIDTH, min(MAX_ALLOWED_BOX_WIDTH, box_width))
|
box_width = max(MIN_ALLOWED_BOX_WIDTH, min(MAX_ALLOWED_BOX_WIDTH, box_width))
|
||||||
return sedona_home, grid_size, add_timestamp, box_width
|
left_pane_width = max(
|
||||||
|
MIN_ALLOWED_LEFT_PANE_WIDTH, min(MAX_ALLOWED_LEFT_PANE_WIDTH, left_pane_width)
|
||||||
|
)
|
||||||
|
return sedona_home, grid_size, add_timestamp, box_width, left_pane_width
|
||||||
|
|
||||||
|
|
||||||
def save_preferences():
|
def save_preferences():
|
||||||
@@ -105,7 +120,8 @@ def save_preferences():
|
|||||||
"sedona.home": SEDONA_HOME,
|
"sedona.home": SEDONA_HOME,
|
||||||
"grid_size": str(GRID_SIZE),
|
"grid_size": str(GRID_SIZE),
|
||||||
"add_timestamp": str(ADD_TIMESTAMP),
|
"add_timestamp": str(ADD_TIMESTAMP),
|
||||||
"box_width": str(BOX_WIDTH)
|
"box_width": str(BOX_WIDTH),
|
||||||
|
"left_pane_width": str(LEFT_PANE_WIDTH)
|
||||||
}
|
}
|
||||||
with open(PROPERTIES_FILE, "w") as configfile:
|
with open(PROPERTIES_FILE, "w") as configfile:
|
||||||
config.write(configfile)
|
config.write(configfile)
|
||||||
@@ -148,4 +164,4 @@ def mark_clean():
|
|||||||
canvas.config(bg="white")
|
canvas.config(bg="white")
|
||||||
|
|
||||||
|
|
||||||
SEDONA_HOME, GRID_SIZE, ADD_TIMESTAMP, BOX_WIDTH = load_preferences()
|
SEDONA_HOME, GRID_SIZE, ADD_TIMESTAMP, BOX_WIDTH, LEFT_PANE_WIDTH = load_preferences()
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import filedialog, messagebox, ttk
|
from tkinter import filedialog, font as tkfont, messagebox, ttk
|
||||||
|
|
||||||
import app_state
|
import app_state
|
||||||
import manage_kits
|
import manage_kits
|
||||||
@@ -14,6 +14,23 @@ import undo
|
|||||||
import wiresheet
|
import wiresheet
|
||||||
from sax_dom import get_component_path, read_meta_property, update_meta_property
|
from sax_dom import get_component_path, read_meta_property, update_meta_property
|
||||||
|
|
||||||
|
# Tree column measurements, in pixels. The padding covers the expander arrow and the
|
||||||
|
# cell's own inset, which sit ahead of the text ttk lets us measure.
|
||||||
|
TREE_INDENT = 20
|
||||||
|
TREE_TEXT_PADDING = 28
|
||||||
|
TREE_MIN_COLUMN_WIDTH = 40
|
||||||
|
|
||||||
|
# Rows each list asks for. This is the only real floor ttk offers on the left column's own
|
||||||
|
# split: a pane keeps its requested height, and a short window takes the shortfall from the
|
||||||
|
# panes in proportion to their weights. Left at the ten-row default the palette asked for
|
||||||
|
# so much that the tree, carrying the larger weight, gave up all of its height first.
|
||||||
|
TREE_REQUESTED_ROWS = 6
|
||||||
|
PALETTE_REQUESTED_ROWS = 5
|
||||||
|
|
||||||
|
# Below this the shortfall is deep enough to shrink the tree past a usable few rows, and
|
||||||
|
# ttk has no way to hold a pane at a floor once the room is simply not there.
|
||||||
|
MIN_WINDOW_HEIGHT = 300
|
||||||
|
|
||||||
|
|
||||||
def refresh_all_views():
|
def refresh_all_views():
|
||||||
"""Rebuilds tree and canvas from the DOM. Registered as undo/redo's view refresh."""
|
"""Rebuilds tree and canvas from the DOM. Registered as undo/redo's view refresh."""
|
||||||
@@ -550,6 +567,108 @@ def on_canvas_release(event):
|
|||||||
wiresheet.draw_links()
|
wiresheet.draw_links()
|
||||||
|
|
||||||
|
|
||||||
|
def fit_tree_column(tree):
|
||||||
|
"""Sizes a Treeview's tree column to its widest visible row.
|
||||||
|
|
||||||
|
ttk derives the horizontal scroll range from the column width, so a column left to
|
||||||
|
stretch with the widget can never scroll - long text is simply clipped. Sizing it to
|
||||||
|
the content is what gives the horizontal scrollbar something to move, and the max()
|
||||||
|
keeps a short list filling the width it has instead of leaving a bare strip.
|
||||||
|
"""
|
||||||
|
if tree is None or not tree.winfo_exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
style = ttk.Style()
|
||||||
|
text_font = tkfont.nametofont(style.lookup("Treeview", "font") or "TkDefaultFont")
|
||||||
|
try:
|
||||||
|
indent = int(style.lookup("Treeview", "indent") or TREE_INDENT)
|
||||||
|
except (ValueError, tk.TclError):
|
||||||
|
indent = TREE_INDENT
|
||||||
|
|
||||||
|
widest = 0
|
||||||
|
|
||||||
|
def measure(node, depth):
|
||||||
|
nonlocal widest
|
||||||
|
for child in tree.get_children(node):
|
||||||
|
width = depth * indent + text_font.measure(tree.item(child, "text"))
|
||||||
|
widest = max(widest, width)
|
||||||
|
# A closed branch hides its children, so they are not part of the width.
|
||||||
|
if tree.item(child, "open"):
|
||||||
|
measure(child, depth + 1)
|
||||||
|
|
||||||
|
measure("", 1)
|
||||||
|
tree.column("#0", width=max(widest + TREE_TEXT_PADDING, tree.winfo_width() - 2),
|
||||||
|
minwidth=TREE_MIN_COLUMN_WIDTH, stretch=False)
|
||||||
|
|
||||||
|
|
||||||
|
def attach_tree_scrollbars(container, tree):
|
||||||
|
"""Puts a Treeview in its container with scrollbars that show only when they help.
|
||||||
|
|
||||||
|
The tree/palette column is a fixed width now, so a deep path or a long type name has
|
||||||
|
to be reachable by scrolling sideways rather than by widening the window. A bar that
|
||||||
|
has nothing to scroll is taken out of the grid so it costs no room.
|
||||||
|
"""
|
||||||
|
vertical = ttk.Scrollbar(container, orient=tk.VERTICAL, command=tree.yview)
|
||||||
|
horizontal = ttk.Scrollbar(container, orient=tk.HORIZONTAL, command=tree.xview)
|
||||||
|
vertical_cell = {"row": 0, "column": 1, "sticky": "ns"}
|
||||||
|
horizontal_cell = {"row": 1, "column": 0, "sticky": "ew"}
|
||||||
|
|
||||||
|
def show_when_needed(bar, cell, first, last):
|
||||||
|
if float(first) <= 0.0 and float(last) >= 1.0:
|
||||||
|
bar.grid_remove()
|
||||||
|
else:
|
||||||
|
bar.grid(**cell)
|
||||||
|
bar.set(first, last)
|
||||||
|
|
||||||
|
tree.configure(
|
||||||
|
yscrollcommand=lambda first, last: show_when_needed(vertical, vertical_cell, first, last),
|
||||||
|
xscrollcommand=lambda first, last: show_when_needed(horizontal, horizontal_cell, first, last),
|
||||||
|
)
|
||||||
|
|
||||||
|
container.rowconfigure(0, weight=1)
|
||||||
|
container.columnconfigure(0, weight=1)
|
||||||
|
tree.grid(row=0, column=0, sticky="nsew")
|
||||||
|
|
||||||
|
# Resizing, and opening or closing a branch, both change what the widest row is.
|
||||||
|
tree.bind("<Configure>", lambda event: fit_tree_column(tree), add="+")
|
||||||
|
tree.bind("<<TreeviewOpen>>", lambda event: tree.after_idle(fit_tree_column, tree), add="+")
|
||||||
|
tree.bind("<<TreeviewClose>>", lambda event: tree.after_idle(fit_tree_column, tree), add="+")
|
||||||
|
|
||||||
|
|
||||||
|
def clamp_left_pane_split(event=None):
|
||||||
|
"""Keeps both halves of the left column visible, whatever the sash or the window does.
|
||||||
|
|
||||||
|
ttk lets a sash drag run to either end, and it remembers that position when the window
|
||||||
|
is made shorter afterwards, so either list can be left with no height at all. Each
|
||||||
|
pane's requested height is the line. Once the column is too short to seat both, the
|
||||||
|
floors are dropped and ttk's own proportional squeeze takes over.
|
||||||
|
"""
|
||||||
|
total = left_pane.winfo_height()
|
||||||
|
tree_floor = frame_tree.winfo_reqheight()
|
||||||
|
palette_floor = frame_palette.winfo_reqheight()
|
||||||
|
if total <= tree_floor + palette_floor:
|
||||||
|
return
|
||||||
|
|
||||||
|
position = left_pane.sashpos(0)
|
||||||
|
clamped = min(max(position, tree_floor), total - palette_floor)
|
||||||
|
if clamped != position:
|
||||||
|
left_pane.sashpos(0, clamped)
|
||||||
|
|
||||||
|
|
||||||
|
def apply_left_pane_width():
|
||||||
|
"""Pins the tree/palette column to the configured width.
|
||||||
|
|
||||||
|
Setting the sash before the paned window has been laid out collapses the column
|
||||||
|
instead of sizing it, so the startup call retries until there is a width to divide.
|
||||||
|
This is also how a changed preference reaches an already open window.
|
||||||
|
"""
|
||||||
|
app_state.window.update_idletasks()
|
||||||
|
if paned_window.winfo_width() <= 1:
|
||||||
|
paned_window.after(50, apply_left_pane_width)
|
||||||
|
return
|
||||||
|
paned_window.sashpos(0, app_state.LEFT_PANE_WIDTH)
|
||||||
|
|
||||||
|
|
||||||
# --- Dialogs ---
|
# --- Dialogs ---
|
||||||
|
|
||||||
def open_preferences_dialog():
|
def open_preferences_dialog():
|
||||||
@@ -558,7 +677,7 @@ def open_preferences_dialog():
|
|||||||
|
|
||||||
dialog = tk.Toplevel(window)
|
dialog = tk.Toplevel(window)
|
||||||
dialog.title("Preferences")
|
dialog.title("Preferences")
|
||||||
dialog.geometry("470x250")
|
dialog.geometry("470x290")
|
||||||
dialog.resizable(False, False)
|
dialog.resizable(False, False)
|
||||||
dialog.transient(window)
|
dialog.transient(window)
|
||||||
dialog.grab_set()
|
dialog.grab_set()
|
||||||
@@ -605,8 +724,15 @@ def open_preferences_dialog():
|
|||||||
entry_box_width.insert(0, str(app_state.BOX_WIDTH))
|
entry_box_width.insert(0, str(app_state.BOX_WIDTH))
|
||||||
entry_box_width.grid(row=2, column=1, sticky="ew", pady=(0, 10))
|
entry_box_width.grid(row=2, column=1, sticky="ew", pady=(0, 10))
|
||||||
|
|
||||||
|
lbl_left_pane = ttk.Label(frame_padded, text="Tree/palette width (pixels):", anchor="w")
|
||||||
|
lbl_left_pane.grid(row=3, column=0, sticky="w", pady=(0, 10), padx=(0, 10))
|
||||||
|
|
||||||
|
entry_left_pane = ttk.Entry(frame_padded)
|
||||||
|
entry_left_pane.insert(0, str(app_state.LEFT_PANE_WIDTH))
|
||||||
|
entry_left_pane.grid(row=3, column=1, sticky="ew", pady=(0, 10))
|
||||||
|
|
||||||
lbl_timestamp = ttk.Label(frame_padded, text="Save with Date-Time stamp?", anchor="w")
|
lbl_timestamp = ttk.Label(frame_padded, text="Save with Date-Time stamp?", anchor="w")
|
||||||
lbl_timestamp.grid(row=3, column=0, sticky="w", pady=(0, 15), padx=(0, 10))
|
lbl_timestamp.grid(row=4, column=0, sticky="w", pady=(0, 15), padx=(0, 10))
|
||||||
|
|
||||||
var_timestamp = tk.BooleanVar(value=app_state.ADD_TIMESTAMP)
|
var_timestamp = tk.BooleanVar(value=app_state.ADD_TIMESTAMP)
|
||||||
chk_timestamp = ttk.Checkbutton(
|
chk_timestamp = ttk.Checkbutton(
|
||||||
@@ -614,7 +740,7 @@ def open_preferences_dialog():
|
|||||||
text="Yes",
|
text="Yes",
|
||||||
variable=var_timestamp
|
variable=var_timestamp
|
||||||
)
|
)
|
||||||
chk_timestamp.grid(row=3, column=1, sticky="w", pady=(0, 15))
|
chk_timestamp.grid(row=4, column=1, sticky="w", pady=(0, 15))
|
||||||
|
|
||||||
def apply_preferences():
|
def apply_preferences():
|
||||||
try:
|
try:
|
||||||
@@ -635,6 +761,22 @@ def open_preferences_dialog():
|
|||||||
f"{app_state.MAX_ALLOWED_BOX_WIDTH} pixels."
|
f"{app_state.MAX_ALLOWED_BOX_WIDTH} pixels."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
pane_width = int(entry_left_pane.get().strip())
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError(
|
||||||
|
f"Tree/palette width must be a whole number between "
|
||||||
|
f"{app_state.MIN_ALLOWED_LEFT_PANE_WIDTH} and "
|
||||||
|
f"{app_state.MAX_ALLOWED_LEFT_PANE_WIDTH}."
|
||||||
|
)
|
||||||
|
if not (app_state.MIN_ALLOWED_LEFT_PANE_WIDTH <= pane_width
|
||||||
|
<= app_state.MAX_ALLOWED_LEFT_PANE_WIDTH):
|
||||||
|
raise ValueError(
|
||||||
|
f"Tree/palette width must be between "
|
||||||
|
f"{app_state.MIN_ALLOWED_LEFT_PANE_WIDTH} and "
|
||||||
|
f"{app_state.MAX_ALLOWED_LEFT_PANE_WIDTH} pixels."
|
||||||
|
)
|
||||||
|
|
||||||
# The width cap is applied after each box is fitted to its text, so a cap
|
# The width cap is applied after each box is fitted to its text, so a cap
|
||||||
# that is not a multiple of the grid leaves the widest boxes off-grid.
|
# that is not a multiple of the grid leaves the widest boxes off-grid.
|
||||||
proposed = wiresheet.suggest_box_width_for_grid(width, val)
|
proposed = wiresheet.suggest_box_width_for_grid(width, val)
|
||||||
@@ -652,12 +794,14 @@ def open_preferences_dialog():
|
|||||||
entry_box_width.insert(0, str(width))
|
entry_box_width.insert(0, str(width))
|
||||||
|
|
||||||
app_state.BOX_WIDTH = width
|
app_state.BOX_WIDTH = width
|
||||||
|
app_state.LEFT_PANE_WIDTH = pane_width
|
||||||
app_state.GRID_SIZE = val
|
app_state.GRID_SIZE = val
|
||||||
app_state.ADD_TIMESTAMP = var_timestamp.get()
|
app_state.ADD_TIMESTAMP = var_timestamp.get()
|
||||||
app_state.SEDONA_HOME = entry_home.get().strip()
|
app_state.SEDONA_HOME = entry_home.get().strip()
|
||||||
|
|
||||||
app_state.save_preferences()
|
app_state.save_preferences()
|
||||||
|
|
||||||
|
apply_left_pane_width()
|
||||||
wiresheet.draw_grid()
|
wiresheet.draw_grid()
|
||||||
if app_state.active_components_list:
|
if app_state.active_components_list:
|
||||||
wiresheet.render_components(app_state.active_components_list)
|
wiresheet.render_components(app_state.active_components_list)
|
||||||
@@ -671,7 +815,7 @@ def open_preferences_dialog():
|
|||||||
messagebox.showerror("Invalid Input", message, parent=dialog)
|
messagebox.showerror("Invalid Input", message, parent=dialog)
|
||||||
|
|
||||||
btn_save_pref = ttk.Button(frame_padded, text="Apply Changes", command=apply_preferences)
|
btn_save_pref = ttk.Button(frame_padded, text="Apply Changes", command=apply_preferences)
|
||||||
btn_save_pref.grid(row=4, column=1, sticky="e")
|
btn_save_pref.grid(row=5, column=1, sticky="e")
|
||||||
|
|
||||||
if not app_state.SEDONA_HOME:
|
if not app_state.SEDONA_HOME:
|
||||||
entry_home.focus_set()
|
entry_home.focus_set()
|
||||||
@@ -775,6 +919,8 @@ window = tk.Tk()
|
|||||||
app_state.window = window
|
app_state.window = window
|
||||||
window.title(app_state.base_window_title())
|
window.title(app_state.base_window_title())
|
||||||
window.geometry("950x600")
|
window.geometry("950x600")
|
||||||
|
# Width is left alone; only the height needs a floor, to keep the tree pane off zero.
|
||||||
|
window.minsize(1, MIN_WINDOW_HEIGHT)
|
||||||
|
|
||||||
# --- Context Popups Structures ---
|
# --- Context Popups Structures ---
|
||||||
canvas_context_menu = tk.Menu(window, tearoff=0)
|
canvas_context_menu = tk.Menu(window, tearoff=0)
|
||||||
@@ -876,18 +1022,15 @@ paned_window.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
|||||||
left_pane = ttk.PanedWindow(paned_window, orient=tk.VERTICAL)
|
left_pane = ttk.PanedWindow(paned_window, orient=tk.VERTICAL)
|
||||||
|
|
||||||
frame_tree = ttk.Frame(left_pane)
|
frame_tree = ttk.Frame(left_pane)
|
||||||
tree_scrollbar = ttk.Scrollbar(frame_tree)
|
|
||||||
tree_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
|
||||||
|
|
||||||
tree = ttk.Treeview(
|
tree = ttk.Treeview(
|
||||||
frame_tree,
|
frame_tree,
|
||||||
yscrollcommand=tree_scrollbar.set,
|
|
||||||
show="tree",
|
show="tree",
|
||||||
selectmode="browse",
|
selectmode="browse",
|
||||||
|
height=TREE_REQUESTED_ROWS,
|
||||||
)
|
)
|
||||||
app_state.tree = tree
|
app_state.tree = tree
|
||||||
tree.pack(fill=tk.BOTH, expand=True)
|
attach_tree_scrollbars(frame_tree, tree)
|
||||||
tree_scrollbar.config(command=tree.yview)
|
|
||||||
tree.bind("<<TreeviewSelect>>", on_tree_select)
|
tree.bind("<<TreeviewSelect>>", on_tree_select)
|
||||||
|
|
||||||
tree.bind("<ButtonPress-3>", on_tree_right_click)
|
tree.bind("<ButtonPress-3>", on_tree_right_click)
|
||||||
@@ -909,17 +1052,13 @@ palette_kit_combo.pack(fill=tk.X, padx=4)
|
|||||||
frame_palette_list = ttk.Frame(frame_palette)
|
frame_palette_list = ttk.Frame(frame_palette)
|
||||||
frame_palette_list.pack(fill=tk.BOTH, expand=True, padx=4, pady=4)
|
frame_palette_list.pack(fill=tk.BOTH, expand=True, padx=4, pady=4)
|
||||||
|
|
||||||
palette_scrollbar = ttk.Scrollbar(frame_palette_list)
|
|
||||||
palette_scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
|
||||||
|
|
||||||
palette_tree = ttk.Treeview(
|
palette_tree = ttk.Treeview(
|
||||||
frame_palette_list,
|
frame_palette_list,
|
||||||
yscrollcommand=palette_scrollbar.set,
|
|
||||||
show="tree",
|
show="tree",
|
||||||
selectmode="browse",
|
selectmode="browse",
|
||||||
|
height=PALETTE_REQUESTED_ROWS,
|
||||||
)
|
)
|
||||||
palette_tree.pack(fill=tk.BOTH, expand=True)
|
attach_tree_scrollbars(frame_palette_list, palette_tree)
|
||||||
palette_scrollbar.config(command=palette_tree.yview)
|
|
||||||
|
|
||||||
palette_status = ttk.Label(frame_palette, text="", foreground="#666666")
|
palette_status = ttk.Label(frame_palette, text="", foreground="#666666")
|
||||||
palette_status.pack(anchor="w", padx=4, pady=(0, 4))
|
palette_status.pack(anchor="w", padx=4, pady=(0, 4))
|
||||||
@@ -942,6 +1081,7 @@ def populate_palette_types(event=None):
|
|||||||
palette_tree.insert("", "end", text=type_name, values=(f"{kit_name}::{type_name}", base))
|
palette_tree.insert("", "end", text=type_name, values=(f"{kit_name}::{type_name}", base))
|
||||||
|
|
||||||
palette_status.config(text=f"{len(types)} component{'' if len(types) == 1 else 's'}")
|
palette_status.config(text=f"{len(types)} component{'' if len(types) == 1 else 's'}")
|
||||||
|
fit_tree_column(palette_tree)
|
||||||
|
|
||||||
|
|
||||||
def refresh_palette_kits():
|
def refresh_palette_kits():
|
||||||
@@ -971,6 +1111,7 @@ def refresh_palette_kits():
|
|||||||
|
|
||||||
palette_status.config(text=f"{len(palette_kit_names)} kits")
|
palette_status.config(text=f"{len(palette_kit_names)} kits")
|
||||||
palette_kit_var.set("")
|
palette_kit_var.set("")
|
||||||
|
fit_tree_column(palette_tree)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1081,7 +1222,10 @@ palette_tree.bind("<B1-Motion>", on_palette_motion)
|
|||||||
palette_tree.bind("<ButtonRelease-1>", on_palette_release)
|
palette_tree.bind("<ButtonRelease-1>", on_palette_release)
|
||||||
|
|
||||||
left_pane.add(frame_palette, weight=2)
|
left_pane.add(frame_palette, weight=2)
|
||||||
paned_window.add(left_pane, weight=1)
|
|
||||||
|
# weight=0 keeps the tree/palette column out of the share-out of spare width, so growing
|
||||||
|
# the window feeds the wiresheet alone and the column stays at its configured width.
|
||||||
|
paned_window.add(left_pane, weight=0)
|
||||||
|
|
||||||
frame_editor = ttk.Frame(paned_window)
|
frame_editor = ttk.Frame(paned_window)
|
||||||
|
|
||||||
@@ -1116,7 +1260,14 @@ canvas.bind("<ButtonPress-2>", on_canvas_right_click)
|
|||||||
paned_window.add(frame_editor, weight=3)
|
paned_window.add(frame_editor, weight=3)
|
||||||
|
|
||||||
undo.set_refresh_callback(refresh_all_views)
|
undo.set_refresh_callback(refresh_all_views)
|
||||||
|
sax_file.set_tree_filled_callback(lambda: fit_tree_column(tree))
|
||||||
refresh_palette_kits()
|
refresh_palette_kits()
|
||||||
|
apply_left_pane_width()
|
||||||
|
|
||||||
|
# Moving the sash changes the split directly; resizing the window changes what fits.
|
||||||
|
left_pane.bind("<B1-Motion>", clamp_left_pane_split, add="+")
|
||||||
|
left_pane.bind("<ButtonRelease-1>", clamp_left_pane_split, add="+")
|
||||||
|
left_pane.bind("<Configure>", clamp_left_pane_split, add="+")
|
||||||
|
|
||||||
window.after(100, verify_startup_configurations)
|
window.after(100, verify_startup_configurations)
|
||||||
window.mainloop()
|
window.mainloop()
|
||||||
|
|||||||
+14
@@ -19,6 +19,15 @@ from sax_dom import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
_tree_filled = None
|
||||||
|
|
||||||
|
|
||||||
|
def set_tree_filled_callback(callback):
|
||||||
|
"""Registers what should run once the navigation tree has been rebuilt."""
|
||||||
|
global _tree_filled
|
||||||
|
_tree_filled = callback
|
||||||
|
|
||||||
|
|
||||||
def parse_xml_to_tree(treeview, parent_node, xml_element):
|
def parse_xml_to_tree(treeview, parent_node, xml_element):
|
||||||
"""Recursively traverses XML elements and adds <comp> tags to the Treeview."""
|
"""Recursively traverses XML elements and adds <comp> tags to the Treeview."""
|
||||||
for child in xml_element:
|
for child in xml_element:
|
||||||
@@ -47,6 +56,8 @@ def refresh_treeview_from_dom(xml_root):
|
|||||||
tree.delete(item)
|
tree.delete(item)
|
||||||
|
|
||||||
if not app_state.current_file_path or xml_root is None:
|
if not app_state.current_file_path or xml_root is None:
|
||||||
|
if _tree_filled is not None:
|
||||||
|
_tree_filled()
|
||||||
return
|
return
|
||||||
|
|
||||||
file_name = os.path.basename(app_state.current_file_path)
|
file_name = os.path.basename(app_state.current_file_path)
|
||||||
@@ -58,6 +69,9 @@ def refresh_treeview_from_dom(xml_root):
|
|||||||
else:
|
else:
|
||||||
parse_xml_to_tree(tree, root_node, xml_root)
|
parse_xml_to_tree(tree, root_node, xml_root)
|
||||||
|
|
||||||
|
if _tree_filled is not None:
|
||||||
|
_tree_filled()
|
||||||
|
|
||||||
|
|
||||||
def reset_to_startup_state():
|
def reset_to_startup_state():
|
||||||
"""Returns the app to its just-launched state: no file, empty tree, empty sheet.
|
"""Returns the app to its just-launched state: no file, empty tree, empty sheet.
|
||||||
|
|||||||
Reference in New Issue
Block a user