package nxm.sys.libg;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.GraphicsEnvironment;
import java.awt.GridLayout;
import java.awt.LayoutManager;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.regex.Pattern;

import javax.swing.Icon;
import javax.swing.JCheckBox;
import javax.swing.JColorChooser;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;

import nxm.sys.inc.InternalUseOnly;
import nxm.sys.inc.MessageHandler;
import nxm.sys.lib.Convert;
import nxm.sys.lib.Message;
import nxm.sys.lib.MidasException;
import nxm.sys.lib.Parser;
import nxm.sys.lib.Shell;
import nxm.sys.lib.StringUtil;
import nxm.sys.lib.Table;

/** This provides an easy way to create popup dialog boxes. A number of the basic options map to
    {@link JOptionPane} however there are a number of more complex types found here that are not
    available in {@link JOptionPane}. <br>
    <br>
    For each popup type there are usually two methods: a <code>create&lt;Name&gt;</code> simply
    creates a new popup; <code>show&lt;Name&gt;</code> creates the pupup, displays it, and waits
    for user input. The <code>create&lt;Name&gt;</code> provides much more freedom for control
    over how the popup is displayed (position, modality, blocking, etc), while the
    <code>show&lt;Name&gt;</code> works just like the <code>JOptionPane.show&lt;Name&gt;Dialog</code>
    methods.<br>
    <br>
    <table border="1">
      <caption style="visibility:hidden">""</caption>
      <tr>
        <th>Popup Name</th>
        <th>Description</th>
        <th>User Input Type</th>
        <th>Clickable Buttons</th>
        <th>Methods</th>
      </tr>
      <tr>
        <td>Alert</td>
        <td>Indicates an "alert" condition is in progress</td>
        <td><i>n/a</i></td>
        <td><i>no buttons</i></td>
        <td>{@link #createAlert createAlert}</td>
      </tr>
      <tr>
        <td>Buttons</td>
        <td>Allows user to choose from various options</td>
        <td>JButton</td>
        <td><i>customizable</i></td>
        <td>{@link #createButtons createButtons}<br>{@link #showButtons showButtons}</td>
      </tr>
      <tr>
        <td>Choice</td>
        <td>Allows user to choose from various options</td>
        <td>JComboBox</td>
        <td>'Ok', 'Cancel'</td>
        <td>{@link #createChoice createChoice}<br>{@link #showChoice showChoice}</td>
      </tr>
      <tr>
        <td>Color</td>
        <td>Allows user to pick a color from a color chooser</td>
        <td>JColorChooser</td>
        <td>'Ok', 'Cancel'</td>
        <td>{@link #createColor createColor}<br>{@link #showColor showColor}</td>
      </tr>
      <tr>
        <td>Group</td>
        <td>TBD</td>
        <td>TBD</td>
        <td>'Ok', 'Cancel'</td>
        <td>TBD</td>
      </tr>
      <tr>
        <td>IntMask</td>
        <td><b>Legacy:</b> Allows selection of an integer mask</td>
        <td>JCheckBox</td>
        <td>'Ok', 'Cancel'</td>
        <td>{@link #createIntMask createIntMask}</td>
      </tr>
      <tr>
        <td>Login</td>
        <td>Prompts user for user name and password</td>
        <td>JTextField<br>JPasswordField</td>
        <td>'Ok', 'Cancel'</td>
        <td>{@link #createLogin createLogin}<br>{@link #showLogin showLogin}</td>
      </tr>
      <tr>
        <td>Mask</td>
        <td>Allows user to select a set of options (i.e. a "mask" value)</td>
        <td>JCheckBox</td>
        <td>'Ok', 'Cancel'</td>
        <td>{@link #createMask(Component,String,int,Object,Object[],Object,boolean) createMask}<br>
            {@link #showMaskArray showMaskArray}<br>{@link #showMaskString showMaskString}</td>
      </tr>
      <tr>
        <td>Ok</td>
        <td>Simple notification popup</td>
        <td><i>n/a</i></td>
        <td>'Ok'</td>
        <td>{@link #createOk createOk}<br>{@link #showOk showOk}</td>
      </tr>
      <tr>
        <td>OkCancel</td>
        <td>Simple notification popup</td>
        <td><i>n/a</i></td>
        <td>'Ok', 'Cancel'</td>
        <td>{@link #createOkCancel createOkCancel}<br>{@link #showOkCancel showOkCancel}</td>
      </tr>
      <tr>
        <td>Password</td>
        <td>Prompts user to enter a password</td>
        <td>JPasswordField</td>
        <td>'Ok', 'Cancel'</td>
        <td>{@link #createPassword createPassword}<br>{@link #showPassword showPassword}</td>
      </tr>
      <tr>
        <td>Text</td>
        <td>Prompts user for text input</td>
        <td>JTextField</td>
        <td>'Ok', 'Cancel'</td>
        <td>{@link #createText createText}<br>{@link #showText showText}</td>
      </tr>
      <tr>
        <td>YesNo</td>
        <td>Asks the user a question</td>
        <td><i>n/a</i></td>
        <td>'Yes', 'No'</td>
        <td>{@link #createYesNo createYesNo}<br>{@link #showYesNo showYesNo}</td>
      </tr>
      <tr>
        <td>YesNoCancel</td>
        <td>Asks the user a question</td>
        <td><i>n/a</i></td>
        <td>'Yes', 'No', 'Cancel'</td>
        <td>{@link #createYesNoCancel createYesNoCancel}<br>{@link #showYesNoCancel showYesNoCancel}</td>
      </tr>
    </table><br>
    <br>
    Notes:
    <ul>
      <li>Prior to NeXtMidas 2.5.0 the only way to create a popup was using the
          nxm.sys.prim.popup (from a macro) or by using {@link JOptionPane} from a
          primitive. Since NeXtMidas 2.5.0 the nxm.sys.prim.popup (still used from
          a macro) just uses this library. However, primitives can now make use of this
          functionality.</li>
      <li>The names of the different popup types match the <code>POPUP</code> command.</li>
      <li>If the parent component is in a JScrollPane, and you want the popup to be centered,
          use <code>myComponent.getParent()</code> as the parent. Otherwise the center point
          will be computed based on the entire scrollable area (not just the visible part) and
          may be off-screen.</li>
    </ul>
    @author  J. Czechowski IV
    @since   NeXtMidas 2.5.0
*/
@SuppressWarnings({"rawtypes","serial","unchecked"}) // <-- NOTE: JDK7 has some rawtypes & unchecked Xlint warnings that CANNOT be fixed since we still support JDK6
public class MJPopup {
  private static boolean DEBUG = false; // Turn on debugging

  private static final String[] NO_BUTTONS   = new String[0];   // Hide all buttons
  private static final Integer  ZERO         = Integer.valueOf(0);  // Number Zero
  private static final Integer  USER_CANCEL  = Integer.valueOf(-1); // User-initiated cancel
  private static final Integer  FORCE_CANCEL = Integer.valueOf(-2); // Programmatic cancel
  private static final Pattern  SEP_REGEX    = Pattern.compile("[,|]"); // Regex for ',' or '|'

  /** Popup position: Centered over parent (or centered on display if parent==null). */
  public static final int[] POSITION_CENTER = null;

  /** Popup position: Centered over current mouse position (if mouse position is not
      available default to {@link #POSITION_CENTER}).
   */
  public static final int[] POSITION_MOUSE  = new int[0];

  /** List of options. */
  public static final String blockingOptionsList = "Macro,Mouse";

  /** Blocking mask: Block macro execution. */
  public static final int BLOCK_MACRO   = 0x0001;
  /** Blocking mask: Block mouse interaction. */
  public static final int BLOCK_MOUSE   = 0x0002;

  private Component   parent;      // The parent component to use.
  private JOptionPane optionPane;  // The option pane to use.
  private JDialog     dialogBox;   // The dislog box holding the option pane.
  private HasValue    valuator;    // If not null, this holds the value of the user's input.
  private boolean     cancel;      // Externally canceled.
  private int         blocking;    // Blocking options mask.
  private static int  popWidth=80; // max width (in characters) of the message in the popup

  /** Create an empty instance of an MJPopup. This is only used to set the width of
      the text message in the popup, which must be done PRIOR to creating the popup itself.
      @param popWidth The width in characters of the text message
      @since NeXtMidas 2.7.3
   */
  public MJPopup (int popWidth) {
    MJPopup.popWidth = popWidth;
  }

  /** Private -- internal use only */
  private MJPopup(Component parent, JOptionPane optionPane, JDialog dialogBox, HasValue valuator) {
    this.parent     = parent;
    this.optionPane = optionPane;
    this.dialogBox  = dialogBox;
    this.valuator   = valuator;
  }

  /** Indicates if the popup was canceled either by the user or by calling {@link #cancel()}.
      @return if popup was canceled
   */
  public boolean getCancel() {
    Object[] val = getInfoValue();
    return (val[2] != ZERO);
  }

  /** Gets the info of the object (usually 0).
      @return info of this object
   */
  public int getInfo() {
    Object[] infoValue = getInfoValue();
    return ((Integer)infoValue[0]).intValue();
  }

  /** Gets the value of the object.
      @return value of this object
   */
  public Object getValue() {
    Object[] infoValue = getInfoValue();
    return infoValue[1];
  }

  /** Dispose of the popup and release the graphical resources it was using. */
  public void dispose() {
    if (dialogBox  != null) {
      dialogBox.dispose();
      dialogBox = null;
    }
    optionPane = null;
  }

  /** Indicates if it is a YesNo dialogue box rather than an Ok one. */
  private boolean isYesNo() {
    int type = optionPane.getOptionType();
    return (type == JOptionPane.YES_NO_CANCEL_OPTION) || (type == JOptionPane.YES_NO_OPTION);
  }

  /** Gets both the info and value from the popup. */
  private Object[] getInfoValue() {
    Object  value = (optionPane == null)? null: optionPane.getValue();
    Object  input = null;
    Integer info  = ZERO;
    Integer cancl = ZERO;

    if (cancel || (value == JOptionPane.UNINITIALIZED_VALUE)) {
      cancl = FORCE_CANCEL;
    } else if (value == null) {
      cancl = USER_CANCEL;
    } else {
      int optionType = optionPane.getOptionType();

      info  = ZERO;
      input = value.toString();

      if (value instanceof Number) {
        int button = ((Number)value).intValue(); // Note JOptionPane.OK_OPTION = JOptionPane.YES_OPTION

        if ((button == JOptionPane.OK_OPTION) && (valuator != null)) {
          info  = Integer.valueOf(valuator.getInfo());
          input = valuator.getValue();
        } else if (button == JOptionPane.OK_OPTION) {
          input = (isYesNo())? "YES": "OK";
        } else if (button == JOptionPane.NO_OPTION) {
          input = "NO";
        } else {
          cancl = USER_CANCEL;
        }
      } else if (true) { // Used with "BUTTONS" input type
        Object[] options = optionPane.getOptions();

        info = null;
        for (int i = 0; (i < options.length) && (info == null); i++) {
          if (options[i] == input) {
            info = Integer.valueOf(i+1);
          }
        }
      }
    }
    if (cancl != ZERO) {
      info  = cancl;
      input = "CANCEL";
    }
    return new Object[] { info, input, cancl };
  }

  /** Indicates if the popup is done.
      @return true if popup is done
   */
  public boolean isDone() {
    boolean uninit = (optionPane!=null) && (optionPane.getValue()==JOptionPane.UNINITIALIZED_VALUE);
    return cancel || !uninit;
  }

  /** Cancels the popup programmatically. */
  public void cancel() {
    cancel = true;
  }

  /** Displays the dialog. Note that this can only be called once. This is the
      same as <code>display(BLOCK_MACRO|BLOCK_MOUSE)</code>, except that is returns
      the entered value when done.
      @return The value entered by the user, or null if the dialog was canceled.
   */
  private Object display() {
    display(BLOCK_MACRO|BLOCK_MOUSE);
    Object[] val = getInfoValue();
    return (val[2] == ZERO)? val[1]: null;
  }

  /** Displays the dialog. Note that this can only be called once. This is the
      same as <code>display(blocking,POSITION_CENTER)</code>.
      @param blocking The blocking mode flags to use.
   */
  public void display(int blocking) {
    display(blocking,POSITION_CENTER);
  }

  /** Displays the dialog. Note that this can only be called once.
      @param blocking The blocking mode flags to use.
      @param position The position specifier ({@link #POSITION_CENTER}, {@link #POSITION_MOUSE},
                      <code>{x,y}</code> or <code>{x,y,w,h}</code>).
   */
  public void display(int blocking, int[] position) {
    display(null,blocking,POSITION_CENTER);
  }

  /** Displays the dialog. Note that this can only be called once.
      @param mw       The MWindow used for finding current mouse position. (Ignored when
                      using Java 5 or later).
      @param blocking The blocking mode flags to use.
      @param position The position specifier ({@link #POSITION_CENTER}, {@link #POSITION_MOUSE},
                      <code>{x,y}</code> or <code>{x,y,w,h}</code>).
   */
  public void display(MWindow mw, int blocking, int[] position) {
    display(mw,null,null,blocking,position);
  }

  /** Displays the dialog. Note that this can only be called once.
      @param mw       The MWindow used for finding current mouse position. (Ignored when
                      using Java 5 or later).
      @param msgHandler
      @param blocking The blocking mode flags to use.
      @param position The position specifier ({@link #POSITION_CENTER}, {@link #POSITION_MOUSE},
                      <code>{x,y}</code> or <code>{x,y,w,h}</code>).
   */
  private void display(MWindow mw, MessageHandler msgHandler, String msgName, int blocking, int[] position) {
    boolean modal = ((blocking & BLOCK_MOUSE) != 0);
    boolean block = ((blocking & BLOCK_MACRO) != 0);

    // SET DIALOG POSITION
    setDialogPosition(position,mw);

    // DISPLAY DIALOGUE
    dialogBox.setModal(modal);
    if (block) {
      dialogBox.setVisible(true);
    } else {
      (new MJPopupThread(msgHandler,msgName)).start();
    }
  }

  /** Sends a message to the message handler. Only called after dialog is done. */
  private void sendMessage(MessageHandler msgHandler, String msgName) {
    if (msgHandler != null) {
      Message msg = new Message(msgName, getInfo(), getValue());
      msgHandler.processMessage(msg);
    }
  }

  /** Gets the bounds of a given component, if null the bounds for the screen are returned. */
  private static Rectangle getBounds(Component parent) {
    Rectangle parentBounds;
    if (parent != null) {
      parentBounds = parent.getBounds();
    } else {
      parentBounds = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
    }
    return parentBounds;
  }

  /** Gets the center point of a given component, if null the center point for the screen is returned. */
  private static Point getCenterPoint(Component parent) {
    Point centerPoint;
    if ((parent != null) && parent.isShowing()) { // Add check for parent showing 4.1.4
      Rectangle parentBounds = parent.getBounds();
      centerPoint  = parent.getLocationOnScreen();
      centerPoint.x += parentBounds.width/2;
      centerPoint.y += parentBounds.height/2;
    } else {
      centerPoint  = GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint();
    }
    return centerPoint;
  }

  /** Gets the suggested max size of the dialog based on its parent. */
  private static Dimension getMaxSize(Component parent) {
    double scaleFact = (parent != null)? 1.00: 0.50;
    return getMaxSize(parent,scaleFact);
  }

  /** Gets the suggested absolute size of the dialog based on its parent. */
  private static Dimension getAbsSize(Component parent) {
    return getMaxSize(parent,1.00);
  }

  /** Gets the suggested max size of the dialog based on its parent. */
  private static Dimension getMaxSize(Component parent, double scaleFact) {
    Rectangle parentBounds = getBounds(parent);

    int maxWidth  = Math.max(400, (int)(scaleFact * parentBounds.getWidth()));
    int maxHeight = Math.max(200, (int)(scaleFact * parentBounds.getHeight()));

    return new Dimension(maxWidth,maxHeight);
  }

  /** Get the **suggested** number of columns to use for a grid layout.
      @param count   Number of elements.
      @param width   Max element width.
      @param height  Max element height.
      @param maxSize Max size allowable for grid (not null).
      @param absSize Absolute screen size (can be null).
      @return The suggested number of columns.
   */
  private static int findColCount(int count, int width, int height, Dimension maxSize, Dimension absSize) {
    int stdWidth  = (int)(1.5f * width);
    int stdHeight = (int)(1.5f * height);
    int columns   = -1;

    for (int cols = 1; (columns <= 0); cols++) {
      int rows      = ((count+cols-1) / cols);
      int totWidth  = stdWidth  * (count / rows);
      int totHeight = stdHeight * (count / cols);

      if (totWidth > maxSize.width) {
        // Nothing works with maxSize
        if (absSize != null) {
          columns = findColCount(count,width,height,absSize,null); // Try absSize.
        } else {
          columns = cols; // Just pick arbitrary constraint.
        }
      } else if (totHeight > maxSize.height) {
        // keep trying
      } else {
        columns = cols;
      }
    }
    return columns;
  }

  /** Sets the position of the dialog.
      @param position The position specifier ({@link #POSITION_CENTER}, {@link #POSITION_MOUSE},
                      <code>{x,y}</code> or <code>{x,y,w,h}</code>).
      @param mw       The MWindow used for finding current mouse position. (Ignored when
                      using Java 5 or later).
   */
  private void setDialogPosition(int[] position, MWindow mw) {
    Rectangle parentBounds = getBounds(parent);
    Point     centerPoint  = getCenterPoint(parent);
    Dimension size         = dialogBox.getSize();
    boolean   manual       = false;

    int x = -1; // Position (-1=Centered)
    int y = -1; // Position (-1=Centered)
    int w = dialogBox.getWidth();
    int h = dialogBox.getHeight();

    if (position == POSITION_CENTER) {
      // Nothing to do, this is the default.
    } else if (position == POSITION_MOUSE) {
      centerPoint = getMousePosition(mw);
    } else if ((position.length == 2) || (position.length == 4)) {
      Point los = (parent == null)? new Point(0,0): parent.getLocationOnScreen();
      manual = true;
      x = position[0] + los.x;
      y = position[1] + los.y;

      if (position.length == 4) { // manual Width and Height are set
        w = position[2];
        h = position[3];
      }
    } else {
      throw new MidasException("MJPopup: Illegal dialog position, expected POSITION_CENTER, "
                             + "POSITION_MOUSE, {x,y}, or {x,y,w,h} but got " + Arrays.toString(position));
    }
    if (x < 0) { x = centerPoint.x - w/2; }
    if (y < 0) { y = centerPoint.y - h/2; }

    if (!manual) { // Fix dimensions so it fits on screen.
      Rectangle screen = getBounds(null);
      int maxX = screen.x + screen.width;
      int maxY = screen.y + screen.height;
      w = Math.min(w, screen.width);
      h = Math.min(h, screen.height);

      if (x < screen.x) { x = screen.x; } else if ((x+w) > maxX) { x = maxX - w; }
      if (y < screen.y) { y = screen.y; } else if ((y+h) > maxY) { y = maxY - h; }
    }
    dialogBox.setBounds(x,y,w,h);
  }

  /** Gets the current mouse position. Requires Java 5 or later */
  private static Point getMousePosition(MWindow mw) {
    PointerInfo pointerInfo = MouseInfo.getPointerInfo(); // Returns null if no mouse present
    return (pointerInfo == null)? getCenterPoint(null): pointerInfo.getLocation();
  }

  /** Internal create method: OkCancel, no options */
  private static MJPopup create(Component parent, String title, int messageType, HasValue valuator) {
    return create(parent,title,messageType,valuator,null,JOptionPane.OK_CANCEL_OPTION);
  }

  /** Internal create method: OkCancel, with options */
  private static MJPopup create(Component parent, String title, int messageType, Object message, Object[] options) {
    return create(parent,title,messageType,message,options,JOptionPane.OK_CANCEL_OPTION);
  }

  /** Internal create method: type given */
  private static MJPopup create(Component parent, String title, int messageType, Object message, int optionType) {
    return create(parent,title,messageType,message,null,optionType);
  }

  /** Internal create method: everything given, except icon and initialValue */
  private static MJPopup create(Component parent, String title, int messageType, Object message, Object[] options, int optionType) {
    HasValue valuator     = null;
    Icon     icon         = null;
    Object   initialValue = null;

    if (message instanceof HasValue) {
      valuator = (HasValue)message;
    }

    if (DEBUG) {
      System.out.println("MJPopup.create");
      System.out.println("  parent       = "+parent);
      System.out.println("  message      = "+message);
      System.out.println("  messageType  = "+messageType);
      System.out.println("  optionType   = "+optionType);
      System.out.println("  icon         = "+icon);
      System.out.println("  options      = "+options);
      System.out.println("  initialValue = "+initialValue);
      System.out.println("  valuator     = "+valuator);
    }

    // CREATE OPTIONPANE and set max width in characters of the text message
    JOptionPane optionPane = new JOptionPane(message,messageType,optionType,icon,options,initialValue) {
      public int getMaxCharactersPerLineCount() { return popWidth; }
    };
    optionPane.setWantsInput(false);

    // CREATE DIALOGUE FROM OPTIONPANE
    JDialog dialogBox = optionPane.createDialog(parent,title);
    dialogBox.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
    dialogBox.setResizable(true);
    // Since 3.7.1 for NXM-2872 make popup width adequate even in Windows
    if(dialogBox.getTitle() != null) {
      FontMetrics fm = dialogBox.getFontMetrics(dialogBox.getFont());
      // +75 to allow for icon button
      int width = fm.stringWidth(dialogBox.getTitle()) + 75;
      width = Math.max(width, dialogBox.getPreferredSize().width);
      dialogBox.setSize(new Dimension(width, dialogBox.getPreferredSize().height));
    }


    return new MJPopup(parent,optionPane,dialogBox,valuator);
  }

  /** Quick string conversion with default value. */
  private static final String toString(Object obj, String def) {
    return (obj == null)? def: obj.toString();
  }

  /** Quick string conversion, uses null as default value. */
  private static final String toString(Object obj) {
    return (obj == null)? null: obj.toString();
  }

  /** Quick string array conversion. */
  private static final String[] toStringArray(Object obj, boolean upcase) {
    String str = MJPopup.toString(obj,"");

    if (upcase) { str = str.toUpperCase(); }

    return SEP_REGEX.split(str);
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // PUBLIC CREATE METHODS
  /////////////////////////////////////////////////////////////////////////////////////////////////

  /** Creates a popup with 'Yes' and 'No' buttons.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @return The popup.
   */
  public static MJPopup createYesNo(Component parent, String title, int messageType, Object message) {
    return create(parent, title, messageType, message, JOptionPane.YES_NO_OPTION);
  }

  /** Creates a popup with 'Yes', 'No' and 'Cancel' buttons.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @return The popup.
   */
  public static MJPopup createYesNoCancel(Component parent, String title, int messageType, Object message) {
    return create(parent, title, messageType, message, JOptionPane.YES_NO_CANCEL_OPTION);
  }

  /** Creates a popup with only an 'Ok' button.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @return The popup.
   */
  public static MJPopup createOk(Component parent, String title, int messageType, Object message) {
    return create(parent, title, messageType, message, JOptionPane.DEFAULT_OPTION);
  }

  /** Creates a popup with 'Ok' and 'Cancel' buttons.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @return The popup.
   */
  public static MJPopup createOkCancel(Component parent, String title, int messageType, Object message) {
    return create(parent, title, messageType, message, JOptionPane.OK_CANCEL_OPTION);
  }

  /** Creates a popup with the given list of buttons.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param choices     <b>The available choices (i.e. the names on the buttons).</b>
      @return The popup.
   */
  public static MJPopup createButtons(Component parent, String title, int messageType, Object message, Object[] choices) {
    return create(parent, title, messageType, message, choices);
  }

  /** Creates a popup with no buttons. Commonly used for an "alert" message that will be displayed
      at the start of the "alert" and programmatically hidden when the "alert" is over.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @return The popup.
   */
  public static MJPopup createAlert(Component parent, String title, int messageType, Object message) {
    return createButtons(parent, title, messageType, message, NO_BUTTONS);
  }

  /** Creates a popup with a text prompt (along with 'Ok' and 'Cancel' buttons).
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param defValue    <b>The default value to display.</b>
      @return The popup.
   */
  public static MJPopup createText(Component parent, String title, int messageType, Object message, Object defValue) {
    return create(parent, title, messageType, new TextPanel(null,message,defValue,false));
  }

  /** Creates a popup with a text prompt for a password (along with 'Ok' and 'Cancel' buttons). The
      password will be displayed as asterisks ('*').
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param defValue    <b>The default password value to display.</b>
      @return The popup.
      @see #createLogin(Component,String,int,Object,Object)
   */
  public static MJPopup createPassword(Component parent, String title, int messageType, Object message, Object defValue) {
    return create(parent, title, messageType, new TextPanel(null,message,defValue,true));
  }

  /** Creates a popup with a text prompt for a user name and a text prompt for a password (along
      with 'Ok' and 'Cancel' buttons). The password will be displayed as asterisks ('*').
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param defValue    <b>The default user name to display.</b>
      @return The popup.
      @see #createPassword(Component,String,int,Object,Object)
   */
  public static MJPopup createLogin(Component parent, String title, int messageType, Object message, Object defValue) {
    return create(parent, title, messageType, new LoginPanel(null,message,defValue));
  }

  /** Creates a popup with a choice menu (along with 'Ok' and 'Cancel' buttons). The choice menu
      will be displayed as a combo box.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param choices     <b>The available choices (i.e. the names in the combo box).</b>
      @param defValue    <b>The value that is initially selected.</b>
      @return The popup.
   */
  public static MJPopup createChoice(Component parent, String title, int messageType, Object message, Object[] choices, Object defValue) {
    return create(parent, title, messageType, new ChoicePanel(null,message,choices,defValue));
  }

  /** Creates a popup with that allows selection of a mask value. This is the same as
      <code>createMask(parent,title,messageType,message,choices,defValue,false)</code>.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param choices     <b>The available choices (i.e. the names of the check boxes).</b>
      @param defValue    <b>The mask of the currently-selected values.</b>
      @return The popup.
   */
  public static MJPopup createMask(Component parent, String title, int messageType, Object message, Object[] choices, Object defValue) {
    return createMask(parent,title,messageType,message,choices,defValue,false);
  }

  /** Creates a popup with that allows selection of a mask value (along with 'Ok' and 'Cancel'
      buttons). The mask value is displayed via check boxes where the selected ones are included in
      the mask, the deselected ones are omitted. <br>
      <br>
      The "unusual" choice of output being a comma-separated string rather than a pipe-separated
      string was to support macro users who are using (this was the first use-case for this method):
      <pre>
        FOREACH val INLIST msg.data
          ...
        ENDFOR
      </pre>
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param choices     <b>The available choices (i.e. the names of the check boxes).</b>
      @param defValue    <b>The mask of the currently-selected values (e.g. "A|B|C" or "A,B,C").</b>
      @param outputArray <b>Should the output be an array of the selected options (true), or a
                         comma-separated string of them concatenated (false)</b>
      @return The popup.
   */
  public static MJPopup createMask(Component parent, String title, int messageType, Object message, Object[] choices, Object defValue, boolean outputArray) {
    return create(parent, title, messageType, new MaskPanel(null,message,choices,defValue,getMaxSize(parent),getAbsSize(null),outputArray));
  }

  /** <b>Legacy:</b> Creates a popup with that allows selection of a mask value (along with 'Ok' and
      'Cancel' buttons). This is a legacy function where the output is an integer mask value. This
      limits the choices to those that would map to a 32-bit integer mask. Most users will want to
      use {@link #createMask(Component,String,int,Object,Object[],Object,boolean)} in place of this.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param choices     <b>The available choices (i.e. the names of the check boxes).</b>
      @param defValue    <b>The mask of the currently-selected values.</b>
      @return The popup.
      @see #createMask(Component,String,int,Object,Object[],Object,boolean)
   */
  public static MJPopup createIntMask(Component parent, String title, int messageType, Object message, Object[] choices, Object defValue) {
    int maxWidth = getMaxSize(parent).width;
    return create(parent, title, messageType, new IntMaskPanel(null,message,choices,defValue,maxWidth));
  }

  /** Creates a popup with that allows selection of a color value (along with 'Ok' and 'Cancel'
      buttons).
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param defValue    <b>The default value.</b> <i>(Currently ignored.)</i>
      @return The popup.
   */
  public static MJPopup createColor(Component parent, String title, int messageType, Object message, Object defValue) {
    return create(parent, title, messageType, new ColorPanel(null,message,defValue));
  }

  /** Creates a popup with that allows selection of a file value (along with 'Ok' and 'Cancel'
      buttons). This is the same as <code>createFile(parent,title,messageType,message,defValue,null,null,null)</code>
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param defValue    <b>The default value.</b>
      @return The popup.
   */
  public static MJPopup createFile(Component parent, String title, int messageType, Object message,
                                   Object defValue) {
    return createFile(parent,title,messageType,message,defValue,null,null,null);
  }

  /** Creates a popup with that allows selection of a file value (along with 'Ok' and 'Cancel'
      buttons).
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param defValue    <b>The default value.</b>
      @param filter      <b>The filename filter options (null=DisplayAll)</b>
      @param auxList     <b>The selected AUX list (null=ShowReadAux)</b>
      @param pathOpt     <b>The filename path option (see MJFileChooser.pathOptionList}).</b>
      @return The popup.
   */
  public static MJPopup createFile(Component parent, String title, int messageType, Object message,
                                   Object defValue, String filter, String auxList, String pathOpt) {
    return create(parent, title, messageType, new MJFileChooser(null,message,defValue,filter,auxList,pathOpt,null));
  }

  /** TBD -- Future use, do not use at this time.
      @param parent       The parent component.
      @param title        The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param parameters  Table of parameters
      @return the popup
   */
  @InternalUseOnly
  public static MJPopup createGroup(Component parent, String title, int messageType, Object message, Table parameters) {
    return create(parent, title, messageType, new GroupPanel(null,parameters));
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // PUBLIC SHOW METHODS
  /////////////////////////////////////////////////////////////////////////////////////////////////

  /** Displays a popup with 'Yes' and 'No' buttons, and waits for user input.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @return The user entry or null if canceled.
   */
  public static String showYesNo(Component parent, String title, int messageType, Object message) {
    return toString(createYesNo(parent, title, messageType, message).display());
  }

  /** Displays a popup with 'Yes', 'No' and 'Cancel' buttons, and waits for user input.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @return The user entry or null if canceled.
   */
  public static String showYesNoCancel(Component parent, String title, int messageType, Object message) {
    return toString(createYesNoCancel(parent, title, messageType, message).display());
  }

  /** Displays a popup with only an 'Ok' button, and waits for user input.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @return The user entry or null if canceled.
   */
  public static String showOk(Component parent, String title, int messageType, Object message) {
    return toString(createOk(parent, title, messageType, message).display());
  }

  /** Displays a popup with 'Ok' and 'Cancel' buttons, and waits for user input.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @return The user entry or null if canceled.
   */
  public static String showOkCancel(Component parent, String title, int messageType, Object message) {
    return toString(createOkCancel(parent, title, messageType, message).display());
  }

  /** Displays a popup with the given list of buttons and waits for user input.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param choices     <b>The available choices (i.e. the names on the buttons).</b>
      @return The user entry or null if canceled.
   */
  public static Object showButtons(Component parent, String title, int messageType, Object message, Object[] choices) {
    return createButtons(parent, title, messageType, message, choices).display();
  }

  /** Displays a popup with a text prompt and waits for user input.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param defValue    <b>The default value to display.</b>
      @return The user entry or null if canceled.
   */
  public static String showText(Component parent, String title, int messageType, Object message, Object defValue) {
    return toString(createText(parent, title, messageType, message, defValue).display());
  }

  /** Displays a popup with a text prompt for a password and waits for user input.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param defValue    <b>The default password value to display.</b>
      @return The user entry or null if canceled.
      @see #showLogin(Component,String,int,Object,Object)
   */
  public static String showPassword(Component parent, String title, int messageType, Object message, Object defValue) {
    return toString(createPassword(parent, title, messageType, message, defValue).display());
  }

  /** Displays a popup with prompts for a user name and password, and waits for user input.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param defValue    <b>The default user name to display.</b>
      @return The user entry (<code>{USER=...,PASS=...}</code>) or null if canceled.
      @see #showPassword(Component,String,int,Object,Object)
   */
  public static Table showLogin(Component parent, String title, int messageType, Object message, Object defValue) {
    return Convert.o2t(createLogin(parent, title, messageType, message, defValue).display());
  }

  /** Displays a popup with a choice menu (combo box) and waits for user input.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param choices     <b>The available choices (i.e. the names in the combo box).</b>
      @param defValue    <b>The value that is initially selected.</b>
      @return The user entry or null if canceled.
   */
  public static Object showChoice(Component parent, String title, int messageType, Object message, Object[] choices, Object defValue) {
    return createChoice(parent, title, messageType, message, choices, defValue).display();
  }

  /** Displays a popup that allows selection of a mask value and waits for user input. (This uses
      <code>createMask(..)</code> with <code>outputArray=false</code>.)
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param choices     <b>The available choices (i.e. the names of the check boxes).</b>
      @param defValue    <b>The mask of the currently-selected values (e.g. "A|B|C" or "A,B,C").</b>
      @return The user entry or null if canceled.
   */
  public static String showMaskString(Component parent, String title, int messageType, Object message, Object[] choices, Object defValue) {
    return toString(createMask(parent, title, messageType, message, choices, defValue, false).display());
  }

  /** Displays a popup that allows selection of a mask value and waits for user input. (This uses
      <code>createMask(..)</code> with <code>outputArray=true</code>.)
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param choices     <b>The available choices (i.e. the names of the check boxes).</b>
      @param defValue    <b>The mask of the currently-selected values (e.g. "A|B|C" or "A,B,C").</b>
      @return The user entry or null if canceled.
   */
  public static Object[] showMaskArray(Component parent, String title, int messageType, Object message, Object[] choices, Object defValue) {
    return (Object[])createMask(parent, title, messageType, message, choices, defValue, true).display();
  }

  /** Displays a popup that allows selection of a color value and waits for input.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param defValue    <b>The default value.</b> <i>(Currently ignored.)</i>
      @return The user entry or null if canceled.
   */
  public static Color showColor(Component parent, String title, int messageType, Object message, Object defValue) {
    Object val = createColor(parent, title, messageType, message, defValue).display();
    return (val == null)? null: MColor.getColor(val);
  }

  /** Displays a popup with that allows selection of a file value. This is the same as
      <code>showFile(parent,title,messageType,message,defValue,null,null,null)</code>.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param defValue    <b>The default value.</b>
      @return The user entry or null if canceled.
   */
  public static String showFile(Component parent, String title, int messageType, Object message,
                                Object defValue) {
    return showFile(parent,title,messageType,message,defValue,null,null,null);
  }

  /** Creates a popup with that allows selection of a file value.
      @param parent      The parent component.
      @param title       The title for the popup.
      @param messageType The popup message type ({@link JOptionPane#INFORMATION_MESSAGE},
                         {@link JOptionPane#ERROR_MESSAGE}, {@link JOptionPane#WARNING_MESSAGE},
                         {@link JOptionPane#QUESTION_MESSAGE}, {@link JOptionPane#PLAIN_MESSAGE}).
      @param message     The message to display.
      @param defValue    <b>The default value.</b>
      @param filter      <b>The filename filter options (null=DisplayAll)</b>
      @param auxList     <b>The selected AUX list (null=ShowReadAux)</b>
      @param pathOpt     <b>The filename path option (see MJFileChooser.pathOptionList}).</b>
      @return The user entry or null if canceled.
   */
  public static String showFile(Component parent, String title, int messageType, Object message,
                                Object defValue, String filter, String auxList, String pathOpt) {
    return toString(createFile(parent,title,messageType,message,defValue,filter,auxList,pathOpt).display());
  }


  //////////////////////////////////////////////////////////////////////////////////////////////////
  // Nested classes
  //////////////////////////////////////////////////////////////////////////////////////////////////
  /** Thread for popups running in their own thread. */
  private class MJPopupThread extends Thread {
    private MessageHandler msgHandler = null;
    private String         msgName    = null;

    public MJPopupThread(MessageHandler msgHandler, String msgName) {
      super("MJPopup: "+dialogBox.getTitle());
      this.msgHandler = msgHandler;
      this.msgName    = (msgName == null)? "POPUP": msgName;
    }

    public void run() {
      dialogBox.setVisible(true); // Does not return until done.
    }
  }

  /** Marks objects with a value. */
  public static interface HasValue {
    /** Gets the name of the object.
        @return name of this object
     */
    public String getName();

    /** Gets the value of the object.
        @return value of this object
     */
    public Object getValue();

    /** Gets the info of the object (usually 0).
        @return info of this object
     */
    public int getInfo();
  }

  /** A simple extension of JPanel that implements HasValue. */
  private static abstract class ValuePanel extends JPanel implements HasValue {
    private String name;

    public ValuePanel(LayoutManager layout, String name, String defName) {
      super(layout);
      this.name = (name == null)? defName: name;
    }
    public String getName()  { return name; }
    public Object getValue() { return null; }
    public int    getInfo()  { return 0;    }
  }

  /** Holds multiple valuators. */
  private static class GroupPanel extends ValuePanel {
    private HasValue[] valuators;

    public GroupPanel(String name, Table tbl) {
      super(new GridLayout(0,2),name,"GROUP");
      String[] keys = tbl.getKeys();

      valuators = new HasValue[keys.length];
      for (int i = 0; i < keys.length; i++) {
        Table  params   = tbl.getTable(keys[i]);
        String type     = params.getS("TYPE");
        Object title    = params.getO("TITLE");
        Object options  = params.getO("OPTIONS");
        Object defaults = params.getO("DEFAULTS");

        addValuator(i,keys[i],type,title,options,defaults);
      }
    }

    private void addValuator(int index, String name, String type, Object title, Object options, Object defaults) {
      System.out.println("addValuator("+type+", "+title+", "+options+", "+defaults+")");
      valuators[index] = new TextPanel(name,"","",false); // <-- FIX THIS!!!
    }

    public Object getValue() {
      Table values = new Table();
      for (int i = 0; i < valuators.length; i++) {
        values.put(valuators[i].getName(), valuators[i].getValue());
      }
      return values;
    }
  }

  /** Used for a text input. */
  private static class ColorPanel extends ValuePanel {
    private JColorChooser input;

    public ColorPanel(String name, Object msg, Object defValue) {
      super(new BorderLayout(), name, "COLOR");
      input = new JColorChooser(MColor.getColor(defValue));
      add(new JLabel(msg.toString()), BorderLayout.NORTH);
      add(input, BorderLayout.CENTER);
    }
    private Color getColor() {
      Color color = input.getColor();
      return (color == null)? Color.black: color;
    }
    public Object getValue() { return MColor.toString(getColor()); } // <-- JC4: Changed in 2.5.0
    public int    getInfo()  { return getColor().getRGB();         }
  }

  /** Used for a text input. */
  private static class ChoicePanel extends ValuePanel {
    private JComboBox input;

    public ChoicePanel(String name, Object msg, Object[] choices, Object defValue) {
      super(new BorderLayout(), name, "CHOICE");
      input = new JComboBox(choices);
      input.setSelectedItem(defValue);
      add(new JLabel(msg.toString()), BorderLayout.NORTH);
      add(input, BorderLayout.CENTER);
    }
    public Object getValue() { return input.getSelectedItem();    }
    public int    getInfo()  { return input.getSelectedIndex()+1; }
  }

  /** Used for a mask value input that maps to an integer. */
  private static class MaskPanel extends ValuePanel {
    protected JCheckBox[] checkBoxes;
    protected Object[]    choices;
    protected boolean     outputArray;

    public MaskPanel(String name, Object msg, Object[] choices, Object defValue, Dimension maxSize,
                     Dimension absSize, boolean outputArray) {
      super(new BorderLayout(), name, "MASK");
      this.choices     = choices;
      this.checkBoxes  = new JCheckBox[choices.length];
      this.outputArray = outputArray;

      int width  = 0; // max checkbox width
      int height = 0; // max checkbox height

      String[] defMask = MJPopup.toStringArray(defValue,true);
      Arrays.sort(defMask);

      for (int i = 0; i < checkBoxes.length; i++) {
        String  option  = choices[i].toString().toUpperCase();
        boolean checked = (Arrays.binarySearch(defMask, option) >= 0);

        checkBoxes[i] = new JCheckBox(choices[i].toString(), checked);

        Dimension minSize = checkBoxes[i].getMinimumSize();
        width  = Math.max(width,  minSize.width);
        height = Math.max(height, minSize.height);
      }

      int    cols  = MJPopup.findColCount(checkBoxes.length, width, height, maxSize, absSize);
      JPanel panel = new JPanel(new GridLayout(0,cols));

      for (int i = 0; i < checkBoxes.length; i++) {
        panel.add(checkBoxes[i]);
      }
      add(new JLabel(msg.toString()), BorderLayout.NORTH);
      add(panel, BorderLayout.CENTER);
    }

    /** Gets the list of selected objects. */
    private Object[] getSelected() {
      ArrayList<Object> selected = new ArrayList<>();
      for (int i = 0; i < checkBoxes.length; i++) {
        if (checkBoxes[i].isSelected()) {
          selected.add(choices[i]);
        }
      }
      return selected.toArray();
    }

    public Object getValue() {
      Object[] selected = getSelected();
      return (outputArray)? (Object)selected: (Object)StringUtil.join(selected,",");
    }
  }

  /** Used for a mask value input that maps to an integer.
      (**This is maintained for backwards compatibility.**)
   */
  private static class IntMaskPanel extends ValuePanel {
    private JCheckBox[] checkBoxes;
    private String      list;

    public IntMaskPanel(String name, Object msg, Object[] choices, Object defValue, int maxWidth) {
      super(new BorderLayout(), name, "INTMASK");
      list       = StringUtil.join(choices,",");
      checkBoxes = new JCheckBox[choices.length];

      if (checkBoxes.length >= 32) {
        Shell.getSharedMidasContext().warning("MJPopup: IntMask popup (MASK) will not work correctly "
                                      + "when given 32+ options, use string-based Mask popup "
                                      + "(MASK/S).");
      }

      int mask  = Parser.mask(list, MJPopup.toString(defValue,""), 0);
      int width = 0; // max checkbox width

      for (int i = 0; i < checkBoxes.length; i++) {
        boolean checked = ((mask & 0x1) != 0);

        checkBoxes[i] = new JCheckBox(choices[i].toString(), checked);
        mask  = mask >>> 1;
        width = Math.max(width, checkBoxes[i].getMinimumSize().width);
      }

      int cols = Math.min(choices.length, Math.max(1, (2*maxWidth)/(3*width)));
      JPanel panel = new JPanel(new GridLayout(0,cols));
      add(new JLabel(msg.toString()), BorderLayout.NORTH);
      add(panel, BorderLayout.CENTER);
      for (int i = 0; i < checkBoxes.length; i++) {
        panel.add(checkBoxes[i]);
      }
    }

    public Object getValue() {
      return Parser.mask2s(list, getInfo());
    }

    public int getInfo() {
      int mask  = 0x00;
      int value = 0x01;
      for (int i = 0; i < checkBoxes.length; i++) {
        if (checkBoxes[i].isSelected()) { mask = mask | value; }
        value = value << 1;
      }
      return mask;
    }
  }

  /** Used for a text input. */
  private static class TextPanel extends ValuePanel {
    private JTextField input;

    public TextPanel(String name, Object msg, Object def, boolean passwd) {
      super(new BorderLayout(), name, ((passwd)? "PASSWORD": "TEXT"));
      if (passwd) {
        input = new JPasswordField(MJPopup.toString(def,""));
      } else if (def != null) {
        input = new JTextField(MJPopup.toString(def,""));
      } else {
        input = new JTextField();
      }
      add(new JLabel(msg.toString()), BorderLayout.NORTH);
      add(input, BorderLayout.CENTER);
    }
    public Object getValue() { return input.getText(); }
    public int    getInfo()  { return 0;               }
  }

  /** Used for a Login input. */
  private static class LoginPanel extends ValuePanel {
    private JTextField     user;
    private JPasswordField pass;

    public LoginPanel(String name, Object msg, Object def) {
      super(new BorderLayout(), name, "LOGIN");
      JPanel panel = new JPanel(new GridLayout(2,2));
      user = new JTextField(MJPopup.toString(def,""));
      pass = new JPasswordField(16);
      add(new JLabel(msg.toString()), BorderLayout.NORTH);
      add(panel, BorderLayout.CENTER);
      panel.add(new JLabel("User:"));
      panel.add(user);
      panel.add(new JLabel("Password:"));
      panel.add(pass);
    }
    public Object getValue() {
      return "{USER="+user.getText()+",PASS="+new String(pass.getPassword())+"}";
    }
    public int getInfo() { return 0; }
  }
}
