/* * ShareNav - Copyright (c) 2009 sk750 at users dot sourceforge dot net * See file COPYING */ package net.sharenav.midlet.iconmenu; import java.io.IOException; import javax.microedition.lcdui.Font; import javax.microedition.lcdui.Graphics; import javax.microedition.lcdui.Image; import net.sharenav.sharenav.data.Configuration; import net.sharenav.sharenav.data.Legend; import net.sharenav.midlet.util.ImageCache; import net.sharenav.midlet.util.ImageTools; import net.sharenav.util.Logger; import de.enough.polish.util.Locale; public class LayoutElement { private final static Logger logger = Logger.getInstance(LayoutElement.class,Logger.DEBUG); /** align element at minX of LayoutManager area */ public static final int FLAG_HALIGN_LEFT = (1<<0); /** align element right at maxX of LayoutManager area */ public static final int FLAG_HALIGN_RIGHT = (1<<1); /** center element between minX and maxX of the LayoutManager area */ public static final int FLAG_HALIGN_CENTER = (1<<2); /** center element between minX and maxX of the LayoutManager area */ public static final int FLAG_HALIGN_CENTER_TEXT_IN_BACKGROUND = (1<<3); /** align element at minY of the LayoutManager area */ public static final int FLAG_VALIGN_TOP = (1<<4); /** use numCols / numRows from IconMenuPage for positioning */ public static final int FLAG_ICONMENU_ICON = (1<<5); /** align element at maxY of the LayoutManager area */ public static final int FLAG_VALIGN_BOTTOM = (1<<6); /** center element between minY and maxY of the LayoutManager area */ public static final int FLAG_VALIGN_CENTER = (1<<7); /** center top of element between minY and maxY of the LayoutManager area */ public static final int FLAG_VALIGN_CENTER_AT_TOP_OF_ELEMENT = (1<<8); /** position element above the other element that has to be set with setVRelative() */ public static final int FLAG_VALIGN_ABOVE_RELATIVE = (1<<9); /** position element below the other element that has to be set with setVRelative() */ public static final int FLAG_VALIGN_BELOW_RELATIVE = (1<<10); /** position element on same vertical position as the other element that has to be set with setVRelative() */ public static final int FLAG_VALIGN_WITH_RELATIVE = (1<<11); /** position element left to the other element that has to be set with setHRelative() */ public static final int FLAG_HALIGN_LEFTTO_RELATIVE = (1<<12); /** position element right to the other element that has to be set with setHRelative() */ public static final int FLAG_HALIGN_RIGHTTO_RELATIVE = (1<<13); /** when this element becomes a relative reserve space for this element even if text is empty */ public static final int FLAG_RESERVE_SPACE = (1<<14); /** draw a border as background */ public static final int FLAG_BACKGROUND_BORDER = (1<<15); /** draw a box below as background */ public static final int FLAG_BACKGROUND_BOX = (1<<16); /** make the background as wide as the LayoutManager area */ public static final int FLAG_BACKGROUND_FULL_WIDTH = (1<<17); /** make the background as wide as a percentage of the LayoutManager area. Specify with setWidthPercent(); */ public static final int FLAG_BACKGROUND_HEIGHTPERCENT_WIDTH = (1<<18); public static final int FLAG_BACKGROUND_HEIGHTPERCENT_HEIGHT = (1<<19); public static final int FLAG_BACKGROUND_FONTHEIGHTPERCENT_WIDTH = (1<<20); public static final int FLAG_BACKGROUND_FONTHEIGHTPERCENT_HEIGHT = (1<<21); public static final int FLAG_FONT_SMALL = (1<<22); public static final int FLAG_FONT_MEDIUM = (1<<23); public static final int FLAG_FONT_LARGE = (1<<24); public static final int FLAG_FONT_BOLD = (1<<25); public static final int FONT_FLAGS = FLAG_FONT_BOLD | FLAG_FONT_LARGE | FLAG_FONT_MEDIUM | FLAG_FONT_SMALL; public static final int FLAG_IMAGE_GREY = (1<<26); public static final int FLAG_IMAGE_TOGGLEABLE = (1<<27); public static final int FLAG_TRANSPARENT_BACKGROUND_BOX = (1<<28); protected LayoutManager lm = null; private int flags = 0; private LayoutElement vRelativeTo = null; private LayoutElement hRelativeTo = null; public boolean usedAsRelative = false; protected boolean textIsValid = false; protected boolean oldTextIsValid = false; protected boolean isOnScreen = false; protected boolean touched = false; /** make the element width a percentage of the LayoutManager width or font height*/ private int widthPercent = 100; /** make the element high a percentage of the font height or of the LayoutManager height */ private int heightPercent = 100; private Font font = null; private int fontHeight = 0; private int height = 0; private String text = ""; private String imageName; private String toggleImageName; private Image image = null; private boolean imageToggleState = false; private int textWidth = 0; /** number of chars fitting (if a width flag is set) */ private short numDrawChars = 0; private int width = 0; private int bgColor = 0x00FFFFFF; private int fgColor = 0x00000000; public int textLeft = 0; public int left = 0; /** additional offset to be added to left and textLeft */ private int addOffsX = 0; /** additional offset to be added to top */ private int addOffsY = 0; public int textTop = 0; public int top = 0; public int right = 0; public int bottom = 0; private byte specialElementID; private int eleNr; public int actionID = -1; public LayoutElement(LayoutManager lm) { this.lm = lm; } public void init(int flags) { this.flags = flags; initFont(); } private void initFont() { int fontSize = Font.SIZE_LARGE; int fontStyle = Font.STYLE_PLAIN; if ( (flags & FLAG_FONT_SMALL) > 0 ) { fontSize = Font.SIZE_SMALL; } if ( (flags & FLAG_FONT_MEDIUM) > 0 ) { fontSize = Font.SIZE_MEDIUM; } if ( (flags & FLAG_FONT_BOLD) > 0 ) { fontStyle |= Font.STYLE_BOLD; } this.font = Font.getFont(Font.FACE_PROPORTIONAL, fontStyle, fontSize); this.fontHeight = this.font.getHeight(); this.height = this.fontHeight; lm.recalcPositionsRequired = true; } protected void calcSizeAndPosition() { calcSize(); calcPosition(); } public void setTouched(boolean b) { this.touched = b; } public boolean isTouched() { return this.touched; } protected void calcSize() { if ( textIsValid ) { width = textWidth; } else { width = 0; } if ( (flags & FLAG_BACKGROUND_HEIGHTPERCENT_WIDTH) > 0 ) { width = ((lm.maxY - lm.minY) * widthPercent) / 100; } else if ( (flags & FLAG_BACKGROUND_FONTHEIGHTPERCENT_WIDTH) > 0 ) { width = (fontHeight * widthPercent) / 100; } else if ( (flags & FLAG_BACKGROUND_FULL_WIDTH) > 0 ) { width = lm.maxX - lm.minX; } //#debug debug logger.trace("width:" + width); shortenTextToWidth(); if (specialElementID != 0) { width = lm.getSpecialElementWidth(specialElementID, text, font); height = lm.getSpecialElementHeight(specialElementID, fontHeight); } if ( (flags & FLAG_ICONMENU_ICON) > 0 ) { Image im = image; IconMenuPage imp = (IconMenuPage) lm; if (imp.bgImage != null) { // the width of this element is the width of the icon background image im = imp.bgImage; } width = im.getWidth(); height = im.getHeight(); } } private void shortenTextToWidth() { int realWidth = width; if ( (flags & FLAG_ICONMENU_ICON) > 0 ) { realWidth = calcIconReservedWidth((IconMenuPage) lm); } // check how many characters we can draw if the available width is limited while (textWidth > realWidth) { numDrawChars--; textWidth = font.substringWidth(text, 0, numDrawChars); } } protected void calcPosition() { if ( (flags & FLAG_HALIGN_LEFT) > 0 ) { textLeft = lm.minX; left = lm.minX; } else if ( (flags & FLAG_HALIGN_RIGHT) > 0 ) { textLeft = lm.maxX - textWidth - 1; left = lm.maxX - width - 1; } else if ( (flags & FLAG_HALIGN_CENTER) > 0 ) { textLeft = lm.minX + ( lm.maxX - lm.minX - textWidth ) / 2; left = lm.minX + ( lm.maxX - lm.minX - width ) / 2; } else if ( (flags & FLAG_HALIGN_LEFTTO_RELATIVE) > 0 ) { left = getLeftToOrRightToNextVisibleRelative(true); textLeft = left + (width - textWidth) / 2; } else if ( (flags & FLAG_HALIGN_RIGHTTO_RELATIVE) > 0 ) { left = getLeftToOrRightToNextVisibleRelative(false); textLeft = left + (width - textWidth) / 2; } else if ( (flags & FLAG_ICONMENU_ICON) > 0 ) { IconMenuPage imp = (IconMenuPage) lm; int x = eleNr % imp.numCols; int y = eleNr / imp.numCols; if (imp.numCols == 4 && Configuration.getCfgBitState(Configuration.CFGBIT_ICONMENUS_MAPPED_ICONS)) { // imp.numCols == 4 - arrange elements similarly as they are arranged in the 3-column setup x = eleNr % 3; y = eleNr / 3; if (eleNr >= 9) { x = 3; y = eleNr - 9; } } left = imp.minX + x * calcIconReservedWidth(imp) + (calcIconReservedWidth(imp) - imp.bgImage.getWidth()) / 2; textLeft = imp.minX + x * calcIconReservedWidth(imp) + (calcIconReservedWidth(imp) - textWidth) / 2 ; top = imp.minY + y * calcIconReservedHeight(imp); } left += addOffsX; textLeft += addOffsX; if ( (flags & FLAG_HALIGN_CENTER_TEXT_IN_BACKGROUND) > 0 ) { textLeft = left + (width - textWidth) / 2; } right = left + width; if ( (flags & FLAG_BACKGROUND_HEIGHTPERCENT_HEIGHT) > 0 ) { height = ((lm.maxY - lm.minY) * heightPercent) / 100; } else if ( (flags & FLAG_BACKGROUND_FONTHEIGHTPERCENT_HEIGHT) > 0 ) { height = (int) ((float) (fontHeight * heightPercent) / 100); } if ( (flags & FLAG_VALIGN_TOP) > 0 ) { top = lm.minY; } else if ( (flags & FLAG_VALIGN_BOTTOM) > 0 ) { top = lm.maxY - height; } else if ( (flags & FLAG_VALIGN_CENTER) > 0 ) { top = lm.minY + (lm.maxY - lm.minY - height) / 2; } else if ( (flags & FLAG_VALIGN_CENTER_AT_TOP_OF_ELEMENT) > 0 ) { top = lm.minY + (lm.maxY - lm.minY) / 2; } else if ( (flags & FLAG_VALIGN_BELOW_RELATIVE) > 0 ) { top = getAboveOrBelowNextVisibleRelative(false); } else if ( (flags & FLAG_VALIGN_ABOVE_RELATIVE) > 0 ) { top = getAboveOrBelowNextVisibleRelative(true); } else if ( (flags & FLAG_VALIGN_WITH_RELATIVE) > 0 ) { top = vRelativeTo.top; } //System.out.println("Height for " + text + ": " + height + "/" + fontHeight); top += addOffsY; textTop = top; bottom = top + height; if ( (flags & (FLAG_BACKGROUND_FONTHEIGHTPERCENT_HEIGHT + FLAG_BACKGROUND_HEIGHTPERCENT_HEIGHT)) > 0 ) { textTop = top + (height - fontHeight) / 2; } if ( (flags & FLAG_ICONMENU_ICON) > 0 ) { textTop = bottom + 1; } } private int getAboveOrBelowNextVisibleRelative(boolean getAbove) { LayoutElement eRelative = vRelativeTo; int newTop = 0; while (eRelative != null) { if ( eRelative.textIsValid || (eRelative.flags & FLAG_RESERVE_SPACE) > 0 ) { if (getAbove) { newTop = eRelative.top - height; } else { newTop = eRelative.bottom; } break; } else { newTop = eRelative.top; } eRelative = eRelative.vRelativeTo; } return newTop; } private int getLeftToOrRightToNextVisibleRelative(boolean getLeft) { LayoutElement eRelative = hRelativeTo; int newLeft = 0; while (eRelative != null) { if (getLeft) { newLeft = eRelative.left - width; } else { newLeft = eRelative.right; } if ( eRelative.textIsValid || (eRelative.flags & FLAG_RESERVE_SPACE) > 0 ) { break; } eRelative = eRelative.hRelativeTo; } return newLeft; } /** Sets the base icon name. * For elements which can be toggled, the following applies: * If setToggleImageName() is not used, '0' and '1' get appended to this name * to derive the actual file names. If it is used, the name set here is the name * of the normal icon. * @param name Base name of icon */ public void setImageNameOnly(String name) { imageName = name; unloadImage(); } /** Explicitly sets the name of the alternative icon. If this is set, '0' won't be * needed at the end of the normal icon. * This is the only chance to reuse icons, as else versions of them with '0' and '1' * appended to the names would be needed. * @param name Name of the alternative icon */ public void setToggleImageName(String name) { toggleImageName = name; } public void unloadImage() { image = null; } public void loadImage() { if (image == null) { setAndLoadImage(imageName); calcSizeAndPosition(); } } public void setAndLoadImage(String name) { imageName = name; try { String imageName2 = imageName; if (hasFlag(FLAG_IMAGE_TOGGLEABLE)) { if (toggleImageName == null) { imageName2 += (imageToggleState ? "1" : "0"); } else { if (imageToggleState) { imageName2 = toggleImageName; } } //#debug debug logger.debug("Load toggle image: " + imageName2); } Image orgImage; try { orgImage = Image.createImage("/" + Configuration.getIconPrefix() + imageName2 + ".png"); } catch (IOException ioe) { //#debug debug logger.debug("Fall back to i_*.png for " + imageName2); try { orgImage = Image.createImage("/" + imageName2 + ".png"); } catch (IOException ioe2) { //#debug debug logger.debug("Fall back to *_i_bg.png for " + imageName2); try { orgImage = Image.createImage("/" + Configuration.getIconPrefix() + "i_bg.png"); } catch (IOException ioe3) { //#debug debug logger.debug("Fall back to i_bg.png for " + imageName2); orgImage = Image.createImage("/i_bg.png"); } } } if ( (flags & FLAG_ICONMENU_ICON) > 0) { orgImage = scaleIconImage(orgImage, (IconMenuPage) lm, fontHeight, 0); if ( (flags & FLAG_IMAGE_GREY) > 0 ) { orgImage = ImageTools.getGreyImage(orgImage); } } image = orgImage; } catch (IOException ioe) { logger.exception(Locale.get("layoutelement.FailedToLoadIcon")/*Failed to load icon*/ + " " + imageName, ioe); } } private static int calcIconReservedHeight(IconMenuPage imp) { //System.out.println("maxY " + imp.maxY + " minY: " + imp.minY); return (imp.maxY - imp.minY) / imp.numRows; } private static int calcIconReservedWidth(IconMenuPage imp) { return (imp.maxX - imp.minX) / imp.numCols; } public static Image scaleIconImage(Image image, IconMenuPage imp, int fontHeight, int extraSize) { int imgWidth = calcIconReservedWidth(imp); int imgHeight = calcIconReservedHeight(imp) - fontHeight; int imgHeightRelativeToWidth = (image.getHeight() * imgWidth) / image.getWidth(); int imgWidthRelativeToHeight = (image.getWidth() * imgHeight) / image.getHeight(); if (imgWidth > imgWidthRelativeToHeight) { imgWidth = imgWidthRelativeToHeight; } else if (imgHeight > imgHeightRelativeToWidth) { imgHeight = imgHeightRelativeToWidth; } // shrink the icons until there's enough space between them while (imgWidth > 10 && imgHeight > 10 && (calcIconReservedWidth(imp) - imgWidth < fontHeight / 2 || calcIconReservedHeight(imp) - fontHeight - imgHeight < fontHeight / 2) || // also shrink the icon while it is wider/higher than the bgImage's width/height (imp.bgImage != null && ( imp.bgImage.getWidth() < imgWidth || imp.bgImage.getHeight() < imgHeight) ) ) { // System.out.println("h: " + (calcIconReservedHeight(imp) - fontHeight - imgHeight) + " fh: " + fontHeight / 2); // System.out.println("w: " + (calcIconReservedWidth(imp) - imgWidth) ); imgWidth = imgWidth * 9 / 10; imgHeight = imgHeight * 9 / 10; } // System.out.println("actual Width/Height " + imgWidth + " " + imgHeight); return ImageTools.scaleImage(image, imgWidth + extraSize, imgHeight + extraSize); } /** Sets the state for toggling between two icons. * True = use the alternative icon (with '1' appended to the base name) * False = use the normal icon (with '0' appended to the base name) * @see setToggleImageName to set the image names explicitly * @param state New state */ public void setImageToggleState(boolean state) { boolean oldState = imageToggleState; if (oldState != state) { imageToggleState = state; if (image != null) { image = null; loadImage(); } } } public void makeImageGreyed() { setFlag(FLAG_IMAGE_GREY); if (image != null) { //#debug debug logger.debug("Reload image greyed"); image = null; loadImage(); } } public void makeImageColored() { clearFlag(FLAG_IMAGE_GREY); if (image != null) { //#debug debug logger.debug("Reload image colored"); image = null; loadImage(); } } public void setTextValid() { setText(text); } public void setTextInvalid() { textIsValid = false; } public void setText(String text) { if (!textIsValid || !text.equalsIgnoreCase(this.text) ) { this.text = text; //TODO: There are 6 NPEs at startup on Android in the following line. textWidth = font.stringWidth(text); numDrawChars = (short) text.length(); lm.recalcPositionsRequired = true; if (image != null) { //TODO: there should be evaluated a better way to change the label of icons Image orgImage = image; // recalc available width and shorten text to available width (without image) unloadImage(); calcSize(); image = orgImage; } } oldTextIsValid = textIsValid; textIsValid = numDrawChars!=0; } public String getText() { return text; } public int getFlags() { return flags; } /** Set vertical relative to this relative element's position and height combine * with FLAG_VALIGN_ABOVE_RELATIVE or FLAG_VALIGN_BELOW_RELATIVE for the relative direction. */ public void setVRelative(LayoutElement e) { vRelativeTo = e; vRelativeTo.usedAsRelative = true; lm.recalcPositionsRequired = true; if (vRelativeTo.flags == 0) { logger.error(Locale.get("layoutelement.WarningTriedUninitialisedElement")/*Warning: Tried to use uninitialised element*/ + " " + e + " " + Locale.get("layoutelement.asvRelative")/*as vRelative*/); } } public void setEleNr(int eleNr) { this.eleNr = eleNr; } public void setFlag(int flag) { if ((flag & FONT_FLAGS) > 0) { flags &= ~FONT_FLAGS; flags |= flag; initFont(); } else { flags |= flag; } } public void clearFlag(int flag) { flags &= ~flag; } public boolean hasFlag(int flag) { return (flags & flag) > 0; } /** * Set horizontal relative to this relative element's position and width. * Combine with FLAG_VALIGN_LEFTTO_RELATIVE or FLAG_RIGHTTO_RELATIVE for the relative direction. */ public void setHRelative(LayoutElement e) { hRelativeTo = e; hRelativeTo.usedAsRelative = true; lm.recalcPositionsRequired = true; if (hRelativeTo.flags == 0) { logger.error(Locale.get("layoutelement.WarningTriedUninitialisedElement")/*Warning: Tried to use uninitialised element*/ + " " + e + " " + Locale.get("layoutelement.ashRelative")/*as hRelative*/); } } public void setWidthPercent(int p) { widthPercent = p; lm.recalcPositionsRequired = true; } public void setHeightPercent(int p) { heightPercent = p; lm.recalcPositionsRequired = true; } public void setColor(int color) { fgColor = color; } public void setBackgroundColor(int color) { bgColor = color; } public void setAdditionalOffsX(int offsX) { addOffsX = offsX; } public void setAdditionalOffsY(int offsY) { addOffsY = offsY; } public void setSpecialElementID(byte id) { specialElementID = id; } public void setActionID(int id) { actionID = id; } public int getFontHeight() { return fontHeight; } public Font getFont() { return font; } public String getValidationError() { if (flags == 0) { return "not initialised"; } if ( (flags & (FLAG_HALIGN_LEFT | FLAG_HALIGN_CENTER | FLAG_HALIGN_RIGHT | FLAG_HALIGN_LEFTTO_RELATIVE | FLAG_ICONMENU_ICON | FLAG_HALIGN_RIGHTTO_RELATIVE)) == 0) { return "horizontal position flag missing"; } if ( (flags & (FLAG_VALIGN_BOTTOM | FLAG_VALIGN_CENTER |FLAG_VALIGN_CENTER_AT_TOP_OF_ELEMENT | FLAG_VALIGN_TOP | FLAG_ICONMENU_ICON |FLAG_VALIGN_ABOVE_RELATIVE | FLAG_VALIGN_BELOW_RELATIVE | FLAG_VALIGN_WITH_RELATIVE)) == 0) { return "vertical position flag missing"; } if (vRelativeTo == null && (flags & (FLAG_VALIGN_ABOVE_RELATIVE | FLAG_VALIGN_BELOW_RELATIVE)) > 0) { return "vRelativeTo parameter missing"; } if (hRelativeTo == null && (flags & (FLAG_HALIGN_LEFTTO_RELATIVE | FLAG_HALIGN_RIGHTTO_RELATIVE )) > 0) { return "hRelativeTo parameter missing"; } if ( (flags & (FLAG_FONT_SMALL | FLAG_FONT_MEDIUM | FLAG_FONT_LARGE)) == 0) { return "font size missing"; } return null; } public void paintHighlighted(Graphics g) { //#debug trace logger.trace("draw highlight box at " + left + "," + top + " size: " + (right-left) + "/" + (bottom - top)); g.setColor(lm.touchedElementBackgroundColor); g.fillRect(left, top, right-left, bottom - top); int oldFlags = flags; int oldBgColor = bgColor; clearFlag(FLAG_BACKGROUND_BOX); paint(g); flags = oldFlags; } public void paint(Graphics g) { if (specialElementID != 0 && textIsValid) { if ( (flags & FLAG_TRANSPARENT_BACKGROUND_BOX) > 0 ) { Image imgBackground = ImageCache.getOneColorImage(0x80FFFFFF, right -left - 1, bottom - top - 1); if (imgBackground != null) { g.drawImage(imgBackground, left + 1, top + 1, Graphics.TOP | Graphics.LEFT); } } g.setFont(font); lm.drawSpecialElement(g, specialElementID, text, left, top); isOnScreen = true; } else if ( (numDrawChars > 0 && textIsValid) || image != null) { if ( (flags & FLAG_BACKGROUND_BOX) > 0 ) { g.setColor(bgColor); //#debug trace logger.trace("draw box at " + left + "," + top + " size: " + (right-left) + "/" + (bottom - top)); g.fillRect(left, top, right-left, bottom - top); } if ( (flags & FLAG_BACKGROUND_BORDER) > 0 ) { g.setColor(bgColor); //#debug trace logger.trace("draw border at " + left + "," + top + " size: " + (right-left) + "/" + (bottom - top)); g.setStrokeStyle(Graphics.SOLID); g.drawRect(left, top, right-left, bottom - top); } if ( (flags & FLAG_TRANSPARENT_BACKGROUND_BOX) > 0 ) { Image imgBackground = ImageCache.getOneColorImage(0x80FFFFFF, right -left - 1, bottom - top - 1); if (imgBackground != null) { g.drawImage(imgBackground, left + 1, top + 1, Graphics.TOP | Graphics.LEFT); } } if ( (flags & FLAG_ICONMENU_ICON) > 0 ) { IconMenuPage imp = (IconMenuPage) lm; if (imp.bgImage != null) { g.drawImage(imp.bgImage, left, top, Graphics.TOP|Graphics.LEFT); g.drawImage(image, left + (imp.bgImage.getWidth() - image.getWidth()) / 2, top + (imp.bgImage.getHeight() - image.getHeight()) / 2, Graphics.TOP|Graphics.LEFT); } else { g.drawImage(image, left, top, Graphics.TOP|Graphics.LEFT); } if (this.touched || imp.touchedElement == this) { g.drawImage(imp.hlImage, left - 1, top - 1, Graphics.TOP|Graphics.LEFT); } } if (font != null) { g.setColor(fgColor); g.setFont(font); //#debug trace logger.trace("draw " + text + " at " + textLeft + "," + top ); g.drawSubstring(text, 0, numDrawChars, textLeft, textTop, Graphics.TOP|Graphics.LEFT); isOnScreen = true; } else { //#debug debug logger.debug("no font for element"); } } else { isOnScreen = false; } oldTextIsValid = textIsValid; textIsValid = false; // text is invalid after drawing until it is set with setText() again } public boolean isInElement(int x, int y) { return (isOnScreen && x >= left && x <= right && y >= top && y <= bottom); } public boolean hasAnyValidActionId() { return actionID != -1; } }