/*
 * This file is part of the Poliqarp suite.
 * 
 * Copyright (C) 2004-2009 by Instytut Podstaw Informatyki Polskiej
 * Akademii Nauk (IPI PAN; Institute of Computer Science, Polish
 * Academy of Sciences; cf. www.ipipan.waw.pl).  All rights reserved.
 * 
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 2 as published by the Free Software
 * Foundation and appearing in the file gpl.txt included in the packaging
 * of this file.  (See http://www.gnu.org/licenses/translations.html for
 * unofficial translations.)
 * 
 * A commercial license is available from IPI PAN (contact
 * Michal.Ciesiolka@ipipan.waw.pl or ipi@ipipan.waw.pl for more
 * information).  Licensees holding a valid commercial license from IPI
 * PAN may use this file in accordance with that license.
 * 
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
 * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE.
 */

package ipipan.poliqarp.gui;

import java.awt.*;
import java.awt.event.*;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.NoSuchElementException;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;

import com.jgoodies.forms.layout.*;

import ipipan.poliqarp.util.HotString;
import ipipan.poliqarp.util.PoliqarpUtilities;

/**
 * The configuration dialog.
 */
public final class ConfigDialog extends JDialog
{
   private static final int CMD_OK = 1;
   private static final int CMD_CANCEL = 2;
   private static final int CMD_ADD = 3;
   private static final int CMD_DELETE = 4;

   private Configuration cfg, old;
   private Application app;
   private ActionListener bal;
   private AliasModel aliasModel;
   private Set<MetaDataConstraint> constraintWidgets;
   private int selectedIndex;

   private class ButtonActionListener implements ActionListener
   {
      public void actionPerformed(ActionEvent e) 
      {
         JButton src = (JButton)e.getSource();
         int command = Integer.parseInt(src.getActionCommand());
         switch (command) {
            case CMD_OK: 
               cfg.copyTo(old); 
               updateAliases();
               app.updateConfig();
               app.updateSizes();
               if (constraintWidgets != null) {
                  for (MetaDataConstraint w : constraintWidgets)
                     w.update();
               }
               // fall through
            case CMD_CANCEL:
               dispose();
               break;
            case CMD_ADD:
               aliasModel.add();
               break;
            case CMD_DELETE:
               aliasModel.remove();
               break;
         }
      }
   }

   private void updateAliases()
   {
      if (!app.isConnected() || app.getCorpus() == null)
         return;
      Vector<String[]> newAliases = aliasModel.getNewAliases();
      Vector<String> deletedAliases = aliasModel.getDeletedAliases();
      try {
         for (String[] def : newAliases)
            app.getCorpus().defineAlias(def[0], def[1]);
         for (String name : deletedAliases)
            app.getCorpus().deleteAlias(name);
      } catch (Exception e) {}
   }

   private JButton createButton(int command, String name)
   {
      JButton res = new JButton(cfg.getComponentName("TEXT_" + name));
      res.setActionCommand("" + command);
      res.addActionListener(bal);
      return res;
   }

   /**
    * Constructs a <code>ConfigDialog</code> for the given application and
    * makes the specified frame its parent.
    */
   public ConfigDialog(JFrame parentFrame, Application app)
   {
      super(parentFrame, 
         app.getConfig().getComponentName("TEXT_CONFIG_DIALOG_TITLE"), true);
      this.app = app;
      old = app.getConfig();
      cfg = (Configuration)old.clone();
      bal = new ButtonActionListener();
      JPanel mainPanel = createMainPanel();
      setContentPane(mainPanel);
      Dimension d = new Dimension(600, 400);
      setSize(d);
      d = Toolkit.getDefaultToolkit().getScreenSize();
      setLocation((d.width - getWidth()) / 2, (d.height - getHeight()) / 2);
   }

   /**
    * Shows this configuration dialog.
    */
   public void launch()
   {
      setVisible(true);
   }

   private JPanel createMainPanel()
   {
      JPanel result = new JPanel();
      ColumnSpec
         b = new ColumnSpec(Sizes.DLUX2),
         f = new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, 1.0),
         p = new ColumnSpec(Sizes.DEFAULT);
      RowSpec
         br = new RowSpec(Sizes.DLUX2),
         fr = new RowSpec(RowSpec.FILL, Sizes.DEFAULT, 1.0),
         pr = new RowSpec(Sizes.DEFAULT);
      ColumnSpec[] cols = {f};
      RowSpec[] rows = {fr, br, pr};
      result.setLayout(new FormLayout(cols, rows));
      result.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
      result.add(createTabs(), new CellConstraints(1, 1));
      result.add(createButtonPanel(), new CellConstraints(1, 3));
      return result;
   }

   private JPanel createButtonPanel()
   {
      JPanel result = new JPanel();
      result.add(createButton(CMD_OK, "OK"));
      result.add(createButton(CMD_CANCEL, "CANCEL"));
      return result;
   }

   private void addTab(JTabbedPane pane, String tabName, Component component)
   {
      pane.addTab(cfg.getComponentName("TEXT_CONFIG_" + tabName), component);
   }

   private JPanel createDummyPanel()
   {
      return new JPanel();
   }

   private JPanel center(Component c)
   {
      JPanel result = new JPanel(new GridLayout(1, 1));
      result.add(c, BorderLayout.CENTER);
      return result;
   }

   private JSpinner createSpinner(int min, int max, String optname)
   {
      int optval = Integer.parseInt(cfg.getConfigItem(optname));
      JSpinner res = new JSpinner(new SpinnerNumberModel(optval, min, max, 1));
      final String name = optname;
      res.addChangeListener(new ChangeListener() {
         public void stateChanged(ChangeEvent e) 
         {
            JSpinner s = (JSpinner)(e.getSource());
            cfg.addConfigItem(name, "" +
               ((SpinnerNumberModel)(s.getModel())).getNumber().intValue());
         }
      });
      return res;
   }

   private JTextField createTextField(String optname)
   {
      String val = cfg.getConfigItem(optname);
      JTextField res = new JTextField(val);
      final String name = optname;
      res.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e) 
         {
            JTextField s = (JTextField)(e.getSource());
            cfg.addConfigItem(name, s.getText());
         }
      });
      return res;
   }
  
   private JLabel createLabel(String name, Component c)
   {
      String text = cfg.getComponentName("TEXT_CONFIG_" + name);
      HotString s = PoliqarpUtilities.getHotKey(text);
      JLabel result = new JLabel(s.toString());
      result.setHorizontalAlignment(JLabel.TRAILING);
      result.setLabelFor(c);
      if (s.isHotKey())
         result.setDisplayedMnemonic(s.getHotKey());
      return result;
   }

   private JPanel createContextTab()
   {
      JPanel result = new JPanel();
      ColumnSpec
         b = new ColumnSpec(Sizes.DLUX2),
         f = new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, 1.0),
         p = new ColumnSpec(Sizes.DEFAULT);
      RowSpec
         br = new RowSpec(Sizes.DLUX2),
         fr = new RowSpec(RowSpec.FILL, Sizes.DEFAULT, 1.0),
         pr = new RowSpec(Sizes.DEFAULT);
      ColumnSpec[] cols = {f, p, b, p, f};
      RowSpec[] rows = {fr, pr, br, pr, br, pr, fr};
      result.setLayout(new FormLayout(cols, rows));
      CellConstraints cc = new CellConstraints();
      result.setLayout(new FormLayout(
         "default:grow, default, 2dlu, default, default:grow",
         "default:grow, default, 2dlu, default, 2dlu, default, default:grow"));
      JSpinner spinner;
      spinner = createSpinner(1, 10, cfg.OPT_LC);
      result.add(createLabel("LC", spinner), cc.xy(2, 2));
      result.add(spinner, cc.xy(4, 2));
      spinner = createSpinner(1, 10, cfg.OPT_RC);
      result.add(createLabel("RC", spinner), cc.xy(2, 4));
      result.add(spinner, cc.xy(4, 4));
      spinner = createSpinner(1, 200, cfg.OPT_WC);
      result.add(createLabel("WC", spinner), cc.xy(2, 6));
      result.add(spinner, cc.xy(4, 6));
      return result;
   }

   private JComboBox createComboBox(String name, int num)
   {
      JComboBox result = new JComboBox();
      for (int i = 0; i < num; i++)
         result.addItem(cfg.getComponentName("TEXT_CONFIG_COMBO_" + name +
            "_" + i));
      return result;
   }

   private JRadioButton createRadioButton(String name)
   {
      String text = cfg.getComponentName("TEXT_CONFIG_" + name);
      HotString s = PoliqarpUtilities.getHotKey(text);
      JRadioButton result = new JRadioButton(s.toString());
      if (s.isHotKey())
         result.setMnemonic(s.getHotKey());
      return result;
   }

   private class SortingComboBoxListener implements ItemListener
   {
      int what, alpha, asc;
      JComboBox whatBox, alphaBox, ascBox;

      SortingComboBoxListener(String str, JComboBox whatBox, 
         JComboBox alphaBox, JComboBox ascBox) 
      {
         this.whatBox = whatBox;
         this.alphaBox = alphaBox;
         this.ascBox = ascBox;
         if (str.equals("")) {
            what = alpha = asc = 0;
         } else {
            char ch = str.charAt(0);
            asc = (ch < 'a') ? 0 : 1;
            ch = Character.toLowerCase(ch);
            alpha = (ch < 'e') ? 0 : 1;
            if (alpha == 1)
               ch = (char)((int)ch - 4);
            what = (int)ch - (int)'a';
         }
         whatBox.addItemListener(this);
         alphaBox.addItemListener(this);
         ascBox.addItemListener(this);
         whatBox.setSelectedIndex(what);
         alphaBox.setSelectedIndex(alpha);
         ascBox.setSelectedIndex(asc);
      }
            
      public void itemStateChanged(ItemEvent e) 
      {
         if (e.getStateChange() == e.SELECTED) {
            if (e.getSource() == whatBox) 
               what = whatBox.getSelectedIndex();
            else if (e.getSource() == alphaBox)
               alpha = alphaBox.getSelectedIndex();
            else if (e.getSource() == ascBox)
               asc = ascBox.getSelectedIndex();
            updateConfig();
         }
      }

      void updateConfig()
      {
         int res = what + (int)'A';
         if (alpha == 1)
            res += 4;
         char ch = (char)res;
         if (asc == 1)
            ch = Character.toLowerCase(ch);
         cfg.addConfigItem(cfg.OPT_SORTBY, "" + ch);
      }
   }

   private JPanel createSortingTab()
   {
      JPanel result = new JPanel();
      CellConstraints cc = new CellConstraints();
      ColumnSpec
         b = new ColumnSpec(Sizes.DLUX2),
         f = new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, 1.0),
         p = new ColumnSpec(Sizes.DEFAULT);
      RowSpec
         br = new RowSpec(Sizes.DLUX2),
         fr = new RowSpec(RowSpec.FILL, Sizes.DEFAULT, 1.0),
         pr = new RowSpec(Sizes.DEFAULT);
      ColumnSpec[] cols = {f, p, b, p, b, p, b, p, f};
      RowSpec[] rows = {fr, pr, br, pr, fr};
      result.setLayout(new FormLayout(cols, rows));
      ButtonGroup group = new ButtonGroup();
      final JRadioButton sortButton, noSortButton;
      noSortButton = createRadioButton("NOSORTBTN");
      sortButton = createRadioButton("SORTBTN");
      result.add(noSortButton, cc.xywh(2, 2, 7, 1));
      result.add(sortButton, cc.xy(2, 4));
      final JComboBox whatBox, alphaBox, ascBox;
      result.add(whatBox = createComboBox("WHAT", 4), cc.xy(4, 4));
      result.add(alphaBox = createComboBox("ALPHA", 2), cc.xy(6, 4));
      result.add(ascBox = createComboBox("ASC", 2), cc.xy(8, 4));
      String sortby = cfg.getConfigItem(cfg.OPT_SORTBY);
      final SortingComboBoxListener itemListener = new 
         SortingComboBoxListener(sortby, whatBox, alphaBox, ascBox); 
      ButtonActionListener listener = new ButtonActionListener() {
         public void actionPerformed(ActionEvent e) 
         {
            boolean toggle = (e.getSource() == sortButton);
            whatBox.setEnabled(toggle);
            alphaBox.setEnabled(toggle);
            ascBox.setEnabled(toggle);
            if (toggle)
               itemListener.updateConfig();
            else
               cfg.addConfigItem(cfg.OPT_SORTBY, "");
         }
      };
      noSortButton.addActionListener(listener);
      sortButton.addActionListener(listener);
      if (sortby.equals(""))
         noSortButton.doClick();
      else
         sortButton.doClick();
      group.add(noSortButton);
      group.add(sortButton);
      return result;
   }

   private JCheckBox createCheckBox(String name, String cfgItem, boolean val)
   {
      String text = cfg.getComponentName("TEXT_CONFIG_" + name);
      JCheckBox result = new JCheckBox(text);
      try {
         result.setSelected(cfg.getConfigItem(cfgItem).equals("yes"));
      } catch (NoSuchElementException e) {
         result.setSelected(val);
      }
      final String item = cfgItem;
      result.addItemListener(new ItemListener() {
         public void itemStateChanged(ItemEvent e) 
         {
            cfg.addConfigItem(item, 
               e.getStateChange() == e.SELECTED ? "yes" : "no");
         }
      });
      return result;
   }

   // which == true iff we're creating match format panel
   private JPanel createFormatPanel(String name, boolean which)
   {
      JPanel result = new JPanel();
      result.setBorder(BorderFactory.createCompoundBorder(
         BorderFactory.createTitledBorder(
            cfg.getComponentName("TEXT_CONFIG_" + name)),
         BorderFactory.createEmptyBorder(5, 5, 5, 5)));
      result.setLayout(new BoxLayout(result, BoxLayout.PAGE_AXIS));
      result.add(createCheckBox("FMT_ORTH", 
         which ? cfg.OPT_DISPORTH : cfg.OPT_DISPORTHCONTEXT, true));
      result.add(createCheckBox("FMT_BASE", 
         which ? cfg.OPT_DISPBASE : cfg.OPT_DISPBASECONTEXT, which));
      result.add(createCheckBox("FMT_TAG", 
         which ? cfg.OPT_DISPTAG : cfg.OPT_DISPTAGCONTEXT, which));
      return result;
   }
   
   private JPanel createFormatTab()
   {
      JPanel result = new JPanel();
      ColumnSpec
         b = new ColumnSpec(Sizes.DLUX2),
         f = new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, 1.0),
         p = new ColumnSpec(Sizes.DEFAULT);
      RowSpec
         b3r = new RowSpec(Sizes.DLUX6),
         fr = new RowSpec(RowSpec.FILL, Sizes.DEFAULT, 1.0),
         pr = new RowSpec(Sizes.DEFAULT);
      ColumnSpec[] cols = {f, p, b, p, f};
      RowSpec[] rows = {fr, pr, b3r, pr, fr};
      CellConstraints cc = new CellConstraints();
      result.setLayout(new FormLayout(cols, rows));
      result.add(createFormatPanel("FMT_MATCH", true), cc.xy(2, 2));
      result.add(createFormatPanel("CONTEXT", false), cc.xy(4, 2));
      result.add(createCheckBox("FMT_DISAMB", cfg.OPT_DISPDISAMB, true), 
         cc.xywh(2, 4, 3, 1));
      return result;
   }

   private JPanel createFlagsTab()
   {
      JPanel result = new JPanel();
      ColumnSpec
         b = new ColumnSpec(Sizes.DLUX2),
         f = new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, 1.0),
         p = new ColumnSpec(Sizes.DEFAULT);
      RowSpec
         br = new RowSpec(Sizes.DLUX2),
         fr = new RowSpec(RowSpec.FILL, Sizes.DEFAULT, 1.0),
         pr = new RowSpec(Sizes.DEFAULT);
      ColumnSpec[] cols = {f, p, b, p, f};
      RowSpec[] rows = {fr, pr, fr};
      CellConstraints cc = new CellConstraints();
      result.setLayout(new FormLayout(cols, rows));
      JPanel panel = new JPanel();
      panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
      panel.setBorder(BorderFactory.createCompoundBorder(
         BorderFactory.createTitledBorder(
            cfg.getComponentName("TEXT_CONFIG_FLAGS_QUERY")),
         BorderFactory.createEmptyBorder(5, 5, 5, 5)));
      panel.add(createCheckBox("FLAGS_I", cfg.OPT_QUERYI, true));
      panel.add(createCheckBox("FLAGS_X", cfg.OPT_QUERYX, true));
      result.add(panel, cc.xy(2, 2));
      panel = new JPanel();
      panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
      panel.setBorder(BorderFactory.createCompoundBorder(
         BorderFactory.createTitledBorder(
            cfg.getComponentName("TEXT_TOGGLE0")),
         BorderFactory.createEmptyBorder(5, 5, 5, 5)));
      panel.add(createCheckBox("FLAGS_I", cfg.OPT_METAI, false));
      panel.add(createCheckBox("FLAGS_X", cfg.OPT_METAX, false));
      result.add(panel, cc.xy(4, 2));
      return result;
   }

   private class AliasModel extends AbstractTableModel
   {
      private class Alias 
      {
         public String name;
         public String value;
         public boolean modified;

         public Alias(String name, String value, boolean modified)
         {
            this.name = name;
            this.value = value;
            this.modified = modified;
         }
      }

      private Vector<Alias> data;
      private Map<String, String> mapdata;

      public AliasModel() 
      {
         data = new Vector<Alias>();
         try {
            mapdata = app.getCorpus().getAliases();
            for (Map.Entry<String, String> entry : mapdata.entrySet()) {
               data.add(new Alias(entry.getKey(), entry.getValue(), false));
            }
         } catch (Exception e) {
            mapdata = null;
         }
      }

      public int getColumnCount()
      {
         return 2;
      }

      public int getRowCount()
      {
         return data.size();
      }

      public String getColumnName(int col)
      {
         return cfg.getComponentName("TEXT_ALIAS_COL" + col);
      }

      public Object getValueAt(int row, int col)
      {
         return col == 0 ? data.get(row).name : data.get(row).value;
      }

      public boolean isCellEditable(int row, int col) 
      {
         return true;
      }

      public void remove()
      {
         data.remove(selectedIndex);
         fireTableRowsDeleted(selectedIndex, selectedIndex);
      }

      public void add()
      {
         data.add(new Alias("name", "value", true));
         fireTableRowsInserted(data.size() - 1, data.size() - 1);
      }

      public void setValueAt(Object val, int row, int col)
      {
         String sval = (String)val;
         if (col == 1) {
            data.get(row).value = sval;
            data.get(row).modified = true;
         } else {
            int j = -1;
            for (int i = 0; i < data.size(); i++) 
               if (data.get(i).name.equals(sval)) {
                  j = i;
                  break;
               }
            if (j != -1) {
               if (j != row) {
                  data.get(j).value = data.get(row).value;
                  data.get(j).modified = true;
                  fireTableCellUpdated(j, 1);
                  data.remove(row);
                  fireTableRowsDeleted(row, row);
               }
            } else {
               data.get(row).name = sval;
               data.get(row).modified = true;
            }
         }
      }

      public Vector<String> getDeletedAliases()
      {
         Vector<String> result = new Vector<String>();
         for (Map.Entry<String, String> entry : mapdata.entrySet()) {
            boolean have = false;
            for (Alias alias : data) {
               if (alias.name.equals(entry.getKey())) {
                  have = true;
                  break;
               }
            }
            if (!have)
               result.add(entry.getKey());
         }
         return result;
      }

      public Vector<String[]> getNewAliases()
      {
         Vector<String[]> result = new Vector<String[]>();
         for (Alias alias : data) {
            if (alias.modified) {
               String[] newalias = new String[2];
               newalias[0] = alias.name;
               newalias[1] = alias.value;
               result.add(newalias);
            }
         }
         return result;
      }
   }

   private JTable createAliasTable()
   {
      JTable result = new JTable(aliasModel = new AliasModel());
      result.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
      return result;
   }

   private JPanel createAliasTab()
   {
      JPanel result = new JPanel();
      final JButton delButton;
      CellConstraints cc = new CellConstraints();
      ColumnSpec
         b = new ColumnSpec(Sizes.DLUX2),
         f1 = new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, 1.0),
         f8 = new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, 8.0),
         p = new ColumnSpec(Sizes.DEFAULT);
      RowSpec
         br = new RowSpec(Sizes.DLUX2),
         fr = new RowSpec(RowSpec.FILL, Sizes.DEFAULT, 1.0),
         pr = new RowSpec(Sizes.DEFAULT);
      ColumnSpec[] cols = {f1, f8, f1};
      RowSpec[] rows = {fr, pr, br, pr, fr};
      result.setLayout(new FormLayout(cols, rows));
      JTable table = createAliasTable();
      table.getColumnModel().getColumn(0).setPreferredWidth(100);
      table.getColumnModel().getColumn(1).setPreferredWidth(300);
      int height = Integer.parseInt(cfg.getConfigItem(cfg.OPT_FONTSIZE));
      height = height * 16 / 11;
      table.setRowHeight(height);
      JScrollPane scrollPane = new JScrollPane(table);
      scrollPane.setPreferredSize(new Dimension(400, 100));
      result.add(scrollPane, cc.xy(2, 2));
      JPanel buttonPanel = new JPanel();
      ColumnSpec[] bcols = {f1, p, b, p, f1};
      RowSpec[] brows = {pr};
      buttonPanel.setLayout(new FormLayout(bcols, brows));
      buttonPanel.add(createButton(CMD_ADD, "ADD"), cc.xy(2, 1));
      buttonPanel.add(delButton = createButton(CMD_DELETE, "DELETE"), cc.xy(4, 1));
      delButton.setEnabled(false);
      result.add(buttonPanel, cc.xy(2, 4));
      table.getSelectionModel().addListSelectionListener(
         new ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e)
            {
               ListSelectionModel m = (ListSelectionModel)e.getSource();
               selectedIndex = m.getMinSelectionIndex();
               delButton.setEnabled(selectedIndex != -1);
            }
         });
      return result;
   }

   private JPanel createAdvancedTab()
   {
      JPanel result = new JPanel();
      CellConstraints cc = new CellConstraints();
      ColumnSpec
         b3 = new ColumnSpec(Sizes.DLUX6),
         f = new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, 1.0),
         p = new ColumnSpec(Sizes.DEFAULT);
      RowSpec
         br = new RowSpec(Sizes.DLUX2),
         fr = new RowSpec(RowSpec.FILL, Sizes.DEFAULT, 1.0),
         pr = new RowSpec(Sizes.DEFAULT);
      ColumnSpec[] cols = {f, p, b3, p, f};
      RowSpec[] rows = {fr, pr, br, pr, fr};
      result.setLayout(new FormLayout(cols, rows));
      JTextField serverField = createTextField(cfg.OPT_SERVER);
      result.add(createLabel("CONNECTION_SERVER", serverField), cc.xy(2, 2));
      result.add(serverField, cc.xy(4, 2));
      JSpinner portSpinner = createSpinner(1024, 65535, cfg.OPT_PORT);
      result.add(createLabel("CONNECTION_PORT", portSpinner), cc.xy(2, 4));
      result.add(portSpinner, cc.xy(4, 4));
      return result;
   }
      
   private JPanel createLookAndFeelTab()
   {
      JPanel result = new JPanel();
      CellConstraints cc = new CellConstraints();
      ColumnSpec
         b3 = new ColumnSpec(Sizes.DLUX6),
         f = new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, 1.0),
         p = new ColumnSpec(Sizes.DEFAULT);
      RowSpec
         br = new RowSpec(Sizes.DLUX2),
         fr = new RowSpec(RowSpec.FILL, Sizes.DEFAULT, 1.0),
         pr = new RowSpec(Sizes.DEFAULT);
      ColumnSpec[] cols = {f, p, b3, p, f};
      RowSpec[] rows = {fr, pr, fr};
      result.setLayout(new FormLayout(cols, rows));
      JSpinner fontSpinner = createSpinner(8, 24, cfg.OPT_FONTSIZE);
      result.add(createLabel("LNF_FONTSIZE", fontSpinner), cc.xy(2, 2));
      result.add(fontSpinner, cc.xy(4, 2));
      return result;
   }

   private JScrollPane createMetaTab()
   {
      if (app.getCorpus() == null)
         return new JScrollPane(null); 
      MetaDataConstraintSet constraints = app.getConstraints();
      constraintWidgets = new java.util.HashSet<MetaDataConstraint>();
      JPanel content = new JPanel();
      ColumnSpec
         b = new ColumnSpec(Sizes.DLUX2),
         f = new ColumnSpec(ColumnSpec.FILL, Sizes.DEFAULT, 1.0),
         p = new ColumnSpec(Sizes.DEFAULT);
      RowSpec
         br = new RowSpec(Sizes.DLUX2),
         fr = new RowSpec(RowSpec.FILL, Sizes.DEFAULT, 1.0),
         pr = new RowSpec(Sizes.DEFAULT);
      ColumnSpec[] cols = {f, p, b, p, b, p, b, p, b, p, b, p, f};
      RowSpec[] rows = new RowSpec[constraints.size() * 2 + 1];
      for (int i = 0; i < constraints.size(); i++)
         rows[2 * i + 1] = pr;
      for (int i = 1; i < constraints.size(); i++)
         rows[2 * i] = br;
      rows[0] = rows[2 * constraints.size()] = fr;
      content.setLayout(new FormLayout(cols, rows));
      int i = 1;
      for (MetaDataConstraintModel m : constraints) {
         MetaDataConstraint widget = m.getWidget();
         constraintWidgets.add(widget);
         widget.setWidgets(content, i);
         i += 2;
      }
      JScrollPane scrollPane = new JScrollPane(content);
      JPanel result = new JPanel();
      result.add(scrollPane);
      return scrollPane;
   }

   private JTabbedPane createTabs()
   {
      JTabbedPane result = new JTabbedPane();

      addTab(result, "CONTEXT", createContextTab());
      addTab(result, "SORT", createSortingTab());
      addTab(result, "FMT", createFormatTab());
      addTab(result, "ALIAS", createAliasTab());
      addTab(result, "FLAGS", createFlagsTab());
      addTab(result, "META", createMetaTab());
      addTab(result, "LNF", createLookAndFeelTab());
//      commented out on special request:
//      addTab(result, "ADVANCED", createAdvancedTab());
      if (!app.isConnected()) {
         for (int i = 0; i < 7; i++)
            result.setEnabledAt(i, false);
//         result.setSelectedIndex(7);
      } else if (app.getCorpus() == null)
         result.setEnabledAt(5, false);
      return result;
   }
}
