Пример #1
0
  /** Converts to BibLatex format */
  public static void convertToBiblatex(BibtexEntry entry, NamedCompound ce) {
    for (Map.Entry<String, String> alias : EntryConverter.FIELD_ALIASES_TEX_TO_LTX.entrySet()) {
      String oldFieldName = alias.getKey();
      String newFieldName = alias.getValue();
      String oldValue = entry.getField(oldFieldName);
      String newValue = entry.getField(newFieldName);
      if ((oldValue != null) && (!oldValue.isEmpty()) && (newValue == null)) {
        // There is content in the old field and no value in the new, so just copy
        entry.setField(newFieldName, oldValue);
        ce.addEdit(new UndoableFieldChange(entry, newFieldName, null, oldValue));

        entry.setField(oldFieldName, null);
        ce.addEdit(new UndoableFieldChange(entry, oldFieldName, oldValue, null));
      }
    }

    // Dates: create date out of year and month, save it and delete old fields
    if ((entry.getField("date") == null) || (entry.getField("date").isEmpty())) {
      String newDate = entry.getFieldOrAlias("date");
      String oldYear = entry.getField("year");
      String oldMonth = entry.getField("month");
      entry.setField("date", newDate);
      entry.setField("year", null);
      entry.setField("month", null);

      ce.addEdit(new UndoableFieldChange(entry, "date", null, newDate));
      ce.addEdit(new UndoableFieldChange(entry, "year", oldYear, null));
      ce.addEdit(new UndoableFieldChange(entry, "month", oldMonth, null));
    }
  }
Пример #2
0
  private static int compareSingleField(String field, BibtexEntry one, BibtexEntry two) {
    String s1 = one.getField(field);
    String s2 = two.getField(field);
    if (s1 == null) {
      if (s2 == null) {
        return EMPTY_IN_BOTH;
      }
      return EMPTY_IN_ONE;
    } else if (s2 == null) {
      return EMPTY_IN_TWO;
    }

    if (field.equals("author") || field.equals("editor")) {
      // Specific for name fields.
      // Harmonise case:
      String auth1 =
          AuthorList.fixAuthor_lastNameOnlyCommas(s1, false).replaceAll(" and ", " ").toLowerCase();
      String auth2 =
          AuthorList.fixAuthor_lastNameOnlyCommas(s2, false).replaceAll(" and ", " ").toLowerCase();
      double similarity = DuplicateCheck.correlateByWords(auth1, auth2, false);
      if (similarity > 0.8) {
        return EQUAL;
      }
      return NOT_EQUAL;
    } else if (field.equals("pages")) {
      // Pages can be given with a variety of delimiters, "-", "--", " - ", " -- ".
      // We do a replace to harmonize these to a simple "-":
      // After this, a simple test for equality should be enough:
      s1 = s1.replaceAll("[- ]+", "-");
      s2 = s2.replaceAll("[- ]+", "-");
      if (s1.equals(s2)) {
        return EQUAL;
      }
      return NOT_EQUAL;
    } else if (field.equals("journal")) {
      // We do not attempt to harmonize abbreviation state of the journal names,
      // but we remove periods from the names in case they are abbreviated with
      // and without dots:
      s1 = s1.replaceAll("\\.", "").toLowerCase();
      s2 = s2.replaceAll("\\.", "").toLowerCase();
      double similarity = DuplicateCheck.correlateByWords(s1, s2, true);
      if (similarity > 0.8) {
        return EQUAL;
      }
      return NOT_EQUAL;
    } else {
      s1 = s1.toLowerCase();
      s2 = s2.toLowerCase();
      double similarity = DuplicateCheck.correlateByWords(s1, s2, false);
      if (similarity > 0.8) {
        return EQUAL;
      }
      return NOT_EQUAL;
    }
  }
Пример #3
0
  /**
   * Format page numbers, separated either by commas or double-hyphens. Converts the range number
   * format of the <code>pages</code> field to page_number--page_number.
   *
   * @see{PageNumbersFormatter}
   */
  public void cleanup() {
    final String field = "pages";

    String value = entry.getField(field);
    String newValue = BibtexFieldFormatters.PAGE_NUMBERS.format(value);
    entry.setField(field, newValue);
  }
Пример #4
0
 private static void fixWrongFileEntries(BibtexEntry entry, NamedCompound ce) {
   String oldValue = entry.getField(Globals.FILE_FIELD);
   if (oldValue == null) {
     return;
   }
   FileListTableModel flModel = new FileListTableModel();
   flModel.setContent(oldValue);
   if (flModel.getRowCount() == 0) {
     return;
   }
   boolean changed = false;
   for (int i = 0; i < flModel.getRowCount(); i++) {
     FileListEntry flEntry = flModel.getEntry(i);
     String link = flEntry.getLink();
     String description = flEntry.getDescription();
     if ("".equals(link) && (!"".equals(description))) {
       // link and description seem to be switched, quickly fix that
       flEntry.setLink(flEntry.getDescription());
       flEntry.setDescription("");
       changed = true;
     }
   }
   if (changed) {
     String newValue = flModel.getStringRepresentation();
     assert (!oldValue.equals(newValue));
     entry.setField(Globals.FILE_FIELD, newValue);
     ce.addEdit(new UndoableFieldChange(entry, Globals.FILE_FIELD, oldValue, newValue));
   }
 }
Пример #5
0
 private void doMakePathsRelative(BibtexEntry entry, NamedCompound ce) {
   String oldValue = entry.getField(Globals.FILE_FIELD);
   if (oldValue == null) {
     return;
   }
   FileListTableModel flModel = new FileListTableModel();
   flModel.setContent(oldValue);
   if (flModel.getRowCount() == 0) {
     return;
   }
   boolean changed = false;
   for (int i = 0; i < flModel.getRowCount(); i++) {
     FileListEntry flEntry = flModel.getEntry(i);
     String oldFileName = flEntry.getLink();
     String newFileName =
         FileUtil.shortenFileName(
                 new File(oldFileName), panel.metaData().getFileDirectory(Globals.FILE_FIELD))
             .toString();
     if (!oldFileName.equals(newFileName)) {
       flEntry.setLink(newFileName);
       changed = true;
     }
   }
   if (changed) {
     String newValue = flModel.getStringRepresentation();
     assert (!oldValue.equals(newValue));
     entry.setField(Globals.FILE_FIELD, newValue);
     ce.addEdit(new UndoableFieldChange(entry, Globals.FILE_FIELD, oldValue, newValue));
   }
 }
  @Test
  @Ignore
  public void testAddEntrysFromFiles() throws Exception {
    ParserResult result =
        BibtexParser.parse(new FileReader(ImportDataTest.UNLINKED_FILES_TEST_BIB));
    BibtexDatabase database = result.getDatabase();

    List<File> files = new ArrayList<File>();

    files.add(ImportDataTest.FILE_NOT_IN_DATABASE);
    files.add(ImportDataTest.NOT_EXISTING_PDF);

    EntryFromFileCreatorManager manager = new EntryFromFileCreatorManager();
    List<String> errors = manager.addEntrysFromFiles(files, database, null, true);

    /** One file doesn't exist, so adding it as an entry should lead to an error message. */
    Assert.assertEquals(1, errors.size());

    boolean file1Found = false;
    boolean file2Found = false;
    for (BibtexEntry entry : database.getEntries()) {
      String filesInfo = entry.getField("file");
      if (filesInfo.contains(files.get(0).getName())) {
        file1Found = true;
      }
      if (filesInfo.contains(files.get(1).getName())) {
        file2Found = true;
      }
    }

    Assert.assertTrue(file1Found);
    Assert.assertFalse(file2Found);
  }
Пример #7
0
  /** Runs the field formatter on the entry and records the change. */
  private static void doFieldFormatterCleanup(
      BibtexEntry entry, FieldFormatterCleanup cleaner, NamedCompound ce) {
    String oldValue = entry.getField(cleaner.getField());
    if (oldValue == null) {
      return;
    }

    // run formatter
    cleaner.cleanup(entry);

    String newValue = entry.getField(cleaner.getField());

    // undo action
    if (!oldValue.equals(newValue)) {
      ce.addEdit(new UndoableFieldChange(entry, cleaner.getField(), oldValue, newValue));
    }
  }
Пример #8
0
  public static double compareEntriesStrictly(BibtexEntry one, BibtexEntry two) {
    HashSet<String> allFields = new HashSet<>();
    allFields.addAll(one.getFieldNames());
    allFields.addAll(two.getFieldNames());

    int score = 0;
    for (String field : allFields) {
      Object en = one.getField(field);
      Object to = two.getField(field);
      if (((en != null) && (to != null) && en.equals(to)) || ((en == null) && (to == null))) {
        score++;
      }
    }
    if (score == allFields.size()) {
      return 1.01; // Just to make sure we can
      // use score>1 without
      // trouble.
    }
    return (double) score / allFields.size();
  }
Пример #9
0
 @Override
 public Object getColumnValue(BibtexEntry entry, int column) {
   if (column < PAD) {
     Object o;
     switch (column) {
       case FILE_COL:
         o = entry.getField(Globals.FILE_FIELD);
         if (o != null) {
           FileListTableModel model = new FileListTableModel();
           model.setContent((String) o);
           fileLabel.setToolTipText(model.getToolTipHTMLRepresentation());
           if (model.getRowCount() > 0) {
             fileLabel.setIcon(model.getEntry(0).getType().getIcon());
           }
           return fileLabel;
         } else {
           return null;
         }
       case URL_COL:
         o = entry.getField("url");
         if (o != null) {
           urlLabel.setToolTipText((String) o);
           return urlLabel;
         } else {
           return null;
         }
       default:
         return null;
     }
   } else {
     String field = fields[column - PAD];
     if (field.equals("author") || field.equals("editor")) {
       // For name fields, tap into a MainTableFormat instance and use
       // the same name formatting as is used in the entry table:
       if (frame.basePanel() != null) {
         return frame.basePanel().tableFormat.formatName(entry.getField(field));
       }
     }
     return entry.getField(field);
   }
 }
Пример #10
0
 /**
  * Converts the text in 1st, 2nd, ... to real superscripts by wrapping in \textsuperscript{st},
  * ...
  */
 private static void doCleanUpSuperscripts(BibtexEntry entry, NamedCompound ce) {
   for (String name : entry.getFieldNames()) {
     String oldValue = entry.getField(name);
     // run formatter
     String newValue = BibtexFieldFormatters.SUPERSCRIPTS.format(oldValue);
     // undo action
     if (!oldValue.equals(newValue)) {
       entry.setField(name, newValue);
       ce.addEdit(new UndoableFieldChange(entry, name, oldValue, newValue));
     }
   }
 }
Пример #11
0
  /** Update the merged BibtexEntry with source and preview panel everytime something is changed */
  private void updateAll() {
    if (!doneBuilding) {
      // If we've not done adding everything, do not do anything...
      return;
    }
    // Check if the type is changed
    if (!identical[0]) {
      if (rb[0][0].isSelected()) {
        mergedEntry.setType(one.getType());
      } else {
        mergedEntry.setType(two.getType());
      }
    }

    // Check all fields
    for (int i = 0; i < joint.size(); i++) {
      if (!identical[i + 1]) {
        if (rb[0][i + 1].isSelected()) {
          mergedEntry.setField(jointStrings[i], one.getField(jointStrings[i]));
        } else if (rb[2][i + 1].isSelected()) {
          mergedEntry.setField(jointStrings[i], two.getField(jointStrings[i]));
        } else {
          mergedEntry.setField(jointStrings[i], null);
        }
      }
    }

    // Update the PreviewPanel
    pp.setEntry(mergedEntry);

    // Update the Bibtex source view
    StringWriter sw = new StringWriter();
    try {
      new BibtexEntryWriter(new LatexFieldFormatter(), false).write(mergedEntry, sw);
    } catch (IOException ex) {
      LOGGER.error("Error in entry" + ": " + ex.getMessage(), ex);
    }
    jta.setText(sw.getBuffer().toString());
    jta.setCaretPosition(0);
  }
Пример #12
0
 @Override
 public void mouseClicked(MouseEvent e) {
   if (e.isPopupTrigger()) {
     processPopupTrigger(e);
     return;
   }
   // if (e.)
   final int col = entryTable.columnAtPoint(e.getPoint());
   final int row = entryTable.rowAtPoint(e.getPoint());
   if (col < PAD) {
     BibtexEntry entry = sortedEntries.get(row);
     BasePanel p = entryHome.get(entry);
     switch (col) {
       case FILE_COL:
         Object o = entry.getField(Globals.FILE_FIELD);
         if (o != null) {
           FileListTableModel tableModel = new FileListTableModel();
           tableModel.setContent((String) o);
           if (tableModel.getRowCount() == 0) {
             return;
           }
           FileListEntry fl = tableModel.getEntry(0);
           (new ExternalFileMenuItem(
                   frame, entry, "", fl.getLink(), null, p.metaData(), fl.getType()))
               .actionPerformed(null);
         }
         break;
       case URL_COL:
         Object link = entry.getField("url");
         try {
           if (link != null) {
             JabRefDesktop.openExternalViewer(p.metaData(), (String) link, "url");
           }
         } catch (IOException ex) {
           ex.printStackTrace();
         }
         break;
     }
   }
 }
Пример #13
0
  /**
   * Removes the http://... for each DOI Moves DOIs from URL and NOTE filed to DOI field
   *
   * @param ce
   */
  private static void doCleanUpDOI(BibtexEntry bes, NamedCompound ce) {
    // fields to check
    String[] fields = {"note", "url", "ee"};

    // First check if the Doi Field is empty
    if (bes.getField("doi") != null) {
      String doiFieldValue = bes.getField("doi");

      Optional<DOI> doi = DOI.build(doiFieldValue);

      if (doi.isPresent()) {
        String newValue = doi.get().getDOI();
        if (!doiFieldValue.equals(newValue)) {
          ce.addEdit(new UndoableFieldChange(bes, "doi", doiFieldValue, newValue));
          bes.setField("doi", newValue);
        }

        // Doi field seems to contain Doi
        // -> cleanup note, url, ee field
        for (String field : fields) {
          DOI.build(bes.getField((field))).ifPresent(unused -> removeFieldValue(bes, field, ce));
        }
      }
    } else {
      // As the Doi field is empty we now check if note, url, or ee field contains a Doi
      for (String field : fields) {
        Optional<DOI> doi = DOI.build(bes.getField(field));

        if (doi.isPresent()) {
          // update Doi
          String oldValue = bes.getField("doi");
          String newValue = doi.get().getDOI();
          ce.addEdit(new UndoableFieldChange(bes, "doi", oldValue, newValue));
          bes.setField("doi", newValue);

          removeFieldValue(bes, field, ce);
        }
      }
    }
  }
Пример #14
0
 /** Converts HTML code to LaTeX code */
 private static void doConvertHTML(BibtexEntry entry, NamedCompound ce) {
   final String field = "title";
   String oldValue = entry.getField(field);
   if (oldValue == null) {
     return;
   }
   final HTMLConverter htmlConverter = new HTMLConverter();
   String newValue = htmlConverter.format(oldValue);
   if (!oldValue.equals(newValue)) {
     entry.setField(field, newValue);
     ce.addEdit(new UndoableFieldChange(entry, field, oldValue, newValue));
   }
 }
Пример #15
0
 /** Converts Unicode characters to LaTeX code */
 private static void doConvertUnicode(BibtexEntry entry, NamedCompound ce) {
   final String[] fields = {"title", "author", "abstract"};
   for (String field : fields) {
     String oldValue = entry.getField(field);
     if (oldValue == null) {
       return;
     }
     final HTMLConverter htmlConverter = new HTMLConverter();
     String newValue = htmlConverter.formatUnicode(oldValue);
     if (!oldValue.equals(newValue)) {
       entry.setField(field, newValue);
       ce.addEdit(new UndoableFieldChange(entry, field, oldValue, newValue));
     }
   }
 }
Пример #16
0
  private static void doCleanUpMonth(BibtexEntry entry, NamedCompound ce) {
    // implementation based on patch 3470076 by Mathias Walter
    String oldValue = entry.getField("month");
    if (oldValue == null) {
      return;
    }
    String newValue = oldValue;
    MonthUtil.Month month = MonthUtil.getMonth(oldValue);
    if (month.isValid()) {
      newValue = month.bibtexFormat;
    }

    if (!oldValue.equals(newValue)) {
      entry.setField("month", newValue);
      ce.addEdit(new UndoableFieldChange(entry, "month", oldValue, newValue));
    }
  }
Пример #17
0
  /**
   * Tries to find a fulltext URL for a given BibTex entry.
   *
   * <p>Currently only uses the DOI if found.
   *
   * @param entry The Bibtex entry
   * @return The fulltext PDF URL Optional, if found, or an empty Optional if not found.
   * @throws NullPointerException if no BibTex entry is given
   * @throws java.io.IOException
   */
  public Optional<URL> findFullText(BibtexEntry entry) throws IOException {
    Objects.requireNonNull(entry);
    Optional<URL> pdfLink = Optional.empty();

    // DOI search
    Optional<DOI> doi = DOI.build(entry.getField("doi"));

    if (doi.isPresent()) {
      String source = String.format(SOURCE, doi.get().getDOI());
      // Retrieve PDF link
      Document html = Jsoup.connect(source).ignoreHttpErrors(true).get();
      Element link = html.select(".pdf-high-res a").first();

      if (link != null) {
        LOGGER.info("Fulltext PDF found @ ACS.");
        pdfLink = Optional.of(new URL(source.replaceFirst("/abs/", "/pdf/")));
      }
    }
    return pdfLink;
  }
Пример #18
0
 /**
  * Generates the DML required to populate the entries table with jabref data and writes it to the
  * output PrintStream.
  *
  * @param database_id ID of Jabref database related to the entries to be exported This information
  *     can be gathered using getDatabaseIDByPath(metaData, out)
  * @param entries The BibtexEntries to export
  * @param out The output (PrintStream or Connection) object to which the DML should be written.
  */
 private void populateEntriesTable(int database_id, List<BibtexEntry> entries, Object out)
     throws SQLException {
   String query;
   String val;
   String insert =
       "INSERT INTO entries (jabref_eid, entry_types_id, cite_key, "
           + fieldStr
           + ", database_id) VALUES (";
   for (BibtexEntry entry : entries) {
     query =
         insert
             + '\''
             + entry.getId()
             + '\''
             + ", (SELECT entry_types_id FROM entry_types WHERE label='"
             + entry.getType().getName().toLowerCase()
             + "'), '"
             + entry.getCiteKey()
             + '\'';
     for (int i = 0; i < SQLUtil.getAllFields().size(); i++) {
       query = query + ", ";
       val = entry.getField(SQLUtil.getAllFields().get(i));
       if (val != null) {
         val = val.replace("\\", "\\\\");
         val = val.replace("\"", "\\\"");
         val = val.replace("\'", "''");
         val = val.replace("`", "\\`");
         query = query + '\'' + val + '\'';
       } else {
         query = query + "NULL";
       }
     }
     query = query + ", '" + database_id + "');";
     SQLUtil.processQuery(out, query);
   }
 }
Пример #19
0
    /**
     * If the user has signalled the opening of a context menu, the event gets redirected to this
     * method. Here we open a file link menu if the user is pointing at a file link icon. Otherwise
     * a general context menu should be shown.
     *
     * @param e The triggering mouse event.
     */
    public void processPopupTrigger(MouseEvent e) {
      BibtexEntry entry = sortedEntries.get(entryTable.rowAtPoint(e.getPoint()));
      BasePanel p = entryHome.get(entry);
      int col = entryTable.columnAtPoint(e.getPoint());
      JPopupMenu menu = new JPopupMenu();
      int count = 0;

      if (col == FILE_COL) {
        // We use a FileListTableModel to parse the field content:
        Object o = entry.getField(Globals.FILE_FIELD);
        FileListTableModel fileList = new FileListTableModel();
        fileList.setContent((String) o);
        // If there are one or more links, open the first one:
        for (int i = 0; i < fileList.getRowCount(); i++) {
          FileListEntry flEntry = fileList.getEntry(i);
          String description = flEntry.getDescription();
          if ((description == null) || (description.trim().isEmpty())) {
            description = flEntry.getLink();
          }
          menu.add(
              new ExternalFileMenuItem(
                  p.frame(),
                  entry,
                  description,
                  flEntry.getLink(),
                  flEntry.getType().getIcon(),
                  p.metaData(),
                  flEntry.getType()));
          count++;
        }
      }

      if (count > 0) {
        menu.show(entryTable, e.getX(), e.getY());
      }
    }
Пример #20
0
 private static void removeFieldValue(
     BibtexEntry entry, String fieldName, NamedCompound compound) {
   String origValue = entry.getField(fieldName);
   compound.addEdit(new UndoableFieldChange(entry, fieldName, origValue, null));
   entry.setField(fieldName, null);
 }
Пример #21
0
  private void doRenamePDFs(BibtexEntry entry, NamedCompound ce) {
    // Extract the path
    String oldValue = entry.getField(Globals.FILE_FIELD);
    if (oldValue == null) {
      return;
    }
    FileListTableModel flModel = new FileListTableModel();
    flModel.setContent(oldValue);
    if (flModel.getRowCount() == 0) {
      return;
    }
    boolean changed = false;

    for (int i = 0; i < flModel.getRowCount(); i++) {
      String realOldFilename = flModel.getEntry(i).getLink();

      if (cleanUpRenamePDFonlyRelativePaths.isSelected()
          && (new File(realOldFilename).isAbsolute())) {
        continue;
      }

      String newFilename = Util.getLinkedFileName(panel.database(), entry);
      // String oldFilename = bes.getField(GUIGlobals.FILE_FIELD); // would have to be stored for
      // undoing purposes

      // Add extension to newFilename
      newFilename = newFilename + "." + flModel.getEntry(i).getType().getExtension();

      // get new Filename with path
      // Create new Path based on old Path and new filename
      File expandedOldFile =
          FileUtil.expandFilename(
              realOldFilename, panel.metaData().getFileDirectory(Globals.FILE_FIELD));
      if (expandedOldFile.getParent() == null) {
        // something went wrong. Just skip this entry
        continue;
      }
      String newPath =
          expandedOldFile
              .getParent()
              .concat(System.getProperty("file.separator"))
              .concat(newFilename);

      if (new File(newPath).exists()) {
        // we do not overwrite files
        // TODO: we could check here if the newPath file is linked with the current entry. And if
        // not, we could add a link
        continue;
      }

      // do rename
      boolean renameSuccessful = FileUtil.renameFile(expandedOldFile.toString(), newPath);

      if (renameSuccessful) {
        changed = true;

        // Change the path for this entry
        String description = flModel.getEntry(i).getDescription();
        ExternalFileType type = flModel.getEntry(i).getType();
        flModel.removeEntry(i);

        // we cannot use "newPath" to generate a FileListEntry as newPath is absolute, but we want
        // to keep relative paths whenever possible
        File parent = (new File(realOldFilename)).getParentFile();
        String newFileEntryFileName;
        if (parent == null) {
          newFileEntryFileName = newFilename;
        } else {
          newFileEntryFileName =
              parent.toString().concat(System.getProperty("file.separator")).concat(newFilename);
        }
        flModel.addEntry(i, new FileListEntry(description, newFileEntryFileName, type));
      } else {
        unsuccessfulRenames++;
      }
    }

    if (changed) {
      String newValue = flModel.getStringRepresentation();
      assert (!oldValue.equals(newValue));
      entry.setField(Globals.FILE_FIELD, newValue);
      // we put an undo of the field content here
      // the file is not being renamed back, which leads to inconsistencies
      // if we put a null undo object here, the change by "doMakePathsRelative" would overwrite the
      // field value nevertheless.
      ce.addEdit(new UndoableFieldChange(entry, Globals.FILE_FIELD, oldValue, newValue));
    }
  }
Пример #22
0
  /** Main function for building the merge entry JPanel */
  private void initialize() {

    joint = new TreeSet<>(one.getFieldNames());
    joint.addAll(two.getFieldNames());

    // Remove field starting with __
    TreeSet<String> toberemoved = new TreeSet<>();
    for (String field : joint) {
      if (field.startsWith("__")) {
        toberemoved.add(field);
      }
    }

    for (String field : toberemoved) {
      joint.remove(field);
    }

    // Create storage arrays
    rb = new JRadioButton[3][joint.size() + 1];
    ButtonGroup[] rbg = new ButtonGroup[joint.size() + 1];
    identical = new Boolean[joint.size() + 1];
    jointStrings = new String[joint.size()];

    // Create main layout
    String colSpecMain =
        "left:pref, 5px, center:3cm:grow, 5px, center:pref, 3px, center:pref, 3px, center:pref, 5px, center:3cm:grow";
    String colSpecMerge =
        "left:pref, 5px, fill:3cm:grow, 5px, center:pref, 3px, center:pref, 3px, center:pref, 5px, fill:3cm:grow";
    String rowSpec = "pref, pref, 10px, fill:5cm:grow, 10px, pref, 10px, fill:3cm:grow";
    StringBuilder rowBuilder = new StringBuilder("");
    for (int i = 0; i < joint.size(); i++) {
      rowBuilder.append("pref, ");
    }
    rowBuilder.append("pref");

    FormLayout mainLayout = new FormLayout(colSpecMain, rowSpec);
    FormLayout mergeLayout = new FormLayout(colSpecMerge, rowBuilder.toString());
    mainPanel.setLayout(mainLayout);
    mergePanel.setLayout(mergeLayout);

    JLabel label = new JLabel(Localization.lang("Use"));
    Font font = label.getFont();
    label.setFont(font.deriveFont(font.getStyle() | Font.BOLD));

    mainPanel.add(label, cc.xyw(4, 1, 7, "center, bottom"));

    // Set headings
    JLabel headingLabels[] = new JLabel[6];
    for (int i = 0; i < 6; i++) {
      headingLabels[i] = new JLabel(columnHeadings[i]);
      font = headingLabels[i].getFont();
      headingLabels[i].setFont(font.deriveFont(font.getStyle() | Font.BOLD));
      mainPanel.add(headingLabels[i], cc.xy(1 + (i * 2), 2));
    }

    mainPanel.add(new JSeparator(), cc.xyw(1, 3, 11));

    // Start with entry type
    EntryType type1 = one.getType();
    EntryType type2 = two.getType();

    mergedEntry.setType(type1);
    label = new JLabel(Localization.lang("Entry type"));
    font = label.getFont();
    label.setFont(font.deriveFont(font.getStyle() | Font.BOLD));
    mergePanel.add(label, cc.xy(1, 1));

    JTextArea type1ta = new JTextArea(type1.getName());
    type1ta.setEditable(false);
    mergePanel.add(type1ta, cc.xy(3, 1));
    if (type1.compareTo(type2) != 0) {
      identical[0] = false;
      rbg[0] = new ButtonGroup();
      for (int k = 0; k < 3; k += 2) {
        rb[k][0] = new JRadioButton();
        rbg[0].add(rb[k][0]);
        mergePanel.add(rb[k][0], cc.xy(5 + (k * 2), 1));
        rb[k][0].addChangeListener(
            new ChangeListener() {

              @Override
              public void stateChanged(ChangeEvent e) {
                updateAll();
              }
            });
      }
      rb[0][0].setSelected(true);
    } else {
      identical[0] = true;
    }
    JTextArea type2ta = new JTextArea(type2.getName());
    type2ta.setEditable(false);
    mergePanel.add(type2ta, cc.xy(11, 1));

    // For all fields in joint add a row and possibly radio buttons
    int row = 2;
    int maxLabelWidth = -1;
    int tmpLabelWidth = 0;
    for (String field : joint) {
      jointStrings[row - 2] = field;
      label = new JLabel(CaseChangers.UPPER_FIRST.format(field));
      font = label.getFont();
      label.setFont(font.deriveFont(font.getStyle() | Font.BOLD));
      mergePanel.add(label, cc.xy(1, row));
      String string1 = one.getField(field);
      String string2 = two.getField(field);
      identical[row - 1] = false;
      if ((string1 != null) && (string2 != null)) {
        if (string1.equals(string2)) {
          identical[row - 1] = true;
        }
      }

      tmpLabelWidth = label.getPreferredSize().width;
      if (tmpLabelWidth > maxLabelWidth) {
        maxLabelWidth = tmpLabelWidth;
      }

      if ("abstract".equals(field) || "review".equals(field)) {
        // Treat the abstract and review fields special
        JTextArea tf = new JTextArea();
        tf.setLineWrap(true);
        tf.setEditable(false);
        JScrollPane jsptf = new JScrollPane(tf);

        mergeLayout.setRowSpec(row, RowSpec.decode("center:2cm:grow"));
        mergePanel.add(jsptf, cc.xy(3, row, "f, f"));
        tf.setText(string1);
        tf.setCaretPosition(0);

      } else {
        JTextArea tf = new JTextArea(string1);
        mergePanel.add(tf, cc.xy(3, row));
        tf.setCaretPosition(0);
        tf.setEditable(false);
      }

      // Add radio buttons if the two entries do not have identical fields
      if (!identical[row - 1]) {
        rbg[row - 1] = new ButtonGroup();
        for (int k = 0; k < 3; k++) {
          rb[k][row - 1] = new JRadioButton();
          rbg[row - 1].add(rb[k][row - 1]);
          mergePanel.add(rb[k][row - 1], cc.xy(5 + (k * 2), row));
          rb[k][row - 1].addChangeListener(
              new ChangeListener() {

                @Override
                public void stateChanged(ChangeEvent e) {
                  updateAll();
                }
              });
        }
        if (string1 != null) {
          mergedEntry.setField(field, string1);
          rb[0][row - 1].setSelected(true);
          if (string2 == null) {
            rb[2][row - 1].setEnabled(false);
          }
        } else {
          rb[0][row - 1].setEnabled(false);
          mergedEntry.setField(field, string2);
          rb[2][row - 1].setSelected(true);
        }
      } else {
        mergedEntry.setField(field, string1);
      }
      if ("abstract".equals(field) || "review".equals(field)) {
        // Again, treat abstract and review special
        JTextArea tf = new JTextArea();
        tf.setLineWrap(true);
        tf.setEditable(false);
        JScrollPane jsptf = new JScrollPane(tf);

        mergePanel.add(jsptf, cc.xy(11, row, "f, f"));
        tf.setText(string2);
        tf.setCaretPosition(0);

      } else {
        JTextArea tf = new JTextArea(string2);
        mergePanel.add(tf, cc.xy(11, row));
        tf.setCaretPosition(0);
        tf.setEditable(false);
      }

      row++;
    }

    JScrollPane scrollPane =
        new JScrollPane(
            mergePanel,
            JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
            JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
    scrollPane.setBorder(BorderFactory.createEmptyBorder());
    mainPanel.add(scrollPane, cc.xyw(1, 4, 11));
    mainPanel.add(new JSeparator(), cc.xyw(1, 5, 11));

    // Synchronize column widths
    String rbAlign[] = {"right", "center", "left"};
    mainLayout.setColumnSpec(1, ColumnSpec.decode(Integer.toString(maxLabelWidth) + "px"));
    Integer maxRBWidth = -1;
    Integer tmpRBWidth;
    for (int k = 0; k < 3; k++) {
      tmpRBWidth = headingLabels[k + 2].getPreferredSize().width;
      if (tmpRBWidth > maxRBWidth) {
        maxRBWidth = tmpRBWidth;
      }
    }
    for (int k = 0; k < 3; k++) {
      mergeLayout.setColumnSpec(
          5 + (k * 2), ColumnSpec.decode(rbAlign[k] + ":" + maxRBWidth + "px"));
    }

    // Setup a PreviewPanel and a Bibtex source box for the merged entry
    label = new JLabel(Localization.lang("Merged entry"));
    font = label.getFont();
    label.setFont(font.deriveFont(font.getStyle() | Font.BOLD));
    mainPanel.add(label, cc.xyw(1, 6, 6));

    String layoutString = Globals.prefs.get(JabRefPreferences.PREVIEW_0);
    pp = new PreviewPanel(null, mergedEntry, null, new MetaData(), layoutString);
    // JScrollPane jsppp = new JScrollPane(pp, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
    // JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
    mainPanel.add(pp, cc.xyw(1, 8, 6));

    label = new JLabel(Localization.lang("Merged BibTeX source code"));
    font = label.getFont();
    label.setFont(font.deriveFont(font.getStyle() | Font.BOLD));
    mainPanel.add(label, cc.xyw(8, 6, 4));

    jta = new JTextArea();
    jta.setLineWrap(true);
    JScrollPane jspta = new JScrollPane(jta);
    mainPanel.add(jspta, cc.xyw(8, 8, 4));
    jta.setEditable(false);
    StringWriter sw = new StringWriter();
    try {
      new BibtexEntryWriter(new LatexFieldFormatter(), false).write(mergedEntry, sw);
    } catch (IOException ex) {
      LOGGER.error("Error in entry" + ": " + ex.getMessage(), ex);
    }
    jta.setText(sw.getBuffer().toString());
    jta.setCaretPosition(0);

    // Add some margin around the layout
    mainLayout.appendRow(RowSpec.decode("10px"));
    mainLayout.appendColumn(ColumnSpec.decode("10px"));
    mainLayout.insertRow(1, RowSpec.decode("10px"));
    mainLayout.insertColumn(1, ColumnSpec.decode("10px"));

    if (mainPanel.getHeight() > DIM.height) {
      mainPanel.setSize(new Dimension(mergePanel.getWidth(), DIM.height));
    }
    if (mainPanel.getWidth() > DIM.width) {
      mainPanel.setSize(new Dimension(DIM.width, mergePanel.getHeight()));
    }

    // Everything done, allow any action to actually update the merged entry
    doneBuilding = true;

    // Show what we've got
    mainPanel.setVisible(true);
    javax.swing.SwingUtilities.invokeLater(
        new Runnable() {
          @Override
          public void run() {
            scrollPane.getVerticalScrollBar().setValue(0);
          }
        });
  }