Unify copyright file headers, update years to include 2011.
[jSite.git] / src / de / todesbaum / jsite / gui / ProjectFilesPage.java
1 /*
2  * jSite - ProjectFilesPage.java - Copyright © 2006–2011 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 java.awt.BorderLayout;
22 import java.awt.Dimension;
23 import java.awt.FlowLayout;
24 import java.awt.GridBagConstraints;
25 import java.awt.GridBagLayout;
26 import java.awt.Insets;
27 import java.awt.event.ActionEvent;
28 import java.awt.event.ActionListener;
29 import java.awt.event.KeyEvent;
30 import java.text.MessageFormat;
31 import java.util.ArrayList;
32 import java.util.Collections;
33 import java.util.HashSet;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Set;
37
38 import javax.swing.AbstractAction;
39 import javax.swing.Action;
40 import javax.swing.DefaultComboBoxModel;
41 import javax.swing.JButton;
42 import javax.swing.JCheckBox;
43 import javax.swing.JComboBox;
44 import javax.swing.JComponent;
45 import javax.swing.JLabel;
46 import javax.swing.JList;
47 import javax.swing.JOptionPane;
48 import javax.swing.JPanel;
49 import javax.swing.JScrollPane;
50 import javax.swing.JSpinner;
51 import javax.swing.JTextField;
52 import javax.swing.ListSelectionModel;
53 import javax.swing.SpinnerNumberModel;
54 import javax.swing.SwingUtilities;
55 import javax.swing.border.EmptyBorder;
56 import javax.swing.event.ChangeEvent;
57 import javax.swing.event.ChangeListener;
58 import javax.swing.event.DocumentEvent;
59 import javax.swing.event.DocumentListener;
60 import javax.swing.event.ListSelectionEvent;
61 import javax.swing.event.ListSelectionListener;
62 import javax.swing.text.BadLocationException;
63 import javax.swing.text.Document;
64
65 import de.todesbaum.jsite.application.FileOption;
66 import de.todesbaum.jsite.application.Project;
67 import de.todesbaum.jsite.i18n.I18n;
68 import de.todesbaum.jsite.i18n.I18nContainer;
69 import de.todesbaum.util.mime.DefaultMIMETypes;
70 import de.todesbaum.util.swing.TLabel;
71 import de.todesbaum.util.swing.TWizard;
72 import de.todesbaum.util.swing.TWizardPage;
73
74 /**
75  * Wizard page that lets the user manage the files of a project.
76  *
77  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
78  */
79 public class ProjectFilesPage extends TWizardPage implements ActionListener, ListSelectionListener, DocumentListener, FileScannerListener, ChangeListener {
80
81         /** The project. */
82         private Project project;
83
84         /** The “scan files” action. */
85         private Action scanAction;
86
87         /** The “edit container” action. */
88         private Action editContainerAction;
89
90         /** The “add container” action. */
91         private Action addContainerAction;
92
93         /** The “delete container” action. */
94         protected Action deleteContainerAction;
95
96         /** The “ignore hidden files” checkbox. */
97         private JCheckBox ignoreHiddenFilesCheckBox;
98
99         /** The list of project files. */
100         private JList projectFileList;
101
102         /** The “default file” checkbox. */
103         private JCheckBox defaultFileCheckBox;
104
105         /** The “insert” checkbox. */
106         private JCheckBox fileOptionsInsertCheckBox;
107
108         /** The “insert redirect” checkbox. */
109         private JCheckBox fileOptionsInsertRedirectCheckBox;
110
111         /** The “custom key” textfield. */
112         private JTextField fileOptionsCustomKeyTextField;
113
114         /** The “rename” check box. */
115         private JCheckBox fileOptionsRenameCheckBox;
116
117         /** The “new name” text field. */
118         private JTextField fileOptionsRenameTextField;
119
120         /** The “mime type” combo box. */
121         private JComboBox fileOptionsMIMETypeComboBox;
122
123         /** The “mime type” combo box model. */
124         private DefaultComboBoxModel containerComboBoxModel;
125
126         /** The “container” combo box. */
127         private JComboBox fileOptionsContainerComboBox;
128
129         /** The “edition replacement range” spinner. */
130         private JSpinner replaceEditionRangeSpinner;
131
132         /** The “replacement” check box. */
133         private JCheckBox replacementCheckBox;
134
135         /**
136          * Creates a new project file page.
137          *
138          * @param wizard
139          *            The wizard the page belongs to
140          */
141         public ProjectFilesPage(final TWizard wizard) {
142                 super(wizard);
143                 pageInit();
144         }
145
146         /**
147          * Initializes the page and all its actions and components.
148          */
149         private void pageInit() {
150                 createActions();
151                 setLayout(new BorderLayout(12, 12));
152                 add(createProjectFilesPanel(), BorderLayout.CENTER);
153         }
154
155         /**
156          * Creates all actions.
157          */
158         private void createActions() {
159                 scanAction = new AbstractAction(I18n.getMessage("jsite.project-files.action.rescan")) {
160
161                         @SuppressWarnings("synthetic-access")
162                         public void actionPerformed(ActionEvent actionEvent) {
163                                 actionScan();
164                         }
165                 };
166                 scanAction.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_S);
167                 scanAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.rescan.tooltip"));
168
169                 addContainerAction = new AbstractAction(I18n.getMessage("jsite.project-files.action.add-container")) {
170
171                         @SuppressWarnings("synthetic-access")
172                         public void actionPerformed(ActionEvent actionEvent) {
173                                 actionAddContainer();
174                         }
175                 };
176                 addContainerAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.add-container.tooltip"));
177                 addContainerAction.setEnabled(false);
178
179                 editContainerAction = new AbstractAction(I18n.getMessage("jsite.project-files.action.edit-container")) {
180
181                         @SuppressWarnings("synthetic-access")
182                         public void actionPerformed(ActionEvent actionEvent) {
183                                 actionEditContainer();
184                         }
185                 };
186                 editContainerAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.edit-container.tooltip"));
187                 editContainerAction.setEnabled(false);
188
189                 deleteContainerAction = new AbstractAction(I18n.getMessage("jsite.project-files.action.delete-container")) {
190
191                         @SuppressWarnings("synthetic-access")
192                         public void actionPerformed(ActionEvent actionEvent) {
193                                 actionDeleteContainer();
194                         }
195                 };
196                 deleteContainerAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.delete-container.tooltip"));
197                 deleteContainerAction.setEnabled(false);
198
199                 I18nContainer.getInstance().registerRunnable(new Runnable() {
200
201                         @SuppressWarnings("synthetic-access")
202                         public void run() {
203                                 scanAction.putValue(Action.NAME, I18n.getMessage("jsite.project-files.action.rescan"));
204                                 scanAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.rescan.tooltip"));
205                                 addContainerAction.putValue(Action.NAME, I18n.getMessage("jsite.project-files.action.add-container"));
206                                 addContainerAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.add-container.tooltip"));
207                                 editContainerAction.putValue(Action.NAME, I18n.getMessage("jsite.project-files.action.edit-container"));
208                                 editContainerAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.edit-container.tooltip"));
209                                 deleteContainerAction.putValue(Action.NAME, I18n.getMessage("jsite.project-files.action.delete-container"));
210                                 deleteContainerAction.putValue(Action.SHORT_DESCRIPTION, I18n.getMessage("jsite.project-files.action.delete-container.tooltip"));
211                         }
212                 });
213         }
214
215         /**
216          * {@inheritDoc}
217          */
218         @Override
219         public void pageAdded(TWizard wizard) {
220                 actionScan();
221                 this.wizard.setPreviousName(I18n.getMessage("jsite.wizard.previous"));
222                 this.wizard.setNextName(I18n.getMessage("jsite.project-files.insert-now"));
223                 this.wizard.setQuitName(I18n.getMessage("jsite.wizard.quit"));
224         }
225
226         /**
227          * Creates the panel contains the project file list and options.
228          *
229          * @return The created panel
230          */
231         private JComponent createProjectFilesPanel() {
232                 JPanel projectFilesPanel = new JPanel(new BorderLayout(12, 12));
233
234                 projectFileList = new JList();
235                 projectFileList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
236                 projectFileList.setMinimumSize(new Dimension(250, projectFileList.getPreferredSize().height));
237                 projectFileList.addListSelectionListener(this);
238
239                 projectFilesPanel.add(new JScrollPane(projectFileList), BorderLayout.CENTER);
240
241                 JPanel fileOptionsAlignmentPanel = new JPanel(new BorderLayout(12, 12));
242                 projectFilesPanel.add(fileOptionsAlignmentPanel, BorderLayout.PAGE_END);
243                 JPanel fileOptionsPanel = new JPanel(new GridBagLayout());
244                 fileOptionsAlignmentPanel.add(fileOptionsPanel, BorderLayout.PAGE_START);
245
246                 ignoreHiddenFilesCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.ignore-hidden-files"));
247                 ignoreHiddenFilesCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.ignore-hidden-files.tooltip"));
248                 ignoreHiddenFilesCheckBox.setName("ignore-hidden-files");
249                 ignoreHiddenFilesCheckBox.addActionListener(this);
250                 fileOptionsPanel.add(ignoreHiddenFilesCheckBox, new GridBagConstraints(0, 0, 5, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
251
252                 fileOptionsPanel.add(new JButton(scanAction), new GridBagConstraints(0, 1, 5, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 0, 0, 0), 0, 0));
253
254                 final JLabel fileOptionsLabel = new JLabel("<html><b>" + I18n.getMessage("jsite.project-files.file-options") + "</b></html>");
255                 fileOptionsPanel.add(fileOptionsLabel, new GridBagConstraints(0, 2, 5, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 0, 0, 0), 0, 0));
256
257                 defaultFileCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.default"));
258                 defaultFileCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.default.tooltip"));
259                 defaultFileCheckBox.setName("default-file");
260                 defaultFileCheckBox.addActionListener(this);
261                 defaultFileCheckBox.setEnabled(false);
262
263                 fileOptionsPanel.add(defaultFileCheckBox, new GridBagConstraints(0, 3, 5, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 18, 0, 0), 0, 0));
264
265                 fileOptionsInsertCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.insert"), true);
266                 fileOptionsInsertCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.insert.tooltip"));
267                 fileOptionsInsertCheckBox.setName("insert");
268                 fileOptionsInsertCheckBox.setMnemonic(KeyEvent.VK_I);
269                 fileOptionsInsertCheckBox.addActionListener(this);
270                 fileOptionsInsertCheckBox.setEnabled(false);
271
272                 fileOptionsPanel.add(fileOptionsInsertCheckBox, new GridBagConstraints(0, 4, 5, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0));
273
274                 fileOptionsCustomKeyTextField = new JTextField(45);
275                 fileOptionsCustomKeyTextField.setToolTipText(I18n.getMessage("jsite.project-files.custom-key.tooltip"));
276                 fileOptionsCustomKeyTextField.setEnabled(false);
277                 fileOptionsCustomKeyTextField.getDocument().addDocumentListener(this);
278
279                 fileOptionsInsertRedirectCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.insert-redirect"), false);
280                 fileOptionsInsertRedirectCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.insert-redirect.tooltip"));
281                 fileOptionsInsertRedirectCheckBox.setName("insert-redirect");
282                 fileOptionsInsertRedirectCheckBox.setMnemonic(KeyEvent.VK_R);
283                 fileOptionsInsertRedirectCheckBox.addActionListener(this);
284                 fileOptionsInsertRedirectCheckBox.setEnabled(false);
285
286                 final TLabel customKeyLabel = new TLabel(I18n.getMessage("jsite.project-files.custom-key") + ":", KeyEvent.VK_K, fileOptionsCustomKeyTextField);
287                 fileOptionsPanel.add(fileOptionsInsertRedirectCheckBox, new GridBagConstraints(0, 5, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0));
288                 fileOptionsPanel.add(customKeyLabel, new GridBagConstraints(1, 5, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 6, 0, 0), 0, 0));
289                 fileOptionsPanel.add(fileOptionsCustomKeyTextField, new GridBagConstraints(2, 5, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
290
291                 fileOptionsRenameCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.rename"), false);
292                 fileOptionsRenameCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.rename.tooltip"));
293                 fileOptionsRenameCheckBox.setName("rename");
294                 fileOptionsRenameCheckBox.setMnemonic(KeyEvent.VK_N);
295                 fileOptionsRenameCheckBox.addActionListener(this);
296                 fileOptionsRenameCheckBox.setEnabled(false);
297
298                 fileOptionsRenameTextField = new JTextField();
299                 fileOptionsRenameTextField.setEnabled(false);
300                 fileOptionsRenameTextField.getDocument().addDocumentListener(new DocumentListener() {
301
302                         @SuppressWarnings("synthetic-access")
303                         private void storeText(DocumentEvent documentEvent) {
304                                 FileOption fileOption = getSelectedFile();
305                                 Document document = documentEvent.getDocument();
306                                 int documentLength = document.getLength();
307                                 try {
308                                         fileOption.setChangedName(document.getText(0, documentLength).trim());
309                                 } catch (BadLocationException ble1) {
310                                         /* ignore, it should never happen. */
311                                 }
312                         }
313
314                         public void changedUpdate(DocumentEvent documentEvent) {
315                                 storeText(documentEvent);
316                         }
317
318                         public void insertUpdate(DocumentEvent documentEvent) {
319                                 storeText(documentEvent);
320                         }
321
322                         public void removeUpdate(DocumentEvent documentEvent) {
323                                 storeText(documentEvent);
324                         }
325
326                 });
327
328                 fileOptionsPanel.add(fileOptionsRenameCheckBox, new GridBagConstraints(0, 6, 2, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0));
329                 fileOptionsPanel.add(fileOptionsRenameTextField, new GridBagConstraints(2, 6, 3, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
330
331                 fileOptionsMIMETypeComboBox = new JComboBox(DefaultMIMETypes.getAllMIMETypes());
332                 fileOptionsMIMETypeComboBox.setToolTipText(I18n.getMessage("jsite.project-files.mime-type.tooltip"));
333                 fileOptionsMIMETypeComboBox.setName("project-files.mime-type");
334                 fileOptionsMIMETypeComboBox.addActionListener(this);
335                 fileOptionsMIMETypeComboBox.setEditable(true);
336                 fileOptionsMIMETypeComboBox.setEnabled(false);
337
338                 final TLabel mimeTypeLabel = new TLabel(I18n.getMessage("jsite.project-files.mime-type") + ":", KeyEvent.VK_M, fileOptionsMIMETypeComboBox);
339                 fileOptionsPanel.add(mimeTypeLabel, new GridBagConstraints(0, 7, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0));
340                 fileOptionsPanel.add(fileOptionsMIMETypeComboBox, new GridBagConstraints(1, 7, 4, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
341
342                 containerComboBoxModel = new DefaultComboBoxModel();
343                 fileOptionsContainerComboBox = new JComboBox(containerComboBoxModel);
344                 fileOptionsContainerComboBox.setToolTipText(I18n.getMessage("jsite.project-files.container.tooltip"));
345                 fileOptionsContainerComboBox.setName("project-files.container");
346                 fileOptionsContainerComboBox.addActionListener(this);
347                 fileOptionsContainerComboBox.setEnabled(false);
348                 fileOptionsContainerComboBox.setVisible(false);
349
350                 final TLabel containerLabel = new TLabel(I18n.getMessage("jsite.project-files.container") + ":", KeyEvent.VK_C, fileOptionsContainerComboBox);
351                 containerLabel.setVisible(false);
352                 JButton addContainerButton = new JButton(addContainerAction);
353                 addContainerButton.setVisible(false);
354                 JButton editContainerButton = new JButton(editContainerAction);
355                 editContainerButton.setVisible(false);
356                 JButton deleteContainerButton = new JButton(deleteContainerAction);
357                 deleteContainerButton.setVisible(false);
358                 fileOptionsPanel.add(containerLabel, new GridBagConstraints(0, 8, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.NONE, new Insets(6, 18, 0, 0), 0, 0));
359                 fileOptionsPanel.add(fileOptionsContainerComboBox, new GridBagConstraints(1, 8, 1, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
360                 fileOptionsPanel.add(addContainerButton, new GridBagConstraints(2, 8, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
361                 fileOptionsPanel.add(editContainerButton, new GridBagConstraints(3, 8, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
362                 fileOptionsPanel.add(deleteContainerButton, new GridBagConstraints(4, 8, 1, 1, 0.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 6, 0, 0), 0, 0));
363
364                 JPanel fileOptionsReplacementPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 6, 6));
365                 fileOptionsReplacementPanel.setBorder(new EmptyBorder(-6, -6, -6, -6));
366
367                 replacementCheckBox = new JCheckBox(I18n.getMessage("jsite.project-files.replacement"));
368                 replacementCheckBox.setName("project-files.replace-edition");
369                 replacementCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.replacement.tooltip"));
370                 replacementCheckBox.addActionListener(this);
371                 replacementCheckBox.setEnabled(false);
372                 replacementCheckBox.setVisible(false);
373                 fileOptionsReplacementPanel.add(replacementCheckBox);
374
375                 replaceEditionRangeSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 99, 1));
376                 replaceEditionRangeSpinner.setName("project-files.replace-edition-range");
377                 replaceEditionRangeSpinner.setToolTipText(I18n.getMessage("jsite.project-files.replacement.edition-range.tooltip"));
378                 replaceEditionRangeSpinner.addChangeListener(this);
379                 replaceEditionRangeSpinner.setEnabled(false);
380                 replaceEditionRangeSpinner.setVisible(false);
381                 final JLabel editionRangeLabel = new JLabel(I18n.getMessage("jsite.project-files.replacement.edition-range"));
382                 editionRangeLabel.setVisible(false);
383                 fileOptionsReplacementPanel.add(editionRangeLabel);
384                 fileOptionsReplacementPanel.add(replaceEditionRangeSpinner);
385
386                 fileOptionsPanel.add(fileOptionsReplacementPanel, new GridBagConstraints(0, 9, 5, 1, 1.0, 0.0, GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, new Insets(6, 18, 0, 0), 0, 0));
387
388                 I18nContainer.getInstance().registerRunnable(new Runnable() {
389
390                         @SuppressWarnings("synthetic-access")
391                         public void run() {
392                                 ignoreHiddenFilesCheckBox.setText(I18n.getMessage("jsite.project-files.ignore-hidden-files"));
393                                 ignoreHiddenFilesCheckBox.setToolTipText(I18n.getMessage("jsite.projet-files.ignore-hidden-files.tooltip"));
394                                 fileOptionsLabel.setText("<html><b>" + I18n.getMessage("jsite.project-files.file-options") + "</b></html>");
395                                 defaultFileCheckBox.setText(I18n.getMessage("jsite.project-files.default"));
396                                 defaultFileCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.default.tooltip"));
397                                 fileOptionsInsertCheckBox.setText(I18n.getMessage("jsite.project-files.insert"));
398                                 fileOptionsInsertCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.insert.tooltip"));
399                                 fileOptionsInsertRedirectCheckBox.setText(I18n.getMessage("jsite.project-files.insert-redirect"));
400                                 fileOptionsInsertRedirectCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.insert-redirect.tooltip"));
401                                 fileOptionsCustomKeyTextField.setToolTipText(I18n.getMessage("jsite.project-files.custom-key.tooltip"));
402                                 customKeyLabel.setText(I18n.getMessage("jsite.project-files.custom-key") + ":");
403                                 fileOptionsRenameCheckBox.setText("jsite.project-files.rename");
404                                 fileOptionsRenameCheckBox.setToolTipText("jsite.project-files.rename.tooltip");
405                                 fileOptionsMIMETypeComboBox.setToolTipText(I18n.getMessage("jsite.project-files.mime-type.tooltip"));
406                                 mimeTypeLabel.setText(I18n.getMessage("jsite.project-files.mime-type") + ":");
407                                 fileOptionsContainerComboBox.setToolTipText(I18n.getMessage("jsite.project-files.container.tooltip"));
408                                 containerLabel.setText(I18n.getMessage("jsite.project-files.container") + ":");
409                                 replacementCheckBox.setText(I18n.getMessage("jsite.project-files.replacement"));
410                                 replacementCheckBox.setToolTipText(I18n.getMessage("jsite.project-files.replacement.tooltip"));
411                                 replaceEditionRangeSpinner.setToolTipText(I18n.getMessage("jsite.project-files.replacement.edition-range.tooltip"));
412                                 editionRangeLabel.setText(I18n.getMessage("jsite.project-files.replacement.edition-range"));
413                         }
414                 });
415
416                 return projectFilesPanel;
417         }
418
419         /**
420          * Sets the project whose files to manage.
421          *
422          * @param project
423          *            The project whose files to manage
424          */
425         public void setProject(final Project project) {
426                 this.project = project;
427                 setHeading(MessageFormat.format(I18n.getMessage("jsite.project-files.heading"), project.getName()));
428                 setDescription(I18n.getMessage("jsite.project-files.description"));
429                 ignoreHiddenFilesCheckBox.setSelected(project.isIgnoreHiddenFiles());
430                 I18nContainer.getInstance().registerRunnable(new Runnable() {
431
432                         public void run() {
433                                 setHeading(MessageFormat.format(I18n.getMessage("jsite.project-files.heading"), project.getName()));
434                                 setDescription(I18n.getMessage("jsite.project-files.description"));
435                         }
436                 });
437         }
438
439         /**
440          * Returns a list of all project files.
441          *
442          * @return All project files
443          */
444         private List<String> getProjectFiles() {
445                 List<String> files = new ArrayList<String>();
446                 for (int index = 0, size = projectFileList.getModel().getSize(); index < size; index++) {
447                         files.add((String) projectFileList.getModel().getElementAt(index));
448                 }
449                 return files;
450         }
451
452         /**
453          * Updates the container combo box model.
454          */
455         private void rebuildContainerComboBox() {
456                 /* scan files for containers */
457                 List<String> files = getProjectFiles();
458                 List<String> containers = new ArrayList<String>(); // ComboBoxModel
459                 // sucks. No
460                 // contains()!
461                 containers.add("");
462                 for (String filename : files) {
463                         String container = project.getFileOption(filename).getContainer();
464                         if (!containers.contains(container)) {
465                                 containers.add(container);
466                         }
467                 }
468                 Collections.sort(containers);
469                 containerComboBoxModel.removeAllElements();
470                 for (String container : containers) {
471                         containerComboBoxModel.addElement(container);
472                 }
473         }
474
475         //
476         // ACTIONS
477         //
478
479         /**
480          * Rescans the project’s files.
481          */
482         private void actionScan() {
483                 projectFileList.clearSelection();
484                 projectFileList.setListData(new Object[0]);
485
486                 wizard.setNextEnabled(false);
487                 wizard.setPreviousEnabled(false);
488                 wizard.setQuitEnabled(false);
489
490                 FileScanner fileScanner = new FileScanner(project);
491                 fileScanner.addFileScannerListener(this);
492                 new Thread(fileScanner).start();
493         }
494
495         /**
496          * Adds a container.
497          */
498         private void actionAddContainer() {
499                 String containerName = JOptionPane.showInputDialog(wizard, I18n.getMessage("jsite.project-files.action.add-container.message") + ":", null, JOptionPane.INFORMATION_MESSAGE);
500                 if (containerName == null) {
501                         return;
502                 }
503                 containerName = containerName.trim();
504                 String filename = (String) projectFileList.getSelectedValue();
505                 FileOption fileOption = project.getFileOption(filename);
506                 fileOption.setContainer(containerName);
507                 rebuildContainerComboBox();
508                 fileOptionsContainerComboBox.setSelectedItem(containerName);
509         }
510
511         /**
512          * Edits the container.
513          */
514         private void actionEditContainer() {
515                 String selectedFilename = (String) projectFileList.getSelectedValue();
516                 FileOption fileOption = project.getFileOption(selectedFilename);
517                 String oldContainerName = fileOption.getContainer();
518                 String containerName = JOptionPane.showInputDialog(wizard, I18n.getMessage("jsite.project-files.action.edit-container.message") + ":", oldContainerName);
519                 if (containerName == null) {
520                         return;
521                 }
522                 if (containerName.equals("")) {
523                         fileOption.setContainer("");
524                         fileOptionsContainerComboBox.setSelectedItem("");
525                         return;
526                 }
527                 List<String> files = getProjectFiles();
528                 for (String filename : files) {
529                         fileOption = project.getFileOption(filename);
530                         if (fileOption.getContainer().equals(oldContainerName)) {
531                                 fileOption.setContainer(containerName);
532                         }
533                 }
534                 rebuildContainerComboBox();
535                 fileOptionsContainerComboBox.setSelectedItem(containerName);
536         }
537
538         /**
539          * Deletes the container.
540          */
541         private void actionDeleteContainer() {
542                 if (JOptionPane.showConfirmDialog(wizard, I18n.getMessage("jsite.project-files.action.delete-container.message"), null, JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE) == JOptionPane.OK_OPTION) {
543                         String containerName = (String) fileOptionsContainerComboBox.getSelectedItem();
544                         List<String> files = getProjectFiles();
545                         for (String filename : files) {
546                                 FileOption fileOption = project.getFileOption(filename);
547                                 if (fileOption.getContainer().equals(containerName)) {
548                                         fileOption.setContainer("");
549                                 }
550                         }
551                         fileOptionsContainerComboBox.setSelectedItem("");
552                 }
553         }
554
555         /**
556          * {@inheritDoc}
557          * <p>
558          * Updates the file list.
559          */
560         public void fileScannerFinished(FileScanner fileScanner) {
561                 final boolean error = fileScanner.isError();
562                 if (!error) {
563                         final List<String> files = fileScanner.getFiles();
564                         SwingUtilities.invokeLater(new Runnable() {
565
566                                 @SuppressWarnings("synthetic-access")
567                                 public void run() {
568                                         projectFileList.setListData(files.toArray(new String[files.size()]));
569                                         projectFileList.clearSelection();
570                                         rebuildContainerComboBox();
571                                 }
572                         });
573                         Set<String> entriesToRemove = new HashSet<String>();
574                         Iterator<String> filenames = new HashSet<String>(project.getFileOptions().keySet()).iterator();
575                         while (filenames.hasNext()) {
576                                 String filename = filenames.next();
577                                 if (!files.contains(filename)) {
578                                         entriesToRemove.add(filename);
579                                 }
580                         }
581                         for (String filename : entriesToRemove) {
582                                 project.setFileOption(filename, null);
583                         }
584                 } else {
585                         JOptionPane.showMessageDialog(wizard, I18n.getMessage("jsite.project-files.scan-error"), null, JOptionPane.ERROR_MESSAGE);
586                 }
587                 SwingUtilities.invokeLater(new Runnable() {
588
589                         @SuppressWarnings("synthetic-access")
590                         public void run() {
591                                 wizard.setPreviousEnabled(true);
592                                 wizard.setNextEnabled(!error);
593                                 wizard.setQuitEnabled(true);
594                         }
595                 });
596         }
597
598         /**
599          * Returns the {@link FileOption file options} for the currently selected
600          * file.
601          *
602          * @return The {@link FileOption}s for the selected file, or {@code null} if
603          *         no file is selected
604          */
605         private FileOption getSelectedFile() {
606                 String filename = (String) projectFileList.getSelectedValue();
607                 if (filename == null) {
608                         return null;
609                 }
610                 return project.getFileOption(filename);
611         }
612
613         //
614         // INTERFACE ActionListener
615         //
616
617         /**
618          * {@inheritDoc}
619          */
620         public void actionPerformed(ActionEvent actionEvent) {
621                 Object source = actionEvent.getSource();
622                 if ((source instanceof JCheckBox) && ("ignore-hidden-files".equals(((JCheckBox) source).getName()))) {
623                         project.setIgnoreHiddenFiles(((JCheckBox) source).isSelected());
624                         actionScan();
625                         return;
626                 }
627                 String filename = (String) projectFileList.getSelectedValue();
628                 if (filename == null) {
629                         return;
630                 }
631                 FileOption fileOption = project.getFileOption(filename);
632                 if (source instanceof JCheckBox) {
633                         JCheckBox checkBox = (JCheckBox) source;
634                         if ("default-file".equals(checkBox.getName())) {
635                                 if (checkBox.isSelected()) {
636                                         if (filename.indexOf('/') > -1) {
637                                                 JOptionPane.showMessageDialog(wizard, I18n.getMessage("jsite.project-files.invalid-default-file"), null, JOptionPane.ERROR_MESSAGE);
638                                                 checkBox.setSelected(false);
639                                         } else {
640                                                 project.setIndexFile(filename);
641                                         }
642                                 } else {
643                                         if (filename.equals(project.getIndexFile())) {
644                                                 project.setIndexFile(null);
645                                         }
646                                 }
647                         } else if ("insert".equals(checkBox.getName())) {
648                                 boolean isInsert = checkBox.isSelected();
649                                 fileOption.setInsert(isInsert);
650                                 if (!isInsert) {
651                                         fileOptionsContainerComboBox.setSelectedItem("");
652                                 }
653                                 fileOptionsInsertRedirectCheckBox.setEnabled(!isInsert);
654                         } else if ("insert-redirect".equals(checkBox.getName())) {
655                                 boolean isInsertRedirect = checkBox.isSelected();
656                                 fileOption.setInsertRedirect(isInsertRedirect);
657                                 fileOptionsCustomKeyTextField.setEnabled(isInsertRedirect);
658                         } else if ("rename".equals(checkBox.getName())) {
659                                 boolean isRenamed = checkBox.isSelected();
660                                 fileOptionsRenameTextField.setEnabled(isRenamed);
661                                 fileOption.setChangedName(isRenamed ? fileOptionsRenameTextField.getText() : "");
662                         } else if ("project-files.replace-edition".equals(checkBox.getName())) {
663                                 boolean replaceEdition = checkBox.isSelected();
664                                 fileOption.setReplaceEdition(replaceEdition);
665                                 replaceEditionRangeSpinner.setEnabled(replaceEdition);
666                         }
667                 } else if (source instanceof JComboBox) {
668                         JComboBox comboBox = (JComboBox) source;
669                         if ("project-files.mime-type".equals(comboBox.getName())) {
670                                 fileOption.setMimeType((String) comboBox.getSelectedItem());
671                         } else if ("project-files.container".equals(comboBox.getName())) {
672                                 String containerName = (String) comboBox.getSelectedItem();
673                                 fileOption.setContainer(containerName);
674                                 boolean enabled = !"".equals(containerName);
675                                 editContainerAction.setEnabled(enabled);
676                                 deleteContainerAction.setEnabled(enabled);
677                                 if (enabled) {
678                                         fileOptionsInsertCheckBox.setSelected(true);
679                                 }
680                         }
681                 }
682         }
683
684         //
685         // INTERFACE ListSelectionListener
686         //
687
688         /**
689          * {@inheritDoc}
690          */
691         public void valueChanged(ListSelectionEvent e) {
692                 String filename = (String) projectFileList.getSelectedValue();
693                 boolean enabled = filename != null;
694                 boolean insert = fileOptionsInsertCheckBox.isSelected();
695                 defaultFileCheckBox.setEnabled(enabled);
696                 fileOptionsInsertCheckBox.setEnabled(enabled);
697                 fileOptionsRenameCheckBox.setEnabled(enabled);
698                 fileOptionsMIMETypeComboBox.setEnabled(enabled);
699                 fileOptionsContainerComboBox.setEnabled(enabled);
700                 addContainerAction.setEnabled(enabled);
701                 editContainerAction.setEnabled(enabled);
702                 deleteContainerAction.setEnabled(enabled);
703                 replacementCheckBox.setEnabled(enabled && insert);
704                 if (filename != null) {
705                         FileOption fileOption = project.getFileOption(filename);
706                         defaultFileCheckBox.setSelected(filename.equals(project.getIndexFile()));
707                         fileOptionsInsertCheckBox.setSelected(fileOption.isInsert());
708                         fileOptionsInsertRedirectCheckBox.setEnabled(!fileOption.isInsert());
709                         fileOptionsInsertRedirectCheckBox.setSelected(fileOption.isInsertRedirect());
710                         fileOptionsCustomKeyTextField.setEnabled(fileOption.isInsertRedirect());
711                         fileOptionsCustomKeyTextField.setText(fileOption.getCustomKey());
712                         fileOptionsRenameCheckBox.setSelected(fileOption.hasChangedName());
713                         fileOptionsRenameTextField.setEnabled(fileOption.hasChangedName());
714                         fileOptionsRenameTextField.setText(fileOption.getChangedName());
715                         fileOptionsMIMETypeComboBox.getModel().setSelectedItem(fileOption.getMimeType());
716                         fileOptionsContainerComboBox.setSelectedItem(fileOption.getContainer());
717                         replacementCheckBox.setSelected(fileOption.getReplaceEdition());
718                         replaceEditionRangeSpinner.setValue(fileOption.getEditionRange());
719                         replaceEditionRangeSpinner.setEnabled(fileOption.getReplaceEdition());
720                 } else {
721                         defaultFileCheckBox.setSelected(false);
722                         fileOptionsInsertCheckBox.setSelected(true);
723                         fileOptionsInsertRedirectCheckBox.setEnabled(false);
724                         fileOptionsInsertRedirectCheckBox.setSelected(false);
725                         fileOptionsCustomKeyTextField.setEnabled(false);
726                         fileOptionsCustomKeyTextField.setText("CHK@");
727                         fileOptionsRenameCheckBox.setEnabled(false);
728                         fileOptionsRenameCheckBox.setSelected(false);
729                         fileOptionsRenameTextField.setEnabled(false);
730                         fileOptionsRenameTextField.setText("");
731                         fileOptionsMIMETypeComboBox.getModel().setSelectedItem(DefaultMIMETypes.DEFAULT_MIME_TYPE);
732                         fileOptionsContainerComboBox.setSelectedItem("");
733                         replacementCheckBox.setSelected(false);
734                         replaceEditionRangeSpinner.setValue(0);
735                 }
736         }
737
738         //
739         // INTERFACE DocumentListener
740         //
741
742         /**
743          * Updates the options of the currently selected file with the changes made
744          * in the “custom key” textfield.
745          *
746          * @param documentEvent
747          *            The document event to process
748          */
749         private void processDocumentUpdate(DocumentEvent documentEvent) {
750                 String filename = (String) projectFileList.getSelectedValue();
751                 if (filename == null) {
752                         return;
753                 }
754                 FileOption fileOption = project.getFileOption(filename);
755                 Document document = documentEvent.getDocument();
756                 try {
757                         String text = document.getText(0, document.getLength());
758                         fileOption.setCustomKey(text);
759                 } catch (BadLocationException ble1) {
760                         /* ignore. */
761                 }
762         }
763
764         /**
765          * {@inheritDoc}
766          */
767         public void changedUpdate(DocumentEvent documentEvent) {
768                 processDocumentUpdate(documentEvent);
769         }
770
771         /**
772          * {@inheritDoc}
773          */
774         public void insertUpdate(DocumentEvent documentEvent) {
775                 processDocumentUpdate(documentEvent);
776         }
777
778         /**
779          * {@inheritDoc}
780          */
781         public void removeUpdate(DocumentEvent documentEvent) {
782                 processDocumentUpdate(documentEvent);
783         }
784
785         //
786         // INTERFACE ChangeListener
787         //
788
789         /**
790          * {@inheritDoc}
791          */
792         public void stateChanged(ChangeEvent changeEvent) {
793                 String filename = (String) projectFileList.getSelectedValue();
794                 if (filename == null) {
795                         return;
796                 }
797                 FileOption fileOption = project.getFileOption(filename);
798                 Object source = changeEvent.getSource();
799                 if (source instanceof JSpinner) {
800                         JSpinner spinner = (JSpinner) source;
801                         fileOption.setEditionRange((Integer) spinner.getValue());
802                 }
803         }
804
805 }