ccd749fdd6f35288a6a50f11c055c6f011e1520c
[jSite.git] / src / main / java / de / todesbaum / jsite / gui / ProjectFilesPage.java
1 /*
2  * jSite - ProjectFilesPage.java - Copyright © 2006–2014 David Roden
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18
19 package de.todesbaum.jsite.gui;
20
21 import static java.util.Optional.ofNullable;
22
23 import java.awt.BorderLayout;
24 import java.awt.Dimension;
25 import java.awt.GridBagConstraints;
26 import java.awt.GridBagLayout;
27 import java.awt.Insets;
28 import java.awt.event.ActionEvent;
29 import java.awt.event.ActionListener;
30 import java.awt.event.KeyEvent;
31 import java.awt.event.WindowAdapter;
32 import java.awt.event.WindowEvent;
33 import java.text.MessageFormat;
34 import java.util.Collection;
35 import java.util.HashSet;
36 import java.util.Iterator;
37 import java.util.Optional;
38 import java.util.Set;
39 import java.util.function.Consumer;
40 import javax.swing.AbstractAction;
41 import javax.swing.Action;
42 import javax.swing.BorderFactory;
43 import javax.swing.JButton;
44 import javax.swing.JCheckBox;
45 import javax.swing.JComboBox;
46 import javax.swing.JComponent;
47 import javax.swing.JDialog;
48 import javax.swing.JLabel;
49 import javax.swing.JList;
50 import javax.swing.JOptionPane;
51 import javax.swing.JPanel;
52 import javax.swing.JProgressBar;
53 import javax.swing.JScrollPane;
54 import javax.swing.JTextField;
55 import javax.swing.ListSelectionModel;
56 import javax.swing.SwingConstants;
57 import javax.swing.SwingUtilities;
58 import javax.swing.WindowConstants;
59 import javax.swing.event.DocumentEvent;
60 import javax.swing.event.DocumentListener;
61 import javax.swing.event.ListSelectionEvent;
62 import javax.swing.event.ListSelectionListener;
63 import javax.swing.text.BadLocationException;
64 import javax.swing.text.Document;
65 import javax.swing.text.JTextComponent;
66
67 import net.pterodactylus.util.io.MimeTypes;
68 import net.pterodactylus.util.swing.SwingUtils;
69 import net.pterodactylus.util.thread.StoppableDelay;
70
71 import de.todesbaum.jsite.application.FileOption;
72 import de.todesbaum.jsite.application.Project;
73 import de.todesbaum.jsite.i18n.I18n;
74 import de.todesbaum.jsite.i18n.I18nContainer;
75 import de.todesbaum.util.swing.TLabel;
76 import de.todesbaum.util.swing.TWizard;
77 import de.todesbaum.util.swing.TWizardPage;
78
79 /**
80  * Wizard page that lets the user manage the files of a project.
81  *
82  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
83  */
84 public class ProjectFilesPage extends TWizardPage implements ActionListener, ListSelectionListener, DocumentListener, FileScannerListener {
85
86         /** The project. */
87         private Project project;
88
89         /** The “scan files” action. */
90         private Action scanAction;
91
92         /** The “always force insert” checkbox. */
93         private JCheckBox alwaysForceInsertCheckBox;
94
95         /** The “ignore hidden files” checkbox. */
96         private JCheckBox ignoreHiddenFilesCheckBox;
97
98         /** The list of project files. */
99         private JList projectFileList;
100
101         /** The “default file” checkbox. */
102         private JCheckBox defaultFileCheckBox;
103
104         /** The “insert” checkbox. */
105         private JCheckBox fileOptionsInsertCheckBox;
106
107         /** The “force insert” checkbox. */
108         private JCheckBox fileOptionsForceInsertCheckBox;
109
110         /** The “insert redirect” checkbox. */
111         private JCheckBox fileOptionsInsertRedirectCheckBox;
112
113         /** The “custom key” textfield. */
114         private JTextField fileOptionsCustomKeyTextField;
115
116         /** The “rename” check box. */
117         private JCheckBox fileOptionsRenameCheckBox;
118
119         /** The “new name” text field. */
120         private JTextField fileOptionsRenameTextField;
121
122         /** The “mime type” combo box. */
123         private JComboBox fileOptionsMIMETypeComboBox;
124
125         /** Delayed notification for file scanning. */
126         private StoppableDelay delayedNotification;
127
128         /** Dialog to display while scanning. */
129         private JDialog scanningFilesDialog;
130
131         /** The file scanner. */
132         private FileScanner fileScanner;
133
134         /** The progress bar. */
135         private JProgressBar progressBar;
136
137         /**
138          * Creates a new project file page.
139          *
140          * @param wizard
141          *            The wizard the page belongs to
142          */
143         public ProjectFilesPage(final TWizard wizard) {
144                 super(wizard);
145                 pageInit();
146         }
147
148         /**
149          * Initializes the page and all its actions and components.
150          */
151         private void pageInit() {
152                 createActions();
153                 setLayout(new BorderLayout(12, 12));
154                 add(createProjectFilesPanel(), BorderLayout.CENTER);
155         }
156
157         /**
158          * Creates all actions.
159          */
160         private void createActions() {
161                 scanAction = new AbstractAction(I18n.getMessage("jsite.project-files.action.rescan")) {
162
163                         @Override
164                         @SuppressWarnings("synthetic-access")
165                         public void actionPerformed(ActionEvent actionEvent) {
166                                 actionScan();
167                         }
168                 };
169                 scanAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_S);
170                 scanAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.rescan.tooltip"));
171
172                 I18nContainer.getInstance().registerRunnable(new Runnable() {
173
174                         @Override
175                         @SuppressWarnings("synthetic-access")
176                         public void run() {
177                                 scanAction.putValue(Action.NAME, I18n.getMessage("jsite.project-files.action.rescan"));
178                                 scanAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.rescan.tooltip"));
179                         }
180                 });
181         }
182
183         /**
184          * {@inheritDoc}
185          */
186         @Override
187         public void pageAdded(TWizard wizard) {
188                 /* create file scanner. */
189                 fileScanner = new FileScanner(project, this);
190
191                 actionScan();
192                 this.wizard.setPreviousName(I18n.getMessage("jsite.wizard.previous"));
193                 this.wizard.setNextName(I18n.getMessage("jsite.project-files.insert-now"));
194                 this.wizard.setQuitName(I18n.getMessage("jsite.wizard.quit"));
195         }
196
197         /**
198          * Creates the panel contains the project file list and options.
199          *
200          * @return The created panel
201          */
202         private JComponent createProjectFilesPanel() {
203                 JPanel projectFilesPanel = new JPanel(new BorderLayout(12, 12));
204
205                 projectFileList = new JList();
206                 projectFileList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
207                 projectFileList.setMinimumSize(new Dimension(250, projectFileList.getPreferredSize().height));
208                 projectFileList.addListSelectionListener(this);
209
210                 projectFilesPanel.add(new JScrollPane(projectFileList), BorderLayout.CENTER);
211
212                 JPanel fileOptionsAlignmentPanel = new JPanel(new BorderLayout(12, 12));
213                 projectFilesPanel.add(fileOptionsAlignmentPanel, BorderLayout.PAGE_END);
214                 JPanel fileOptionsPanel = new JPanel(new GridBagLayout());
215                 fileOptionsAlignmentPanel.add(fileOptionsPanel, BorderLayout.PAGE_START);
216
217                 alwaysForceInsertCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.always-force-insert"));
218                 alwaysForceInsertCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.always-force-insert.tooltip"));
219                 alwaysForceInsertCheckBox.setName("always-force-insert");
220                 alwaysForceInsertCheckBox.addActionListener(this);
221                 fileOptionsPanel.add(alwaysForceInsertCheckBox, new GridBagConstraints(0, 0, 5, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
222
223                 ignoreHiddenFilesCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.ignore-hidden-files"));
224                 ignoreHiddenFilesCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.ignore-hidden-files.tooltip"));
225                 ignoreHiddenFilesCheckBox.setName("ignore-hidden-files");
226                 ignoreHiddenFilesCheckBox.addActionListener(this);
227                 fileOptionsPanel.add(ignoreHiddenFilesCheckBox, new GridBagConstraints(0, 1, 5, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
228
229                 fileOptionsPanel.add(new JButton(scanAction), new GridBagConstraints(0, 2, 5, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 0, 0, 0), 0, 0));
230
231                 final JLabel fileOptionsLabel = new JLabel("<html><b>" + I18n.getMessage("jsite.project-files.file-options") + "</b></html>");
232                 fileOptionsPanel.add(fileOptionsLabel, new GridBagConstraints(0, 3, 5, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 0, 0, 0), 0, 0));
233
234                 defaultFileCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.default"));
235                 defaultFileCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.default.tooltip"));
236                 defaultFileCheckBox.setName("default-file");
237                 defaultFileCheckBox.addActionListener(this);
238                 defaultFileCheckBox.setEnabled(false);
239
240                 fileOptionsPanel.add(defaultFileCheckBox, new GridBagConstraints(0, 4, 5, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 18, 0, 0), 0, 0));
241
242                 fileOptionsInsertCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.insert"), true);
243                 fileOptionsInsertCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.insert.tooltip"));
244                 fileOptionsInsertCheckBox.setName("insert");
245                 fileOptionsInsertCheckBox.setMnemonic(KeyEvent.VK_I);
246                 fileOptionsInsertCheckBox.addActionListener(this);
247                 fileOptionsInsertCheckBox.setEnabled(false);
248
249                 fileOptionsPanel.add(fileOptionsInsertCheckBox, new GridBagConstraints(0, 5, 5, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0));
250
251                 fileOptionsForceInsertCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.force-insert"));
252                 fileOptionsForceInsertCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.force-insert.tooltip"));
253                 fileOptionsForceInsertCheckBox.setName("force-insert");
254                 fileOptionsForceInsertCheckBox.setMnemonic(KeyEvent.VK_F);
255                 fileOptionsForceInsertCheckBox.addActionListener(this);
256                 fileOptionsForceInsertCheckBox.setEnabled(false);
257
258                 fileOptionsPanel.add(fileOptionsForceInsertCheckBox, new GridBagConstraints(0, 6, 5, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0));
259
260                 fileOptionsCustomKeyTextField = new JTextField(45);
261                 fileOptionsCustomKeyTextField.setToolTipText(I18n.getMessage("jsite.project-files.custom-key.tooltip"));
262                 fileOptionsCustomKeyTextField.setEnabled(false);
263                 fileOptionsCustomKeyTextField.getDocument().addDocumentListener(this);
264
265                 fileOptionsInsertRedirectCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.insert-redirect"), false);
266                 fileOptionsInsertRedirectCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.insert-redirect.tooltip"));
267                 fileOptionsInsertRedirectCheckBox.setName("insert-redirect");
268                 fileOptionsInsertRedirectCheckBox.setMnemonic(KeyEvent.VK_R);
269                 fileOptionsInsertRedirectCheckBox.addActionListener(this);
270                 fileOptionsInsertRedirectCheckBox.setEnabled(false);
271
272                 final TLabel customKeyLabel = new TLabel(I18n.getMessage("jsite.project-files.custom-key") + ":", KeyEvent.VK_K, fileOptionsCustomKeyTextField);
273                 fileOptionsPanel.add(fileOptionsInsertRedirectCheckBox, new GridBagConstraints(0, 7, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0));
274                 fileOptionsPanel.add(customKeyLabel, new GridBagConstraints(1, 7, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 6, 0, 0), 0, 0));
275                 fileOptionsPanel.add(fileOptionsCustomKeyTextField, new GridBagConstraints(2, 7, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
276
277                 fileOptionsRenameCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.rename"), false);
278                 fileOptionsRenameCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.rename.tooltip"));
279                 fileOptionsRenameCheckBox.setName("rename");
280                 fileOptionsRenameCheckBox.setMnemonic(KeyEvent.VK_N);
281                 fileOptionsRenameCheckBox.addActionListener(this);
282                 fileOptionsRenameCheckBox.setEnabled(false);
283
284                 fileOptionsRenameTextField = new JTextField();
285                 fileOptionsRenameTextField.setEnabled(false);
286                 fileOptionsRenameTextField.getDocument().addDocumentListener(new StoreDocument(this::updateChangedName));
287
288                 fileOptionsPanel.add(fileOptionsRenameCheckBox, new GridBagConstraints(0, 8, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0));
289                 fileOptionsPanel.add(fileOptionsRenameTextField, new GridBagConstraints(2, 8, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
290
291                 fileOptionsMIMETypeComboBox = new JComboBox(MimeTypes.getAllMimeTypes().toArray());
292                 fileOptionsMIMETypeComboBox.setToolTipText(I18n.getMessage("jsite.project-files.mime-type.tooltip"));
293                 fileOptionsMIMETypeComboBox.setName("project-files.mime-type");
294                 fileOptionsMIMETypeComboBox.addActionListener(this);
295                 fileOptionsMIMETypeComboBox.setEditable(true);
296                 fileOptionsMIMETypeComboBox.setEnabled(false);
297                 ((JTextComponent) fileOptionsMIMETypeComboBox.getEditor().getEditorComponent())
298                                 .getDocument()
299                                 .addDocumentListener(new StoreDocument(this::updateMimeType));
300
301                 final TLabel mimeTypeLabel = new TLabel(I18n.getMessage("jsite.project-files.mime-type") + ":", KeyEvent.VK_M, fileOptionsMIMETypeComboBox);
302                 fileOptionsPanel.add(mimeTypeLabel, new GridBagConstraints(0, 9, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0));
303                 fileOptionsPanel.add(fileOptionsMIMETypeComboBox, new GridBagConstraints(1, 9, 4, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
304
305                 /* create dialog to show while scanning. */
306                 scanningFilesDialog = new JDialog(wizard);
307                 scanningFilesDialog.setModal(true);
308                 scanningFilesDialog.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
309
310                 JPanel progressPanel = new JPanel(new BorderLayout(12, 12));
311                 scanningFilesDialog.getContentPane().add(progressPanel, BorderLayout.CENTER);
312                 progressPanel.setBorder(BorderFactory.createEmptyBorder(12, 12, 12, 12));
313
314                 final JLabel scanningLabel = new JLabel(I18n.getMessage("jsite.project-files.scanning"), SwingConstants.CENTER);
315                 progressPanel.add(scanningLabel, BorderLayout.NORTH);
316                 progressBar = new JProgressBar(SwingConstants.HORIZONTAL);
317                 progressPanel.add(progressBar, BorderLayout.SOUTH);
318                 progressBar.setIndeterminate(true);
319                 progressBar.setStringPainted(true);
320                 progressBar.setPreferredSize(new Dimension(progressBar.getPreferredSize().width * 2, progressBar.getPreferredSize().height));
321
322                 scanningFilesDialog.pack();
323                 scanningFilesDialog.addWindowListener(new WindowAdapter() {
324
325                         /**
326                          * {@inheritDoc}
327                          */
328                         @Override
329                         @SuppressWarnings("synthetic-access")
330                         public void windowOpened(WindowEvent e) {
331                                 SwingUtils.center(scanningFilesDialog, wizard);
332                         }
333                 });
334
335                 I18nContainer.getInstance().registerRunnable(new Runnable() {
336
337                         @Override
338                         @SuppressWarnings("synthetic-access")
339                         public void run() {
340                                 alwaysForceInsertCheckBox.setText(I18n.getMessage("jsite.project-files.always-force-insert"));
341                                 alwaysForceInsertCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.always-force-insert.tooltip"));
342                                 ignoreHiddenFilesCheckBox.setText(I18n.getMessage("jsite.project-files.ignore-hidden-files"));
343                                 ignoreHiddenFilesCheckBox.setToolTipText(I18n.getMessage("jsite.projet-files.ignore-hidden-files.tooltip"));
344                                 fileOptionsLabel.setText("<html><b>" + I18n.getMessage("jsite.project-files.file-options") + "</b></html>");
345                                 defaultFileCheckBox.setText(I18n.getMessage("jsite.project-files.default"));
346                                 defaultFileCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.default.tooltip"));
347                                 fileOptionsInsertCheckBox.setText(I18n.getMessage("jsite.project-files.insert"));
348                                 fileOptionsInsertCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.insert.tooltip"));
349                                 fileOptionsForceInsertCheckBox.setText(I18n.getMessage("jsite.project-files.force-insert"));
350                                 fileOptionsForceInsertCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.force-insert.tooltip"));
351                                 fileOptionsInsertRedirectCheckBox.setText(I18n.getMessage("jsite.project-files.insert-redirect"));
352                                 fileOptionsInsertRedirectCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.insert-redirect.tooltip"));
353                                 fileOptionsCustomKeyTextField.setToolTipText(I18n.getMessage("jsite.project-files.custom-key.tooltip"));
354                                 customKeyLabel.setText(I18n.getMessage("jsite.project-files.custom-key") + ":");
355                                 fileOptionsRenameCheckBox.setText("jsite.project-files.rename");
356                                 fileOptionsRenameCheckBox.setToolTipText("jsite.project-files.rename.tooltip");
357                                 fileOptionsMIMETypeComboBox.setToolTipText(I18n.getMessage("jsite.project-files.mime-type.tooltip"));
358                                 mimeTypeLabel.setText(I18n.getMessage("jsite.project-files.mime-type") + ":");
359                                 scanningLabel.setText(I18n.getMessage("jsite.project-files.scanning"));
360                         }
361                 });
362
363                 return projectFilesPanel;
364         }
365
366         /**
367          * Sets the project whose files to manage.
368          *
369          * @param project
370          *            The project whose files to manage
371          */
372         public void setProject(final Project project) {
373                 this.project = project;
374                 setHeading(MessageFormat.format(I18n.getMessage("jsite.project-files.heading"), project.getName()));
375                 setDescription(I18n.getMessage("jsite.project-files.description"));
376                 ignoreHiddenFilesCheckBox.setSelected(project.isIgnoreHiddenFiles());
377                 alwaysForceInsertCheckBox.setSelected(project.isAlwaysForceInsert());
378                 I18nContainer.getInstance().registerRunnable(new Runnable() {
379
380                         @Override
381                         public void run() {
382                                 setHeading(MessageFormat.format(I18n.getMessage("jsite.project-files.heading"), project.getName()));
383                                 setDescription(I18n.getMessage("jsite.project-files.description"));
384                         }
385                 });
386         }
387
388         //
389         // ACTIONS
390         //
391
392         /**
393          * Rescans the project’s files.
394          */
395         private void actionScan() {
396                 projectFileList.clearSelection();
397                 projectFileList.setListData(new Object[0]);
398
399                 wizard.setNextEnabled(false);
400                 wizard.setPreviousEnabled(false);
401                 wizard.setQuitEnabled(false);
402
403                 ignoreHiddenFilesCheckBox.setEnabled(false);
404                 scanAction.setEnabled(false);
405
406                 delayedNotification = new StoppableDelay(new Runnable() {
407
408                         @Override
409                         @SuppressWarnings("synthetic-access")
410                         public void run() {
411                                 scanningFilesDialog.setVisible(true);
412                         }
413                 }, new Runnable() {
414
415                         @Override
416                         @SuppressWarnings("synthetic-access")
417                         public void run() {
418                                 scanningFilesDialog.setVisible(false);
419                         }
420                 }, 2000);
421                 fileScanner.startInBackground();
422                 new Thread(delayedNotification).start();
423                 new Thread(new Runnable() {
424
425                         @Override
426                         @SuppressWarnings("synthetic-access")
427                         public void run() {
428                                 while (!delayedNotification.isFinished()) {
429                                         try {
430                                                 Thread.sleep(250);
431                                         } catch (InterruptedException ie1) {
432                                                 /* ignore. */
433                                         }
434                                         progressBar.setString(fileScanner.getLastFilename());
435                                 }
436                         }
437                 }).start();
438         }
439
440         /**
441          * {@inheritDoc}
442          * <p>
443          * Updates the file list.
444          */
445         @Override
446         public void fileScannerFinished(boolean error, Collection<ScannedFile> files) {
447                 delayedNotification.finish();
448                 if (!error) {
449                         SwingUtilities.invokeLater(new Runnable() {
450
451                                 @Override
452                                 @SuppressWarnings("synthetic-access")
453                                 public void run() {
454                                         projectFileList.setListData(files.toArray());
455                                         projectFileList.clearSelection();
456                                 }
457                         });
458                         Set<String> entriesToRemove = new HashSet<String>();
459                         Iterator<String> filenames = new HashSet<String>(project.getFileOptions().keySet()).iterator();
460                         while (filenames.hasNext()) {
461                                 String filename = filenames.next();
462                                 boolean found = false;
463                                 for (ScannedFile scannedFile : files) {
464                                         if (scannedFile.getFilename().equals(filename)) {
465                                                 found = true;
466                                                 break;
467                                         }
468                                 }
469                                 if (!found) {
470                                         entriesToRemove.add(filename);
471                                 }
472                         }
473                         for (String filename : entriesToRemove) {
474                                 project.setFileOption(filename, null);
475                         }
476                 } else {
477                         JOptionPane.showMessageDialog(wizard, I18n.getMessage("jsite.project-files.scan-error"), null, JOptionPane.ERROR_MESSAGE);
478                 }
479                 SwingUtilities.invokeLater(new Runnable() {
480
481                         @Override
482                         @SuppressWarnings("synthetic-access")
483                         public void run() {
484                                 wizard.setPreviousEnabled(true);
485                                 wizard.setNextEnabled(!error);
486                                 wizard.setQuitEnabled(true);
487                                 ignoreHiddenFilesCheckBox.setEnabled(true);
488                                 scanAction.setEnabled(true);
489                         }
490                 });
491         }
492
493         private Optional<FileOption> getSelectedFile() {
494                 return ofNullable(((ScannedFile) projectFileList.getSelectedValue()))
495                                 .map(scannedFile -> project.getFileOption(scannedFile.getFilename()));
496         }
497
498         //
499         // INTERFACE ActionListener
500         //
501
502         /**
503          * {@inheritDoc}
504          */
505         @Override
506         public void actionPerformed(ActionEvent actionEvent) {
507                 Object source = actionEvent.getSource();
508                 if (source instanceof JCheckBox) {
509                         String checkboxName = ((JCheckBox) source).getName();
510                         if ("ignore-hidden-files".equals(checkboxName)) {
511                                 project.setIgnoreHiddenFiles(((JCheckBox) source).isSelected());
512                                 actionScan();
513                                 return;
514                         } else if ("always-force-insert".equals(checkboxName)) {
515                                 project.setAlwaysForceInsert(((JCheckBox) source).isSelected());
516                                 valueChanged(null);
517                                 return;
518                         }
519                 }
520                 ScannedFile scannedFile = (ScannedFile) projectFileList.getSelectedValue();
521                 if (scannedFile == null) {
522                         return;
523                 }
524                 String filename = scannedFile.getFilename();
525                 FileOption fileOption = project.getFileOption(filename);
526                 if (source instanceof JCheckBox) {
527                         JCheckBox checkBox = (JCheckBox) source;
528                         if ("default-file".equals(checkBox.getName())) {
529                                 if (checkBox.isSelected()) {
530                                         if (filename.indexOf('/') > -1) {
531                                                 JOptionPane.showMessageDialog(wizard, I18n.getMessage("jsite.project-files.invalid-default-file"), null, JOptionPane.ERROR_MESSAGE);
532                                                 checkBox.setSelected(false);
533                                         } else {
534                                                 project.setIndexFile(filename);
535                                         }
536                                 } else {
537                                         if (filename.equals(project.getIndexFile())) {
538                                                 project.setIndexFile(null);
539                                         }
540                                 }
541                         } else if ("insert".equals(checkBox.getName())) {
542                                 boolean isInsert = checkBox.isSelected();
543                                 fileOption.setInsert(isInsert);
544                                 fileOptionsInsertRedirectCheckBox.setEnabled(!isInsert);
545                         } else if ("force-insert".equals(checkBox.getName())) {
546                                 boolean isForceInsert = checkBox.isSelected();
547                                 fileOption.setForceInsert(isForceInsert);
548                         } else if ("insert-redirect".equals(checkBox.getName())) {
549                                 boolean isInsertRedirect = checkBox.isSelected();
550                                 fileOption.setInsertRedirect(isInsertRedirect);
551                                 fileOptionsCustomKeyTextField.setEnabled(isInsertRedirect);
552                         } else if ("rename".equals(checkBox.getName())) {
553                                 boolean isRenamed = checkBox.isSelected();
554                                 fileOptionsRenameTextField.setEnabled(isRenamed);
555                                 fileOption.setChangedName(isRenamed ? fileOptionsRenameTextField.getText() : "");
556                         }
557                 } else if (source instanceof JComboBox) {
558                         JComboBox comboBox = (JComboBox) source;
559                         if ("project-files.mime-type".equals(comboBox.getName())) {
560                                 updateMimeType((String) comboBox.getSelectedItem());
561                         }
562                 }
563         }
564
565         private void updateMimeType(String mimeType) {
566                 getSelectedFile().ifPresent(fileOption -> fileOption.setMimeType(mimeType));
567         }
568
569         private void updateChangedName(String changedName) {
570                 getSelectedFile().ifPresent(fileOption -> fileOption.setChangedName(changedName));
571         }
572
573         //
574         // INTERFACE ListSelectionListener
575         //
576
577         /**
578          * {@inheritDoc}
579          */
580         @Override
581         @SuppressWarnings("null")
582         public void valueChanged(ListSelectionEvent e) {
583                 ScannedFile scannedFile = (ScannedFile) projectFileList.getSelectedValue();
584                 boolean enabled = scannedFile != null;
585                 String filename = (scannedFile == null) ? null : scannedFile.getFilename();
586                 defaultFileCheckBox.setEnabled(enabled);
587                 fileOptionsInsertCheckBox.setEnabled(enabled);
588                 fileOptionsRenameCheckBox.setEnabled(enabled);
589                 fileOptionsMIMETypeComboBox.setEnabled(enabled);
590                 if (filename != null) {
591                         FileOption fileOption = project.getFileOption(filename);
592                         defaultFileCheckBox.setSelected(filename.equals(project.getIndexFile()));
593                         fileOptionsInsertCheckBox.setSelected(fileOption.isInsert());
594                         fileOptionsForceInsertCheckBox.setEnabled(!project.isAlwaysForceInsert() && scannedFile.getHash().equals(fileOption.getLastInsertHash()));
595                         fileOptionsForceInsertCheckBox.setSelected(fileOption.isForceInsert());
596                         fileOptionsInsertRedirectCheckBox.setEnabled(!fileOption.isInsert());
597                         fileOptionsInsertRedirectCheckBox.setSelected(fileOption.isInsertRedirect());
598                         fileOptionsCustomKeyTextField.setEnabled(fileOption.isInsertRedirect());
599                         fileOptionsCustomKeyTextField.setText(fileOption.getCustomKey());
600                         fileOptionsRenameCheckBox.setSelected(fileOption.getChangedName().isPresent());
601                         fileOptionsRenameTextField.setEnabled(fileOption.getChangedName().isPresent());
602                         fileOptionsRenameTextField.setText(fileOption.getChangedName().orElse(""));
603                         fileOptionsMIMETypeComboBox.getModel().setSelectedItem(fileOption.getMimeType());
604                 } else {
605                         defaultFileCheckBox.setSelected(false);
606                         fileOptionsInsertCheckBox.setSelected(true);
607                         fileOptionsForceInsertCheckBox.setEnabled(false);
608                         fileOptionsForceInsertCheckBox.setSelected(false);
609                         fileOptionsInsertRedirectCheckBox.setEnabled(false);
610                         fileOptionsInsertRedirectCheckBox.setSelected(false);
611                         fileOptionsCustomKeyTextField.setEnabled(false);
612                         fileOptionsCustomKeyTextField.setText("CHK@");
613                         fileOptionsRenameCheckBox.setEnabled(false);
614                         fileOptionsRenameCheckBox.setSelected(false);
615                         fileOptionsRenameTextField.setEnabled(false);
616                         fileOptionsRenameTextField.setText("");
617                         fileOptionsMIMETypeComboBox.getModel().setSelectedItem(MimeTypes.DEFAULT_CONTENT_TYPE);
618                 }
619         }
620
621         //
622         // INTERFACE DocumentListener
623         //
624
625         /**
626          * Updates the options of the currently selected file with the changes made
627          * in the “custom key” textfield.
628          *
629          * @param documentEvent
630          *            The document event to process
631          */
632         private void processDocumentUpdate(DocumentEvent documentEvent) {
633                 ScannedFile scannedFile = (ScannedFile) projectFileList.getSelectedValue();
634                 if (scannedFile == null) {
635                         return;
636                 }
637                 FileOption fileOption = project.getFileOption(scannedFile.getFilename());
638                 Document document = documentEvent.getDocument();
639                 try {
640                         String text = document.getText(0, document.getLength());
641                         fileOption.setCustomKey(text);
642                 } catch (BadLocationException ble1) {
643                         /* ignore. */
644                 }
645         }
646
647         /**
648          * {@inheritDoc}
649          */
650         @Override
651         public void changedUpdate(DocumentEvent documentEvent) {
652                 processDocumentUpdate(documentEvent);
653         }
654
655         /**
656          * {@inheritDoc}
657          */
658         @Override
659         public void insertUpdate(DocumentEvent documentEvent) {
660                 processDocumentUpdate(documentEvent);
661         }
662
663         /**
664          * {@inheritDoc}
665          */
666         @Override
667         public void removeUpdate(DocumentEvent documentEvent) {
668                 processDocumentUpdate(documentEvent);
669         }
670
671         private static class StoreDocument implements DocumentListener {
672
673                 private final Consumer<String> consumer;
674
675                 public StoreDocument(Consumer<String> consumer) {
676                         this.consumer = consumer;
677                 }
678
679                 private void storeDocument(DocumentEvent e) {
680                         try {
681                                 consumer.accept(e.getDocument().getText(0, e.getDocument().getLength()));
682                         } catch (BadLocationException e1) {
683                                 e1.printStackTrace();
684                         }
685                 }
686
687                 @Override
688                 public void insertUpdate(DocumentEvent e) {
689                         storeDocument(e);
690                 }
691
692                 @Override
693                 public void removeUpdate(DocumentEvent e) {
694                         storeDocument(e);
695                 }
696
697                 @Override
698                 public void changedUpdate(DocumentEvent e) {
699                         storeDocument(e);
700                 }
701
702         }
703
704 }