Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Infinite loop in _draw_text (on adding marker) #110

Open
Poikilos opened this issue Dec 17, 2024 · 0 comments
Open

Infinite loop in _draw_text (on adding marker) #110

Poikilos opened this issue Dec 17, 2024 · 0 comments

Comments

@Poikilos
Copy link

Poikilos commented Dec 17, 2024

There should be other break condition(s) in _draw_text (other than the if...break below, which is the only break implemented). In my first attempt at running example code, the first create_marker call hangs.

  • How much of the timeline is visible is what should stop the loop according to the current implementation of _draw_text
    • Even if I use root.after to call my create_markers function (to ensure Tk has finished the initial window layout) the hang still occurs.
    • There are only 3 markers in my example.

The following patch to _draw_text in timeline.py reveals the issue is an infinite loop, and the output of my raise is "RuntimeError: Too many (1000) bounding boxes. (7 - -7) >= (15.0 - 5.0) :: 14 >= 10.0":

        x1_r, _, x2_r, _ = coords
+        count = 0
+        limit = 999
        while True:
+            count += 1
            text_id = self._timeline.create_text(
                (0, 0), text=text,
                fill=foreground if foreground != "default" else self._marker_foreground,
                font=font if font != "default" else self._marker_font,
                tags=("marker",)
            )
            x1_t, _, x2_t, _ = self._timeline.bbox(text_id)
            if (x2_t - x1_t) < (x2_r - x1_r):
                break
+            if count > limit:
+                raise RuntimeError(
+                    "Too many ({}) bounding boxes. ({} - {}) >= ({} - {}) :: {} >= {}"
+                    .format(count, x2_t, x1_t, x2_r, x1_r, (x2_t - x1_t), (x2_r - x1_r)))
            self._timeline.delete(text_id)
            text = text[:-4] + "..."
  • I am not suggesting this is a solution, or the proper break condition. The limit conditional code merely reveals that the loop should have ended earlier (through some better conditional)
    • The patch is also useful during testing to reveal the state of the variables in the infinite loop condition.
    • However, adding this (with 9999 or something) could be useful such as if you add a unit test based on my example code below in order to ensure there is not a regression once the root cause can be determined and fixed.

Example code (If there is nonsense in the code, it is because it is ChatGPT...regardless, the library should have some kind of way to exit the loop above better, even if there is some nonsense input):

import tkinter as tk
from ttkwidgets.timeline import TimeLine
from collections import OrderedDict
timeline = None

def create_markers():
    print("create marker...")
    # Add markers to the timeline using create_marker()
    timeline.create_marker(
        category="Development",
        start=5.0,                 # Start time for the marker
        finish=15.0,               # Finish time for the marker
        text="Requirement Analysis",
        foreground="white",
        background="blue",
        outline="darkblue",
        border=2,
    )
    print("create marker...")
    timeline.create_marker(
        category="Testing",
        start=20.0,                # Start time for the marker
        finish=30.0,               # Finish time for the marker
        text="Unit Testing",
        foreground="black",
        background="green",
        outline="darkgreen",
        border=2,
    )
    print("create marker...")
    timeline.create_marker(
        category="Deployment",
        start=40.0,                # Start time for the marker
        finish=50.0,               # Finish time for the marker
        text="Initial Deployment",
        foreground="white",
        background="red",
        outline="darkred",
        border=2,
    )

def create_timeline():
    global timeline
    # Create the main window
    root = tk.Tk()
    root.title("TimeLine Widget Example")
    root.geometry("1200x800")

    # Define categories with valid ttk.Label options
    categories = OrderedDict({
        "Development": {
            "text": "Development",
            "foreground": "white",
            "background": "blue",
        },
        "Testing": {
            "text": "Testing",
            "foreground": "black",
            "background": "green",
        },
        "Deployment": {
            "text": "Deployment",
            "foreground": "white",
            "background": "red",
        },
    })

    # Create the TimeLine widget
    timeline = TimeLine(
        root,
        width=1000,                # Width of the timeline in pixels
        height=300,                # Height of the timeline in pixels
        extend=True,               # Allow extending timeline when needed
        start=0.0,                 # Start value for the timeline (float)
        finish=100.0,              # Finish value for the timeline (float)
        resolution=1.0,            # Seconds per pixel (float)
        tick_resolution=10.0,      # Ticks every 10 seconds (float)
        unit="s",                  # Seconds as the unit
        zoom_enabled=True,         # Enable zooming
        categories=categories,     # Add categories to the timeline
        background="white",        # Background color
        style="TFrame",            # Apply a style to the frame
        zoom_factors=(1.0, 2.0, 5.0),  # Allowed zoom levels
        zoom_default=1.0,          # Default zoom level
        snap_margin=5,             # Snap to ticks within 5 pixels
        autohidescrollbars=True    # Use AutoHideScrollbars
    )
    print("pack...")
    timeline.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)

    # root.after(500, create_markers)  # attempted solve of issue #110, but doesn't solve
    create_markers()
    print("mainloop...")
    # Run the Tkinter main loop
    root.mainloop()

if __name__ == "__main__":
    create_timeline()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant