gt:                     menu.show(thisBtn, thisBtn.getWidth() / 2, thisBtn.getHeight());
context: /* Copyright (C) 2022, Specify Collections Consortium
   * 
   * Specify Collections Consortium, Biodiversity Institute, University of Kansas,
   * 1345 Jayhawk Boulevard, Lawrence, Kansas, 66045, USA, support@specifysoftware.org
   * 
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License
   * as published by the Free Software Foundation; either version 2
   * of the License, or (at your option) any later version.
   * 
   * This program is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   * 
   * You should have received a copy of the GNU General Public License
   * along with this program; if not, write to the Free Software
   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
  package edu.ku.brc.af.ui.forms.formatters;
  
  import static edu.ku.brc.ui.UIHelper.createButton;
  import static edu.ku.brc.ui.UIHelper.createI18NFormLabel;
  import static edu.ku.brc.ui.UIHelper.createI18NLabel;
  import static edu.ku.brc.ui.UIHelper.createTextField;
  import static edu.ku.brc.ui.UIRegistry.getResourceString;
  
  import java.awt.Color;
  import java.awt.Cursor;
  import java.awt.Dimension;
  import java.awt.Font;
  import java.awt.Frame;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  import java.util.HashSet;
  import java.util.Set;
  import java.util.Vector;
  
  import javax.swing.BorderFactory;
  import javax.swing.JButton;
  import javax.swing.JLabel;
  import javax.swing.JMenuItem;
  import javax.swing.JPopupMenu;
  import javax.swing.JTextField;
  import javax.swing.JTextPane;
  import javax.swing.SwingConstants;
  import javax.swing.event.ChangeListener;
  import javax.swing.event.DocumentEvent;
  import javax.swing.event.DocumentListener;
  import javax.swing.event.TreeSelectionEvent;
  import javax.swing.event.TreeSelectionListener;
  import javax.swing.text.AttributeSet;
  import javax.swing.text.BadLocationException;
  import javax.swing.text.DefaultStyledDocument;
  import javax.swing.text.Document;
  import javax.swing.text.Element;
  import javax.swing.text.PlainDocument;
  import javax.swing.text.SimpleAttributeSet;
  import javax.swing.text.StyleConstants;
  import javax.swing.tree.DefaultMutableTreeNode;
  
  import org.apache.commons.lang.StringUtils;
  
  import com.jgoodies.forms.builder.PanelBuilder;
  import com.jgoodies.forms.layout.CellConstraints;
  import com.jgoodies.forms.layout.FormLayout;
  
  import edu.ku.brc.af.core.db.DBFieldInfo;
  import edu.ku.brc.af.core.db.DBTableInfo;
  import edu.ku.brc.af.prefs.AppPreferences;
  import edu.ku.brc.ui.CustomDialog;
  import edu.ku.brc.ui.DocumentAdaptor;
  import edu.ku.brc.ui.UIHelper;
  import edu.ku.brc.ui.UIRegistry;
  
  /**
   * @author ricardo
   *
   * @code_status Alpha
   *
   *
   */
  public class DataObjFieldFormatSinglePanel extends DataObjFieldFormatPanel 
  {
      protected AvailableFieldsComponent availableFieldsComp;
  
      // ui controls
      protected JTextPane          formatEditor;
      protected JButton            addFieldBtn;
      protected JTextField         sepText;
      protected JLabel             sepLbl;
      
      protected Set<String>        uniqueNameSet = new HashSet<String>();
  
      protected SimpleAttributeSet normalAttr;
      protected SimpleAttributeSet fieldDefAttr;
      
      protected boolean            ignoreFmtChange = false;
      
      
      /**
       * @param tableInfo
       * @param availableFieldsComp
       * @param formatContainer
       * @param okButton
       * @param uiFieldFormatterMgrCache
       */
      public DataObjFieldFormatSinglePanel(final DBTableInfo                          tableInfo,
                                           final DataObjSwitchFormatterContainerIface formatContainer,
                                           final DataObjFieldFormatMgr                dataObjFieldFormatMgrCache,
                                           final UIFieldFormatterMgr                  uiFieldFormatterMgrCache,    
                                           final ChangeListener                       listener,
                                           final JButton                              okButton)
      {
          super(tableInfo, formatContainer, dataObjFieldFormatMgrCache, uiFieldFormatterMgrCache, listener, okButton);
      }
      
      /* (non-Javadoc)
       * @see edu.ku.brc.ui.forms.formatters.DataObjFieldFormatPanelBuilder#buildUI()
       */
      protected void buildUI() 
      {
          CellConstraints cc = new CellConstraints();
          
          JLabel currentFieldsLbl = createI18NLabel("DOF_DISPLAY_FORMAT");
          formatEditor = new JTextPane();
          // to make sure the component shrinks with the dialog
          formatEditor.setMinimumSize(new Dimension(200, 50));
          formatEditor.setPreferredSize(new Dimension(350, 100));
          
          formatEditor.getDocument().addDocumentListener(new DocumentAdaptor()
          {
              @Override
              public void changed(DocumentEvent e)
              {
                  updateUIEnabled();
              }
          }
          );
  
          PanelBuilder addFieldPB = new PanelBuilder(new FormLayout("p,2px,p,f:p:g,r:m", "p,2px,p"));  
          sepText     = createTextField(4);
          addFieldBtn = createButton(getResourceString("DOF_ADD_FIELD")); 
          sepLbl      = createI18NFormLabel("DOF_SEP_TXT");
          
          addFieldPB.add(sepLbl,      cc.xy(1,1));
          addFieldPB.add(sepText,     cc.xy(3,1));
          addFieldPB.add(addFieldBtn, cc.xy(5,1));
          
          sepText.setDocument(new FilteredDoc());
          
          addFieldBtn.setEnabled(false);
          sepLbl.setEnabled(false);
          sepText.setEnabled(false);
          
          // For when it is standalone
          if (AppPreferences.hasRemotePrefs())
          {
              sepText.setText(AppPreferences.getRemote().get("DOF_SEP", ", "));
              
          } else
          {
              sepText.setText(", ");
          }
  
          PanelBuilder pb = new PanelBuilder(new FormLayout("f:d:g",  
                  "10px,"       +   // empty space on top of panel 
                  "p,f:p:g,"    +   // Label & format text editor
                  "2px,p,"      +   // separator & add field
                  "10px,p,"     +   // separator & label
                  "f:250px:g,"      // list box for available fields 
                  ), this);
          
          // layout components on main panel        
          int y = 2; // leave first row blank
          
          pb.add(currentFieldsLbl, cc.xy(1, y)); y += 1;
          pb.add(UIHelper.createScrollPane(formatEditor), cc.xy(1, y)); y += 2;
          
          pb.add(addFieldPB.getPanel(), cc.xy(1, y)); y += 2;
          
          JLabel availableFieldsLbl = createI18NFormLabel("DOF_AVAILABLE_FIELDS", SwingConstants.LEFT);
          pb.add(availableFieldsLbl, cc.xy(1,y)); y += 1;
          
          // create field tree that will be re-used in all instances of single switch formatter editing panel
          availableFieldsComp = new AvailableFieldsComponent(tableInfo, dataObjFieldFormatMgrCache, uiFieldFormatterMgrCache);
  
          pb.add(UIHelper.createScrollPane(availableFieldsComp.getTree()), cc.xy(1, y)); y += 2;
          
          availableFieldsComp.getTree().addTreeSelectionListener(new TreeSelectionListener()
          {
              @Override
              public void valueChanged(TreeSelectionEvent e)
              {
                  boolean enable = availableFieldsComp.getTree().getSelectionCount() > 0;
                  addFieldBtn.setEnabled(enable);
                  sepText.setEnabled(enable);
                  sepLbl.setEnabled(enable);
              }
          });
          
          this.mainPanelBuilder = pb;
          
          fillWithObjFormatter(formatContainer.getSelectedFormatter());
  
          addFormatTextListeners();
          
          // must be called after list of available fields has been created
          addFieldListeners();
      }
      
      private void updateUIEnabled() 
      {
          okButton.setEnabled(!isInError() && StringUtils.isNotEmpty(formatEditor.getText()));
      }
      
      
      /* (non-Javadoc)
       * @see javax.swing.JComponent#setVisible(boolean)
       */
      @Override
      public void setVisible(boolean flag)
      {
          super.setVisible(flag);
          if (!flag)
          {
              formatContainer.getSelectedFormatter().setSingle(null);
          }
      }
  
      /**
       * 
       */
      public void addFieldListeners()
      {
          // action listener for add field button
          addFieldBtn.addActionListener(new ActionListener()
          {
              public void actionPerformed(ActionEvent e)
              {
                  addField();
              }
          });
          
          // mouse adapter to detect double-click on list of available fields 
          availableFieldsComp.addMouseListener(new MouseAdapter()
          {
              public void mouseClicked(MouseEvent e)
              {
                  if(e.getClickCount() == 2)
                  {
                      addField();
                  }
              }
          });
      }
  
      /**
       * 
       */
      public void addFormatTextListeners()
      {
          formatEditor.getDocument().addDocumentListener(new DocumentAdaptor()
          {
              @Override
              protected void changed(DocumentEvent e)
              {
                  if (!ignoreFmtChange)
                  {
                      formatChanged();
                  }
              }
          });
          
          sepText.getDocument().addDocumentListener(new DocumentListener()
          {
              public void removeUpdate (DocumentEvent e) { changed(e); }
              public void insertUpdate (DocumentEvent e) { changed(e); }
              public void changedUpdate(DocumentEvent e) { changed(e); }
  
              private void changed(@SuppressWarnings("unused") DocumentEvent e)
              { 
                  if (AppPreferences.hasRemotePrefs())
                  {
                      AppPreferences.getRemote().put("DOF_SEP", sepText.getText()); 
                  } 
              }
          });
      }
      
      /**
       * Updates the formatter being composed whenever the text in the format text field changes.
       */
      public void formatChanged()
      {
          // for now, just create formatter from scratch, but later we can just detect changes and act accordingly
          setFormatterFromTextPane(formatContainer.getSelectedFormatter());
          setHasChanged(true);
      }
      
      /*
       * Adds a field to the format being composed
       */
      public void addField()
      {
          DefaultMutableTreeNode node = (DefaultMutableTreeNode) availableFieldsComp.getTree().getLastSelectedPathComponent();
          if (node == null || !node.isLeaf() || !(node.getUserObject() instanceof DataObjDataFieldWrapper) )
          {
              return; // not really a field that can be added, just empty or a string
          }
  
          Object obj = node.getUserObject();
          if (obj instanceof DataObjDataFieldWrapper)
          {
              DataObjDataFieldWrapper wrapper = (DataObjDataFieldWrapper) obj;
              String sep = sepText.getText();
              if (StringUtils.isNotEmpty(sep))
              {
                  try
                  {
                      DefaultStyledDocument doc = (DefaultStyledDocument) formatEditor.getStyledDocument();
                      if (doc.getLength() > 0)
                      {
                          doc.insertString(doc.getLength(), sep, null);
                      }
                  }
                  catch (BadLocationException ble) {}
                  
              }
              insertFieldIntoTextEditor(wrapper);
              setHasChanged(true);
          }
      }
  
      /* (non-Javadoc)
       * @see edu.ku.brc.ui.forms.formatters.DataObjFieldFormatPanelBuilder#fillWithObjFormatter(edu.ku.brc.ui.forms.formatters.DataObjSwitchFormatter)
       */
      public void fillWithObjFormatter(final DataObjSwitchFormatter switchFormatter)
      {
          if (switchFormatter != null)
          {
              fillWithObjFormatter(switchFormatter.getSingle());
          }
          else
          {
              fillWithObjFormatter((DataObjDataFieldFormatIFace) null);
          }
      }
      
      /**
       * @param singleFormatter
       */
      protected void fillWithObjFormatter(final DataObjDataFieldFormatIFace singleFormatter)
      {
          ignoreFmtChange = true;
          try
          {
              formatEditor.setText("");
              
              if (singleFormatter == null)
              {
                  return;
              }
              
              
              Document doc = formatEditor.getDocument();
              DataObjDataField[] fields = singleFormatter.getFields();
              if (fields == null)
              {
                  return;
              }
              
              for (DataObjDataField field : fields)
              {
                  try
                  {
                      doc.insertString(doc.getLength(), field.getSep(), null);
                      
                      //System.err.println("["+field.getName()+"]["+field.getSep()+"]["+field.getFormat()+"]["+field.toString()+"]");
                      insertFieldIntoTextEditor(new DataObjDataFieldWrapper(field));
                  }
                  catch (BadLocationException ble) {}
              }
          } finally
          {
              ignoreFmtChange = false;
          }
      }
      
      /**
       * @param wrapper
       */
      protected void insertFieldIntoTextEditor(final DataObjDataFieldWrapper wrapper)
      {
          formatEditor.insertComponent(new FieldDefinitionComp(wrapper));
      }
  
      /**
       * Wrapper for formatters.
       * Created to modify toString() method and display item nicely on JList. 
       * 
       * @author Ricardo
       */
      protected class FieldDefinitionComp extends JLabel
      {
          protected final DataObjDataFieldWrapper dataObjFieldWrapper;
          
          public FieldDefinitionComp(DataObjDataFieldWrapper dataObjFieldWrapper)
          {
              this.dataObjFieldWrapper = dataObjFieldWrapper;
              
              //System.err.println("-------> ["+dataObjFieldWrapper.toString()+"]");
              setText(dataObjFieldWrapper.toString());
              setCursor(Cursor.getDefaultCursor());
              setFont(new Font("Arial", Font.PLAIN, 11));
              setAlignmentY(0.75f);
              setBackground(new Color(0,0,0,30));
              setOpaque(true);
              setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
             
              // create popup menu
              final JPopupMenu menu = createPopupMenu();
              
              addMouseListener(new MouseAdapter()
              {
                  @Override
                  public void mouseClicked(MouseEvent e)
                  {
                      JLabel thisBtn = FieldDefinitionComp.this;
  