Beispiel #1
0
  /** Store changes to table preferences. This method is called when the user clicks Ok. */
  @Override
  public void storeSettings() {

    prefs.putBoolean(JabRefPreferences.NAMES_AS_IS, namesAsIs.isSelected());
    prefs.putBoolean(JabRefPreferences.NAMES_FIRST_LAST, namesFf.isSelected());
    prefs.putBoolean(JabRefPreferences.NAMES_NATBIB, namesNatbib.isSelected());
    prefs.putBoolean(JabRefPreferences.NAMES_LAST_ONLY, lastNamesOnly.isSelected());
    prefs.putBoolean(JabRefPreferences.ABBR_AUTHOR_NAMES, abbrNames.isSelected());

    prefs.putInt(
        JabRefPreferences.AUTO_RESIZE_MODE,
        autoResizeMode.isSelected() ? JTable.AUTO_RESIZE_ALL_COLUMNS : JTable.AUTO_RESIZE_OFF);
    prefs.putBoolean(JabRefPreferences.PRIMARY_SORT_DESCENDING, priDesc.isSelected());
    prefs.putBoolean(JabRefPreferences.SECONDARY_SORT_DESCENDING, secDesc.isSelected());
    prefs.putBoolean(JabRefPreferences.TERTIARY_SORT_DESCENDING, terDesc.isSelected());
    prefs.put(JabRefPreferences.PRIMARY_SORT_FIELD, priField.getText().toLowerCase().trim());
    prefs.put(JabRefPreferences.SECONDARY_SORT_FIELD, secField.getText().toLowerCase().trim());
    prefs.put(JabRefPreferences.TERTIARY_SORT_FIELD, terField.getText().toLowerCase().trim());

    prefs.putBoolean(JabRefPreferences.FLOAT_MARKED_ENTRIES, floatMarked.isSelected());
    // updatefont

    String oldVal = prefs.get(JabRefPreferences.NUMERIC_FIELDS);
    String newVal = numericFields.getText().trim();
    if (newVal.isEmpty()) {
      newVal = null;
    }
    if (newVal != null && oldVal == null
        || newVal == null && oldVal != null
        || newVal != null && !newVal.equals(oldVal)) {
      prefs.put(JabRefPreferences.NUMERIC_FIELDS, newVal);
      BibtexFields.setNumericFieldsFromPrefs();
    }
  }
  /**
   * Set column widths according to which field is shown, and lock icon columns to a suitable width.
   */
  protected void setWidths() {
    TableColumnModel cm = entryTable.getColumnModel();
    for (int i = 0; i < PAD; i++) {
      // Lock the width of icon columns.
      cm.getColumn(i).setPreferredWidth(GUIGlobals.WIDTH_ICON_COL);
      cm.getColumn(i).setMinWidth(GUIGlobals.WIDTH_ICON_COL);
      cm.getColumn(i).setMaxWidth(GUIGlobals.WIDTH_ICON_COL);
    }

    for (int i = 0; i < fields.length; i++) {
      int width = BibtexFields.getFieldLength(fields[i]);
      cm.getColumn(i + PAD).setPreferredWidth(width);
    }
  }
Beispiel #3
0
  /**
   * Customization of external program paths.
   *
   * @param prefs a <code>JabRefPreferences</code> value
   */
  public TablePrefsTab(JabRefPreferences prefs) {
    this.prefs = prefs;
    setLayout(new BorderLayout());

    /**
     * Added Bibtexkey to combobox.
     *
     * <p>[ 1540646 ] default sort order: bibtexkey
     *
     * <p>http://sourceforge.net/tracker/index.php?func=detail&aid=1540646&group_id=92314&atid=600306
     */
    Vector<String> fieldNames = new Vector<String>(Arrays.asList(BibtexFields.getAllFieldNames()));
    fieldNames.add(BibtexFields.KEY_FIELD);
    Collections.sort(fieldNames);
    String[] allPlusKey = fieldNames.toArray(new String[fieldNames.size()]);
    priSort = new JComboBox(allPlusKey);
    secSort = new JComboBox(allPlusKey);
    terSort = new JComboBox(allPlusKey);

    autoResizeMode = new JCheckBox(Localization.lang("Fit table horizontally on screen"));

    namesAsIs = new JRadioButton(Localization.lang("Show names unchanged"));
    namesFf = new JRadioButton(Localization.lang("Show 'Firstname Lastname'"));
    namesFl = new JRadioButton(Localization.lang("Show 'Lastname, Firstname'"));
    namesNatbib = new JRadioButton(Localization.lang("Natbib style"));
    noAbbrNames = new JRadioButton(Localization.lang("Do not abbreviate names"));
    abbrNames = new JRadioButton(Localization.lang("Abbreviate names"));
    lastNamesOnly = new JRadioButton(Localization.lang("Show last names only"));

    floatMarked = new JCheckBox(Localization.lang("Float marked entries"));

    priField = new JTextField(10);
    secField = new JTextField(10);
    terField = new JTextField(10);

    numericFields = new JTextField(30);

    priSort.insertItemAt(Localization.lang("<select>"), 0);
    secSort.insertItemAt(Localization.lang("<select>"), 0);
    terSort.insertItemAt(Localization.lang("<select>"), 0);

    priSort.addActionListener(
        new ActionListener() {

          @Override
          public void actionPerformed(ActionEvent e) {
            if (priSort.getSelectedIndex() > 0) {
              priField.setText(priSort.getSelectedItem().toString());
              priSort.setSelectedIndex(0);
            }
          }
        });
    secSort.addActionListener(
        new ActionListener() {

          @Override
          public void actionPerformed(ActionEvent e) {
            if (secSort.getSelectedIndex() > 0) {
              secField.setText(secSort.getSelectedItem().toString());
              secSort.setSelectedIndex(0);
            }
          }
        });
    terSort.addActionListener(
        new ActionListener() {

          @Override
          public void actionPerformed(ActionEvent e) {
            if (terSort.getSelectedIndex() > 0) {
              terField.setText(terSort.getSelectedItem().toString());
              terSort.setSelectedIndex(0);
            }
          }
        });

    ButtonGroup nameStyle = new ButtonGroup();
    nameStyle.add(namesAsIs);
    nameStyle.add(namesNatbib);
    nameStyle.add(namesFf);
    nameStyle.add(namesFl);
    ButtonGroup nameAbbrev = new ButtonGroup();
    nameAbbrev.add(lastNamesOnly);
    nameAbbrev.add(abbrNames);
    nameAbbrev.add(noAbbrNames);
    priDesc = new JCheckBox(Localization.lang("Descending"));
    secDesc = new JCheckBox(Localization.lang("Descending"));
    terDesc = new JCheckBox(Localization.lang("Descending"));

    FormLayout layout =
        new FormLayout(
            "1dlu, 8dlu, left:pref, 4dlu, fill:pref, 4dlu, fill:60dlu, 4dlu, fill:pref", "");
    DefaultFormBuilder builder = new DefaultFormBuilder(layout);
    JLabel lab;
    JPanel pan = new JPanel();

    builder.appendSeparator(Localization.lang("Format of author and editor names"));
    DefaultFormBuilder nameBuilder =
        new DefaultFormBuilder(new FormLayout("left:pref, 8dlu, left:pref", ""));

    nameBuilder.append(namesAsIs);
    nameBuilder.append(noAbbrNames);
    nameBuilder.nextLine();
    nameBuilder.append(namesFf);
    nameBuilder.append(abbrNames);
    nameBuilder.nextLine();
    nameBuilder.append(namesFl);
    nameBuilder.append(lastNamesOnly);
    nameBuilder.nextLine();
    nameBuilder.append(namesNatbib);
    builder.append(pan);
    builder.append(nameBuilder.getPanel());
    builder.nextLine();

    builder.appendSeparator(Localization.lang("Default sort criteria"));
    // Create a new panel with its own FormLayout for these items:
    FormLayout layout2 =
        new FormLayout("left:pref, 8dlu, fill:pref, 4dlu, fill:60dlu, 4dlu, left:pref", "");
    DefaultFormBuilder builder2 = new DefaultFormBuilder(layout2);
    lab = new JLabel(Localization.lang("Primary sort criterion"));
    builder2.append(lab);
    builder2.append(priSort);
    builder2.append(priField);
    builder2.append(priDesc);
    builder2.nextLine();
    lab = new JLabel(Localization.lang("Secondary sort criterion"));
    builder2.append(lab);
    builder2.append(secSort);
    builder2.append(secField);
    builder2.append(secDesc);
    builder2.nextLine();
    lab = new JLabel(Localization.lang("Tertiary sort criterion"));
    builder2.append(lab);
    builder2.append(terSort);
    builder2.append(terField);
    builder2.append(terDesc);
    builder.nextLine();
    builder.append(pan);
    builder.append(builder2.getPanel());
    builder.nextLine();
    builder.append(pan);
    builder.append(floatMarked);
    builder.nextLine();
    builder.append(pan);
    builder2 = new DefaultFormBuilder(new FormLayout("left:pref, 8dlu, fill:pref", ""));
    builder2.append(Localization.lang("Sort the following fields as numeric fields") + ':');
    builder2.append(numericFields);
    builder.append(builder2.getPanel(), 5);
    builder.nextLine();
    builder.appendSeparator(Localization.lang("General"));
    builder.append(pan);
    builder.append(autoResizeMode);
    builder.nextLine();

    pan = builder.getPanel();
    pan.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    add(pan, BorderLayout.CENTER);

    namesNatbib.addChangeListener(
        new ChangeListener() {

          @Override
          public void stateChanged(ChangeEvent changeEvent) {
            abbrNames.setEnabled(!namesNatbib.isSelected());
            lastNamesOnly.setEnabled(!namesNatbib.isSelected());
            noAbbrNames.setEnabled(!namesNatbib.isSelected());
          }
        });
  }
public class EntryCustomizationDialog2 extends JDialog
    implements ListSelectionListener, ActionListener {

  private final JabRefFrame frame;
  protected GridBagLayout gbl = new GridBagLayout();
  protected GridBagConstraints con = new GridBagConstraints();
  private FieldSetComponent reqComp;
  private FieldSetComponent optComp;
  private FieldSetComponent optComp2;
  private EntryTypeList typeComp;
  private JButton ok;
  private JButton cancel;
  private JButton apply;
  protected JButton helpButton;
  protected JButton delete;
  protected JButton importTypes;
  protected JButton exportTypes;
  private final List<String> preset = java.util.Arrays.asList(BibtexFields.getAllFieldNames());
  private String lastSelected = null;
  private final Map<String, List<String>> reqLists = new HashMap<String, List<String>>();
  private final Map<String, List<String>> optLists = new HashMap<String, List<String>>();
  private final Map<String, List<String>> opt2Lists = new HashMap<String, List<String>>();
  private final Set<String> defaulted = new HashSet<String>();
  private final Set<String> changed = new HashSet<String>();

  private boolean biblatexMode;

  /** Creates a new instance of EntryCustomizationDialog2 */
  public EntryCustomizationDialog2(JabRefFrame frame) {
    super(frame, Globals.lang("Customize entry types"), false);

    this.frame = frame;
    initGui();
  }

  private void initGui() {
    Container pane = getContentPane();
    pane.setLayout(new BorderLayout());

    biblatexMode = Globals.prefs.getBoolean("biblatexMode");

    JPanel main = new JPanel(), buttons = new JPanel(), right = new JPanel();
    main.setLayout(new BorderLayout());
    right.setLayout(new GridLayout(biblatexMode ? 2 : 1, 2));

    java.util.List<String> entryTypes = new ArrayList<String>();
    for (String s : BibtexEntryType.ALL_TYPES.keySet()) {
      entryTypes.add(s);
    }

    typeComp = new EntryTypeList(entryTypes);
    typeComp.addListSelectionListener(this);
    typeComp.addAdditionActionListener(this);
    typeComp.addDefaultActionListener(new DefaultListener());
    typeComp.setListSelectionMode(ListSelectionModel.SINGLE_SELECTION);

    // typeComp.setEnabled(false);
    reqComp =
        new FieldSetComponent(
            Globals.lang("Required fields"), new ArrayList<String>(), preset, true, true);
    reqComp.setEnabled(false);
    reqComp.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
    ListDataListener dataListener = new DataListener();
    reqComp.addListDataListener(dataListener);
    optComp =
        new FieldSetComponent(
            Globals.lang("Optional fields"), new ArrayList<String>(), preset, true, true);
    optComp.setEnabled(false);
    optComp.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
    optComp.addListDataListener(dataListener);
    right.add(reqComp);
    right.add(optComp);

    if (biblatexMode) {
      optComp2 =
          new FieldSetComponent(
              Globals.lang("Optional fields") + " 2", new ArrayList<String>(), preset, true, true);
      optComp2.setEnabled(false);
      optComp2.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
      optComp2.addListDataListener(dataListener);
      right.add(new JPanel());
      right.add(optComp2);
    }

    // right.setBorder(BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(),
    // Globals.lang("Fields")));
    right.setBorder(BorderFactory.createEtchedBorder());
    ok = new JButton("Ok");
    cancel = new JButton(Globals.lang("Cancel"));
    apply = new JButton(Globals.lang("Apply"));
    ok.addActionListener(this);
    apply.addActionListener(this);
    cancel.addActionListener(this);
    ButtonBarBuilder bb = new ButtonBarBuilder(buttons);
    buttons.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
    bb.addGlue();
    bb.addButton(ok);
    bb.addButton(apply);
    bb.addButton(cancel);
    bb.addGlue();

    AbstractAction closeAction =
        new AbstractAction() {

          @Override
          public void actionPerformed(ActionEvent e) {
            dispose();
          }
        };
    ActionMap am = main.getActionMap();
    InputMap im = main.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
    im.put(Globals.prefs.getKey("Close dialog"), "close");
    am.put("close", closeAction);

    // con.fill = GridBagConstraints.BOTH;
    // con.weightx = 0.3;
    // con.weighty = 1;
    // gbl.setConstraints(typeComp, con);
    main.add(typeComp, BorderLayout.WEST);
    main.add(right, BorderLayout.CENTER);
    main.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
    pane.add(main, BorderLayout.CENTER);
    pane.add(buttons, BorderLayout.SOUTH);
    pack();
  }

  @Override
  public void valueChanged(ListSelectionEvent e) {
    if (e.getValueIsAdjusting()) {
      return;
    }

    if (lastSelected != null) {
      // The entry type lastSelected is now unselected, so we store the current settings
      // for that type in our two maps.
      reqLists.put(lastSelected, reqComp.getFields());
      optLists.put(lastSelected, optComp.getFields());
      if (biblatexMode) {
        opt2Lists.put(lastSelected, optComp2.getFields());
      }
    }

    String s = typeComp.getFirstSelected();
    if (s == null) {
      return;
    }
    List<String> rl = reqLists.get(s);
    if (rl == null) {
      BibtexEntryType type = BibtexEntryType.getType(s);
      if (type != null) {
        String[] rf = type.getRequiredFieldsForCustomization(), of = type.getOptionalFields();
        List<String> req, opt;
        if (rf != null) {
          req = java.util.Arrays.asList(rf);
        } else {
          req = new ArrayList<String>();
        }

        if (!biblatexMode) {
          if (of != null) {
            opt = java.util.Arrays.asList(of);
          } else {
            opt = new ArrayList<String>();
          }
        } else {
          String[] priOf = type.getPrimaryOptionalFields();
          if (priOf != null) {
            opt = java.util.Arrays.asList(priOf);
          } else {
            opt = new ArrayList<String>();
          }
          List<String> opt2 = new ArrayList<String>();
          if (of != null) {
            for (String anOf : of) {
              if (!opt.contains(anOf)) {
                opt2.add(anOf);
              }
            }
          }
          optComp2.setFields(opt2);
          optComp2.setEnabled(true);
        }

        reqComp.setFields(req);
        reqComp.setEnabled(true);
        optComp.setFields(opt);
        optComp.setEnabled(true);
      } else {
        // New entry, veintle
        reqComp.setFields(new ArrayList<String>());
        reqComp.setEnabled(true);
        optComp.setFields(new ArrayList<String>());
        optComp.setEnabled(true);
        if (biblatexMode) {
          optComp2.setFields(new ArrayList<String>());
          optComp2.setEnabled(true);
        }
        new FocusRequester(reqComp);
      }
    } else {
      reqComp.setFields(rl);
      optComp.setFields(optLists.get(s));
      if (biblatexMode) {
        optComp2.setFields(opt2Lists.get(s));
      }
    }

    lastSelected = s;
    typeComp.enable(s, changed.contains(lastSelected) && !defaulted.contains(lastSelected));
  }

  private void applyChanges() {
    valueChanged(new ListSelectionEvent(new JList(), 0, 0, false));
    // Iterate over our map of required fields, and list those types if necessary:

    List<String> types = typeComp.getFields();
    for (Map.Entry<String, List<String>> stringListEntry : reqLists.entrySet()) {
      if (!types.contains(stringListEntry.getKey())) {
        continue;
      }

      List<String> reqFields = stringListEntry.getValue();
      List<String> optFields = optLists.get(stringListEntry.getKey());
      List<String> opt2Fields = opt2Lists.get(stringListEntry.getKey());
      String[] reqStr = new String[reqFields.size()];
      reqStr = reqFields.toArray(reqStr);
      String[] optStr = new String[optFields.size()];
      optStr = optFields.toArray(optStr);
      String[] opt2Str;
      if (opt2Fields != null) {
        opt2Str = opt2Fields.toArray(new String[opt2Fields.size()]);
      } else {
        opt2Str = new String[0];
      }

      // If this type is already existing, check if any changes have
      // been made
      boolean changesMade = true;

      if (defaulted.contains(stringListEntry.getKey())) {
        // This type should be reverted to its default setup.
        // System.out.println("Defaulting: "+typeName);
        String nm = StringUtil.nCase(stringListEntry.getKey());
        BibtexEntryType.removeType(nm);

        updateTypesForEntries(nm);
        continue;
      }

      BibtexEntryType oldType = BibtexEntryType.getType(stringListEntry.getKey());
      if (oldType != null) {
        String[] oldReq = oldType.getRequiredFields(), oldOpt = oldType.getOptionalFields();
        if (biblatexMode) {
          String[] priOpt = oldType.getPrimaryOptionalFields();
          String[] secOpt = Util.getRemainder(oldOpt, priOpt);
          if (equalArrays(oldReq, reqStr)
              && equalArrays(oldOpt, optStr)
              && equalArrays(secOpt, opt2Str)) {
            changesMade = false;
          }
        } else if (equalArrays(oldReq, reqStr) && equalArrays(oldOpt, optStr)) {
          changesMade = false;
        }
      }

      if (changesMade) {
        // System.out.println("Updating: "+typeName);
        CustomEntryType typ =
            biblatexMode
                ? new CustomEntryType(
                    StringUtil.nCase(stringListEntry.getKey()), reqStr, optStr, opt2Str)
                : new CustomEntryType(StringUtil.nCase(stringListEntry.getKey()), reqStr, optStr);

        BibtexEntryType.ALL_TYPES.put(stringListEntry.getKey().toLowerCase(), typ);
        updateTypesForEntries(typ.getName());
      }
    }

    Set<Object> toRemove = new HashSet<Object>();
    for (String o : BibtexEntryType.ALL_TYPES.keySet()) {
      if (!types.contains(o)) {
        toRemove.add(o);
      }
    }

    // Remove those that should be removed:
    if (!toRemove.isEmpty()) {
      for (Object aToRemove : toRemove) {
        typeDeletion((String) aToRemove);
      }
    }

    updateTables();
  }

  private void typeDeletion(String name) {
    BibtexEntryType type = BibtexEntryType.getType(name);

    if (type instanceof CustomEntryType) {
      if (BibtexEntryType.getStandardType(name) == null) {
        int reply =
            JOptionPane.showConfirmDialog(
                frame,
                Globals.lang(
                    "All entries of this " + "type will be declared " + "typeless. Continue?"),
                Globals.lang("Delete custom format") + " '" + StringUtil.nCase(name) + '\'',
                JOptionPane.YES_NO_OPTION,
                JOptionPane.WARNING_MESSAGE);
        if (reply != JOptionPane.YES_OPTION) {
          return;
        }
      }
      BibtexEntryType.removeType(name);
      updateTypesForEntries(StringUtil.nCase(name));
      changed.remove(name);
      reqLists.remove(name);
      optLists.remove(name);
      if (biblatexMode) {
        opt2Lists.remove(name);
      }
    }
    // messageLabel.setText("'"+type.getName()+"' "+
    //        Globals.lang("is a standard type."));

  }

  private boolean equalArrays(String[] one, String[] two) {
    if ((one == null) && (two == null)) {
      return true; // Both null.
    }
    if ((one == null) || (two == null)) {
      return false; // One of them null, the other not.
    }
    if (one.length != two.length) {
      return false; // Different length.
    }
    // If we get here, we know that both are non-null, and that they have the same length.
    for (int i = 0; i < one.length; i++) {
      if (!one[i].equals(two[i])) {
        return false;
      }
    }
    // If we get here, all entries have matched.
    return true;
  }

  @Override
  public void actionPerformed(ActionEvent e) {
    if (e.getSource() == ok) {
      applyChanges();
      dispose();
    } else if (e.getSource() == cancel) {
      dispose();
    } else if (e.getSource() == apply) {
      applyChanges();
    } else if (e.getSource() == typeComp) {
      // System.out.println("add: "+e.getActionCommand());
      typeComp.selectField(e.getActionCommand());
    }
  }

  /**
   * Cycle through all databases, and make sure everything is updated with the new type
   * customization. This includes making sure all entries have a valid type, that no obsolete entry
   * editors are around, and that the right-click menus' change type menu is up-to-date.
   */
  private void updateTypesForEntries(String typeName) {
    if (frame.getTabbedPane().getTabCount() == 0) {
      return;
    }
    for (int i = 0; i < frame.getTabbedPane().getTabCount(); i++) {
      BasePanel bp = (BasePanel) frame.getTabbedPane().getComponentAt(i);

      // Invalidate associated cached entry editor
      bp.entryEditors.remove(typeName);

      for (BibtexEntry entry : bp.database().getEntries()) {
        entry.updateType();
      }
    }
  }

  private void updateTables() {
    if (frame.getTabbedPane().getTabCount() == 0) {
      return;
    }
    for (int i = 0; i < frame.getTabbedPane().getTabCount(); i++) {
      frame.getTabbedPane().getComponentAt(i);
    }
  }

  // DEFAULT button pressed. Remember that this entry should be reset to default,
  // unless changes are made later.
  private class DefaultListener implements ActionListener {

    @Override
    public void actionPerformed(ActionEvent e) {
      if (lastSelected == null) {
        return;
      }
      defaulted.add(lastSelected);

      BibtexEntryType type = BibtexEntryType.getStandardType(lastSelected);
      if (type != null) {
        String[] rf = type.getRequiredFieldsForCustomization(), of = type.getOptionalFields();
        List<String> req, opt1, opt2;
        if (rf != null) {
          req = java.util.Arrays.asList(rf);
        } else {
          req = new ArrayList<String>();
        }

        opt1 = new ArrayList<String>();
        opt2 = new ArrayList<String>();
        if (biblatexMode) {
          if (of != null) {
            String[] priOptArray = type.getPrimaryOptionalFields();
            String[] secOptArray = Util.getRemainder(of, priOptArray);
            if (priOptArray != null) {
              opt1 = java.util.Arrays.asList(priOptArray);
            }
            if (secOptArray != null) {
              opt2 = java.util.Arrays.asList(secOptArray);
            }
          }
        } else {
          if (of != null) {
            opt1 = java.util.Arrays.asList(of);
          }
        }

        reqComp.setFields(req);
        reqComp.setEnabled(true);
        optComp.setFields(opt1);
        if (biblatexMode) {
          optComp2.setFields(opt2);
        }
      }
    }
  }

  class DataListener implements ListDataListener {

    @Override
    public void intervalAdded(javax.swing.event.ListDataEvent e) {
      record();
    }

    @Override
    public void intervalRemoved(javax.swing.event.ListDataEvent e) {
      record();
    }

    @Override
    public void contentsChanged(javax.swing.event.ListDataEvent e) {
      record();
    }

    private void record() {
      if (lastSelected == null) {
        return;
      }
      defaulted.remove(lastSelected);
      changed.add(lastSelected);
      typeComp.enable(lastSelected, true);
    }
  }
}