org.ut.biolab.medsavant.client.query.QueryViewController.java Source code

Java tutorial

Introduction

Here is the source code for org.ut.biolab.medsavant.client.query.QueryViewController.java

Source

/**
 * See the NOTICE file distributed with this work for additional information
 * regarding copyright ownership.
 *
 * This is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This software 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 Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this software; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA, or see the FSF
 * site: http://www.fsf.org.
 */
package org.ut.biolab.medsavant.client.query;

import com.healthmarketscience.sqlbuilder.ComboCondition;
import com.healthmarketscience.sqlbuilder.Condition;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.PrintWriter;
import java.text.ParseException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;

import net.miginfocom.swing.MigLayout;
import org.apache.commons.httpclient.NameValuePair;

import org.apache.commons.lang.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.ut.biolab.medsavant.MedSavantClient;
import org.ut.biolab.medsavant.client.filter.FilterController;
import org.ut.biolab.medsavant.client.view.login.LoginController;
import org.ut.biolab.medsavant.client.project.ProjectController;
import org.ut.biolab.medsavant.client.reference.ReferenceController;
import org.ut.biolab.medsavant.client.view.genetics.QueryUtils;
import org.ut.biolab.medsavant.client.view.util.DialogUtils;
import org.ut.biolab.medsavant.client.view.util.ViewUtil;
import org.ut.biolab.medsavant.shared.model.GenomicRegion;
import org.ut.biolab.medsavant.client.query.SearchConditionGroupItem.QueryRelation;
import org.ut.biolab.medsavant.client.query.SearchConditionItem.SearchConditionListener;
import org.ut.biolab.medsavant.client.query.medsavant.complex.ConditionUtils;
import org.ut.biolab.medsavant.client.query.view.PillView;
import org.ut.biolab.medsavant.client.query.view.ConditionPopupGenerator;
import org.ut.biolab.medsavant.client.query.view.SearchConditionItemView;
import org.ut.biolab.savant.analytics.savantanalytics.AnalyticsAgent;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 * @author mfiume
 */
public class QueryViewController extends JPanel implements SearchConditionListener {

    private static Log LOG = LogFactory.getLog(QueryViewController.class);
    //Width of the bottom (i.e. root group) search text box.
    private static final int BOTTOM_SEARCH_TEXTFIELD_WIDTH = 270;
    private final SearchConditionGroupItem rootGroup;
    private final HashMap<SearchConditionItem, SearchConditionItemView> itemToViewMap;
    private Map<SearchConditionGroupItem, Boolean> expandedItemsMap = new HashMap<SearchConditionGroupItem, Boolean>();
    private final ConditionViewGenerator conditionViewGenerator;
    private boolean didChangeSinceLastApply;
    private JButton applyButton;
    private final JLabel warningText;

    public QueryViewController(SearchConditionGroupItem model, ConditionViewGenerator c) {
        this.rootGroup = model;
        this.conditionViewGenerator = c;
        model.addListener(this);
        this.setOpaque(false);
        this.setFocusable(true);
        warningText = new JLabel("search conditions have changed");
        warningText.setFont(ViewUtil.getSmallTitleFont());
        warningText.setForeground(ViewUtil.getSubtleTitleColor());
        applyButton = new JButton("Search");
        applyButton.setFocusable(false);
        setConditionsChanged(false);
        applyButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                applySearchConditions();

                try {
                    AnalyticsAgent.log(new NameValuePair[] { new NameValuePair("view-event", "SearchPerformed") });
                } catch (Exception e) {
                }
            }
        });

        this.refreshView();
        itemToViewMap = new HashMap<SearchConditionItem, SearchConditionItemView>();
    }

    /**
     * Re-executes the current query with the given genomic region restrictions,
     * and returns the results. Note the search bar is not changed. gr and alt
     * should have a one-to-one correspondence, where the alt for the ith
     * genomic region is in alt[i].
     */
    public List<Object[]> restrictToRegion(List<GenomicRegion> gr, List<String> alt, int limit) {
        try {
            long st = System.currentTimeMillis();
            Condition r;
            if (rootGroup.getItems().size() > 0) {
                r = getSQLConditionsFrom(rootGroup);
                SearchConditionGroupItem rg = QueryUtils.getRegionGroup(gr.get(0), alt.get(0), false);
                r = ComboCondition.and(r, getSQLConditionsFrom(rg));
            } else {
                SearchConditionGroupItem rg = QueryUtils.getRegionGroup(gr.get(0), alt.get(0), false);
                r = getSQLConditionsFrom(rg);
            }

            for (int i = 1; i < gr.size(); ++i) {
                SearchConditionGroupItem rg = QueryUtils.getRegionGroup(gr.get(i), alt.get(i), false);
                r = ComboCondition.and(r, getSQLConditionsFrom(rg));
            }

            return MedSavantClient.VariantManager.getVariants(LoginController.getSessionID(),
                    ProjectController.getInstance().getCurrentProjectID(),
                    ReferenceController.getInstance().getCurrentReferenceID(), new Condition[][] { { r } }, 0,
                    limit); //DEBUG CODE, sets limit to 10!

        } catch (Exception ex) {
            LOG.error(ex);
            ex.printStackTrace();
            DialogUtils.displayException("Error", "There was an error performing your search", ex);
        }

        return null;
    }

    public List<Object[]> restrictToRegion(GenomicRegion gr, String alt, int limit) {
        List<GenomicRegion> grl = new ArrayList<GenomicRegion>(1);
        List<String> al = new ArrayList<String>(1);
        grl.add(gr);
        al.add(alt);
        return restrictToRegion(grl, al, limit);
    }

    private void applySearchConditions() {

        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Condition c;
                    c = getSQLConditionsFrom(rootGroup);
                    if (c == null) {
                        c = ConditionUtils.TRUE_CONDITION;
                    }

                    //LOG.info(c.toString());
                    SwingUtilities.invokeAndWait(new Runnable() {
                        @Override
                        public void run() {
                            warningText.setVisible(false);
                            applyButton.setText("Searching...");
                            applyButton.setEnabled(false);
                            applyButton.updateUI();
                        }
                    });
                    FilterController.getInstance().setConditions(c);
                    SwingUtilities.invokeAndWait(new Runnable() {
                        @Override
                        public void run() {
                            applyButton.setText("Search");
                            applyButton.updateUI();
                        }
                    });
                } catch (final IllegalArgumentException ex) {
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            DialogUtils.displayError("Search Error", ex.getMessage());
                        }
                    });
                } catch (Exception ex) {
                    LOG.info(ex);
                    ex.printStackTrace();
                    DialogUtils.displayException("Error", "There was an error performing your search", ex);
                }
            }
        });
        t.start();

    }

    public void saveConditions(File file) {
        String xml = rootGroup.toXML();
        try {
            PrintWriter out = new PrintWriter(file);
            out.println(xml);
            out.close();
        } catch (Exception ex) {
            LOG.error(ex);
            DialogUtils.displayException("Error", "There was an error saving your search", ex);
        }

    }

    private SearchConditionItem getItemFromXML(Element element, SearchConditionGroupItem parentGroup)
            throws ParseException {

        String name = StringEscapeUtils.unescapeXml(element.getAttribute("name"));

        QueryRelation qr;
        if (element.getAttribute("queryRelation").equalsIgnoreCase("OR")) {
            qr = QueryRelation.OR;
        } else if (element.getAttribute("queryRelation").equalsIgnoreCase("AND")) {
            qr = QueryRelation.AND;
        } else {
            throw new ParseException("Malformed input file contains invalid query relation", 0);
        }

        SearchConditionItem sci = new SearchConditionItem(name, qr, parentGroup);

        String encodedConditions = StringEscapeUtils.unescapeXml(element.getAttribute("encodedConditions"));
        if (encodedConditions != null && encodedConditions.length() > 0) {
            sci.setSearchConditionEncoding(encodedConditions);
        }

        //The description does not need to be unescaped.
        String desc = element.getAttribute("description");
        if (desc != null && desc.length() > 0) {
            sci.setDescription(desc);
        }
        generateItemViewAndAddToGroup(sci, parentGroup);
        return sci;
    }

    private SearchConditionGroupItem getGroupFromXML(Element rootElement, SearchConditionGroupItem parentGroup)
            throws ParseException {
        if (!rootElement.getNodeName().toLowerCase().equals("group")) {
            DialogUtils.displayError("ERROR: Malformed/Invalid Input file");
        }

        QueryRelation qr;
        if (rootElement.getAttribute("queryRelation").equalsIgnoreCase("OR")) {
            qr = QueryRelation.OR;
        } else if (rootElement.getAttribute("queryRelation").equalsIgnoreCase("AND")) {
            qr = QueryRelation.AND;
        } else {
            throw new ParseException("Malformed input file contains invalid query relation", 0);
        }

        String desc = rootElement.getAttribute("description");

        SearchConditionGroupItem scg;
        if (parentGroup == null) {
            scg = this.rootGroup;
        } else {
            scg = new SearchConditionGroupItem(qr, null, parentGroup);
            if (desc != null && desc.length() > 0) {
                scg.setDescription(desc);
            }
        }

        NodeList nl = rootElement.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node n = nl.item(i);
            if (n.getNodeType() == Node.ELEMENT_NODE) {
                Element el = (Element) n;
                if (el.getNodeName().equalsIgnoreCase("group")) {
                    SearchConditionGroupItem childGroup = getGroupFromXML(el, scg);
                    scg.addItem(childGroup);
                } else if (el.getNodeName().equalsIgnoreCase("item")) {
                    SearchConditionItem sci = getItemFromXML(el, scg);
                    //scg.addItem(sci);
                } else {
                    throw new ParseException("Malformed input file contains invalid XML element", 0);
                }
            }
        }

        return scg;

    }

    public List<SearchConditionItem> getFirstLevelItemsByDesc(String itemDesc) {
        List<SearchConditionItem> results = new LinkedList<SearchConditionItem>();
        SearchConditionItem sci = null;
        for (SearchConditionItem i : getQueryRootGroup().getItems()) {
            if (i.getDescription() != null && i.getDescription().equals(itemDesc)) {
                results.add(i);
            }
        }
        return results;
    }

    public List<SearchConditionItem> getFirstLevelItemsByName(String itemName) {
        List<SearchConditionItem> results = new LinkedList<SearchConditionItem>();
        for (SearchConditionItem i : getQueryRootGroup().getItems()) {
            if (i.getName() != null && i.getName().equals(itemName)) {
                results.add(i);
            }
        }
        return results;
    }

    public void replaceFirstLevelItem(String name, String encodedConditions, String description) {
        List<SearchConditionItem> sciList = getFirstLevelItemsByName(name);
        for (SearchConditionItem sci : sciList) {
            getQueryRootGroup().removeItem(sci);
            if (sci instanceof SearchConditionGroupItem) {
                expandedItemsMap.remove((SearchConditionGroupItem) sci);
            }
        }

        SearchConditionItem sci = new SearchConditionItem(name, QueryRelation.AND, getQueryRootGroup());
        generateItemViewAndAddToGroup(sci, getQueryRootGroup());

        sci.setSearchConditionEncoding(encodedConditions);
        sci.setDescription(description);
    }

    /**
     * Replaces the items in the first level group (i.e. a group immediately
     * under the root node) with the description "groupDesc" with those in the
     * list 'sciList'.
     *
     * If the group does not exist, a new one is created under the root query
     * group.
     *
     * No views are created or updated by this function.
     *
     * @return The SearchConditionGroupItem matching the description, or the one
     * that was created.
     */
    public SearchConditionGroupItem replaceFirstLevelGroup(String groupDesc, List<SearchConditionItem> sciList,
            QueryRelation qr, boolean listIsItems) {

        List<SearchConditionItem> scgList = getFirstLevelItemsByDesc(groupDesc);
        for (SearchConditionItem sci : scgList) {
            getQueryRootGroup().removeItem(sci);
            //expandedItemsMap.remove(getQueryRootGroup());
            expandedItemsMap.remove(sci);
        }

        SearchConditionGroupItem scg = new SearchConditionGroupItem(qr, null, getQueryRootGroup());
        scg.setDescription(groupDesc);
        getQueryRootGroup().addItem(scg);

        if (sciList != null) {
            for (SearchConditionItem sci : sciList) {
                sci.setParent(scg);
                if (listIsItems) {
                    generateItemViewAndAddToGroup(sci, scg);
                } else {
                    scg.addItem(sci);
                }
            }
        }

        return scg;
    }

    //clears all search terms, and refreshes the view only if refresh is set.
    private void clearSearch(boolean refresh) {

        try {
            AnalyticsAgent.log(new NameValuePair[] { new NameValuePair("view-event", "SearchCleared") });
        } catch (Exception e) {
        }

        this.rootGroup.clearItems();
        this.itemToViewMap.clear();
        this.expandedItemsMap.clear();
        if (refresh) {
            refreshView();
        }
    }

    /**
     * Clears all search terms and refreshes the view
     */
    public void clearSearch() {
        clearSearch(true);
    }

    public void loadConditions(File f) {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        try {
            DocumentBuilder db = dbf.newDocumentBuilder();
            Element rootElement = db.parse(f).getDocumentElement();
            clearSearch(false);
            getGroupFromXML(rootElement, null);
            refreshView();
        } catch (Exception ex) {
            LOG.error(ex);
            DialogUtils.displayException("Error", "There was an error loading your search", ex);
        }
    }

    public final void refreshView() {
        List<JComponent> cs;

        cs = getComponentsFromQueryModel(rootGroup);
        this.setLayout(new BorderLayout());

        JPanel p = ViewUtil.getClearPanel();
        //  p.setBorder(ViewUtil.getBottomLineBorder());
        p.setLayout(new MigLayout("hidemode 1, nogrid, fillx, insets 12 12 12 20"));

        this.removeAll();

        JLabel l = ViewUtil.getEmphasizedLabel("SEARCH CONDITIONS");
        p.add(l, "left");

        p.add(ViewUtil.getHelpButton("How to search",
                "Type a search condition into the search box, e.g. \"Chromosome\". "
                        + "Press Enter / Return to accept the selected condition name. "
                        + "You\'ll then be prompted to specify parameters for this condition."),
                "left, wrap");

        for (JComponent c : cs) {
            p.add(c, "left, wrap");
        }

        p.add(warningText, "left, wrap");
        p.add(applyButton, "left, wrap, gapy n 1");

        JScrollPane jsp = ViewUtil.getClearBorderlessScrollPane(p);
        jsp.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
        this.add(jsp, BorderLayout.CENTER);

        this.invalidate();
        this.updateUI();
    }

    private String getGroupTitle(SearchConditionGroupItem scg) {
        if (scg.getDescription() != null && scg.getDescription().length() > 0) {
            return (scg.getParent().isFirstItem(scg) ? "" : scg.getRelation().toString()) + " "
                    + scg.getDescription();
        } else {
            return (scg.getParent().isFirstItem(scg) ? "" : scg.getRelation().toString()) + " "
                    + scg.getItems().size() + " grouped condition(s)";
        }
    }

    private List<JComponent> getComponentsFromQueryModel(SearchConditionGroupItem g) {
        return getComponentsFromQueryModel(g, 0);
    }

    private List<JComponent> getComponentsFromQueryModel(SearchConditionGroupItem g, int depth) {
        List<JComponent> components = new ArrayList<JComponent>();
        int w = 0;
        for (final SearchConditionItem item : g.getItems()) {
            if (item instanceof SearchConditionGroupItem) {
                String addition = "";
                if (item.getParent() != null) {
                    addition = item.getRelation() + "";
                }

                final JPanel p = ViewUtil.getClearPanel();
                MigLayout ml = new MigLayout("wrap 1, hidemode 1, insets 2");
                p.setLayout(ml);
                Border border = ViewUtil.getThickLeftLineBorder();
                p.setBorder(border);

                final PillView pv = new PillView(true);
                pv.indent(depth);

                pv.setActivated(true);

                final ActionListener toggleGroupExpand = new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent ae) {
                        expandedItemsMap.put((SearchConditionGroupItem) item, !p.isVisible());
                        p.setVisible(!p.isVisible());
                        pv.setText(getGroupTitle((SearchConditionGroupItem) item));
                    }
                };

                pv.setExpandListener(toggleGroupExpand);

                pv.setPopupGenerator(new ConditionPopupGenerator() {
                    @Override
                    public JPopupMenu generatePopup() {
                        final SearchConditionGroupItem groupItem = (SearchConditionGroupItem) item;
                        final JPopupMenu m = new JPopupMenu();

                        if (!item.getParent().isFirstItem(item)) {
                            if (item.getRelation() == QueryRelation.AND) {
                                JMenuItem b = new JMenuItem("Change to \"or\"");
                                b.addActionListener(new ActionListener() {
                                    @Override
                                    public void actionPerformed(ActionEvent ae) {
                                        item.setRelation(QueryRelation.OR);
                                        pv.setText(getGroupTitle(groupItem));
                                    }
                                });
                                m.add(b);
                            } else {
                                JMenuItem b = new JMenuItem("Change to \"and\"");
                                b.addActionListener(new ActionListener() {
                                    @Override
                                    public void actionPerformed(ActionEvent ae) {
                                        item.setRelation(QueryRelation.AND);
                                        pv.setText(getGroupTitle(groupItem));
                                    }
                                });
                                m.add(b);
                            }
                        }

                        JMenuItem delgroup = new JMenuItem("Delete");
                        delgroup.addActionListener(new ActionListener() {
                            @Override
                            public void actionPerformed(ActionEvent ae) {
                                groupItem.getParent().removeItem(groupItem);
                                refreshView();
                                expandedItemsMap.remove((SearchConditionGroupItem) item);
                            }
                        });
                        m.add(delgroup);

                        return m;
                    }
                });
                SearchConditionGroupItem g2 = (SearchConditionGroupItem) item;

                pv.setText(getGroupTitle(g2));
                Boolean exp = expandedItemsMap.get((SearchConditionGroupItem) item);

                if (exp == null) {
                    p.setVisible(false);
                    pv.collapse();
                } else {
                    p.setVisible(true);
                    pv.expand();
                }

                components.add(pv);
                for (JComponent c : getComponentsFromQueryModel((SearchConditionGroupItem) item, depth + 1)) {
                    p.add(c, "left");
                }
                components.add(p);

                w = pv.getMaximumSize().width;
            } else {
                SearchConditionItemView sciv = itemToViewMap.get(item);
                ((PillView) sciv).indent(depth);
                w = sciv.getMaximumSize().width;
                sciv.refresh();
                components.add(sciv);
            }
        }

        if (w == 0) {
            w = BOTTOM_SEARCH_TEXTFIELD_WIDTH;
        }
        JComponent c = getInputFieldForGroup(g, w);
        components.add(c);
        return components;
    }

    private int c = 0;

    @Override
    public void searchConditionsOrderChanged(SearchConditionItem m) {
        setConditionsChanged(true);
        this.refreshView();
    }

    public void registerViewWithItem(SearchConditionItemView editor, SearchConditionItem item) {

        itemToViewMap.put(item, editor);
    }

    @Override
    public void searchConditionItemRemoved(SearchConditionItem m) {
        itemToViewMap.remove(m);
    }

    public void generateItemViewAndAddToGroup(SearchConditionItem sci, SearchConditionGroupItem parent) {
        SearchConditionItemView view = conditionViewGenerator.generateViewForItem(sci);
        addItemToGroup(sci, view, parent);
    }

    public SearchConditionItemView generateItemViewAndAddToGroup(String fieldName,
            SearchConditionGroupItem parent) {
        SearchConditionItem item = new SearchConditionItem(fieldName, parent);
        SearchConditionItemView view = conditionViewGenerator.generateViewForItem(item);
        addItemToGroup(item, view, parent);
        return view;
    }

    public void addItemToGroup(SearchConditionItem item, SearchConditionItemView view,
            SearchConditionGroupItem parent) {
        registerViewWithItem(view, item);
        parent.addItem(item);
    }

    public void addGroupToGroup(SearchConditionGroupItem child, SearchConditionGroupItem parent) {
        parent.addItem(child);
    }

    public SearchConditionGroupItem getQueryRootGroup() {
        return this.rootGroup;
    }

    private JComponent getInputFieldForGroup(final SearchConditionGroupItem g, int width) {

        final QueryViewController instance = this;

        final Map<String, List<String>> possible = conditionViewGenerator.getAllowableItemNames();
        final CaseInsensitiveArrayList allPossible = new CaseInsensitiveArrayList();
        for (String key : possible.keySet()) {
            allPossible.addAll(possible.get(key));
        }

        final JButton addCondition = ViewUtil.getSoftButton("Add search condition...");
        final CustomKeyListener keyListener = new CustomKeyListener(this, possible, allPossible, g);
        addCondition.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                keyListener.refreshPopup(addCondition);
            }

        });

        JPanel p = ViewUtil.getClearPanel();
        ViewUtil.applyHorizontalBoxLayout(p);
        p.add(addCondition);

        return p;
    }

    @Override
    public void searchConditionItemAdded(SearchConditionItem m) {
    }

    @Override
    public void searchConditionEdited(SearchConditionItem item) {
        setConditionsChanged(true);
    }

    private void setConditionsChanged(boolean b) {
        if (applyButton != null) {
            warningText.setVisible(b);
            applyButton.setSelected(b);
            applyButton.setEnabled(b);
        }
    }

    private Condition getSQLConditionsFrom(SearchConditionItem item) throws Exception {

        if (item instanceof SearchConditionGroupItem) {

            SearchConditionGroupItem group = (SearchConditionGroupItem) item;
            Condition childrenConditions = null;
            for (SearchConditionItem child : group.getItems()) {
                if (childrenConditions == null) {
                    childrenConditions = getSQLConditionsFrom(child);
                } else {
                    if (child.getRelation() == QueryRelation.AND) {
                        childrenConditions = ComboCondition.and(childrenConditions, getSQLConditionsFrom(child));
                    } else if (child.getRelation() == QueryRelation.OR) {
                        childrenConditions = ComboCondition.or(childrenConditions, getSQLConditionsFrom(child));
                    }
                }
            }

            return childrenConditions;
        } else {
            return conditionViewGenerator.generateConditionForItem(item);
        }
    }

    private static class CustomKeyListener {

        private final Map<String, List<String>> possible;
        private final CaseInsensitiveArrayList allPossible;
        private SearchConditionGroupItem group;
        private QueryViewController qvc;

        public CustomKeyListener(QueryViewController qvc, Map<String, List<String>> p, CaseInsensitiveArrayList ap,
                SearchConditionGroupItem g) {
            this.qvc = qvc;
            this.possible = p;
            this.allPossible = ap;
            this.group = g;
        }

        ;

        private ArrayList<JComponent> menuComponents;
        List<JMenuItem> menuItems = new ArrayList<JMenuItem>();
        int currentlySelectedIndex;

        public void addItemBasedOnField(String validTerm) {
            final SearchConditionItemView view = qvc.generateItemViewAndAddToGroup(validTerm, group);
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    view.showDialog(view.getLocationOnScreen());
                }
            });
        }

        public void refreshPopup(JComponent parent) {

            QueryConditionDialog m = new QueryConditionDialog(possible);
            m.setVisible(true);
            String choice = m.getChosenConditionName();
            if (choice != null) {
                addItemBasedOnField(choice);
            }

            /*
             currentlySelectedIndex = -1;
                
             Dimension d = new Dimension(field.getWidth(), 23);
                
             menuComponents = new ArrayList<JComponent>();
                
             menuItems.removeAll(menuItems);
                
             for (String key : possible.keySet()) {
             JMenuItem l = new JMenuItem(key.toUpperCase());
             Font f = new Font(l.getFont().getFamily(), Font.PLAIN, 10);
             l.setFont(f);
             l.setEnabled(false);
             l.setBackground(new Color(179, 189, 199));
             l.setForeground(Color.white);
             Dimension d2 = new Dimension(d.width, d.height - 5);
             l.setMinimumSize(d2);
             l.setMaximumSize(d2);
             l.setPreferredSize(d2);
                
             int headerIndex = menuComponents.size();
             boolean sectionHasMatch = false;
                
             Collections.sort(possible.get(key));
                
             for (String s : possible.get(key)) {
             int indexOfMatch = s.toLowerCase().indexOf(field.getText().toLowerCase());
             if (indexOfMatch > -1) {
             sectionHasMatch = true;
             int to = indexOfMatch + field.getText().length();
                
             final JMenuItem i = new JMenuItem("<html>"
             + s.substring(0, indexOfMatch)
             + "<b>" + s.substring(indexOfMatch, to) + "</b>"
             + s.substring(to) + "</html>");
             fixSize(i, d);
             i.addActionListener(new ActionListener() {
             @Override
             public void actionPerformed(ActionEvent ae) {
             field.setText(i.getText().replaceAll("\\<.*?>", ""));
             }
             });
             menuItems.add(i);
             menuComponents.add(i);
             }
             }
                
             if (sectionHasMatch) {
             menuComponents.add(headerIndex, l);
             }
             }
                
             int originalheight = 0;
             for (Component c : menuComponents) {
             originalheight += c.getPreferredSize().getHeight();
             }
                
             //int maxHeight = 100;
             int adjustedheight = originalheight;
                
             m = new ScrollableJPopupMenu(15);
                
             m.setFocusable(false);
             m.removeAll();
                
             for (JComponent c : menuComponents) {
             m.addComponent(c);
             }
                
             Dimension dadjusted = new Dimension(m.getPreferredSize().width, adjustedheight + 10);
             //fixSize(m, dadjusted);
                
             //m.pack();
             m.setPopupSize(200, 400);
             m.show(field, 0, 23);
             m.updateUI();
             */
        }
    }

    public class CaseInsensitiveArrayList extends ArrayList<String> {

        @Override
        public boolean contains(Object o) {
            String paramStr = (String) o;
            for (String s : this) {
                if (paramStr.equalsIgnoreCase(s)) {
                    return true;
                }
            }
            return false;
        }

        public String getInsensitiveMatch(String o) {
            String paramStr = (String) o;
            for (String s : this) {
                if (paramStr.equalsIgnoreCase(s)) {
                    return s;
                }
            }
            return null;
        }
    }
}