1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
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
;
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.
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. */
46 volatile CommandListener _cmdListener
;
48 /** The ticker of the displayable. */
50 volatile Ticker _ticker
;
52 /** The layout policy of this displayable. */
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
=
63 /** The current menu bar. */
64 final MenuLayoutBar _menuBar
;
66 /** The default menu. */
67 private final Menu _menuDefault
;
70 * Initializes the base displayable object.
77 DisplayableState state
= new DisplayableState(this);
81 MenuLayoutBar menuBar
= new MenuLayoutBar(
82 state
.scritchApi(), this);
83 this._menuBar
= menuBar
;
86 Menu menuDefault
= new Menu("App", "Application",
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());
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.
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.
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.
132 public void addCommand(Command __c
)
133 throws DisplayCapabilityException
, NullPointerException
136 throw new NullPointerException("NARG");
138 // Have the event loop handle this
139 this._state
.scritchApi().eventLoop()
140 .execute(new __ExecDisplayableDefaultCommand__(this,
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}.
158 public CommandLayoutPolicy
getCommandLayoutPolicy()
160 return this._layoutPolicy
;
164 * Returns the command listener.
166 * @return The command listener.
170 protected CommandListener
getCommandListener()
172 return this._cmdListener
;
176 * Gets the commands which are available to use.
178 * @return The available commands.
182 public Command
[] getCommands()
184 try (MenuLayoutLock lock
= this._layoutLock
.open(false))
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)
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.
208 public Display
getCurrentDisplay()
210 return this.__getCurrentDisplay();
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.
226 public Ticker
getTicker()
232 * Returns the title of this displayable.
234 * @return The title of this displayable.
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.
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.
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.
277 public void removeCommand(Command __c
)
282 // Have the event loop handle this
283 this._state
.scritchApi().eventLoop()
284 .execute(new __ExecDisplayableDefaultCommand__(this,
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
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.
309 public void setCommand(Command __c
, int __p
)
310 throws DisplayCapabilityException
, IllegalArgumentException
,
311 IllegalStateException
, NullPointerException
313 try (MenuLayoutLock lock
= this._layoutLock
.open(false))
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
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.
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
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.
367 public void setMenu(Menu __m
, int __p
)
368 throws DisplayCapabilityException
, IllegalArgumentException
,
369 IllegalStateException
, NullPointerException
371 try (MenuLayoutLock lock
= this._layoutLock
.open(false))
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}
392 public void setTicker(Ticker __t
)
394 throw Debugging
.todo();
396 // Removing old ticker?
397 Ticker old = this._ticker;
407 // Remove from display list
408 old._displayables.remove(this);
410 // Perform ticker update
411 this.__updateTicker();
414 // Setting the same ticker?
418 // Add new ticker, note they can be associated with many displays
421 // Add to displayable list
422 __t._displayables.addUniqueObjRef(this);
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.
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.
456 protected void sizeChanged(int __w
, int __h
)
458 // Implemented by subclasses
462 * Rebuilds the displayable menu.
469 void __execMenuRebuild()
471 throw Debugging
.todo();
475 * Performs revalidation for ScritchUI, overridden as needed.
477 * @param __parent The parent display.
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.
496 @SquirrelJMEVendorApi
497 private Display
__getCurrentDisplay()
499 DisplayState display
= this._state
.currentDisplay();
502 return display
.display();
507 * Returns if this displayable is currently being shown.
509 * @return If the displayable is being shown.
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;
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
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.
544 private void __layoutActionSet(MenuAction __a
, int __p
)
545 throws DisplayCapabilityException
, IllegalArgumentException
,
546 IllegalStateException
, NullPointerException
549 throw new NullPointerException("NARG");
551 throw Debugging
.todo();
553 /* {@squirreljme.error EB3i The current display does not support
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;
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
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
599 policy.onCommandLayout(this);
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)
616 // Cancel the layout state
626 * Lays out commands using a default means.
628 * @param __layout The layout state.
629 * @throws NullPointerException On null arguments.
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
653 for (int place : prefPlace)
654 if (__layout.get(place) == null)
660 // Otherwise, try to set the item at the lowest placement
663 // Determine the priority of this item
664 int actPriority = __Action__.__getPriority(action);
666 // The position and the currently lowest scoring slot
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)
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
691 __layout.set(action, usePlace);
696 * Executes the given layout.
698 * @param __layout The layout to execute.
699 * @throws NullPointerException On null arguments.
704 private void __layoutExecute(__Layout__ __layout
)
705 throws NullPointerException
707 if (__layout
== null)
708 throw new NullPointerException("NARG");
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.
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.
745 static String
__defaultTitle()
747 // Try getting a sensible name from a system property
748 MIDlet amid
= ActiveMidlet
.optional();
752 String midName
= amid
.getAppProperty("midlet-name");
756 // Otherwise this might not be a MIDlet, so just use the main
758 String midClass
= amid
.getAppProperty("main-class");
759 if (midClass
!= null)
763 // Use basic name of the application, if there is one
764 String basicName
= ApplicationHandler
.currentName();
765 if (basicName
!= null)
768 // Fallback to just using SquirrelJME
769 return "SquirrelJME";