source: main/trunk/gli/src/org/greenstone/gatherer/gui/ConfigFileEditor.java@ 32093

Last change on this file since 32093 was 32093, checked in by ak19, 6 years ago

Ctr (or equiv on Mac) +F on GS3 GLI's ConfigFileEditor now focuses on the searchField.

File size: 14.6 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Gatherer application, part of the Greenstone digital
5 * library suite from the New Zealand Digital Library Project at the
6 * University of Waikato, New Zealand.
7 *
8 * Copyright (C) 1999 New Zealand Digital Library Project
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *########################################################################
24 */
25
26package org.greenstone.gatherer.gui;
27
28// for RSyntaxTextArea editor's search functionality:
29import org.fife.ui.rtextarea.SearchEngine;
30import org.fife.ui.rtextarea.SearchContext;
31
32import org.greenstone.gatherer.Configuration;
33import org.greenstone.gatherer.Dictionary;
34import org.greenstone.gatherer.Gatherer;
35import org.greenstone.gatherer.util.StaticStrings;
36import org.greenstone.gatherer.util.Utility;
37import org.greenstone.gatherer.util.XMLTools;
38
39import org.w3c.dom.*;
40
41import java.io.File;
42import java.io.IOException;
43
44import java.awt.*;
45import java.awt.event.*;
46import java.util.*;
47import javax.swing.*;
48import javax.swing.border.*;
49import javax.swing.event.*;
50
51
52public class ConfigFileEditor extends ModalDialog
53 implements ActionListener, DocumentListener
54{
55 public final File config_file;
56
57 private GLIButton cancel_button = null;
58 private GLIButton save_button = null;
59 private NumberedJTextArea editor = null;
60 private JTextArea editor_msgarea;
61
62
63 // https://github.com/bobbylight/RSyntaxTextArea/wiki/Example:-Using-Find-and-Replace
64 final private JTextField searchField;
65 private JCheckBox regexCB;
66 private JCheckBox matchCaseCB;
67 private JButton nextButton;
68 private JButton prevButton;
69
70 //private final String DEFAULT_PROCESSING_INSTRUCTION = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
71
72 private static final Dimension SIZE = new Dimension(850,550);
73
74 public ConfigFileEditor() {
75
76 super(Gatherer.g_man, true);
77 setModal(true);
78 setSize(SIZE);
79
80 String collection_folder_path = Gatherer.getCollectDirectoryPath();
81 String collectionName = Gatherer.c_man.getCollection().getName(); // we won't be in this constructor if collection is null.
82 this.config_file = new File(collection_folder_path + File.separator + collectionName, Utility.CONFIG_GS3_FILE); // col config editing is a GS3 feature
83
84
85 // Most of the validation_msg_panel code is from Format4gs3Manager.java
86 JPanel validation_msg_panel = new JPanel();
87 JLabel validation_label = new JLabel(Dictionary.get("CDM.FormatManager.MessageBox"));
88 validation_label.setBorder(new EmptyBorder(10,10,10,10)); // add some margin
89 editor_msgarea = new JTextArea();
90
91 editor_msgarea.setCaretPosition(0);
92 editor_msgarea.setLineWrap(true);
93 editor_msgarea.setRows(3);
94 editor_msgarea.setWrapStyleWord(false);
95 editor_msgarea.setEditable(false);
96 editor_msgarea.setToolTipText(Dictionary.get("CDM.FormatManager.MessageBox_Tooltip"));
97 validation_msg_panel.setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0));
98 validation_msg_panel.setLayout(new BorderLayout(5, 0));
99 validation_msg_panel.add(validation_label, BorderLayout.WEST);
100 validation_msg_panel.add(new JScrollPane(editor_msgarea), BorderLayout.CENTER);
101
102 // Find functionality - can extend for Find and Replace
103 // taken from https://github.com/bobbylight/RSyntaxTextArea/wiki/Example:-Using-Find-and-Replace
104 // Create a toolbar with searching options.
105 JToolBar toolBar = new JToolBar();
106 searchField = new JTextField(30);
107 toolBar.add(searchField);
108 nextButton = new JButton("Find Next");
109 nextButton.setActionCommand("FindNext");
110 nextButton.addActionListener(this);
111 toolBar.add(nextButton);
112 searchField.addActionListener(this);
113 prevButton = new JButton("Find Previous");
114 prevButton.setActionCommand("FindPrev");
115 prevButton.addActionListener(this);
116 toolBar.add(prevButton);
117 regexCB = new JCheckBox("Regex");
118 toolBar.add(regexCB);
119 matchCaseCB = new JCheckBox("Match Case");
120 toolBar.add(matchCaseCB);
121
122 // the all important XML editor
123 editor = new NumberedJTextArea(Dictionary.get("ConfigFileEditor.Tooltip"));
124 editor.getDocument().addDocumentListener(this);
125 // if Ctrl (or Mac equiv) + F is pressed in the config file editing area,
126 // then move the focus to the searchField, so the user can start typing the searchTerm
127 editor.addKeyListener(new ConfigFileEditorKeyListener());
128
129 save_button = new GLIButton(Dictionary.get("General.Save"), Dictionary.get("ConfigFileEditor.Save_Tooltip"));
130 cancel_button = new GLIButton(Dictionary.get("General.Cancel"), Dictionary.get("ConfigFileEditor.Cancel_Tooltip")); // tooltip is the same
131 cancel_button.addActionListener(this);
132 save_button.addActionListener(this);
133
134 // LAYOUT
135 JPanel button_panel = new JPanel(new FlowLayout());
136 button_panel.setComponentOrientation(Dictionary.getOrientation());
137 button_panel.add(editor.undoButton);
138 button_panel.add(editor.redoButton);
139 button_panel.add(cancel_button);
140 button_panel.add(save_button);
141
142 // toolbars_panel to contain find-and-replace toolbar and undo/redo/cancel/save button panel
143 JPanel toolbars_panel = new JPanel(new BorderLayout());
144 toolbars_panel.add(toolBar, BorderLayout.NORTH);
145 toolbars_panel.add(button_panel, BorderLayout.CENTER);
146
147 JPanel content_pane = (JPanel) getContentPane();
148 content_pane.setComponentOrientation(Dictionary.getOrientation());
149 content_pane.setLayout(new BorderLayout());
150 content_pane.add(validation_msg_panel, BorderLayout.NORTH);
151 content_pane.add(new JScrollPane(editor), BorderLayout.CENTER); // make editor scrollable
152 content_pane.add(toolbars_panel, BorderLayout.SOUTH);
153
154 // NOW WE CAN PREPARE THE ACTUAL CONTENT TO GO INTO THE XML EDITOR TEXTAREA
155 Document xmlDoc = null;
156 xmlDoc = XMLTools.parseXMLFile(config_file);
157
158 if(xmlDoc != null) {
159
160 // Save the collectionConfig.xml before loading it.
161 String[] nonEscapingTagNames = { StaticStrings.FORMAT_STR, StaticStrings.DISPLAYITEM_STR };
162 XMLTools.writeXMLFile(this.config_file, xmlDoc, nonEscapingTagNames); //prepends proc instruction
163
164 // now load the contents
165 editor_msgarea.setBackground(Color.white);
166 StringBuffer sb = new StringBuffer();
167 XMLTools.xmlNodeToString(sb, xmlDoc.getDocumentElement(), true, " ", 0);
168 editor.setText(sb.toString());
169 //editor.setText(elementToString(xmlDoc.getDocumentElement(), true)); // reading in file this way adds processing instruction, which the parser doesn't like.
170
171 } else { // parsing failed
172 // so read in file as text and display as text and show the validation box in red
173
174 // Won't re-save file that is already invalid before it's even been edited through this Editor
175 System.err.println("*** Warning: Parsing error in file " + config_file + ".");
176 System.err.println("*** Not saving ahead of editing.");
177
178 String xml_str = Utility.readFile(config_file);
179
180 // re-parse to get error msg to display in validation field
181
182 // but first get rid of the preprocessing instruction, as this will fail parsing
183 String processingInstruction = getProcessingInstruction(xml_str);
184 xml_str = xml_str.replace(processingInstruction, "").trim(); // also trim newline at start
185
186 if(!xml_str.equals("")) {
187
188 String msg = XMLTools.parse(xml_str); // re-parse
189 editor_msgarea.setBackground(Color.red);
190 editor_msgarea.setText(msg); // display the parsing error
191 save_button.setEnabled(false);
192
193 editor.setText(xml_str); // display the xml contents with error and all
194
195 } else {
196 editor.setText("");
197 editor_msgarea.setText("Empty collection config file");
198 }
199 }
200
201 // Final dialog setup & positioning.
202 setDefaultCloseOperation(DISPOSE_ON_CLOSE); // get rid of this dialog when it's closed (on dispose())
203 getRootPane().setDefaultButton(save_button);
204 Dimension screen_size = Configuration.screen_size;
205 setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
206 editor.setCaretPosition(0); // start at top of config XML
207
208 // Following has no effect: not sure why on dialog show, with or without requesting focus,
209 // it takes some time for the editor to get focus and only if user's mouse curser is
210 // hovering over ConfigFileEditor and moves about a bit there.
211 // Give editor focus on dialog show, so that Ctrl-F, to shift focus to searchField, will be
212 // responsive too. Not working until mouse moves about over editor, despite the suggestion
213 // at https://stackoverflow.com/questions/17828264/java-swing-jdialog-default-focus
214 editor.requestFocusInWindow();
215 }
216
217 public void actionPerformed(ActionEvent e) {
218
219 if(e.getSource() == cancel_button) {
220 this.dispose(); // get rid of this dialog, we're done
221 }
222 else if(e.getSource() == save_button) {
223 // write out the XML to the collectionConfig.xml file and we're done.
224
225 // already know the xml in the textarea is valid, else the save_button would have been inactive
226 Document xml_file_doc = XMLTools.getDOM(editor.getText());
227 // This code from FormatConversionDialog.dispose()
228 // saves the XML, prepending the processing instruction (<?xml ... ?>)
229 String[] nonEscapingTagNames = { StaticStrings.FORMAT_STR, StaticStrings.DISPLAYITEM_STR };
230 XMLTools.writeXMLFile(this.config_file, xml_file_doc, nonEscapingTagNames);
231
232 this.dispose(); // get rid of this dialog, we're done
233
234 // close and reopen the collection in GLI
235 String current_collection_filepath = Gatherer.c_man.getCollection().getCollectionPath();
236 Gatherer.c_man.closeCollection();
237 Gatherer.c_man.loadCollection(current_collection_filepath);
238 }
239
240 // Find functionality, can extend for Find and Replace
241 // taken from https://github.com/bobbylight/RSyntaxTextArea/wiki/Example:-Using-Find-and-Replace
242 else if(e.getSource() == searchField) {
243 nextButton.doClick(0);
244 }
245 else if(e.getSource() == nextButton || e.getSource() == prevButton) {
246 // "FindNext" => search forward, "FindPrev" => search backward
247 String command = e.getActionCommand();
248 boolean forward = "FindNext".equals(command);
249
250 // Create an object defining our search parameters.
251 SearchContext context = new SearchContext();
252 String text = searchField.getText();
253 if (text.length() == 0) {
254 return;
255 }
256 context.setSearchFor(text);
257 context.setMatchCase(matchCaseCB.isSelected());
258 context.setRegularExpression(regexCB.isSelected());
259 context.setSearchForward(forward);
260 context.setWholeWord(false);
261
262 //boolean found = SearchEngine.find(this.editor, context).wasFound();
263 boolean found = false;
264 try {
265 found = SearchEngine.find(this.editor, context);
266 // if the current search string is a correction to a previous invalid regex,
267 // then return background colour back to normal
268 searchField.setBackground(Color.white);
269 if (!found) {
270 JOptionPane.showMessageDialog(this, "Text not found");
271 }
272 } catch(java.util.regex.PatternSyntaxException regex_exception) {
273 searchField.setBackground(Color.red);
274 JOptionPane.showMessageDialog(this, "Invalid regex");
275 }
276
277 }
278 }
279
280
281 // This method returns a proper processing instruction (starts with <?xml and ends with ?>)
282 // else the empty string is returned
283 public String getProcessingInstruction(String xmlStr) {
284 String processingInstruction = "";
285
286 xmlStr = xmlStr.trim();
287 if(xmlStr.startsWith("<?xml")) {
288 int endIndex = xmlStr.indexOf("?>"); // end of processing instruction
289
290 if(endIndex != -1) {
291 endIndex += 2;
292 processingInstruction = xmlStr.substring(0, endIndex);
293 }
294 }
295 return processingInstruction;
296 }
297
298
299 // THE FOLLOWING FUNCTIONS ARE LARGELY FROM Format4gs3Manager.java.EditorListener
300 public void changedUpdate(DocumentEvent e)
301 {
302 update();
303 }
304
305 public void insertUpdate(DocumentEvent e)
306 {
307 update();
308 updateUndo("insert");
309
310 }
311
312 public void removeUpdate(DocumentEvent e)
313 {
314 update();
315 updateUndo("remove");
316
317 }
318
319 private void updateUndo(String from)
320 {
321
322 editor.undoButton.setEnabled(true);
323 }
324
325 public void update()
326 {
327
328 String xml_str = editor.getText();
329
330 // Processing instructions "<?xml...?>" are no longer displayed in the editor (but
331 // they are written out to the file on save) so we don't need to deal with them here.
332
333 // can't parse with processing instruction <?xml...?>, so remove that
334 ///String processingInstruction = getProcessingInstruction(xml_str);
335 ///xml_str = xml_str.replace(processingInstruction, "");
336 // now can parse the XML portion without the processing instruction,
337 // while the original XML remains unchanged in the editor area
338 // If the processing instruction was incomplete, then xml_str would still
339 // include the incomplete processing instruction, and parsing below will catch the error.
340
341 String msg = XMLTools.parse(xml_str);
342 editor_msgarea.setText(msg);
343
344 if (msg.startsWith(XMLTools.WELLFORMED))
345 {
346 editor_msgarea.setBackground(Color.white);
347 save_button.setEnabled(true);
348 }
349 else
350 {
351 editor_msgarea.setBackground(Color.red);
352 save_button.setEnabled(false);
353 }
354 }
355
356
357 // if Ctrl (or Mac equiv) + F is pressed in the config file editing area,
358 // then move the focus to the searchField, so the user can start typing the searchTerm
359 private class ConfigFileEditorKeyListener
360 extends KeyAdapter
361 {
362 /** Gives notification of key events on the text field */
363 public void keyPressed(KeyEvent key_event)
364 {
365 // Don't hardcode check for Ctrl key "(e.getModifiers() & KeyEvent.CTRL_MASK) != 0)"
366 // because Mac uses another modifier key, Command-F instead of Ctrl-F.
367 // https://stackoverflow.com/questions/5970765/java-detect-ctrlx-key-combination-on-a-jtree
368 if((key_event.getModifiers() == Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())
369 && key_event.getKeyCode() == KeyEvent.VK_F) {
370
371 // Instead of requestFocus() or the more forceful sounding grabFocus(),
372 // use requestFocusInWindow(), see
373 // https://stackoverflow.com/questions/1425392/how-do-you-set-a-focus-on-textfield-in-swing
374 ConfigFileEditor.this.searchField.requestFocusInWindow();
375 //JOptionPane.showMessageDialog(ConfigFileEditor.this, "Got a key", "Got key " + key_event.getKeyCode(), JOptionPane.INFORMATION_MESSAGE);
376 }
377 }
378 }
379}
Note: See TracBrowser for help on using the repository browser.