Java base for menu initialization; Add base for deletion of ScritchUI objects.
[SquirrelJME.git] / modules / midp-lcdui / src / main / java / javax / microedition / lcdui / Displayable.java
blobeb1b9fd3923b8a4f28d8ea94003c99ba7f77bf8b
1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
3 // SquirrelJME
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the Mozilla Public License Version 2.0.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package javax.microedition.lcdui;
12 import cc.squirreljme.jvm.mle.constants.UIItemPosition;
13 import cc.squirreljme.jvm.mle.scritchui.annotation.ScritchEventLoop;
14 import cc.squirreljme.runtime.cldc.annotation.Api;
15 import cc.squirreljme.runtime.cldc.annotation.SquirrelJMEVendorApi;
16 import cc.squirreljme.runtime.cldc.debug.Debugging;
17 import cc.squirreljme.runtime.lcdui.SerializedEvent;
18 import cc.squirreljme.runtime.lcdui.scritchui.DisplayState;
19 import cc.squirreljme.runtime.lcdui.scritchui.DisplayableState;
20 import cc.squirreljme.runtime.lcdui.scritchui.MenuAction;
21 import cc.squirreljme.runtime.lcdui.scritchui.MenuLayoutBar;
22 import cc.squirreljme.runtime.lcdui.scritchui.MenuLayoutLock;
23 import cc.squirreljme.runtime.lcdui.scritchui.TextTracker;
24 import cc.squirreljme.runtime.midlet.ActiveMidlet;
25 import cc.squirreljme.runtime.midlet.ApplicationHandler;
26 import javax.microedition.midlet.MIDlet;
27 import org.jetbrains.annotations.Async;
28 import org.jetbrains.annotations.MustBeInvokedByOverriders;
30 /**
31 * A displayable is a primary container such as a form or a canvas that can be
32 * set on a display. A display may only have a single displayable associated
33 * with it and a displayable may only be associated with a single display.
35 * @since 2016/10/08
37 @Api
38 @SuppressWarnings("OverlyComplexClass")
39 public abstract class Displayable
41 /** The displayable state. */
42 final DisplayableState _state;
44 /** The command listener to call into when commands are generated. */
45 @Deprecated
46 volatile CommandListener _cmdListener;
48 /** The ticker of the displayable. */
49 @Deprecated
50 volatile Ticker _ticker;
52 /** The layout policy of this displayable. */
53 @Deprecated
54 private CommandLayoutPolicy _layoutPolicy;
56 /** The tracker for title text. */
57 final TextTracker _trackerTitle;
59 /** The lock for layout editing and otherwise. */
60 final MenuLayoutLock _layoutLock =
61 new MenuLayoutLock();
63 /** The current menu bar. */
64 final MenuLayoutBar _menuBar;
66 /** The default menu. */
67 private final Menu _menuDefault;
69 /**
70 * Initializes the base displayable object.
72 * @since 2016/10/08
74 Displayable()
76 // Setup new state
77 DisplayableState state = new DisplayableState(this);
78 this._state = state;
80 // Setup menu bar
81 MenuLayoutBar menuBar = new MenuLayoutBar(
82 state.scritchApi(), this);
83 this._menuBar = menuBar;
85 // Setup default menu
86 Menu menuDefault = new Menu("App", "Application",
87 null);
88 this._menuDefault = menuDefault;
90 // Add and use this menu
91 menuBar.insert(Integer.MAX_VALUE, menuDefault);
92 menuBar.pin(menuDefault);
94 // Setup tracker for title changes, it needs the event loop handler
95 this._trackerTitle = new TextTracker(state.scritchApi().eventLoop(),
96 Displayable.__defaultTitle());
99 /**
100 * Returns the height of this displayable's content area in pixels (the
101 * area the developer can use).
103 * @return The current height of this displayable in pixels, if it is not
104 * visible then the default height is returned.
105 * @since 2017/02/08
107 @Api
108 public abstract int getHeight();
111 * Returns the width of this displayable's content area in pixels (the
112 * area the developer can use).
114 * @return The current width of this displayable in pixels, if it is not
115 * visible then the default width is returned.
116 * @since 2017/02/08
118 @Api
119 public abstract int getWidth();
122 * Adds the specified command to this displayable, if it was already added
123 * then there is no effect (object references are checked).
125 * @param __c The command to add.
126 * @throws DisplayCapabilityException If this is being displayed and
127 * the display does not support commands.
128 * @throws NullPointerException On null arguments.
129 * @since 2018/11/17
131 @Api
132 public void addCommand(Command __c)
133 throws DisplayCapabilityException, NullPointerException
135 if (__c == null)
136 throw new NullPointerException("NARG");
138 // Have the event loop handle this
139 this._state.scritchApi().eventLoop()
140 .execute(new __ExecDisplayableDefaultCommand__(this,
141 __c, true));
144 @Api
145 public Command getCommand(int __p)
147 throw Debugging.todo();
151 * Returns the current command layout policy. The policy here takes
152 * precedence over the one set in {@link Display}.
154 * @return The current command layout policy, may be {@code null}.
155 * @since 2020/09/27
157 @Api
158 public CommandLayoutPolicy getCommandLayoutPolicy()
160 return this._layoutPolicy;
164 * Returns the command listener.
166 * @return The command listener.
167 * @since 2019/05/18
169 @Api
170 protected CommandListener getCommandListener()
172 return this._cmdListener;
176 * Gets the commands which are available to use.
178 * @return The available commands.
179 * @since 2019/05/17
181 @Api
182 public Command[] getCommands()
184 try (MenuLayoutLock lock = this._layoutLock.open(false))
186 if (true)
187 throw Debugging.todo();
190 throw Debugging.todo();
192 List<Command> rv = new ArrayList<>();
193 for (__Action__ a : this._actions)
194 if (a instanceof Command)
195 rv.add((Command)a);
196 return rv.<Command>toArray(new Command[rv.size()]);
202 * Returns the display that is associated with this displayable.
204 * @return The owning display or {@code null} if not found.
205 * @since 2016/10/08
207 @Api
208 public Display getCurrentDisplay()
210 return this.__getCurrentDisplay();
213 @Api
214 public Menu getMenu(int __p)
216 throw Debugging.todo();
220 * Gets the ticker which is being shown on this displayable.
222 * @return The ticker being shown or {@code null} if there is none.
223 * @since 2018/03/26
225 @Api
226 public Ticker getTicker()
228 return this._ticker;
232 * Returns the title of this displayable.
234 * @return The title of this displayable.
235 * @since 2016/10/08
237 @Api
238 public String getTitle()
240 return this._trackerTitle.get();
244 * Invalidates the command layout and forces it to be recalculated, if the
245 * display is not visible or focused then this will be ignored.
247 * @since 2020/09/27
249 @Api
250 public void invalidateCommandLayout()
252 if (this.__isShown())
253 this.__layoutCommands();
257 * Returns if this displayable is currently being shown.
259 * @return If the displayable is being shown.
260 * @since 2018/12/02
262 @Api
263 public boolean isShown()
265 return this.__isShown();
269 * Removes the specified command. If the command is {@code null} or it
270 * has never been added, this does nothing. If a command is removed then
271 * the display will be updated.
273 * @param __c The command to remove.
274 * @since 2019/04/15
276 @Api
277 public void removeCommand(Command __c)
279 if (__c == null)
280 return;
282 // Have the event loop handle this
283 this._state.scritchApi().eventLoop()
284 .execute(new __ExecDisplayableDefaultCommand__(this,
285 __c, false));
288 @Api
289 public void removeCommandOrMenu(int __p)
291 throw Debugging.todo();
295 * Sets the command at the given position.
297 * @param __c The command to set.
298 * @param __p The position to set.
299 * @throws DisplayCapabilityException If the display does not support
300 * commands.
301 * @throws IllegalArgumentException If the placement is not valid.
302 * @throws IllegalStateException If this was not called from within the
303 * {@link CommandLayoutPolicy#onCommandLayout(Displayable)} method or
304 * the command layout passed is not the same.
305 * @throws NullPointerException On null arguments.
306 * @since 2020/09/27
308 @Api
309 public void setCommand(Command __c, int __p)
310 throws DisplayCapabilityException, IllegalArgumentException,
311 IllegalStateException, NullPointerException
313 try (MenuLayoutLock lock = this._layoutLock.open(false))
315 if (true)
316 throw Debugging.todo();
319 throw Debugging.todo();
321 this.__layoutActionSet(__c, __p);
327 * Sets the command layout policy to use for any commands or menu items.
329 * @param __p The policy to use, {@code null} will use the default one for
330 * the display.
331 * @since 2021/11/30
333 @Api
334 public void setCommandLayoutPolicy(CommandLayoutPolicy __p)
336 this._layoutPolicy = __p;
340 * Sets the command listener for this given displayable.
342 * @param __l The listener to use for callbacks, if {@code null} this
343 * the listener is cleared.
344 * @since 2017/08/19
346 @Api
347 public void setCommandListener(CommandListener __l)
349 this._cmdListener = __l;
353 * Sets the menu at the given position.
355 * @param __m The menu to set.
356 * @param __p The position to set.
357 * @throws DisplayCapabilityException If the display does not support
358 * commands.
359 * @throws IllegalArgumentException If the placement is not valid.
360 * @throws IllegalStateException If this was not called from within the
361 * {@link CommandLayoutPolicy#onCommandLayout(Displayable)} method or
362 * the command layout passed is not the same.
363 * @throws NullPointerException On null arguments.
364 * @since 2020/09/27
366 @Api
367 public void setMenu(Menu __m, int __p)
368 throws DisplayCapabilityException, IllegalArgumentException,
369 IllegalStateException, NullPointerException
371 try (MenuLayoutLock lock = this._layoutLock.open(false))
373 if (true)
374 throw Debugging.todo();
377 throw Debugging.todo();
379 this.__layoutActionSet(__m, __p);
385 * Sets or clears the ticker to be shown on this displayable.
387 * @param __t The ticker to be shown on the displayable or {@code null}
388 * to clear it.
389 * @since 2018/03/26
391 @Api
392 public void setTicker(Ticker __t)
394 throw Debugging.todo();
396 // Removing old ticker?
397 Ticker old = this._ticker;
398 if (__t == null)
400 // Nothing to do?
401 if (old == null)
402 return;
404 // Clear
405 this._ticker = null;
407 // Remove from display list
408 old._displayables.remove(this);
410 // Perform ticker update
411 this.__updateTicker();
414 // Setting the same ticker?
415 else if (old == __t)
416 return;
418 // Add new ticker, note they can be associated with many displays
419 else
421 // Add to displayable list
422 __t._displayables.addUniqueObjRef(this);
424 // Set
425 this._ticker = __t;
427 // Perform ticker updates
428 this.__updateTicker();
435 * Sets the title of this displayable.
437 * @param __t The title to use, {@code null} clears it.
438 * @since 2016/10/08
440 @Api
441 public void setTitle(String __t)
443 this._trackerTitle.set(__t);
447 * This is called when the size of the displayable has changed.
449 * @param __w The new width of the displayable.
450 * @param __h The new height of the displayable.
451 * @since 2016/10/10
453 @Api
454 @SerializedEvent
455 @Async.Execute
456 protected void sizeChanged(int __w, int __h)
458 // Implemented by subclasses
462 * Rebuilds the displayable menu.
464 * @since 2024/07/18
466 @ScritchEventLoop
467 @SerializedEvent
468 @Async.Execute
469 void __execMenuRebuild()
471 throw Debugging.todo();
475 * Performs revalidation for ScritchUI, overridden as needed.
477 * @param __parent The parent display.
478 * @since 2024/03/18
480 @ScritchEventLoop
481 @SerializedEvent
482 @Async.Execute
483 @MustBeInvokedByOverriders
484 void __execRevalidate(DisplayState __parent)
486 // Reparent the display
487 this._state.setParent(__parent);
491 * Returns the display that is associated with this displayable.
493 * @return The owning display or {@code null} if not found.
494 * @since 2017/07/18
496 @SquirrelJMEVendorApi
497 private Display __getCurrentDisplay()
499 DisplayState display = this._state.currentDisplay();
501 if (display != null)
502 return display.display();
503 return null;
507 * Returns if this displayable is currently being shown.
509 * @return If the displayable is being shown.
510 * @since 2020/09/27
512 final boolean __isShown()
514 throw Debugging.todo();
516 // If there is no display then this cannot possibly be shown
517 Display display = this._display;
518 if (display == null)
519 return false;
521 // When checking if shown, actually probe the current form on the
522 // display as another task may have taken the display from us
523 UIBackend backend = this.__backend();
524 return backend.equals(this.__state(__DisplayableState__.class)._uiForm,
525 backend.displayCurrent(display._uiDisplay));
531 * Sets the command or menu at the given position.
533 * @param __a The action to set.
534 * @param __p The position to set.
535 * @throws DisplayCapabilityException If the display does not support
536 * commands.
537 * @throws IllegalArgumentException If the placement is not valid.
538 * @throws IllegalStateException If this was not called from within the
539 * {@link CommandLayoutPolicy#onCommandLayout(Displayable)} method or
540 * the command layout passed is not the same.
541 * @throws NullPointerException On null arguments.
542 * @since 2020/09/27
544 private void __layoutActionSet(MenuAction __a, int __p)
545 throws DisplayCapabilityException, IllegalArgumentException,
546 IllegalStateException, NullPointerException
548 if (__a == null)
549 throw new NullPointerException("NARG");
551 throw Debugging.todo();
553 /* {@squirreljme.error EB3i The current display does not support
554 commands.} * /
555 Display display = this._display;
556 int caps = (display == null ? Display.__defaultCapabilities() :
557 display.getCapabilities());
558 if (0 == (caps & Display.SUPPORTS_COMMANDS))
559 throw new IllegalArgumentException("EB3i");
561 /* {@squirreljme.error EB3h The current displayable is not getting
562 its layout calculated.} * /
563 __Layout__ layout = this._layout;
564 if (layout == null)
565 throw new IllegalStateException("EB3h");
567 // Forward to the layout
568 layout.set(__a, __p);
574 * Performs the laying out the commands, in the event they have changed
575 * or otherwise.
577 * @since 2020/09/27
579 @SerializedEvent
580 @Async.Execute
581 private void __layoutCommands()
583 throw Debugging.todo();
585 // Get our own policy or the one specified by the display
586 Display display = this._display;
587 CommandLayoutPolicy policy = this.getCommandLayoutPolicy();
588 if (policy == null && display != null)
589 policy = display.getCommandLayoutPolicy();
591 // Setup new layout and set state
592 try (__Layout__ layout = new __Layout__())
594 // Any layout calls will affect this one
595 this._layout = layout;
597 // Either use the user specified policy or a default one
598 if (policy != null)
599 policy.onCommandLayout(this);
600 else
601 this.__layoutDefault(layout);
603 // Make whatever state was set in the layout as set
604 this.__layoutExecute(layout);
607 // If this failed, print it out
608 catch (RuntimeException e)
610 e.printStackTrace();
612 // re-toss
613 throw e;
616 // Cancel the layout state
617 finally
619 this._layout = null;
626 * Lays out commands using a default means.
628 * @param __layout The layout state.
629 * @throws NullPointerException On null arguments.
630 * @since 2020/09/27
632 private void __layoutDefault(__Layout__ __layout)
633 throws NullPointerException
635 if (__layout == null)
636 throw new NullPointerException("NARG");
638 throw Debugging.todo();
640 Display display = this._display;
642 // Go through commands and menu items, try to place them in their
643 // default normal positions where possible
644 for (__Action__ action : this._actions)
646 // Get the preferred placement for these items
647 int[] prefPlace = ((action instanceof Command) ?
648 display.getCommandPreferredPlacements(((Command)action)._type)
649 : display.getMenuPreferredPlacements());
651 // Use the first available place for anything that is empty
652 int usePlace = -1;
653 for (int place : prefPlace)
654 if (__layout.get(place) == null)
656 usePlace = place;
657 break;
660 // Otherwise, try to set the item at the lowest placement
661 if (usePlace < 0)
663 // Determine the priority of this item
664 int actPriority = __Action__.__getPriority(action);
666 // The position and the currently lowest scoring slot
667 int plopPlace = -1;
668 int plopPriority = Integer.MIN_VALUE;
670 // Find the spot with the lowest priority
671 for (int place : prefPlace)
673 int tickPriority = __layout.getPriority(place);
675 // Does this have a lowest priority?
676 if (plopPlace < 0 || tickPriority > plopPriority)
678 plopPlace = place;
679 plopPriority = tickPriority;
683 // If our item has a higher priority than the the lowest
684 // priority item, that gets replaced
685 if (plopPlace >= 0 && actPriority < plopPriority)
686 usePlace = plopPlace;
689 // If we could place the item here, do that placement
690 if (usePlace > 0)
691 __layout.set(action, usePlace);
696 * Executes the given layout.
698 * @param __layout The layout to execute.
699 * @throws NullPointerException On null arguments.
700 * @since 2020/09/27
702 @SerializedEvent
703 @Async.Execute
704 private void __layoutExecute(__Layout__ __layout)
705 throws NullPointerException
707 if (__layout == null)
708 throw new NullPointerException("NARG");
710 // Left command item
711 this.__layoutExecute(__layout, Display._SOFTKEY_LEFT_COMMAND,
712 Display.__layoutSoftKeyToPos(Display._SOFTKEY_LEFT_COMMAND));
714 // Right command item
715 this.__layoutExecute(__layout, Display._SOFTKEY_RIGHT_COMMAND,
716 Display.__layoutSoftKeyToPos(Display._SOFTKEY_RIGHT_COMMAND));
720 * Executes the given layout.
722 * @param __layout The layout to execute.
723 * @param __from The from position, one of the softkey positions.
724 * @param __to The target position, one of {@link UIItemPosition}.
725 * @throws NullPointerException On null arguments.
726 * @since 2020/09/27
728 @SerializedEvent
729 @Async.Execute
730 private void __layoutExecute(__Layout__ __layout, int __from, int __to)
731 throws NullPointerException
733 if (__layout == null)
734 throw new NullPointerException("NARG");
736 throw Debugging.todo();
740 * Returns a default title to use for the application.
742 * @return Application default title.
743 * @since 2019/05/16
745 static String __defaultTitle()
747 // Try getting a sensible name from a system property
748 MIDlet amid = ActiveMidlet.optional();
749 if (amid != null)
751 // MIDlet Name
752 String midName = amid.getAppProperty("midlet-name");
753 if (midName != null)
754 return midName;
756 // Otherwise this might not be a MIDlet, so just use the main
757 // class instead
758 String midClass = amid.getAppProperty("main-class");
759 if (midClass != null)
760 return midClass;
763 // Use basic name of the application, if there is one
764 String basicName = ApplicationHandler.currentName();
765 if (basicName != null)
766 return basicName;
768 // Fallback to just using SquirrelJME
769 return "SquirrelJME";