diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi index def50ef7c64..f6f8afcd326 100644 --- a/doc/lispref/elisp.texi +++ b/doc/lispref/elisp.texi @@ -1187,6 +1187,12 @@ Window Frame Parameters * Cursor Parameters:: Controlling the cursor appearance. * Font and Color Parameters:: Fonts and colors for the frame text. +Child Frames + +* Child Frame Operations:: Making and investigating child frames. +* Child Frame Properties:: Special properties of child frames. +* Child Frame Peculiarities:: Deviant behaviors of child frames. + Positions * Point:: The special position where editing takes place. diff --git a/doc/lispref/frames.texi b/doc/lispref/frames.texi index 425baacd1c8..197f4c17b46 100644 --- a/doc/lispref/frames.texi +++ b/doc/lispref/frames.texi @@ -2071,7 +2071,9 @@ The special value @code{child-frame} means to make a minibuffer-only child frame (@pxref{Child Frames}) whose parent becomes the frame created. As if specified as @code{nil}, Emacs will set this parameter to the minibuffer window of the child frame but will not select the -child frame after its creation. +child frame after its creation. The value @code{child-frame} has no +effect on text terminals where you have to create a minibuffer-only +frame manually (@pxref{Child Frame Peculiarities}). @vindex buffer-predicate@r{, a frame parameter} @item buffer-predicate @@ -3311,11 +3313,12 @@ be seen even if they are considered visible by this function. @deffn Command iconify-frame &optional frame This function iconifies frame @var{frame}. If you omit @var{frame}, it -iconifies the selected frame. This will also remove any child frames -(@pxref{Child Frames}) of @var{frame} from display. On the top frame of -a text terminal this function has no effect. visible. If @var{frame} is -a child frame, the behavior depends on the value of the variable -@code{iconify-child-frame} (@pxref{Child Frames}). +iconifies the selected frame. This function also removes any child +frames (@pxref{Child Frames}) of @var{frame} and their descendants from +display. If @var{frame} is a child frame itself, the behavior depends +on the value of the variable @code{iconify-child-frame}. If @var{frame} +is the top frame of a text terminal (@pxref{Frames}), this function has +no effect. @end deffn @deffn Command make-frame-visible &optional frame @@ -3438,7 +3441,7 @@ function @code{frame-list-z-order} (@pxref{Finding All Frames}). @defopt minibuffer-auto-raise If this is non-@code{nil}, activation of the minibuffer raises the frame -that the minibuffer window is in. This function has no effect on text +that the minibuffer window is in. This variable has no effect on text terminals. @end defopt @@ -3474,7 +3477,6 @@ unwanted frames are iconified instead. @node Child Frames @section Child Frames @cindex child frames -@cindex parent frames Child frames are objects halfway between windows (@pxref{Windows}) and ``normal'' frames. Like windows, they are attached to an owning frame. @@ -3487,26 +3489,39 @@ with the help of frame parameters (@pxref{Frame Parameters}) without any specialized functions or customizable variables. Child frames are meaningful on graphical and text terminals. - To create a new child frame or to convert a normal frame into a child +@menu +* Child Frame Operations:: Making and investigating child frames. +* Child Frame Properties:: Special properties of child frames. +* Child Frame Peculiarities:: Deviant behaviors of child frames. +@end menu + +@node Child Frame Operations +@subsection Child Frame Operations + +To create a new child frame or to convert a normal frame into a child frame, set that frame's @code{parent-frame} parameter (@pxref{Frame Interaction Parameters}) to that of an already existing frame. The frame specified by that parameter will then be the frame's parent frame -as long as the parameter is not changed or reset. Technically, this -makes the child frame's window-system window a child window of the -parent frame's window-system window. +as long as the parameter is not changed or reset. Technically, on a GUI +this makes the child frame's window-system window a child window of the +parent frame's window-system window. On a text terminal, this makes the +frame usually appear on the same terminal as its parent frame, obscuring +some part of it. -@cindex reparent frame -@cindex nest frame - The @code{parent-frame} parameter can be changed at any time. -Setting it to another frame @dfn{reparents} the child frame. Setting -it to another child frame makes the frame a @dfn{nested} child frame. -Setting it to @code{nil} restores the frame's status as a top-level -frame---a frame whose window-system window is a child of its display's -root window.@footnote{On Haiku, child frames are only visible when a -parent frame is active, owing to a limitation of the Haiku windowing -system. Owing to the same limitation, child frames are only -guaranteed to appear above their top-level parent; that is to say, the -top-most frame in the hierarchy, which does not have a parent frame.} +@cindex reparenting frames +@cindex nesting frames +@cindex top-level frame + The @code{parent-frame} parameter can be changed at any time. Setting +it to another frame @dfn{reparents} the child frame. Setting it to +another child frame makes the frame a @dfn{nested} child frame. Setting +it to @code{nil} restores the frame's status as a top-level frame---a +frame whose window-system window is a child of its display's root +window.@footnote{On Haiku, child frames are only visible when a parent +frame is active, owing to a limitation of the Haiku windowing system. +Owing to the same limitation, child frames are only guaranteed to appear +above their top-level parent; that is to say, the top-most frame in the +hierarchy, which does not have a parent frame.} On text terminals, +top-level frames are called root frames (see below). Since child frames can be arbitrarily nested, a frame can be both a child and a parent frame. Also, the relative roles of child and parent @@ -3515,16 +3530,69 @@ keep the size of a child frame sufficiently smaller than that of its parent). An error will be signaled for the attempt to make a frame an ancestor of itself. - Most window-systems clip a child frame at the native edges -(@pxref{Frame Geometry}) of its parent frame---everything outside these -edges is usually invisible. A child frame's @code{left} and @code{top} + When a parent frame is about to be deleted (@pxref{Deleting Frames}), +its child frames are recursively deleted before it. There is one +exception to this rule: When the child frame serves as a surrogate +minibuffer frame (@pxref{Minibuffers and Frames}) for another frame, it +is retained until the parent frame has been deleted. If, at this time, +no remaining frame uses the child frame as its minibuffer frame, Emacs +will try to delete the child frame too. If that deletion fails for +whatever reason, the child frame is made a top-level frame. Since on +text terminals no such conversion is possible, deleting a frame may +throw an error if a surrogate minibuffer frame to be deleted is used by +a frame that will not be deleted too. + + The following three functions help to understand how parent and child +frames related to each other. + +@defun frame-parent &optional frame +This function returns the parent frame of @var{frame}. It returns +@code{nil} if @var{frame} has no parent frame. +@end defun + +@cindex ancestor frame +@cindex descendant frame +@defun frame-ancestor-p ancestor descendant +This functions returns non-@code{nil} if @var{ancestor} is an ancestor +of @var{descendant}. @var{ancestor} is an ancestor of @var{descendant} +when it is either @var{descendant}'s parent frame or it is an ancestor +of @var{descendant}'s parent frame. Both, @var{ancestor} and +@var{descendant} must specify live frames. +@end defun + +@cindex root frame +@defun frame-root-frame &optional frame +This function returns the root frame of the specified @var{frame}. +@var{frame} must be a live frame and defaults to the selected one. The +root frame of @var{frame} is the frame obtained by following the chain +of parent frames starting with @var{frame} until a frame is reached that +has no parent. If @var{frame} has no parent, its root frame is +@var{frame} itself. +@end defun + +On a text terminal, a root frame is always positioned at the top left +edge of its terminal and always occupies the full size of its terminal. + + +@node Child Frame Properties +@subsection Child Frame Properties + +Most window-systems clip child frames at the native edges (@pxref{Frame +Geometry}) of their parent frame---everything outside these edges is +usually invisible. A child frame's @code{left} and @code{top} parameters specify a position relative to the top-left corner of its parent's native frame. When the parent frame is resized, this position remains conceptually unaltered. - NS builds do not clip child frames at the parent frame's edges, -allowing them to be positioned so they do not obscure the parent frame -while still being visible themselves. + NS builds and text terminals do not clip child frames at the parent +frame's edges, allowing them to be positioned so they do not obscure the +parent frame while still being visible themselves. + + Note also the function @code{window-largest-empty-rectangle} +(@pxref{Coordinates and Windows}) which can be used to inscribe a child +frame in the largest empty area of an existing window. This can be +useful to avoid that a child frame obscures any text shown in that +window. Usually, moving a parent frame moves along all its child frames and their descendants as well, keeping their relative positions unaltered. @@ -3546,24 +3614,21 @@ obscuring parts of it, except on NS builds where it may be positioned beneath the parent. This is comparable to the window-system window of a top-level frame which also always appears on top of its parent window---the desktop's root window. When a parent frame is iconified or -made invisible (@pxref{Visibility of Frames}), its child frames are made -invisible. When a parent frame is deiconified or made visible, its -child frames are made visible. - - When a parent frame is about to be deleted (@pxref{Deleting -Frames}), its child frames are recursively deleted before it. There -is one exception to this rule: When the child frame serves as a -surrogate minibuffer frame (@pxref{Minibuffers and Frames}) for -another frame, it is retained until the parent frame has been deleted. -If, at this time, no remaining frame uses the child frame as its -minibuffer frame, Emacs will try to delete the child frame too. If -that deletion fails for whatever reason, the child frame is made a -top-level frame. +made invisible (@pxref{Visibility of Frames}), any child frames +descending from it will not be shown either even if +@code{frame-visible-p} returns @code{t} for them. When a parent frame +is deiconified or made visible, any child frames descending from it will +be shown again (provided they and all their ancestor frames are visible +too). If a child frame is used as surrogate minibuffer frame +(@pxref{Minibuffers and Frames}), it's up to the application to +guarantee the frame's visibility whenever the minibuffer is activated. Whether a child frame can have a menu or tool bar is window-system or window manager dependent. Most window-systems explicitly disallow menu bars for child frames. It seems advisable to disable both, menu and -tool bars, via the frame's initial parameters settings. +tool bars, via the frame's initial parameters settings. On a text +terminal, child frames use the menu bar of their root frame (provided it +has one). Usually, child frames do not exhibit window manager decorations like a title bar or external borders (@pxref{Frame Geometry}). When the child @@ -3575,15 +3640,18 @@ outer border can be used. On MS-Windows, specifying a non-zero outer border width will show a one-pixel wide external border. Under all window-systems, the internal border can be used. In either case, it's advisable to disable a child frame's window manager decorations with the -@code{undecorated} frame parameter (@pxref{Management Parameters}). +@code{undecorated} frame parameter (@pxref{Management Parameters}). On +a text terminal, on the other hand, it's better to leave that parameter +alone so your child frame will be drawn with an outer border. - To resize or move an undecorated child frame with the mouse, special + To resize or move a border-less child frame with the mouse, special frame parameters (@pxref{Mouse Dragging Parameters}) have to be used. The internal border of a child frame, if present, can be used to resize the frame with the mouse, provided that frame has a non-@code{nil} @code{drag-internal-border} parameter. If set, the @code{snap-width} parameter indicates the number of pixels where the frame @dfn{snaps} at -the respective edge or corner of its parent frame. +the respective edge or corner of its parent frame. On a text terminal, +the outer border can used for resizing. There are two ways to drag an entire child frame with the mouse: The @code{drag-with-mode-line} parameter, if non-@code{nil}, enables @@ -3627,8 +3695,12 @@ to display completions in a separate window, the @code{minibuffer-exit} parameter (@pxref{Frame Interaction Parameters}) is useful in order to deal with the frame when the minibuffer is exited. - The behavior of child frames deviates from that of top-level frames in -a number of other ways as well. Here we sketch a few of them: + +@node Child Frame Peculiarities +@subsection Child Frame Peculiarities + +The behavior of child frames deviates from that of normal frames in a +number of peculiar ways. Here we sketch a few of them: @itemize @bullet @item @@ -3644,7 +3716,7 @@ described below. Raising, lowering and restacking child frames (@pxref{Raising and Lowering}) or changing the @code{z-group} (@pxref{Position Parameters}) of a child frame changes only the stacking order of child frames with -the same parent. +the same parent. Restacking has not been implemented on text terminals. @item Many window-systems are not able to change the opacity (@pxref{Font and @@ -3667,43 +3739,6 @@ work on all window-systems. Some will drop the object on the parent frame or on some ancestor instead. @end itemize - The following three functions can be useful when working with child and -parent frames: - -@defun frame-parent &optional frame -This function returns the parent frame of @var{frame}. The parent frame -of @var{frame} is the Emacs frame whose window-system window is the -parent window of @var{frame}'s window-system window. If such a frame -exists, @var{frame} is considered a child frame of that frame. - -This function returns @code{nil} if @var{frame} has no parent frame. -@end defun - -@cindex ancestor frame -@defun frame-ancestor-p ancestor descendant -This functions returns non-@code{nil} if @var{ancestor} is an ancestor -of @var{descendant}. @var{ancestor} is an ancestor of @var{descendant} -when it is either @var{descendant}'s parent frame or it is an ancestor -of @var{descendant}'s parent frame. Both, @var{ancestor} and -@var{descendant} must specify live frames. -@end defun - -@cindex root frame -@defun frame-root-frame &optional frame -This function returns the root frame of the specified @var{frame}. -@var{frame} must be a live frame and defaults to the selected one. The -root frame of @var{frame} is the frame obtained by following the chain -of parent frames starting with @var{frame} until a frame is reached that -has no parent. If @var{frame} has no parent, its root frame is -@var{frame} itself. -@end defun - -Note also the function @code{window-largest-empty-rectangle} -(@pxref{Coordinates and Windows}) which can be used to inscribe a child -frame in the largest empty area of an existing window. This can be -useful to avoid that a child frame obscures any text shown in that -window. - Customizing the following option can be useful to tweak the behavior of @code{iconify-frame} for child frames. @@ -3724,6 +3759,68 @@ On a text terminal the only feasible values are @code{nil} and @code{make-invisible}. @end defopt +On text terminals exist a few restrictions with respect to reparenting: +One is that a top frame (@pxref{Frames}) cannot be directly made a child +frame---you first have to make another root frame the new top frame of +its terminal. If, on the other hand, you want a child frame to become +the new top frame of its terminal, you have to make it a root frame +first. + + Also, the surrogate minibuffer window of any frame on a text terminal +must reside on a frame with the same root frame. Reparenting will throw +an error whenever it violates this restriction. It also means that it's +more tricky to make a minibuffer-less frame whose minibuffer window +resides on a minibuffer-only child frame. On a GUI, Emacs proceeds as +follows when a user has specified the value @code{child-frame} for the +@code{minibuffer} parameter in @code{initial-frame-alist} +(@pxref{Initial Parameters}): + +@enumerate +@item +Create a minibuffer-only frame. + +@item +Create a minibuffer-less frame with its @code{minibuffer} parameter set +to the window of the minibuffer-only frame. + +@item +Make the minibuffer-less frame the parent frame of the minibuffer-only +frame. + +@item +Delete the originally selected frame. +@end enumerate + + On a text terminal you have to perform these operations manually as +sketched in the following snippet: + +@example +@group +(let* ((selected (selected-frame)) + (mini-only + (make-frame + `((parent-frame . ,selected) + (minibuffer . only) + (left . 1) (top . -1) (width . 20) (height . 1)))) + (mini-less + (make-frame + (append `((parent-frame . ,selected) + (minibuffer . ,(minibuffer-window mini-only))))))) + (set-frame-parameter mini-only 'parent-frame mini-less) + (set-frame-parameter mini-less 'parent-frame nil) + (select-frame mini-less) + (delete-frame selected)) +@end group +@end example + + This means that you first have to install the minibuffer-less and the +minibuffer-only frames both as child frames of the selected frame with +the @code{minibuffer} parameter of the minibuffer-less frame set to the +minibuffer window of the minibuffer-only frame. Then make the +minibuffer-only frame a child frame of the minibuffer-less frame and +make the minibuffer-less frame a new root frame. Finally, select the +minibuffer-less frame and delete the originally selected frame. + @node Mouse Tracking @section Mouse Tracking diff --git a/etc/NEWS b/etc/NEWS index 27d799d3d26..1bd2fd6d486 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -75,11 +75,10 @@ the 'standard-display-table's extra slots with Unicode characters. Please see the documentation of that function to see which slots of the display table it changes. ++++ ** Child frames are now supported on TTY frames. This supports use-cases like Posframe, Corfu, and child frames acting -like tooltips. Other use-cases of child frames are not supported yet. -In particular, a TTY child frame cannot be converted to a root frame or -vice-versa. +like tooltips. To enable tooltips on TTY frames, call 'tty-tip-mode'. diff --git a/src/frame.c b/src/frame.c index 550fd336ff9..7dc9202d6f6 100644 --- a/src/frame.c +++ b/src/frame.c @@ -2070,6 +2070,7 @@ parent window is the window-system's root window) or an embedded window return Qnil; } +/* Return true if frame AF is an ancestor of frame DF. */ bool frame_ancestor_p (struct frame *af, struct frame *df) { @@ -2086,6 +2087,22 @@ frame_ancestor_p (struct frame *af, struct frame *df) return false; } +/* A frame AF subsumes a frame DF if AF and DF are the same or AF is an + ancestor of DF. */ +static bool +frame_subsumes_p (struct frame *af, struct frame *df) +{ + while (df) + { + if (df == af) + return true; + else + df = FRAME_PARENT_FRAME (df); + } + + return false; +} + DEFUN ("frame-ancestor-p", Fframe_ancestor_p, Sframe_ancestor_p, 2, 2, 0, doc: /* Return non-nil if ANCESTOR is an ancestor of DESCENDANT. @@ -2100,7 +2117,6 @@ frame. */) return frame_ancestor_p (af, df) ? Qt : Qnil; } - /* Return the root frame of frame F. Follow the parent_frame chain until we reach a frame that has no parent. That is the root frame. Note that the root of a root frame is itself. */ @@ -2448,6 +2464,18 @@ delete_frame (Lisp_Object frame, Lisp_Object force) XSETFRAME (frame, f); + if (is_tty_frame (f) && NILP (force)) + /* If F is a tty frame, check for surrogate minibuffer frames F + subsumes used by a frame that is not subsumed by F. */ + FOR_EACH_FRAME (frames, frame1) + { + struct frame *f1 = XFRAME (frame1); + + if (frame_subsumes_p (f, WINDOW_XFRAME (XWINDOW (f1->minibuffer_window))) + && !frame_subsumes_p (f, f1)) + error ("Cannot delete surrogate minibuffer frame"); + } + /* Softly delete all frames with this frame as their parent frame or as their `delete-before' frame parameter value. */ FOR_EACH_FRAME (frames, frame1) @@ -3625,7 +3653,7 @@ store_frame_param (struct frame *f, Lisp_Object prop, Lisp_Object val) { if (WINDOWP (val)) { - if (!MINI_WINDOW_P (XWINDOW (val))) + if (!WINDOW_LIVE_P (val) || !MINI_WINDOW_P (XWINDOW (val))) error ("The `minibuffer' parameter does not specify a valid minibuffer window"); else if (FRAME_MINIBUF_ONLY_P (f)) { @@ -3641,6 +3669,10 @@ store_frame_param (struct frame *f, Lisp_Object prop, Lisp_Object val) else error ("Can't change the minibuffer window of a frame with its own minibuffer"); } + else if (is_tty_frame (f) + && (root_frame (WINDOW_XFRAME (XWINDOW (val))) + != root_frame (f))) + error ("A frame and its surrogate minibuffer frame must have the same roots"); else /* Store the chosen minibuffer window. */ fset_minibuffer_window (f, val); @@ -3719,12 +3751,51 @@ store_frame_param (struct frame *f, Lisp_Object prop, Lisp_Object val) val = old_val; } - /* Re-parenting is currently not implemented when changing a root - frame to a child frame or vice versa. */ + /* The parent frame parameter for ttys must be handled specially. */ if (is_tty_frame (f) && EQ (prop, Qparent_frame)) { - if (NILP (f->parent_frame) != NILP (val)) - error ("Making a root frame a child or vice versa is not supported"); + /* Invariant: When a frame F1 uses a surrogate minibuffer frame M1 + on a tty, both F1 and M1 must have the same root frame. */ + Lisp_Object frames, frame1, old_val = f->parent_frame; + + FOR_EACH_FRAME (frames, frame1) + { + struct frame *f1 = XFRAME (frame1); + struct frame *m1 = WINDOW_XFRAME (XWINDOW (f1->minibuffer_window)); + bool mismatch = false; + + /* Temporarily install VAL and check whether our invariant + above gets violated. */ + f->parent_frame = val; + mismatch = root_frame (f1) != root_frame (m1); + f->parent_frame = old_val; + + if (mismatch) + error ("Cannot re-root surrogate minibuffer frame"); + } + + if (f == XFRAME (FRAME_TERMINAL (f)->display_info.tty->top_frame) + && !NILP (val)) + error ("Cannot make tty top frame a child frame"); + else if (NILP (val)) + { + if (!FRAME_HAS_MINIBUF_P (f) + && (!frame_ancestor_p + (f, WINDOW_XFRAME (XWINDOW (f->minibuffer_window))))) + error ("Cannot make tty root frame without valid minibuffer window"); + else + { + /* When making a frame a root frame, expand it to full size, + if necessary, and position it at top left corner. */ + int width, height; + + get_tty_size (fileno (FRAME_TTY (f)->input), &width, &height); + adjust_frame_size (f, width, height - FRAME_TOP_MARGIN (f), 5, 0, + Qterminal_frame); + f->left_pos = 0; + f->top_pos = 0; + } + } SET_FRAME_GARBAGED (root_frame (f)); f->parent_frame = val; @@ -6593,14 +6664,15 @@ of the frame returned by 'mouse-position'. */) { Lisp_Object pos = mouse_position (true); Lisp_Object frame = XCAR (pos); - struct frame *f = XFRAME (frame); - int x = XFIXNUM (XCAR (XCDR (pos))) + f->left_pos; - int y = XFIXNUM (XCDR (XCDR (pos))) + f->top_pos; if (!FRAMEP (frame)) return Qnil; else { + struct frame *f = XFRAME (frame); + int x = XFIXNUM (XCAR (XCDR (pos))) + f->left_pos; + int y = XFIXNUM (XCDR (XCDR (pos))) + f->top_pos; + f = FRAME_PARENT_FRAME (f); while (f)