This file contains developer notes and documentation related to the tkAppFramework solution.

Notes about incorporating tkSimulatorApp, tkSimulatorViewManager, UserQueryWidget, tkUserQueryReceiver, SimulatorModel classes
into the framework.

Relationships between objects:

1) tkSimulatorApp object HAS-A SimulatorModel object, created by a factory method
2) SimulatorModel object HAS-A SimulatorAdapter subclass object, which is passed into the tkSimulatorApp object's __init__(),
	and assigned to the SimulatorModel object.
3) The SimulatorAdapter subclass is specifically created for a particular simulator. This should be the only piece that needs
	to be customized for a different simulator to work with tkSimulatorApp. The SimulatorAdapter subclass is responsible for
	providing a consistent interface for the tkSimulatorApp to start, end, and load simulations.
4) tkSimulatorApp object HAS-A tkSimulatorViewManager object, created by a factory method, and a tkUserQueryViewManager object,
	created by _setup_child_widgets() method of tkSimulatorApp.
5) tkSimulatorViewManager object HAS-A SimulatorShowInfoWidget object which it creates
6) The simulator submits input requests to tkUserQueryReceiver on it's own thread, through execution of a UserQueryCommand object.
7) tkUserQueryReceiver sends the request to the tkUserQueryViewManager (and its user query widgets) by placing a QueryInfo object into the shared query info queue.
	And it posts a tkinter event through a registered callback method of tkUserQueryViewManager object.
8) tkUserQueryViewManager retrieves the QueryInfo object from the queue, shows the query prompt text to the user, and obtains the user's
	raw (text string) response to the query. It packages this raw response into a QueryResponse object, and places that
	object into the tkUserQueryReceiver object's response queue.
9) The tkUserQueryReceiver has been sitting in a tight loop since sending the request to the UserQueryWidget. As soon
	as an object appears in it's response queue, it retrieves it, checks that the query ID is as expected, and returns
	the raw response to the UserQueryCommand object.
10) When the simulator has output it wants to show to the user, it simply uses python logging. The SimulatorAdapter subclass,
	upon __init__() should add a QueueHandler object to the simulator's logger and assign to it the simulator event queue created
	by the SimulatorModel object. (See what is currently done in CribbageSimuator.setup_logging() as an example.)
	Every microsecond a scheduled tkinter event by the SimulatorModel checks the simulator event queue to see if it is empty.
	If it isn't, it retrieves the LogRecord object from the simulator event queue and notifies it's Observers. The Observers
	(typically tkViewManager subclasses) retrieve teh LogRecord from the SimulatorModel and display the information appropriately
	given the set of child widgets that they manage.
11) If the user of the tkSimulatorApp selects File | End Simulation menu item, the tkSimulatorApp object places a
    '<<QueryingThreadTerminationRequest>>' into the response queue of the tkUserQueryReceiver object. When the tkUserQueryReceiver object
	pulls this response from it's queue, it raises a UserQueryReceiver.UserQueryReceiverTerminateQueryingThreadError exception.
	Since this exception is in the thread of the simulator (as opposed to the thread of the tkSimulatorApp), it will abruptly
	terminate the simulator, unless the simulator catches it and handles it, which it should.


*** 10/21/25: tkApp storing menu build info so later menu items can be disabled ***

As we proceed down through cascades towqrds the actual commmand, there is a tkinter.Menu() object that we can store.
The actual command is not associated with a unique object, but should be accessible through the Menu object's entryconfig(...) method,
at least by using an index number, and maybe by using the menu_label string. So in order to later by able to use
entryconfig(...) to disable/enable menu commands, we need a dictionary like as follows, where we want to access and then disable
File | Start Simulation

_menuWids = {'File':(File_menu_obj, {'Start Simulation':(None, index_on_file_menu_obj)})}

Later, when you need to use entryconfig(...) on Start Simulation command:

_menuWids['File'][0].entryconfig(_menuWids['File'][1]['Start Simulation'][1], state=DISABLED)

*** 1/15/2026: Sorting through a gridding issue with tkSimulatorApp ***

important notes:
1. A call to grid() on a widget places that widget into the grid of its parent widget.
2. Calls to rowconfigure() and columnconfigure() on a widget configure the grid of that widget, so you typically want to call
   rowconfigure() and columnconfigure() on the parent widget of the widget you are gridding.

tkSimulatorApp IS-A tkApp, which IS-A ttk.Frame. That Frame is positioned in r0c0 of the root window's grid (grid-0) by  __init__().
tkApp's _setup_child_widgets() method creates self._view_manager, which Is-A ttk.Frame, also. It is positioned in r0c0 of
the tkApp's grid (grid-1), by tkApp._setup_child_widgets().

tkSimulatorApps's _createViewManager() method creates a tkSimulatorViewManager object, which is a child of tkViewManager.
When tkSimulatorApp's _setup_child_widgets() method calls super()._setup_child_widgets(), the tkSimulatorViewManager is
created and gridded as above. Then it is moved to r1c0 of the tkApp's grid (grid-1). And a tkUserQueryViewManager object is created
and gridded into r0c0 of the tkApp's grid (grid-1).

tkSimulatorViewManager IS-A tkViewManager. When it's _CreateWidgets() method is called, it creates a SimulatorShowInfoWidget object,
which IS-A ttk.LabelFrame. The SimulatorShowInfoWidget object is gridded into r0c0 of the tkSimulatorViewManager's grid (grid-2).
The child widgets of the SimulatorShowInfoWidget are gridded into the SimulatorShowInfoWidget's grid (grid-3).

tkUserQueryViewManager IS-A tkViewManager. When it's _CreateWidgets() method is called, it creates a QueryPromptWidget object,
a QueryResponseWidget object, etc. Each of these widgets are gridded into the tkUserQueryViewManager's grid (grid-2). The
widgets that make up each of these widgets are gridded into their respective grids (grid-3).


*** 11-March-2026: Notes on parsing markdown/xhtml and displaying in tkHelpApp as help content ***

Requirements for traversing ElementTree and inserting and tagging text in Text widget:

1. It may be necessary to recurse the ElementTree rather than use built in Element.iter() and Element.itertext() methods.
These methods iterate through the entire tree beneath Element, rather than itering only through Element's direct children. The issues
is that any 'tail' text cannot be tagged with the direct parent if these functions are used, because it is not possible to
identify the direct parent.
2. However, a potential issue if Element.itertext() is NOT used is that the "layering" of tagging may not be correct. If (as is often 
the case) xhtml tags are nested. formatting in Text widget should be "layered". E.g., ALL heading text should be the right font size,
AND any emphasized text in the heading should ALSO be bold.
3. If Element.itertext() is used, which might help with the layered tagging, then how do we not end up inserting text multiple times.
4. One possibility is that we insert text using Element.itertext(), but then recurse down from child generation to generation to handle
the tagging of text. In this case a challenge may be tracking the start and end indices for tagging, since we get those tags back from the
Text widget when we insert text. One option might be to due a search in the Text widget to find the text that needs to be tagged as we are recursing.
Here there could be an issue with multiple occurence of the same text having been inserted "all at once" but intended to be tagged differently.
5. It is possible to access only the Element's direct children using len(Element) and Element[i].
6. I'm going to try this algorithm. First pass: Manually recurse the tree downward from the root Element using len(Element) and Element[i].
Insert text into the Text widget and capture the ending insertion point. Store in a dict the starting and ending insertion points for the
text in a tuple, keyed by id(Element). Second pass: Iterate all Elements in the tree using Element.iter() from the root. Apply tags in
the Text widget, making use of the starting insertion points stored in the dict for each Element. The ending insertion point will
be determined by, at each element, by adding '+{len(Element.itertext())}c' to the starting insertion point.


First: from the root Element, use itertext() to get all the text and insert it into the Text widget.
Second: Use 
