package org.jabref.migrations;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import org.jabref.Globals;
import org.jabref.gui.BasePanel;
import org.jabref.gui.DialogService;
import org.jabref.gui.FXDialogService;
import org.jabref.gui.entryeditor.EntryEditorTabList;
import org.jabref.gui.importer.actions.GUIPostOpenAction;
import org.jabref.gui.undo.NamedCompound;
import org.jabref.gui.undo.UndoableFieldChange;
import org.jabref.gui.util.DefaultTaskExecutor;
import org.jabref.gui.util.FileDialogConfiguration;
import org.jabref.logic.cleanup.UpgradePdfPsToFileCleanup;
import org.jabref.logic.importer.ParserResult;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.FieldChange;
import org.jabref.model.database.BibDatabase;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.FieldName;
import org.jabref.model.metadata.FileDirectoryPreferences;
import org.jabref.preferences.JabRefPreferences;
import com.jgoodies.forms.builder.FormBuilder;
import com.jgoodies.forms.layout.FormLayout;
/**
* This class defines the warning that can be offered when opening a pre-2.3
* JabRef file into a later version. This warning mentions the new external file
* link system in this version of JabRef, and offers to:
*
* * upgrade old-style PDF/PS links into the "file" field
* * modify General fields to show "file" instead of "pdf" / "ps"
* * modify table column settings to show "file" instead of "pdf" / "ps"
*/
public class FileLinksUpgradeWarning implements GUIPostOpenAction {
private static final String[] FIELDS_TO_LOOK_FOR = new String[] {FieldName.PDF, FieldName.PS};
private boolean offerChangeSettings;
private boolean offerChangeDatabase;
private boolean offerSetFileDir;
/**
* Collect file links from the given set of fields, and add them to the list contained in the field
* GUIGlobals.FILE_FIELD.
*
* @param database The database to modify.
* @param fields The fields to find links in.
* @return A CompoundEdit specifying the undo operation for the whole operation.
*/
private static NamedCompound upgradePdfPsToFile(BibDatabase database) {
NamedCompound ce = new NamedCompound(Localization.lang("Move external links to 'file' field"));
UpgradePdfPsToFileCleanup cleanupJob = new UpgradePdfPsToFileCleanup();
for (BibEntry entry : database.getEntries()) {
List<FieldChange> changes = cleanupJob.cleanup(entry);
for (FieldChange change : changes) {
ce.addEdit(new UndoableFieldChange(change));
}
}
ce.end();
return ce;
}
/**
* This method should be performed if the major/minor versions recorded in the ParserResult
* are less than or equal to 2.2.
* @param pr
* @return true if the file was written by a jabref version <=2.2
*/
@Override
public boolean isActionNecessary(ParserResult pr) {
// Find out which actions should be offered:
// Only offer to change Preferences if file column is not already visible:
offerChangeSettings = !Globals.prefs.getBoolean(JabRefPreferences.FILE_COLUMN) || !showsFileInGenFields();
// Only offer to upgrade links if the pdf/ps fields are used:
offerChangeDatabase = linksFound(pr.getDatabase(), FileLinksUpgradeWarning.FIELDS_TO_LOOK_FOR);
// If the "file" directory is not set, offer to migrate pdf/ps dir:
offerSetFileDir = !Globals.prefs.hasKey(FieldName.FILE + FileDirectoryPreferences.DIR_SUFFIX)
&& (Globals.prefs.hasKey(FieldName.PDF + FileDirectoryPreferences.DIR_SUFFIX)
|| Globals.prefs.hasKey(FieldName.PS + FileDirectoryPreferences.DIR_SUFFIX));
// First check if this warning is disabled:
return Globals.prefs.getBoolean(JabRefPreferences.SHOW_FILE_LINKS_UPGRADE_WARNING)
&& isThereSomethingToBeDone();
}
/**
* This method presents a dialog box explaining and offering to make the
* changes. If the user confirms, the changes are performed.
* @param panel
* @param parserResult
*/
@Override
public void performAction(BasePanel panel, ParserResult parserResult) {
if (!isThereSomethingToBeDone()) {
return; // Nothing to do, just return.
}
JCheckBox changeSettings = new JCheckBox(
Localization.lang("Change table column and General fields settings to use the new feature"),
offerChangeSettings);
JCheckBox changeDatabase = new JCheckBox(
Localization.lang("Upgrade old external file links to use the new feature"),
offerChangeDatabase);
JCheckBox setFileDir = new JCheckBox(Localization.lang("Set main external file directory") + ":",
offerSetFileDir);
JTextField fileDir = new JTextField(30);
JCheckBox doNotShowDialog = new JCheckBox(Localization.lang("Do not show these options in the future"),
false);
JPanel message = new JPanel();
FormBuilder formBuilder = FormBuilder.create().layout(new FormLayout("left:pref", "p"));
// Keep the formatting of these lines. Otherwise, strings have to be translated again.
// See updated JabRef_en.properties modifications by python syncLang.py -s -u
int row = 1;
formBuilder.add(new JLabel("<html>" + Localization.lang("This library uses outdated file links.") + "<br><br>"
+ Localization
.lang("JabRef no longer supports 'ps' or 'pdf' fields.<br>File links are now stored in the 'file' field and files are stored in an external file directory.<br>To make use of this feature, JabRef needs to upgrade file links.<br><br>")
+ "<p>"
+ Localization.lang("Do you want JabRef to do the following operations?") + "</html>")).xy(1, row);
if (offerChangeSettings) {
formBuilder.appendRows("2dlu, p");
row += 2;
formBuilder.add(changeSettings).xy(1, row);
}
if (offerChangeDatabase) {
formBuilder.appendRows("2dlu, p");
row += 2;
formBuilder.add(changeDatabase).xy(1, row);
}
if (offerSetFileDir) {
if (Globals.prefs.hasKey(FieldName.PDF + FileDirectoryPreferences.DIR_SUFFIX)) {
fileDir.setText(Globals.prefs.get(FieldName.PDF + FileDirectoryPreferences.DIR_SUFFIX));
} else {
fileDir.setText(Globals.prefs.get(FieldName.PS + FileDirectoryPreferences.DIR_SUFFIX));
}
JPanel builderPanel = new JPanel();
builderPanel.add(setFileDir);
builderPanel.add(fileDir);
JButton browse = new JButton(Localization.lang("Browse"));
FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder()
.withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)).build();
DialogService ds = new FXDialogService();
browse.addActionListener(
e -> DefaultTaskExecutor.runInJavaFXThread(() -> ds.showFileOpenDialog(fileDialogConfiguration)
.ifPresent(f -> fileDir.setText(f.toAbsolutePath().toString()))));
builderPanel.add(browse);
formBuilder.appendRows("2dlu, p");
row += 2;
formBuilder.add(builderPanel).xy(1, row);
}
formBuilder.appendRows("6dlu, p");
formBuilder.add(doNotShowDialog).xy(1, row + 2);
message.add(formBuilder.build());
int answer = JOptionPane.showConfirmDialog(panel.frame(),
message, Localization.lang("Upgrade file"), JOptionPane.YES_NO_OPTION);
if (doNotShowDialog.isSelected()) {
Globals.prefs.putBoolean(JabRefPreferences.SHOW_FILE_LINKS_UPGRADE_WARNING, false);
}
if (answer == JOptionPane.YES_OPTION) {
makeChanges(panel, parserResult, changeSettings.isSelected(), changeDatabase.isSelected(),
setFileDir.isSelected() ? fileDir.getText() : null);
}
}
private boolean isThereSomethingToBeDone() {
return offerChangeSettings || offerChangeDatabase || offerSetFileDir;
}
/**
* Check the database to find out whether any of a set of fields are used
* for any of the entries.
* @param database The BIB database.
* @param fields The set of fields to look for.
* @return true if at least one of the given fields is set in at least one entry,
* false otherwise.
*/
private boolean linksFound(BibDatabase database, String[] fields) {
for (BibEntry entry : database.getEntries()) {
for (String field : fields) {
if (entry.hasField(field)) {
return true;
}
}
}
return false;
}
/**
* This method performs the actual changes.
* @param panel
* @param pr
* @param fileDir The path to the file directory to set, or null if it should not be set.
*/
private void makeChanges(BasePanel panel, ParserResult pr, boolean upgradePrefs,
boolean upgradeDatabase, String fileDir) {
if (upgradeDatabase) {
// Update file links links in the database:
NamedCompound ce = upgradePdfPsToFile(pr.getDatabase());
panel.getUndoManager().addEdit(ce);
panel.markBaseChanged();
}
if (fileDir != null) {
Globals.prefs.put(FieldName.FILE + FileDirectoryPreferences.DIR_SUFFIX, fileDir);
}
if (upgradePrefs) {
// Exchange table columns:
Globals.prefs.putBoolean(JabRefPreferences.FILE_COLUMN, Boolean.TRUE);
// Modify General fields if necessary:
// If we don't find the file field, insert it at the bottom of the first tab:
if (!showsFileInGenFields()) {
String gfs = Globals.prefs.get(JabRefPreferences.CUSTOM_TAB_FIELDS + "0");
StringBuilder sb = new StringBuilder(gfs);
if (!gfs.isEmpty()) {
sb.append(';');
}
sb.append(FieldName.FILE);
Globals.prefs.put(JabRefPreferences.CUSTOM_TAB_FIELDS + "0", sb.toString());
Globals.prefs.updateEntryEditorTabList();
}
panel.frame().setupAllTables();
}
}
private boolean showsFileInGenFields() {
boolean found = false;
EntryEditorTabList tabList = Globals.prefs.getEntryEditorTabList();
outer: for (int i = 0; i < tabList.getTabCount(); i++) {
List<String> fields = tabList.getTabFields(i);
for (String field : fields) {
if (field.equals(FieldName.FILE)) {
found = true;
break outer;
}
}
}
return found;
}
}