2 * Jexer - Java Text User Interface
4 * License: LGPLv3 or later
6 * This module is licensed under the GNU Lesser General Public License
7 * Version 3. Please see the file "COPYING" in this directory for more
8 * information about the GNU Lesser General Public License Version 3.
10 * Copyright (C) 2015 Kevin Lamonte
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU Lesser General Public License
14 * as published by the Free Software Foundation; either version 3 of
15 * the License, or (at your option) any later version.
17 * This program is distributed in the hope that it will be useful, but
18 * WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 * General Public License for more details.
22 * You should have received a copy of the GNU Lesser General Public
23 * License along with this program; if not, see
24 * http://www.gnu.org/licenses/, or write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
28 * @author Kevin Lamonte [kevin.lamonte@gmail.com]
33 import java
.io
.InputStream
;
34 import java
.io
.OutputStream
;
35 import java
.io
.UnsupportedEncodingException
;
36 import java
.util
.Collections
;
37 import java
.util
.LinkedList
;
38 import java
.util
.List
;
40 import jexer
.bits
.CellAttributes
;
41 import jexer
.bits
.ColorTheme
;
42 import jexer
.bits
.GraphicsChars
;
43 import jexer
.event
.TCommandEvent
;
44 import jexer
.event
.TInputEvent
;
45 import jexer
.event
.TKeypressEvent
;
46 import jexer
.event
.TMenuEvent
;
47 import jexer
.event
.TMouseEvent
;
48 import jexer
.event
.TResizeEvent
;
49 import jexer
.backend
.Backend
;
50 import jexer
.backend
.ECMA48Backend
;
51 import jexer
.io
.Screen
;
52 import static jexer
.TCommand
.*;
53 import static jexer
.TKeypress
.*;
56 * TApplication sets up a full Text User Interface application.
58 public class TApplication
{
61 * Access to the physical screen, keyboard, and mouse.
63 private Backend backend
;
70 public final Screen
getScreen() {
71 return backend
.getScreen();
75 * Actual mouse coordinate X.
80 * Actual mouse coordinate Y.
85 * Event queue that will be drained by either primary or secondary Fiber.
87 private List
<TInputEvent
> eventQueue
;
90 * Top-level menus in this application.
92 private List
<TMenu
> menus
;
95 * Stack of activated sub-menus in this application.
97 private List
<TMenu
> subMenus
;
100 * The currently acive menu.
102 private TMenu activeMenu
= null;
105 * Windows and widgets pull colors from this ColorTheme.
107 private ColorTheme theme
;
110 * Get the color theme.
114 public final ColorTheme
getTheme() {
119 * The top-level windows (but not menus).
121 private List
<TWindow
> windows
;
124 * When true, exit the application.
126 private boolean quit
= false;
129 * When true, repaint the entire screen.
131 private boolean repaint
= true;
134 * Request full repaint on next screen refresh.
136 public final void setRepaint() {
141 * When true, just flush updates from the screen.
143 private boolean flush
= false;
146 * Y coordinate of the top edge of the desktop. For now this is a
147 * constant. Someday it would be nice to have a multi-line menu or
150 private static final int desktopTop
= 1;
153 * Get Y coordinate of the top edge of the desktop.
155 * @return Y coordinate of the top edge of the desktop
157 public final int getDesktopTop() {
162 * Y coordinate of the bottom edge of the desktop.
164 private int desktopBottom
;
167 * Get Y coordinate of the bottom edge of the desktop.
169 * @return Y coordinate of the bottom edge of the desktop
171 public final int getDesktopBottom() {
172 return desktopBottom
;
176 * Public constructor.
178 * @param input an InputStream connected to the remote user, or null for
179 * System.in. If System.in is used, then on non-Windows systems it will
180 * be put in raw mode; shutdown() will (blindly!) put System.in in cooked
181 * mode. input is always converted to a Reader with UTF-8 encoding.
182 * @param output an OutputStream connected to the remote user, or null
183 * for System.out. output is always converted to a Writer with UTF-8
185 * @throws UnsupportedEncodingException if an exception is thrown when
186 * creating the InputStreamReader
188 public TApplication(final InputStream input
,
189 final OutputStream output
) throws UnsupportedEncodingException
{
191 backend
= new ECMA48Backend(input
, output
);
192 theme
= new ColorTheme();
193 desktopBottom
= getScreen().getHeight() - 1;
194 eventQueue
= new LinkedList
<TInputEvent
>();
195 windows
= new LinkedList
<TWindow
>();
196 menus
= new LinkedList
<TMenu
>();
197 subMenus
= new LinkedList
<TMenu
>();
201 * Invert the cell at the mouse pointer position.
203 private void drawMouse() {
204 CellAttributes attr
= getScreen().getAttrXY(mouseX
, mouseY
);
205 attr
.setForeColor(attr
.getForeColor().invert());
206 attr
.setBackColor(attr
.getBackColor().invert());
207 getScreen().putAttrXY(mouseX
, mouseY
, attr
, false);
210 if (windows
.size() == 0) {
218 public final void drawAll() {
219 if ((flush
) && (!repaint
)) {
220 backend
.flushScreen();
229 // If true, the cursor is not visible
230 boolean cursor
= false;
232 // Start with a clean screen
235 // Draw the background
236 CellAttributes background
= theme
.getColor("tapplication.background");
237 getScreen().putAll(GraphicsChars
.HATCH
, background
);
239 // Draw each window in reverse Z order
240 List
<TWindow
> sorted
= new LinkedList
<TWindow
>(windows
);
241 Collections
.sort(sorted
);
242 Collections
.reverse(sorted
);
243 for (TWindow window
: sorted
) {
244 window
.drawChildren();
247 // Draw the blank menubar line - reset the screen clipping first so
248 // it won't trim it out.
249 getScreen().resetClipping();
250 getScreen().hLineXY(0, 0, getScreen().getWidth(), ' ',
251 theme
.getColor("tmenu"));
252 // Now draw the menus.
254 for (TMenu menu
: menus
) {
255 CellAttributes menuColor
;
256 CellAttributes menuMnemonicColor
;
257 if (menu
.getActive()) {
258 menuColor
= theme
.getColor("tmenu.highlighted");
259 menuMnemonicColor
= theme
.getColor("tmenu.mnemonic.highlighted");
261 menuColor
= theme
.getColor("tmenu");
262 menuMnemonicColor
= theme
.getColor("tmenu.mnemonic");
264 // Draw the menu title
265 getScreen().hLineXY(x
, 0, menu
.getTitle().length() + 2, ' ',
267 getScreen().putStrXY(x
+ 1, 0, menu
.getTitle(), menuColor
);
268 // Draw the highlight character
269 getScreen().putCharXY(x
+ 1 + menu
.getMnemonic().getShortcutIdx(),
270 0, menu
.getMnemonic().getShortcut(), menuMnemonicColor
);
272 if (menu
.getActive()) {
274 // Reset the screen clipping so we can draw the next title.
275 getScreen().resetClipping();
277 x
+= menu
.getTitle().length() + 2;
280 for (TMenu menu
: subMenus
) {
281 // Reset the screen clipping so we can draw the next sub-menu.
282 getScreen().resetClipping();
286 // Draw the mouse pointer
289 // Place the cursor if it is visible
290 TWidget activeWidget
= null;
291 if (sorted
.size() > 0) {
292 activeWidget
= sorted
.get(sorted
.size() - 1).getActiveChild();
293 if (activeWidget
.visibleCursor()) {
294 getScreen().putCursor(true, activeWidget
.getCursorAbsoluteX(),
295 activeWidget
.getCursorAbsoluteY());
302 getScreen().hideCursor();
305 // Flush the screen contents
306 backend
.flushScreen();
313 * Run this application until it exits.
315 public final void run() {
316 List
<TInputEvent
> events
= new LinkedList
<TInputEvent
>();
319 // Timeout is in milliseconds, so default timeout after 1 second
321 int timeout
= getSleepTime(1000);
323 if (eventQueue
.size() > 0) {
324 // Do not wait if there are definitely events waiting to be
325 // processed or a screen redraw to do.
329 // Pull any pending input events
330 backend
.getEvents(events
, timeout
);
331 metaHandleEvents(events
);
334 // Process timers and call doIdle()'s
343 // Shutdown the fibers
344 eventQueue.length = 0;
345 if (secondaryEventFiber !is null) {
346 assert(secondaryEventReceiver !is null);
347 secondaryEventReceiver = null;
348 if (secondaryEventFiber.state == Fiber.State.HOLD) {
349 // Wake up the secondary handler so that it can exit.
350 secondaryEventFiber.call();
354 if (primaryEventFiber.state == Fiber.State.HOLD) {
355 // Wake up the primary handler so that it can exit.
356 primaryEventFiber.call();
364 * Peek at certain application-level events, add to eventQueue, and wake
365 * up the consuming Fiber.
367 * @param events the input events to consume
369 private void metaHandleEvents(final List
<TInputEvent
> events
) {
371 for (TInputEvent event
: events
) {
374 System.err.printf(String.format("metaHandleEvents event: %s\n",
375 event)); System.err.flush();
379 // Do no more processing if the application is already trying
385 if (event
instanceof TKeypressEvent
) {
386 TKeypressEvent keypress
= (TKeypressEvent
) event
;
387 if (keypress
.equals(kbAltX
)) {
394 // Special application-wide events -------------------------------
397 if (event
instanceof TCommandEvent
) {
398 TCommandEvent command
= (TCommandEvent
) event
;
399 if (command
.getCmd().equals(cmAbort
)) {
406 if (event
instanceof TResizeEvent
) {
407 TResizeEvent resize
= (TResizeEvent
) event
;
408 getScreen().setDimensions(resize
.getWidth(),
410 desktopBottom
= getScreen().getHeight() - 1;
417 // Peek at the mouse position
418 if (event
instanceof TMouseEvent
) {
419 TMouseEvent mouse
= (TMouseEvent
) event
;
420 if ((mouseX
!= mouse
.getX()) || (mouseY
!= mouse
.getY())) {
421 mouseX
= mouse
.getX();
422 mouseY
= mouse
.getY();
427 // TODO: change to two separate threads
428 primaryHandleEvent(event
);
432 // Put into the main queue
435 // Have one of the two consumer Fibers peel the events off
437 if (secondaryEventFiber !is null) {
438 assert(secondaryEventFiber.state == Fiber.State.HOLD);
440 // Wake up the secondary handler for these events
441 secondaryEventFiber.call();
443 assert(primaryEventFiber.state == Fiber.State.HOLD);
445 // Wake up the primary handler for these events
446 primaryEventFiber.call();
450 } // for (TInputEvent event: events)
455 * Dispatch one event to the appropriate widget or application-level
456 * event handler. This is the primary event handler, it has the normal
457 * application-wide event handling.
459 * @param event the input event to consume
460 * @see #secondaryHandleEvent(TInputEvent event)
462 private void primaryHandleEvent(final TInputEvent event
) {
464 // System.err.printf("Handle event: %s\n", event);
466 // Special application-wide events -----------------------------------
468 // Peek at the mouse position
469 if (event
instanceof TMouseEvent
) {
470 // See if we need to switch focus to another window or the menu
471 checkSwitchFocus((TMouseEvent
) event
);
474 // Handle menu events
475 if ((activeMenu
!= null) && !(event
instanceof TCommandEvent
)) {
476 TMenu menu
= activeMenu
;
478 if (event
instanceof TMouseEvent
) {
479 TMouseEvent mouse
= (TMouseEvent
) event
;
481 while (subMenus
.size() > 0) {
482 TMenu subMenu
= subMenus
.get(subMenus
.size() - 1);
483 if (subMenu
.mouseWouldHit(mouse
)) {
486 if ((mouse
.getType() == TMouseEvent
.Type
.MOUSE_MOTION
)
487 && (!mouse
.getMouse1())
488 && (!mouse
.getMouse2())
489 && (!mouse
.getMouse3())
490 && (!mouse
.getMouseWheelUp())
491 && (!mouse
.getMouseWheelDown())
495 // We navigated away from a sub-menu, so close it
499 // Convert the mouse relative x/y to menu coordinates
500 assert (mouse
.getX() == mouse
.getAbsoluteX());
501 assert (mouse
.getY() == mouse
.getAbsoluteY());
502 if (subMenus
.size() > 0) {
503 menu
= subMenus
.get(subMenus
.size() - 1);
505 mouse
.setX(mouse
.getX() - menu
.getX());
506 mouse
.setY(mouse
.getY() - menu
.getY());
508 menu
.handleEvent(event
);
515 if (event instanceof TKeypressEvent) {
516 TKeypressEvent keypress = (TKeypressEvent) event;
517 // See if this key matches an accelerator, and if so dispatch the
519 TKeypress keypressLowercase = keypress.getKey().toLowerCase();
520 TMenuItem item = accelerators.get(keypressLowercase);
522 // Let the menu item dispatch
526 // Handle the keypress
527 if (onKeypress(keypress)) {
534 if (event
instanceof TCommandEvent
) {
535 if (onCommand((TCommandEvent
) event
)) {
540 if (event
instanceof TMenuEvent
) {
541 if (onMenu((TMenuEvent
) event
)) {
546 // Dispatch events to the active window -------------------------------
547 for (TWindow window
: windows
) {
548 if (window
.getActive()) {
549 if (event
instanceof TMouseEvent
) {
550 TMouseEvent mouse
= (TMouseEvent
) event
;
551 // Convert the mouse relative x/y to window coordinates
552 assert (mouse
.getX() == mouse
.getAbsoluteX());
553 assert (mouse
.getY() == mouse
.getAbsoluteY());
554 mouse
.setX(mouse
.getX() - window
.getX());
555 mouse
.setY(mouse
.getY() - window
.getY());
557 // System.err("TApplication dispatch event: %s\n", event);
558 window
.handleEvent(event
);
564 * Dispatch one event to the appropriate widget or application-level
565 * event handler. This is the secondary event handler used by certain
566 * special dialogs (currently TMessageBox and TFileOpenBox).
568 * @param event the input event to consume
569 * @see #primaryHandleEvent(TInputEvent event)
571 private void secondaryHandleEvent(final TInputEvent event
) {
576 * Do stuff when there is no user input.
578 private void doIdle() {
581 // Now run any timers that have timed out
582 auto now = Clock.currTime;
583 TTimer [] keepTimers;
584 foreach (t; timers) {
585 if (t.nextTick < now) {
587 if (t.recurring == true) {
597 foreach (w; windows) {
604 * Get the amount of time I can sleep before missing a Timer tick.
606 * @param timeout = initial (maximum) timeout
607 * @return number of milliseconds between now and the next timer event
609 protected int getSleepTime(final int timeout
) {
611 auto now = Clock.currTime;
612 auto sleepTime = dur!("msecs")(timeout);
613 foreach (t; timers) {
614 if (t.nextTick < now) {
617 if ((t.nextTick > now) &&
618 ((t.nextTick - now) < sleepTime)
620 sleepTime = t.nextTick - now;
623 assert(sleepTime.total!("msecs")() >= 0);
624 return cast(uint)sleepTime.total!("msecs")();
626 // TODO: fix timers. Until then, come back after 250 millis.
631 * Close window. Note that the window's destructor is NOT called by this
632 * method, instead the GC is assumed to do the cleanup.
634 * @param window the window to remove
636 public final void closeWindow(final TWindow window
) {
637 int z
= window
.getZ();
639 Collections
.sort(windows
);
641 TWindow activeWindow
= null;
642 for (TWindow w
: windows
) {
644 w
.setZ(w
.getZ() - 1);
647 assert (activeWindow
== null);
655 // Perform window cleanup
665 // Check if we are closing a TMessageBox or similar
666 if (secondaryEventReceiver !is null) {
667 assert(secondaryEventFiber !is null);
669 // Do not send events to the secondaryEventReceiver anymore, the
671 secondaryEventReceiver = null;
673 // Special case: if this is called while executing on a
674 // secondaryEventFiber, call it so that widgetEventHandler() can
676 if (secondaryEventFiber.state == Fiber.State.HOLD) {
677 secondaryEventFiber.call();
679 secondaryEventFiber = null;
681 // Unfreeze the logic in handleEvent()
682 if (primaryEventFiber.state == Fiber.State.HOLD) {
683 primaryEventFiber.call();
690 * Switch to the next window.
692 * @param forward if true, then switch to the next window in the list,
693 * otherwise switch to the previous window in the list
695 public final void switchWindow(final boolean forward
) {
696 // Only switch if there are multiple windows
697 if (windows
.size() < 2) {
701 // Swap z/active between active window and the next in the list
702 int activeWindowI
= -1;
703 for (int i
= 0; i
< windows
.size(); i
++) {
704 if (windows
.get(i
).getActive()) {
709 assert (activeWindowI
>= 0);
711 // Do not switch if a window is modal
712 if (windows
.get(activeWindowI
).isModal()) {
718 nextWindowI
= (activeWindowI
+ 1) % windows
.size();
720 if (activeWindowI
== 0) {
721 nextWindowI
= windows
.size() - 1;
723 nextWindowI
= activeWindowI
- 1;
726 windows
.get(activeWindowI
).setActive(false);
727 windows
.get(activeWindowI
).setZ(windows
.get(nextWindowI
).getZ());
728 windows
.get(nextWindowI
).setZ(0);
729 windows
.get(nextWindowI
).setActive(true);
736 * Add a window to my window list and make it active.
738 * @param window new window to add
740 public final void addWindow(final TWindow window
) {
741 // Do not allow a modal window to spawn a non-modal window
742 if ((windows
.size() > 0) && (windows
.get(0).isModal())) {
743 assert (window
.isModal());
745 for (TWindow w
: windows
) {
747 w
.setZ(w
.getZ() + 1);
750 window
.setActive(true);
755 * Check if there is a system-modal window on top.
757 * @return true if the active window is modal
759 private boolean modalWindowActive() {
760 if (windows
.size() == 0) {
763 return windows
.get(windows
.size() - 1).isModal();
767 * Check if a mouse event would hit either the active menu or any open
770 * @param mouse mouse event
771 * @return true if the mouse would hit the active menu or an open
774 private boolean mouseOnMenu(final TMouseEvent mouse
) {
775 assert (activeMenu
!= null);
776 List
<TMenu
> menus
= new LinkedList
<TMenu
>(subMenus
);
777 Collections
.reverse(menus
);
778 for (TMenu menu
: menus
) {
779 if (menu
.mouseWouldHit(mouse
)) {
783 return activeMenu
.mouseWouldHit(mouse
);
787 * See if we need to switch window or activate the menu based on
790 * @param mouse mouse event
792 private void checkSwitchFocus(final TMouseEvent mouse
) {
794 if ((mouse
.getType() == TMouseEvent
.Type
.MOUSE_DOWN
)
795 && (activeMenu
!= null)
796 && (mouse
.getAbsoluteY() != 0)
797 && (!mouseOnMenu(mouse
))
799 // They clicked outside the active menu, turn it off
800 activeMenu
.setActive(false);
802 for (TMenu menu
: subMenus
) {
803 menu
.setActive(false);
809 // See if they hit the menu bar
810 if ((mouse
.getType() == TMouseEvent
.Type
.MOUSE_DOWN
)
811 && (mouse
.getMouse1())
812 && (!modalWindowActive())
813 && (mouse
.getAbsoluteY() == 0)
816 for (TMenu menu
: subMenus
) {
817 menu
.setActive(false);
821 // They selected the menu, go activate it
822 for (TMenu menu
: menus
) {
823 if ((mouse
.getAbsoluteX() >= menu
.getX())
824 && (mouse
.getAbsoluteX() < menu
.getX()
825 + menu
.getTitle().length() + 2)
827 menu
.setActive(true);
830 menu
.setActive(false);
837 // See if they hit the menu bar
838 if ((mouse
.getType() == TMouseEvent
.Type
.MOUSE_MOTION
)
839 && (mouse
.getMouse1())
840 && (activeMenu
!= null)
841 && (mouse
.getAbsoluteY() == 0)
844 TMenu oldMenu
= activeMenu
;
845 for (TMenu menu
: subMenus
) {
846 menu
.setActive(false);
850 // See if we should switch menus
851 for (TMenu menu
: menus
) {
852 if ((mouse
.getAbsoluteX() >= menu
.getX())
853 && (mouse
.getAbsoluteX() < menu
.getX()
854 + menu
.getTitle().length() + 2)
856 menu
.setActive(true);
860 if (oldMenu
!= activeMenu
) {
861 // They switched menus
862 oldMenu
.setActive(false);
868 // Only switch if there are multiple windows
869 if (windows
.size() < 2) {
873 // Switch on the upclick
874 if (mouse
.getType() != TMouseEvent
.Type
.MOUSE_UP
) {
878 Collections
.sort(windows
);
879 if (windows
.get(0).isModal()) {
880 // Modal windows don't switch
884 for (TWindow window
: windows
) {
885 assert (!window
.isModal());
886 if (window
.mouseWouldHit(mouse
)) {
887 if (window
== windows
.get(0)) {
888 // Clicked on the same window, nothing to do
892 // We will be switching to another window
893 assert (windows
.get(0).getActive());
894 assert (!window
.getActive());
895 windows
.get(0).setActive(false);
896 windows
.get(0).setZ(window
.getZ());
898 window
.setActive(true);
904 // Clicked on the background, nothing to do
911 private void closeMenu() {
912 if (activeMenu
!= null) {
913 activeMenu
.setActive(false);
915 for (TMenu menu
: subMenus
) {
916 menu
.setActive(false);
924 * Turn off a sub-menu.
926 private void closeSubMenu() {
927 assert (activeMenu
!= null);
928 TMenu item
= subMenus
.get(subMenus
.size() - 1);
929 assert (item
!= null);
930 item
.setActive(false);
931 subMenus
.remove(subMenus
.size() - 1);
936 * Switch to the next menu.
938 * @param forward if true, then switch to the next menu in the list,
939 * otherwise switch to the previous menu in the list
941 private void switchMenu(final boolean forward
) {
942 assert (activeMenu
!= null);
944 for (TMenu menu
: subMenus
) {
945 menu
.setActive(false);
949 for (int i
= 0; i
< menus
.size(); i
++) {
950 if (activeMenu
== menus
.get(i
)) {
952 if (i
< menus
.size() - 1) {
960 activeMenu
.setActive(false);
961 activeMenu
= menus
.get(i
);
962 activeMenu
.setActive(true);
970 * Method that TApplication subclasses can override to handle menu or
971 * posted command events.
973 * @param command command event
974 * @return if true, this event was consumed
976 protected boolean onCommand(final TCommandEvent command
) {
979 // Default: handle cmExit
980 if (command.equals(cmExit)) {
981 if (messageBox("Confirmation", "Exit application?",
982 TMessageBox.Type.YESNO).result == TMessageBox.Result.YES) {
989 if (command.equals(cmShell)) {
990 openTerminal(0, 0, TWindow.Flag.RESIZABLE);
995 if (command.equals(cmTile)) {
1000 if (command.equals(cmCascade)) {
1005 if (command.equals(cmCloseAll)) {
1015 * Method that TApplication subclasses can override to handle menu
1018 * @param menu menu event
1019 * @return if true, this event was consumed
1021 protected boolean onMenu(final TMenuEvent menu
) {
1025 // Default: handle MID_EXIT
1026 if (menu.id == TMenu.MID_EXIT) {
1027 if (messageBox("Confirmation", "Exit application?",
1028 TMessageBox.Type.YESNO).result == TMessageBox.Result.YES) {
1031 // System.err.printf("onMenu MID_EXIT result: quit = %s\n", quit);
1036 if (menu.id == TMenu.MID_SHELL) {
1037 openTerminal(0, 0, TWindow.Flag.RESIZABLE);
1042 if (menu.id == TMenu.MID_TILE) {
1047 if (menu.id == TMenu.MID_CASCADE) {
1052 if (menu.id == TMenu.MID_CLOSE_ALL) {
1062 * Method that TApplication subclasses can override to handle keystrokes.
1064 * @param keypress keystroke event
1065 * @return if true, this event was consumed
1067 protected boolean onKeypress(final TKeypressEvent keypress
) {
1068 // Default: only menu shortcuts
1070 // Process Alt-F, Alt-E, etc. menu shortcut keys
1071 if (!keypress
.getKey().getIsKey()
1072 && keypress
.getKey().getAlt()
1073 && !keypress
.getKey().getCtrl()
1074 && (activeMenu
== null)
1077 assert (subMenus
.size() == 0);
1079 for (TMenu menu
: menus
) {
1080 if (Character
.toLowerCase(menu
.getMnemonic().getShortcut())
1081 == Character
.toLowerCase(keypress
.getKey().getCh())
1084 menu
.setActive(true);