001    /*
002     * Copyright 2007, 2012 Stephen Fisher and Junhyong Kim, University of
003     * Pennsylvania.
004     *
005     * This file is part of Glo-DB.
006     * 
007     * Glo-DB is free software: you can redistribute it and/or modify it
008     * under the terms of the GNU General Public License as published by
009     * the Free Software Foundation, either version 3 of the License, or
010     * (at your option) any later version.
011     * 
012     * Glo-DB is distributed in the hope that it will be useful, but
013     * WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015     * General Public License for more details.
016     * 
017     * You should have received a copy of the GNU General Public License
018     * along with Glo-DB. If not, see <http://www.gnu.org/licenses/>.
019     *
020     * @(#)QueryBuilder.java
021     */
022    
023    package edu.upenn.gloDB.gui;
024    
025    import edu.upenn.gloDB.*;
026    import java.awt.*;
027    import java.awt.event.*;
028    import javax.swing.*;
029    import javax.swing.event.*;
030    import javax.swing.border.Border;
031    import javax.swing.text.*;
032    import java.util.Iterator;
033    import java.util.ArrayList;
034    import javax.swing.table.AbstractTableModel;
035    import javax.swing.table.TableColumn;
036    
037    /**
038     * Browse existing Tracks.
039     *
040     * @author  Stephen Fisher
041     * @version $Id: QueryBuilder.java,v 1.1.2.35 2007/03/01 21:17:33 fisher Exp $
042     */
043    
044    public class QueryBuilder extends JFrame {
045             //      private final String[] OPERATORS = { "Intersection: AND", "Intersection (strict): sAND", "Intersection (bitwise): &&", "Union: OR", "Union (bitwise): ||", "Relative complement: MINUS", "Relative complement (strict): sMINUS", "Relative complement (bitwise): -", "Order: POS", "Order (bitwise): ." };
046             private final String[] OPERATORS = { "Intersection: AND", "Intersection (strict): sAND", "Intersection (bitwise): &&", "Union: OR", "Union (bitwise): ||", "Relative complement: MINUS", "Relative complement (strict): sMINUS", "Relative complement (bitwise): -", "Order: POS" };
047    
048             private JTextField queryTF;
049             private JTable table;
050             private ListTableModel tableModel;
051             private String trackID = "";
052             
053             private JButton updateB;
054             private JButton groupB;
055             private JButton ungroupB;
056             private JButton deleteB;
057             private JButton deleteAllB;
058             private JButton copyB;
059             private JButton computeB;
060             private JButton newB;
061    
062             private JList operatorL;
063             private JCheckBox negateChB;
064             private JList trackL;
065             private JCheckBox sequenceChB;
066             private JComboBox sequenceCB;
067    
068             // these aren't forced to be integers here because then the user
069             // would need to know that '-1' is the default value and as
070             // strings we can just use the empty string.
071             private JTextField minWidthTF;
072             private JTextField maxWidthTF;
073             private JTextField minSeqPosTF;
074             private JTextField maxSeqPosTF;
075             private JTextField minRepeatTF;
076             private JTextField maxRepeatTF;
077             private JTextField minWithinTF;
078             private JTextField maxWithinTF;
079             private JTextField minPosTF;
080             private JTextField maxPosTF;
081    
082             QueryBuilder thisFrame;
083    
084             public QueryBuilder() {
085                      this("");
086             }
087    
088        public QueryBuilder(String id) {
089            super("Query Builder");
090    
091                      // keep pointer to self so can 'dispose' Frame below
092                      thisFrame = this;
093    
094                      setDefaultCloseOperation(DISPOSE_ON_CLOSE);
095    
096                      // create various borders
097                      Border emptyBorder = BorderFactory.createEmptyBorder(10,10,10,10);
098                      Border emptyBorder5 = BorderFactory.createEmptyBorder(5,5,5,5);
099                      Border lowBevelBorder = BorderFactory.createLoweredBevelBorder();
100                      Border etchedBorder = BorderFactory.createEtchedBorder();
101                      Border tmpBorder = BorderFactory.createCompoundBorder(emptyBorder, lowBevelBorder);
102                      // create a lower bevel border with outside and inside padding
103                      Border lowBevelPadBorder = BorderFactory.createCompoundBorder(tmpBorder, emptyBorder);
104                      // create an etched border with inside padding
105                      //              Border etchedPadBorder = BorderFactory.createCompoundBorder(etchedBorder, emptyBorder);
106                      // create an etched border with no top padding, for use with titles
107                      Border emptyBorder0 = BorderFactory.createEmptyBorder(0,10,10,10);
108                      Border etchedPadBorder0 = BorderFactory.createCompoundBorder(etchedBorder, emptyBorder0);
109    
110    
111                      // *************************************************
112                      // create query panel
113                      JPanel queryP = new JPanel(new BorderLayout());
114                      queryP.setBorder(lowBevelPadBorder);
115    
116                      // *************************************************
117                      // create text composition panel
118                      JPanel textP = new JPanel(new BorderLayout(10,0));
119                      //              textP.setBorder(BorderFactory.createLoweredBevelBorder());
120                      textP.setBorder(emptyBorder5);
121                      queryTF = new JTextField(50);
122                      queryTF.setEditable(false);
123                      JButton queryB = new JButton("Change Output Track ID");
124                      queryB.addActionListener(new ActionListener() {
125                                             public void actionPerformed(ActionEvent e) {
126                                                      setID(getID());
127                                             }
128                                    });
129                      textP.add(new JLabel(" Query: "), BorderLayout.WEST);
130                      textP.add(queryTF, BorderLayout.CENTER);
131                      textP.add(queryB, BorderLayout.EAST);
132    
133                      // *************************************************
134                      // TABLE SETUP
135                      tableModel = new ListTableModel();
136                      table = new JTable(tableModel);
137            table.setPreferredScrollableViewportSize(new Dimension(500, 70));
138                      table.setColumnSelectionAllowed(false);
139                      //              table.setShowGrid(false);
140                      //              table.setShowHorizontalLines(false);
141                      table.setShowVerticalLines(false);
142                      // XXX this will hopefully disallow moving cols 
143                      table.setDragEnabled(false);  
144            //Create the scroll pane and add the table to it.
145            JScrollPane tableSP = new JScrollPane(table);
146                      /*
147            //Set up tool tip for Track column
148            DefaultTableCellRenderer renderer = new DefaultTableCellRenderer();
149            renderer.setToolTipText("Click for combo box");
150            trackTC.setCellRenderer(renderer);
151                      */
152                      TableColumn column = null;
153                      for (int i = 0; i < tableModel.getColumnCount(); i++) {
154                                    column = table.getColumnModel().getColumn(i);
155                                    // first 2 columns should be smaller
156                                    if (i < 2) column.setPreferredWidth(10); 
157                                    else column.setPreferredWidth(100); 
158                      }
159                      // END TABLE SETUP
160                      // *************************************************
161    
162                      // *************************************************
163                      // create panel with list
164                      DefaultListSelectionModel dlsm = new DefaultListSelectionModel();
165                      table.setSelectionModel(dlsm);
166                      // don't allow discontinuous selection
167                      dlsm.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
168                      dlsm.addListSelectionListener(new ListSelectionListener() {
169                                             public void valueChanged(ListSelectionEvent e) {
170                                                      if (e.getValueIsAdjusting()) return;
171    
172                                                      if (table.getSelectedRowCount() == 0) {
173                                                                    // no selection so disable buttons 
174                                                                    updateB.setEnabled(false);
175                                                                    groupB.setEnabled(false);
176                                                                    ungroupB.setEnabled(false);
177                                                                    deleteB.setEnabled(false);
178    
179                                                                    updateEditPanel(null);
180    
181                                                                    // don't allow operator if first item
182                                                                    if (tableModel.getRowCount() == 0) { 
183                                                                             operatorL.setEnabled(false);
184                                                                             operatorL.clearSelection();
185                                                                    } else {
186                                                                             operatorL.setEnabled(true);
187                                                                    }
188                                                      } else {
189                                                                    // we know that at least 1 row is selected
190                                                                    QueryElement qElement = tableModel.getRow(table.getSelectedRow());
191                                                                    updateEditPanel(qElement);
192    
193                                                                    // enable buttons if selection
194                                                                    groupB.setEnabled(true);
195                                                                    deleteB.setEnabled(true);
196    
197                                                                    // only enable the follwoing buttons if 1 item
198                                                                    // is selected.  For ungroupB, if only 1
199                                                                    // selected item, then only enable if group.
200                                                                    // If multiple selected items then just leave
201                                                                    // enabled.
202                                                                    if (table.getSelectedRowCount() == 1) {
203                                                                             // only enable the appropriate button
204                                                                             if (qElement.isGrouped()) {
205                                                                                      ungroupB.setEnabled(true);
206                                                                             } else {
207                                                                                      ungroupB.setEnabled(false);
208                                                                             }
209                                                                             updateB.setEnabled(true);
210                                                                    } else {
211                                                                             ungroupB.setEnabled(true);
212                                                                             updateB.setEnabled(false);
213                                                                             trackL.clearSelection();
214                                                                    }
215                                                      }
216                                             }
217                                    });
218    
219                      // *************************************************
220                      // create panel with control button
221                      JPanel controlP = new JPanel(new GridLayout(1,0,200,5));
222                      controlP.setBorder(emptyBorder5);
223                      // add delete button
224                      deleteB = new JButton("Delete Selected Elements(s)");
225                      deleteB.setEnabled(false);
226                      deleteB.addActionListener(new ActionListener() {
227                                             public void actionPerformed(ActionEvent e) {
228                                                      tableModel.removeRows(table.getSelectedRows());
229                                                      updateQuery();
230                                             }
231                                    });
232                      controlP.add(deleteB);
233                      // add clear button
234                      deleteAllB = new JButton("Delete All Elements");
235                      deleteAllB.setEnabled(false);
236                      deleteAllB.addActionListener(new ActionListener() {
237                                             public void actionPerformed(ActionEvent e) {
238                                                      String msg = "Are you sure you want to delete all items?";
239                                                      Object[] options = {"Delete", "Cancel"};
240                                                      int flag = JOptionPane.showOptionDialog(null, msg,
241                                                                                                                                                                     "Delete Confirmation",
242                                                                                                                                                                     JOptionPane.YES_NO_OPTION,
243                                                                                                                                                                     JOptionPane.QUESTION_MESSAGE,
244                                                                                                                                                                     null,
245                                                                                                                                                                     options,
246                                                                                                                                                                     options[1]);
247                                                      if (flag == JOptionPane.YES_OPTION) { // "Delete"
248                                                                    tableModel.removeAllRows();
249                                                                    updateQuery();
250                                                      }
251                                             }
252                                    });
253                      controlP.add(deleteAllB);
254    
255                      queryP.add(textP, BorderLayout.NORTH);
256                      queryP.add(tableSP, BorderLayout.CENTER);
257                      queryP.add(controlP, BorderLayout.SOUTH);
258    
259                      // *************************************************
260                      // ************ EDIT PANEL *************
261                      JPanel editP = new JPanel(new BorderLayout());
262                      editP.setBorder(lowBevelPadBorder);
263    
264                      // ********* EDIT BUTTON PANEL ************
265                      JPanel buttonP = new JPanel(new GridLayout(1,0,10,10));
266                      buttonP.setBorder(BorderFactory.createEmptyBorder(10,0,0,0));
267                      // add 'new' button
268                      newB = new JButton("Insert Track");
269                      newB.addActionListener(new ActionListener() {
270                                             public void actionPerformed(ActionEvent e) {
271                                                      QueryElement qElement = new QueryElement((String) trackL.getSelectedValue());
272                                                      updateQueryElement(qElement);
273    
274                                                      int[] selection = table.getSelectedRows();
275                                                      if (selection.length == 0) { // nothing selected, append to end
276                                                                    tableModel.addRow(qElement);
277                                                      } else {  // insert track after current selection
278                                                                    int index = selection[selection.length - 1];
279                                                                    if (index == tableModel.getRowCount()) {
280                                                                             // last row selected so just append
281                                                                             tableModel.addRow(qElement);
282                                                                    } else {
283                                                                             tableModel.addRowAt(qElement, index + 1);
284                                                                    }
285                                                      }
286                                                      updateQuery();
287                                                      updateEditPanel(null);  // reset the query panel
288                                             }
289                                    });
290                      buttonP.add(newB);
291                      // add group button
292                      groupB = new JButton("Group Selected Element(s)");
293                      groupB.setEnabled(false);
294                      groupB.addActionListener(new ActionListener() {
295                                             public void actionPerformed(ActionEvent e) {
296                                                      // get selected elements to put into group
297                                                      int[] rows = table.getSelectedRows();
298    
299                                                      // store where the new group goes
300                                                      int row = rows[0];
301    
302                                                      //make new group
303                                                      QueryElement qElement = new QueryElement();
304                                                      updateQueryElement(qElement);
305    
306                                                      // remove operator from first element in group
307                                                      QueryElement firstQE = tableModel.getRow(row);
308                                                      //                                              firstQE.operator = "";
309                                                      firstQE.operator = -1;
310    
311                                                      // build group element
312                                                      for (int i = 0; i < rows.length; i++) {
313                                                                    qElement.addToGroup(tableModel.getRow(rows[i]));
314                                                      }
315    
316                                                      // remove grouped elements from table
317                                                      tableModel.removeRows(rows);
318    
319                                                      // add group element to table
320                                                      tableModel.addRowAt(qElement, row);
321    
322                                                      updateQuery();
323                                                      updateEditPanel(null);  // reset the query panel
324                                             }
325                                    });
326                      buttonP.add(groupB);
327                      // add ungroup button
328                      ungroupB = new JButton("Ungroup Selected Element(s)");
329                      ungroupB.setEnabled(false);
330                      ungroupB.addActionListener(new ActionListener() {
331                                             public void actionPerformed(ActionEvent e) {
332                                                      // get selected elements to ungroup
333                                                      int[] rows = table.getSelectedRows();
334                                                      // store where the new elements go
335                                                      int row = rows[0];
336    
337                                                      // build an array containing all the selected
338                                                      // items, removing the items from the table. This
339                                                      // array is in reverse order and will be inverted
340                                                      // when the items are ungrouped and added back to
341                                                      // the table.
342                                                      ArrayList selected = new ArrayList();
343                                                      int maxRow = rows[rows.length-1];
344                                                      while (maxRow >= row) {
345                                                                    selected.add(tableModel.getRow(maxRow));
346                                                                    tableModel.removeRow(maxRow--);
347                                                      }
348    
349                                                      // by adding all of the ungrouped items at the
350                                                      // same index location 'row', we are effectively
351                                                      // inverting 'selected'
352                                                      for (Iterator i = selected.iterator(); i.hasNext();) {
353                                                                    QueryElement qElement = (QueryElement) i.next();
354                                                                    // only 'ungroup' if actually a group
355                                                                    if (qElement.isGrouped()) {
356                                                                             // move operator from group to first element
357                                                                             QueryElement firstQE = (QueryElement) qElement.getGroup().get(0);
358                                                                             firstQE.operator = qElement.operator;
359    
360                                                                             // add ungrouped items
361                                                                             int index = row;
362                                                                             for (Iterator it = qElement.groupIterator(); it.hasNext();) {
363                                                                                      tableModel.addRowAt((QueryElement) it.next(), index++);
364                                                                             }
365                                                                    } else {
366                                                                             // not a group so just add the item
367                                                                             tableModel.addRowAt(qElement, row);
368                                                                    }
369                                                      }
370    
371                                                      updateQuery();
372                                             }
373                                    });
374                      buttonP.add(ungroupB);
375                      // add update button
376                      updateB = new JButton("Update Selected Element");
377                      updateB.setEnabled(false);
378                      updateB.addActionListener(new ActionListener() {
379                                             public void actionPerformed(ActionEvent e) {
380                                                      QueryElement qElement = tableModel.getRow(table.getSelectedRow());
381                                                      updateQueryElement(qElement);
382                                                      if (! qElement.isGrouped()) {
383                                                                    qElement.track = (String) trackL.getSelectedValue();
384                                                      }
385                                                      updateQuery();
386    
387                                                      // need to tell table to redraw
388                                                      tableModel.fireTableDataChanged();
389                                             }
390                                    });
391                      buttonP.add(updateB);
392    
393                      // *************************************************
394                      // ************* OPTION PANELS
395                      JPanel optionsP = new JPanel(new GridLayout(1,0,5,0));
396    
397                      // create Operators panel with titled, etched, padded border
398                      JPanel operatorP = new JPanel(new BorderLayout());
399                      //              JPanel operatorP = new JPanel(new GridLayout(0,1,0,5));
400                      operatorP.setBorder(BorderFactory.createTitledBorder(etchedPadBorder0, "Operators"));
401                      operatorL = new JList(OPERATORS);
402                      operatorL.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
403                      operatorL.setEnabled(false);
404                      operatorL.clearSelection();
405                      operatorL.addListSelectionListener(new ListSelectionListener() {
406                                             public void valueChanged(ListSelectionEvent e) {
407                                                      if (e.getValueIsAdjusting()) return;
408                                                      if (operatorL.getSelectedIndex() == -1) { // nothing selected
409                                                                    minPosTF.setEnabled(false);
410                                                      } else {
411                                                                    // test if order operator ('POS'/'.')
412                                                                    if (QueryElement.isOrderOperator(operatorL.getSelectedIndex())) {
413                                                                             minPosTF.setEnabled(true);
414                                                                    } else { 
415                                                                             minPosTF.setEnabled(false);
416                                                                             maxPosTF.setEnabled(false);
417                                                                    }
418                                                      }
419                                             }
420                                    });
421            JScrollPane operatorSP = new JScrollPane(operatorL);
422    
423                      JPanel orderedP = new JPanel(new GridLayout(1,0));
424                      orderedP.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
425                      PlainDocument minPosPD = new PlainDocument();
426                      minPosPD.addDocumentListener(new DocumentListener() {
427                                             public void insertUpdate(DocumentEvent e) {
428                                                      try {
429                                                                    Integer.parseInt(minPosTF.getText().trim());
430                                                                    maxPosTF.setEnabled(minPosTF.getText().trim().length() > 0);
431                                                      } catch (NumberFormatException exception) {
432                                                                    GloDBUtils.printError("Order lower bound must be an integer.");
433                                                                    maxPosTF.setEnabled(false);
434                                                      }
435                                             }
436                                             public void removeUpdate(DocumentEvent e) {
437                                                      try {
438                                                                    Integer.parseInt(minPosTF.getText().trim());
439                                                                    maxPosTF.setEnabled(minPosTF.getText().trim().length() > 0);
440                                                      } catch (NumberFormatException exception) {
441                                                                    // don't need to again show error message,
442                                                                    // just disable maxPosTF
443                                                                    maxPosTF.setEnabled(false);
444                                                      }
445                                             }
446                                             public void changedUpdate(DocumentEvent e) { ; }
447                                    });
448                      minPosTF = new JTextField(minPosPD, "", 0);
449                      minPosTF.setEnabled(false);
450                      orderedP.add(new JLabel("Order (bp):"));
451                      orderedP.add(minPosTF);
452                      orderedP.add(new JLabel("to", JLabel.CENTER));
453                      PlainDocument maxPosPD = new PlainDocument();
454                      maxPosPD.addDocumentListener(new DocumentListener() {
455                                             public void insertUpdate(DocumentEvent e) {
456                                                      try {
457                                                                    Integer.parseInt(maxPosTF.getText().trim());
458                                                      } catch (NumberFormatException exception) {
459                                                                    GloDBUtils.printError("Order upper bound must be an integer.");
460                                                      }
461                                             }
462                                             public void removeUpdate(DocumentEvent e) { ; }
463                                             public void changedUpdate(DocumentEvent e) { ; }
464                                    });
465                      maxPosTF = new JTextField(maxPosPD, "", 0);
466                      maxPosTF.setEnabled(false);
467                      orderedP.add(maxPosTF);
468    
469                      operatorP.add(operatorSP, BorderLayout.CENTER);
470                      operatorP.add(orderedP, BorderLayout.SOUTH);
471              /*
472                      JPanel setOperatorP = new JPanel(new BorderLayout());
473                      setOperatorL = new JList(SET_OPERATORS);
474                      setOperatorL.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
475                      setOperatorL.setEnabled(false);
476                      setOperatorL.clearSelection();
477            JScrollPane setOperatorSP = new JScrollPane(setOperatorL);
478                      setOperatorP.add(new JLabel("Set:"), BorderLayout.NORTH);
479                      setOperatorP.add(setOperatorSP, BorderLayout.CENTER);
480                      JPanel bitOperatorP = new JPanel(new BorderLayout());
481                      bitOperatorL = new JList(BIT_OPERATORS);
482                      bitOperatorL.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
483                      bitOperatorL.setEnabled(false);
484                      bitOperatorL.clearSelection();
485            JScrollPane bitOperatorSP = new JScrollPane(bitOperatorL);
486                      bitOperatorP.add(new JLabel("Bitwise:"), BorderLayout.NORTH);
487                      bitOperatorP.add(bitOperatorSP, BorderLayout.CENTER);
488                      operatorP.add(setOperatorP);
489                      operatorP.add(bitOperatorP);
490              */
491                      optionsP.add(operatorP);
492    
493                      // create panel to stack unitOperatorP and trackP
494                      JPanel middleP = new JPanel(new BorderLayout());
495    
496                      // create Unitary Operator panel with titled, etched, padded border
497                      JPanel unitOperatorP = new JPanel(new BorderLayout());
498                      unitOperatorP.setBorder(BorderFactory.createTitledBorder(etchedPadBorder0, "Unitary Operators"));
499                      JPanel unitOperatorP0 = new JPanel(new BorderLayout());
500                      negateChB = new JCheckBox();
501                      negateChB.setSelected(false);
502                      unitOperatorP0.add(negateChB, BorderLayout.WEST);
503                      unitOperatorP0.add(new JLabel(" Negate"), BorderLayout.CENTER);
504                      unitOperatorP.add(unitOperatorP0, BorderLayout.NORTH);
505                      middleP.add(unitOperatorP, BorderLayout.NORTH);
506    
507                      // create Track panel with titled, etched, padded border
508                      JPanel trackP = new JPanel(new BorderLayout());
509                      trackP.setBorder(BorderFactory.createTitledBorder(etchedPadBorder0, "Track"));
510                      JPanel trackP0 = new JPanel(new BorderLayout());
511                      trackL = new JList(ObjectHandles.getTrackList());
512                      trackL.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
513                      trackL.addListSelectionListener(new ListSelectionListener() {
514                                             public void valueChanged(ListSelectionEvent e) {
515                                                      if (e.getValueIsAdjusting()) return;
516                                                      if (trackL.getSelectedIndex() == -1) { // nothing selected
517                                                                    newB.setEnabled(false);
518                                                      } else {
519                                                                    newB.setEnabled(true);
520                                                      }
521                                             }
522                                    });
523            JScrollPane trackSP = new JScrollPane(trackL);
524                      trackSP.setBorder(emptyBorder5);
525                      trackP0.add(trackSP, BorderLayout.CENTER);
526                      trackP.add(trackP0, BorderLayout.NORTH);
527                      middleP.add(trackP, BorderLayout.CENTER);
528                      optionsP.add(middleP);
529    
530                      // create Qualifier panel with titled, etched, padded border
531                      JPanel qualifiersP = new JPanel(new GridLayout(8,1,5,5));
532                      qualifiersP.setBorder(BorderFactory.createTitledBorder(etchedPadBorder0, "Qualifiers"));
533                      JPanel sequenceP = new JPanel(new BorderLayout());
534                      sequenceChB = new JCheckBox();
535                      sequenceChB.setSelected(false);
536                      sequenceChB.addActionListener(new ActionListener() {
537                                             public void actionPerformed(ActionEvent e) {
538                                                      sequenceCB.setEnabled(sequenceChB.isSelected());
539                                             }
540                                    });
541                      JPanel sequenceP0 = new JPanel(new BorderLayout());
542                      sequenceP0.add(sequenceChB, BorderLayout.WEST);
543                      sequenceP0.add(new JLabel(" Sequence:"), BorderLayout.CENTER);
544                      sequenceP.add(sequenceP0, BorderLayout.WEST);
545                      sequenceCB = new JComboBox(ObjectHandles.getSequenceList());
546                      sequenceCB.setEditable(false);
547                      sequenceCB.setEnabled(false);
548                      sequenceP.add(sequenceCB, BorderLayout.CENTER);
549                      qualifiersP.add(sequenceP);
550                      //              qualifiersP.add(sequenceCB);
551                      qualifiersP.add(new JLabel(""));
552    
553                      JPanel widthP = new JPanel(new GridLayout(1,0));
554                      PlainDocument minWidthPD = new PlainDocument();
555                      minWidthPD.addDocumentListener(new DocumentListener() {
556                                             public void insertUpdate(DocumentEvent e) {
557                                                      try {
558                                                                    Integer.parseInt(minWidthTF.getText().trim());
559                                                                    maxWidthTF.setEnabled(minWidthTF.getText().trim().length() > 0);
560                                                      } catch (NumberFormatException exception) {
561                                                                    GloDBUtils.printError("Width lower bound must be an integer.");
562                                                                    maxWidthTF.setEnabled(false);
563                                                      }
564                                             }
565                                             public void removeUpdate(DocumentEvent e) {
566                                                      try {
567                                                                    Integer.parseInt(minWidthTF.getText().trim());
568                                                                    maxWidthTF.setEnabled(minWidthTF.getText().trim().length() > 0);
569                                                      } catch (NumberFormatException exception) {
570                                                                    // don't need to again show error message,
571                                                                    // just disable maxWidthTF
572                                                                    maxWidthTF.setEnabled(false);
573                                                      }
574                                             }
575                                             public void changedUpdate(DocumentEvent e) { ; }
576                                    });
577                      minWidthTF = new JTextField(minWidthPD, "", 0);
578                      widthP.add(new JLabel("Width (bp):"));
579                      widthP.add(minWidthTF);
580                      widthP.add(new JLabel("to", JLabel.CENTER));
581                      PlainDocument maxWidthPD = new PlainDocument();
582                      maxWidthPD.addDocumentListener(new DocumentListener() {
583                                             public void insertUpdate(DocumentEvent e) {
584                                                      try {
585                                                                    Integer.parseInt(maxWidthTF.getText().trim());
586                                                      } catch (NumberFormatException exception) {
587                                                                    GloDBUtils.printError("Width upper bound must be an integer.");
588                                                      }
589                                             }
590                                             public void removeUpdate(DocumentEvent e) { ; }
591                                             public void changedUpdate(DocumentEvent e) { ; }
592                                    });
593                      maxWidthTF = new JTextField(maxWidthPD, "", 0);
594                      maxWidthTF.setEnabled(false);
595                      widthP.add(maxWidthTF);
596                      //              qualifiersP.add(new JLabel("Width (bp):"));
597                      qualifiersP.add(widthP);
598                      qualifiersP.add(new JLabel(""));
599    
600                      JPanel seqPosP = new JPanel(new GridLayout(1,0));
601                      PlainDocument minSeqPosPD = new PlainDocument();
602                      minSeqPosPD.addDocumentListener(new DocumentListener() {
603                                             public void insertUpdate(DocumentEvent e) {
604                                                      try {
605                                                                    Integer.parseInt(minSeqPosTF.getText().trim());
606                                                                    maxSeqPosTF.setEnabled(minSeqPosTF.getText().trim().length() > 0);
607                                                      } catch (NumberFormatException exception) {
608                                                                    GloDBUtils.printError("Location lower bound must be an integer.");
609                                                                    maxSeqPosTF.setEnabled(false);
610                                                      }
611                                             }
612                                             public void removeUpdate(DocumentEvent e) {
613                                                      try {
614                                                                    Integer.parseInt(minSeqPosTF.getText().trim());
615                                                                    maxSeqPosTF.setEnabled(minSeqPosTF.getText().trim().length() > 0);
616                                                      } catch (NumberFormatException exception) {
617                                                                    // don't need to again show error message,
618                                                                    // just disable maxSeqPosTF
619                                                                    maxSeqPosTF.setEnabled(false);
620                                                      }
621                                             }
622                                             public void changedUpdate(DocumentEvent e) { ; }
623                                    });
624                      minSeqPosTF = new JTextField(minSeqPosPD, "", 0);
625                      seqPosP.add(new JLabel("Location (bp):"));
626                      seqPosP.add(minSeqPosTF);
627                      seqPosP.add(new JLabel("and", JLabel.CENTER));
628                      PlainDocument maxSeqPosPD = new PlainDocument();
629                      maxSeqPosPD.addDocumentListener(new DocumentListener() {
630                                             public void insertUpdate(DocumentEvent e) {
631                                                      try {
632                                                                    Integer.parseInt(maxSeqPosTF.getText().trim());
633                                                      } catch (NumberFormatException exception) {
634                                                                    GloDBUtils.printError("Location upper bound must be an integer.");
635                                                      }
636                                             }
637                                             public void removeUpdate(DocumentEvent e) { ; }
638                                             public void changedUpdate(DocumentEvent e) { ; }
639                                    });
640                      maxSeqPosTF = new JTextField(maxSeqPosPD, "", 0);
641                      maxSeqPosTF.setEnabled(false);
642                      seqPosP.add(maxSeqPosTF);
643                      //              qualifiersP.add(new JLabel("Location (bp):"));
644                      qualifiersP.add(seqPosP);
645                      qualifiersP.add(new JLabel(""));
646    
647                      JPanel repeatP = new JPanel(new GridLayout(1,0));
648                      PlainDocument minRepeatPD = new PlainDocument();
649                      minRepeatPD.addDocumentListener(new DocumentListener() {
650                                             public void insertUpdate(DocumentEvent e) {
651                                                      try {
652                                                                    Integer.parseInt(minRepeatTF.getText().trim());
653                                                                    maxRepeatTF.setEnabled(minRepeatTF.getText().trim().length() > 0);
654                                                                    minWithinTF.setEnabled(minRepeatTF.getText().trim().length() > 0);
655                                                                    maxWithinTF.setEnabled(minWithinTF.getText().trim().length() > 0);
656                                                      } catch (NumberFormatException exception) {
657                                                                    GloDBUtils.printError("Repeat lower bound must be an integer.");
658                                                                    maxRepeatTF.setEnabled(false);
659                                                                    minWithinTF.setEnabled(false);
660                                                                    maxWithinTF.setEnabled(false);
661                                                      }
662                                             }
663                                             public void removeUpdate(DocumentEvent e) {
664                                                      try {
665                                                                    Integer.parseInt(minRepeatTF.getText().trim());
666                                                                    maxRepeatTF.setEnabled(minRepeatTF.getText().trim().length() > 0);
667                                                                    minWithinTF.setEnabled(minRepeatTF.getText().trim().length() > 0);
668                                                                    maxWithinTF.setEnabled(minWithinTF.getText().trim().length() > 0);
669                                                      } catch (NumberFormatException exception) {
670                                                                    // don't need to again show error message,
671                                                                    // just disable maxRepeatTF
672                                                                    maxRepeatTF.setEnabled(false);
673                                                                    minWithinTF.setEnabled(false);
674                                                                    maxWithinTF.setEnabled(false);
675                                                      }
676                                             }
677                                             public void changedUpdate(DocumentEvent e) { ; }
678                                    });
679                      minRepeatTF = new JTextField(minRepeatPD, "", 0);
680                      repeatP.add(new JLabel("Repeat:"));
681                      repeatP.add(minRepeatTF);
682                      repeatP.add(new JLabel("to", JLabel.CENTER));
683                      PlainDocument maxRepeatPD = new PlainDocument();
684                      maxRepeatPD.addDocumentListener(new DocumentListener() {
685                                             public void insertUpdate(DocumentEvent e) {
686                                                      try {
687                                                                    Integer.parseInt(maxRepeatTF.getText().trim());
688                                                      } catch (NumberFormatException exception) {
689                                                                    GloDBUtils.printError("Repeat upper bound must be an integer.");
690                                                      }
691                                             }
692                                             public void removeUpdate(DocumentEvent e) { ; }
693                                             public void changedUpdate(DocumentEvent e) { ; }
694                                    });
695                      maxRepeatTF = new JTextField(maxRepeatPD, "", 0);
696                      maxRepeatTF.setEnabled(false);
697                      repeatP.add(maxRepeatTF);
698                      //              qualifiersP.add(new JLabel("Repeat:"));
699                      qualifiersP.add(repeatP);
700    
701                      JPanel withinP = new JPanel(new GridLayout(1,0));
702                      PlainDocument minWithinPD = new PlainDocument();
703                      minWithinPD.addDocumentListener(new DocumentListener() {
704                                             public void insertUpdate(DocumentEvent e) {
705                                                      try {
706                                                                    Integer.parseInt(minWithinTF.getText().trim());
707                                                                    maxWithinTF.setEnabled(minWithinTF.getText().trim().length() > 0);
708                                                      } catch (NumberFormatException exception) {
709                                                                    GloDBUtils.printError("Within lower bound must be an integer.");
710                                                                    maxWithinTF.setEnabled(false);
711                                                      }
712                                             }
713                                             public void removeUpdate(DocumentEvent e) {
714                                                      try {
715                                                                    Integer.parseInt(minWithinTF.getText().trim());
716                                                                    maxWithinTF.setEnabled(minWithinTF.getText().trim().length() > 0);
717                                                      } catch (NumberFormatException exception) {
718                                                                    // don't need to again show error message,
719                                                                    // just disable maxWithinTF
720                                                                    maxWithinTF.setEnabled(false);
721                                                      }
722                                             }
723                                             public void changedUpdate(DocumentEvent e) { ; }
724                                    });
725                      minWithinTF = new JTextField(minWithinPD, "", 0);
726                      minWithinTF.setEnabled(false);
727                      withinP.add(new JLabel("Within:"));
728                      withinP.add(minWithinTF);
729                      withinP.add(new JLabel("to", JLabel.CENTER));
730                      PlainDocument maxWithinPD = new PlainDocument();
731                      maxWithinPD.addDocumentListener(new DocumentListener() {
732                                             public void insertUpdate(DocumentEvent e) {
733                                                      try {
734                                                                    Integer.parseInt(maxWithinTF.getText().trim());
735                                                      } catch (NumberFormatException exception) {
736                                                                    GloDBUtils.printError("Within upper bound must be an integer.");
737                                                      }
738                                             }
739                                             public void removeUpdate(DocumentEvent e) { ; }
740                                             public void changedUpdate(DocumentEvent e) { ; }
741                                    });
742                      maxWithinTF = new JTextField(maxWithinPD, "", 0);
743                      maxWithinTF.setEnabled(false);
744                      withinP.add(maxWithinTF);
745                      //              qualifiersP.add(new JLabel("Within:"));
746                      qualifiersP.add(withinP);
747    
748                      // pad the remaining spaces
749                      //              qualifiersP.add(new JLabel(""));
750                      //              qualifiersP.add(new JLabel(""));
751                      optionsP.add(qualifiersP);
752    
753                      editP.add(optionsP, BorderLayout.CENTER);
754                      editP.add(buttonP, BorderLayout.SOUTH);
755    
756                      // *************************************************
757                      // create panel with overall control buttons
758                      JPanel bottomButtonsP = new JPanel(new GridLayout(1,0,100,5));
759                      //              JToolBar bottomButtonsP = new JToolBar();
760                      //              bottomButtonsP.setFloatable(false);
761                      bottomButtonsP.setBorder(emptyBorder5);
762                      // add compute button
763                      computeB = new JButton("Compute");
764                      //              computeB = new JButton(new ImageIcon("icons/compute.png"));
765                      //              computeB.setToolTipText("Compute");
766                      computeB.setEnabled(false);
767                      computeB.addActionListener(new ActionListener() {
768                                             public void actionPerformed(ActionEvent e) {
769                                                      if (trackID.length() == 0) {
770                                                                    setID(getID());
771                                                                    if (trackID.length() == 0) {
772                                                                             GloDBUtils.printError("Query Builder requires a target track.");
773                                                                             return;
774                                                                    }
775                                                      }
776                                                      String cmd = "compute(\"" + queryTF.getText() + "\")";
777                                                      Root.runCommand(cmd, true);
778                                             }
779                                    });
780                      bottomButtonsP.add(computeB);
781                      // add copy button
782                      copyB = new JButton("Copy");
783                      //              copyB = new JButton(new ImageIcon("icons/copy.png"));
784                      //              copyB.setToolTipText("Copy");
785                      copyB.setEnabled(false);
786                      copyB.addActionListener(new ActionListener() {
787                                             public void actionPerformed(ActionEvent e) {
788                                                      queryTF.selectAll();
789                                                      queryTF.copy();
790                                             }
791                                    });
792                      bottomButtonsP.add(copyB);
793                      // add cancel button
794                      JButton closeB = new JButton("Close");
795                      //              JButton closeB = new JButton(new ImageIcon("icons/close.png"));
796                      //              closeB.setToolTipText("Close");
797                      closeB.addActionListener(new ActionListener() {
798                                             public void actionPerformed(ActionEvent e) {
799                                                      thisFrame.dispose();
800                                             }
801                                    });
802                      bottomButtonsP.add(closeB);
803    
804                      // get the target Track ID, if necessary.  If user doesn't set
805                      // id here, then the "compute" button will ask.
806                      if (GloDBUtils.isEmpty(id)) {
807                                    id = getID();
808                                    // if user exited ID dialog with the cancel button then
809                                    // don't launch QueryBuilder
810                                    if (id == null) {
811                                             thisFrame.dispose();
812                                             return;
813                                    }
814                                    else setID(id);
815                      } else {
816                                    setID(id);
817                      }
818                      
819                      getContentPane().add(editP, BorderLayout.NORTH);
820                      getContentPane().add(queryP, BorderLayout.CENTER);
821                      getContentPane().add(bottomButtonsP, BorderLayout.SOUTH);
822            pack();
823    
824                      // set the default window size
825                      setSize(getSize().width, getSize().height + 70);
826    
827                      // initialize the edit panel
828                      updateEditPanel(null);
829    
830                      // display the window
831                      //        setVisible(true);
832                      show();
833        }
834    
835             private void updateEditPanel(QueryElement qElement) {
836                      if (qElement == null) {  // reset values
837                                    // if empyt list then clear operatorL selection, else
838                                    // default to first item in list
839                                    if (tableModel.getRowCount() == 0) { 
840                                             operatorL.clearSelection();
841                                    } else {
842                                             operatorL.setSelectedIndex(0);
843                                    }
844                                    minPosTF.setText("");
845                                    maxPosTF.setText("");
846                                    negateChB.setSelected(false);
847                                    trackL.setSelectedIndex(0);
848                                    sequenceChB.setSelected(false);
849                                    sequenceCB.setSelectedIndex(0);
850                                    sequenceCB.setEnabled(false);
851                                    minWidthTF.setText("");
852                                    maxWidthTF.setText("");
853                                    minSeqPosTF.setText("");
854                                    maxSeqPosTF.setText("");
855                                    minRepeatTF.setText("");
856                                    maxRepeatTF.setText("");
857                                    minWithinTF.setText("");
858                                    maxWithinTF.setText("");
859                      } else {
860                                    /*
861                                    if (table.getSelectedRow() == 0) {
862                                             // first row selected so unselect operatorL
863                                             operatorL.clearSelection();
864                                             minPosTF.setText("");
865                                             maxPosTF.setText("");
866                                    } else {
867                                    */
868                                             //                                      operatorL.setSelectedValue(qElement.operator, true);
869                                             operatorL.setSelectedIndex(qElement.operator);
870                                             minPosTF.setText(qElement.getMinPos());
871                                             maxPosTF.setText(qElement.getMaxPos());
872                                             /*
873                                    }
874                                             */
875                                    negateChB.setSelected(qElement.negate);
876                                    if (qElement.isGrouped()) {
877                                             trackL.clearSelection();
878                                    } else {
879                                             trackL.setSelectedValue(qElement.track, true);
880                                    }                                        
881    
882                                    if (qElement.sequence.length() > 0) {
883                                             sequenceChB.setSelected(true);
884                                             sequenceCB.setSelectedItem(qElement.sequence);
885                                             sequenceCB.setEnabled(true);
886                                    } else {
887                                             sequenceChB.setSelected(false);
888                                             sequenceCB.setSelectedIndex(0);
889                                             sequenceCB.setEnabled(false);
890                                    }
891                                    minWidthTF.setText(qElement.getMinLength());
892                                    maxWidthTF.setText(qElement.getMaxLength());
893                                    minSeqPosTF.setText(qElement.getMinSeqPos());
894                                    maxSeqPosTF.setText(qElement.getMaxSeqPos());
895                                    minRepeatTF.setText(qElement.getMinRepeat());
896                                    maxRepeatTF.setText(qElement.getMaxRepeat());
897                                    minWithinTF.setText(qElement.getMinWithin());
898                                    maxWithinTF.setText(qElement.getMaxWithin());
899                      }
900             }
901    
902             private void updateQueryElement(QueryElement qElement) {
903                      // don't allow operator if first item
904                      if (tableModel.getRowCount() == 0) { 
905                                    //                              qElement.operator = "";
906                                    qElement.operator = -1;
907                                    qElement.setMinPos("");
908                                    qElement.setMaxPos("");
909                      } else {
910                                    //                              qElement.operator = (String) operatorL.getSelectedValue();
911                                    qElement.operator = operatorL.getSelectedIndex();
912    
913                                    // only add min/maxPos if order operator ('POS'/'.')
914                                    if (QueryElement.isOrderOperator(qElement.operator)) {
915                                             qElement.setMinPos(minPosTF.getText().trim());
916                                             if (minPosTF.getText().trim().length() > 0) {
917                                                      qElement.setMaxPos(maxPosTF.getText().trim());
918                                             } else {
919                                                      qElement.setMaxPos("");
920                                             }
921                                    } else {  // not order operator, so clear min/maxPos
922                                             qElement.setMinPos("");
923                                             qElement.setMaxPos("");
924                                    }
925                      }
926                      qElement.negate = negateChB.isSelected();
927                      if (sequenceChB.isSelected()) {
928                                    qElement.sequence = (String) sequenceCB.getSelectedItem();
929                      } else {
930                                    qElement.sequence = "";
931                      }
932                      qElement.setMinLength(minWidthTF.getText().trim());
933                      if (minWidthTF.getText().trim().length() > 0) {
934                                    qElement.setMaxLength(maxWidthTF.getText());
935                      } else {
936                                    qElement.setMaxLength("");
937                      }
938                      qElement.setMinSeqPos(minSeqPosTF.getText().trim());
939                      if (minSeqPosTF.getText().trim().length() > 0) {
940                                    qElement.setMaxSeqPos(maxSeqPosTF.getText().trim());
941                      } else {
942                                    qElement.setMaxSeqPos("");
943                      }
944                      qElement.setMinRepeat(minRepeatTF.getText().trim());
945                      if (minRepeatTF.getText().trim().length() > 0) {
946                                    qElement.setMaxRepeat(maxRepeatTF.getText().trim());
947                      } else {
948                                    qElement.setMaxRepeat("");
949                      }
950                      qElement.setMinWithin(minWithinTF.getText().trim());
951                      if (minWithinTF.getText().trim().length() > 0) {
952                                    qElement.setMaxWithin(maxWithinTF.getText().trim());
953                      } else {
954                                    qElement.setMaxWithin("");
955                      }
956             }
957    
958             /** 
959              * Updates the query text field based on the current list
960              * items. 
961              */
962             private void updateQuery() {
963                      String text;
964    
965                      if (trackID.length() == 0) {
966                                    text = "<invalid track> = ";
967                      } else {
968                                    text = trackID + " = ";
969                      }
970    
971                      if (tableModel.getRowCount() == 0) { 
972                                    // no items so stop here
973                                    queryTF.setText(text);
974                                    return;
975                      }
976    
977                      // there is at least 1 item, so make sure the first item
978                      // doesn't have an operator
979                      QueryElement qElement = tableModel.getRow(0);
980                      //              qElement.operator = "";
981                      qElement.operator = -1;
982                      text += qElement.toString();
983    
984                      for (int i = 1; i < tableModel.getRowCount(); i++) {
985                                    text += tableModel.getRow(i).toString();
986                      }
987                      queryTF.setText(text);
988             }
989    
990             private void setID(String id) {
991                      if (id == null) id = "";
992                      trackID = id;
993                      updateQuery();
994             }
995    
996             private String getID() {
997                      String[] labels = {" Output Track ID:"};
998                      // use this to get the return value from FieldEditDialog
999                      ArrayList textFields = new ArrayList();
1000                      textFields.add(new JTextField(20));
1001                      new FieldEditDialog("Output Track ID", labels, textFields);
1002                      
1003                      // check "exit code" for FieldEditDialog, if false then exit
1004                      Boolean exitCode = (Boolean) textFields.get(textFields.size()-1);
1005                      if (! exitCode.booleanValue()) return null;
1006    
1007                      JTextField tf = (JTextField) textFields.get(0);
1008                      return tf.getText();
1009             }
1010    
1011             private class ListTableModel extends AbstractTableModel {
1012                      /** Stores the QueryElements that are in the table.   */
1013                      private ArrayList items = new ArrayList();
1014    
1015            private String[] columnNames = {"Operator", "Negate", "Track/Group", "Qualifiers"};
1016    
1017                      //////////////////////////////////////////
1018                      // Methods added to handle items ArrayList
1019    
1020                      /** 
1021                            * Append a new row to the end of the table.
1022                            */
1023                      public void addRow(QueryElement qElement) { addRowAt(qElement, -1); }
1024    
1025    
1026                      /** 
1027                            * Add new row to table.  If row index is '-1' then add to end
1028                            * of table.
1029                            */
1030                      public void addRowAt(QueryElement qElement, int row) { 
1031                                    if (row == -1) items.add(qElement);
1032                                    else items.add(row, qElement);
1033    
1034                                    // there is now at least 1 item
1035                                    deleteAllB.setEnabled(true);
1036                                    computeB.setEnabled(true);
1037                                    copyB.setEnabled(true);
1038                                    operatorL.setEnabled(true);
1039    
1040                                    fireTableDataChanged();
1041                      }
1042    
1043                      /** 
1044                            * Returns the QueryElement that represents the specified row
1045                            * in the table.
1046                            */
1047                      public QueryElement getRow(int row) { 
1048                                    if (items.isEmpty()) return null;
1049                                    else return (QueryElement) items.get(row); 
1050                      }
1051                      
1052                      /** 
1053                            * Removes the QueryElement that represents the specified row
1054                            * in the table.
1055                            */
1056                      public void removeRow(int row) { removeRows(row, row); }
1057    
1058                      /** 
1059                            * Removes the QueryElements that represent the rows
1060                            * specified.
1061                            */
1062                      public void removeRows(int[] rows) {
1063                                    removeRows(rows[0], rows[rows.length-1]);
1064                      }
1065    
1066                      /** 
1067                            * Removes the QueryElements that represent the specified row
1068                            * in the table.  If the row is -1 then will clear the entire
1069                            * table.
1070                            */
1071                      public void removeRows(int minRow, int maxRow) {
1072                                    if (items.isEmpty()) {
1073                                             // XXX this should throw an exception, as this
1074                                             // condition should never occur
1075                                             GloDBUtils.printWarning("No items in table");
1076                                             return;
1077                                    }
1078    
1079                                    if (minRow == maxRow) {
1080                                             if (minRow == -1) items.clear();  // remove all rows
1081                                             else items.remove(minRow); // remove one row
1082                                    } else {
1083                                             // can't use removeRange() because protected method
1084                                             /*
1085                                             if (minRow > maxRow) {  // make sure minRow < maxRow
1086                                                      int tmp = maxRow;
1087                                                      maxRow = minRow;
1088                                                      minRow = tmp;
1089                                             }
1090                                             */
1091                                             while (maxRow >= minRow) {
1092                                                      items.remove(maxRow--);
1093                                             }
1094                                    }
1095    
1096    
1097                                    // test if list is now empty
1098                                    if (items.isEmpty()) {
1099                                             deleteAllB.setEnabled(false);
1100                                             computeB.setEnabled(false);
1101                                             copyB.setEnabled(false);
1102                                             // don't allow operator if first item
1103                                             operatorL.setEnabled(false);
1104                                             operatorL.clearSelection();
1105                                    }
1106    
1107                                    fireTableDataChanged();
1108                      }
1109    
1110                      /** Removes all rows from the table. */
1111                      public void removeAllRows() { removeRow(-1); }
1112    
1113                      
1114                      //////////////////////////////////////////
1115                      // Standard AbstractTableModel methods
1116    
1117            public int getColumnCount() { return columnNames.length; }
1118    
1119            public int getRowCount() { 
1120                                    if (items.isEmpty()) return 0;
1121                                    else return items.size(); 
1122                      }
1123    
1124            public String getColumnName(int col) { return columnNames[col]; }
1125    
1126                      /** Operator, Negate, Track, Qualifiers       */
1127            public Object getValueAt(int row, int col) {
1128                                    QueryElement qElement = getRow(row);
1129                                    switch (col) {
1130                                    case 0: return " " + qElement.getOperatorVal();
1131                                    case 1: return ((qElement.negate) ? "    !" : "");
1132                                    case 2: 
1133                                             if (qElement.isGrouped()) {
1134                                                      String out = " (";
1135                                                      for (Iterator i = qElement.groupIterator(); i.hasNext();) {
1136                                                                    out += ((QueryElement) i.next()).toString();
1137                                                      }
1138                                                      return out + ")";
1139                                             } else {
1140                                                      return " " + qElement.track;
1141                                             }
1142                                    default: return qElement.toStringQualifiers();
1143                                    }
1144            }
1145        }
1146    
1147     } // QueryBuilder.java