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

Last change on this file since 34246 was 34246, checked in by ak19, 4 years ago

Still part of commit 34241 and now also 34245.

File size: 16.8 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
83 // Gatherer.c_man.saveCollection(); // on Edit > collectionConfig.xml menu click,
84 // collection save has already happened as GLI pane.lostFocus() got called on current GLI Pane
85 // if/where this affects collectionConfig.xml. For an explanation, see GUIManager.java,
86 // look for "if(esrc == menu_bar.edit_config)"
87
88 this.config_file = new File(collection_folder_path + File.separator + collectionName, Utility.CONFIG_GS3_FILE); // col config editing is a GS3 feature
89
90
91 // Most of the validation_msg_panel code is from Format4gs3Manager.java
92 JPanel validation_msg_panel = new JPanel();
93 JLabel validation_label = new JLabel(Dictionary.get("CDM.FormatManager.MessageBox"));
94 validation_label.setBorder(new EmptyBorder(10,10,10,10)); // add some margin
95 editor_msgarea = new JTextArea();
96
97 editor_msgarea.setCaretPosition(0);
98 editor_msgarea.setLineWrap(true);
99 editor_msgarea.setRows(3);
100 editor_msgarea.setWrapStyleWord(false);
101 editor_msgarea.setEditable(false);
102 editor_msgarea.setToolTipText(Dictionary.get("CDM.FormatManager.MessageBox_Tooltip"));
103 validation_msg_panel.setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0));
104 validation_msg_panel.setLayout(new BorderLayout(5, 0));
105 validation_msg_panel.add(validation_label, BorderLayout.WEST);
106 validation_msg_panel.add(new JScrollPane(editor_msgarea), BorderLayout.CENTER);
107
108 // Find functionality - can extend for Find and Replace
109 // taken from https://github.com/bobbylight/RSyntaxTextArea/wiki/Example:-Using-Find-and-Replace
110 // Create a toolbar with searching options.
111 JToolBar toolBar = new JToolBar();
112 searchField = new JTextField(30);
113 toolBar.add(searchField);
114 nextButton = new JButton("Find Next");
115 nextButton.setActionCommand("FindNext");
116 nextButton.addActionListener(this);
117 toolBar.add(nextButton);
118 searchField.addActionListener(this);
119 prevButton = new JButton("Find Previous");
120 prevButton.setActionCommand("FindPrev");
121 prevButton.addActionListener(this);
122 toolBar.add(prevButton);
123 regexCB = new JCheckBox("Regex");
124 toolBar.add(regexCB);
125 matchCaseCB = new JCheckBox("Match Case");
126 toolBar.add(matchCaseCB);
127
128 // the all important XML editor
129 editor = new NumberedJTextArea(Dictionary.get("ConfigFileEditor.Tooltip"));
130 editor.getDocument().addDocumentListener(this);
131 // if Ctrl (or Mac equiv) + F is pressed in the config file editing area,
132 // then move the focus to the searchField, so the user can start typing the searchTerm
133 editor.addKeyListener(new ConfigFileEditorKeyListener());
134
135 save_button = new GLIButton(Dictionary.get("General.Save"), Dictionary.get("ConfigFileEditor.Save_Tooltip"));
136 cancel_button = new GLIButton(Dictionary.get("General.Cancel"), Dictionary.get("ConfigFileEditor.Cancel_Tooltip")); // tooltip is the same
137 cancel_button.addActionListener(this);
138 save_button.addActionListener(this);
139
140 // LAYOUT
141 JPanel button_panel = new JPanel(new FlowLayout());
142 button_panel.setComponentOrientation(Dictionary.getOrientation());
143 button_panel.add(editor.undoButton);
144 button_panel.add(editor.redoButton);
145 button_panel.add(cancel_button);
146 button_panel.add(save_button);
147
148 // toolbars_panel to contain find-and-replace toolbar and undo/redo/cancel/save button panel
149 JPanel toolbars_panel = new JPanel(new BorderLayout());
150 toolbars_panel.add(toolBar, BorderLayout.NORTH);
151 toolbars_panel.add(button_panel, BorderLayout.CENTER);
152
153 JPanel content_pane = (JPanel) getContentPane();
154 content_pane.setComponentOrientation(Dictionary.getOrientation());
155 content_pane.setLayout(new BorderLayout());
156 content_pane.add(validation_msg_panel, BorderLayout.NORTH);
157 content_pane.add(new JScrollPane(editor), BorderLayout.CENTER); // make editor scrollable
158 content_pane.add(toolbars_panel, BorderLayout.SOUTH);
159
160 // NOW WE CAN PREPARE THE ACTUAL CONTENT TO GO INTO THE XML EDITOR TEXTAREA
161 Document xmlDoc = null;
162 xmlDoc = XMLTools.parseXMLFile(config_file);
163
164 if(xmlDoc != null) {
165
166 // Save the collectionConfig.xml before loading it.
167 String[] nonEscapingTagNames = { StaticStrings.FORMAT_STR, StaticStrings.DISPLAYITEM_STR };
168 XMLTools.writeXMLFile(this.config_file, xmlDoc, nonEscapingTagNames); //prepends proc instruction
169
170 // now load the contents
171 editor_msgarea.setBackground(Color.white);
172 StringBuffer sb = new StringBuffer();
173 XMLTools.xmlNodeToString(sb, xmlDoc.getDocumentElement(), true, " ", 0);
174 editor.setText(sb.toString());
175 //editor.setText(elementToString(xmlDoc.getDocumentElement(), true)); // reading in file this way adds processing instruction, which the parser doesn't like.
176
177 } else { // parsing failed
178 // so read in file as text and display as text and show the validation box in red
179
180 // Won't re-save file that is already invalid before it's even been edited through this Editor
181 System.err.println("*** Warning: Parsing error in file " + config_file + ".");
182 System.err.println("*** Not saving ahead of editing.");
183
184 String xml_str = Utility.readFile(config_file);
185
186 // re-parse to get error msg to display in validation field
187
188 // but first get rid of the preprocessing instruction, as this will fail parsing
189 String processingInstruction = getProcessingInstruction(xml_str);
190 xml_str = xml_str.replace(processingInstruction, "").trim(); // also trim newline at start
191
192 if(!xml_str.equals("")) {
193
194 String msg = XMLTools.parse(xml_str); // re-parse
195 editor_msgarea.setBackground(Color.red);
196 editor_msgarea.setText(msg); // display the parsing error
197 save_button.setEnabled(false);
198
199 editor.setText(xml_str); // display the xml contents with error and all
200
201 } else {
202 editor.setText("");
203 editor_msgarea.setText("Empty collection config file");
204 }
205 }
206
207
208 // Final dialog setup & positioning.
209 this.setDefaultCloseOperation(DISPOSE_ON_CLOSE); // get rid of this dialog when it's closed (on dispose())
210 this.addWindowListener(new WindowAdapter() { // called on dispose. Return focus to any curr GLI Pane
211 @Override
212 public void windowClosed(WindowEvent we) {
213 super.windowClosed(we);
214 Gatherer.g_man.doRegainFocus();
215 //Gatherer.g_man.refresh(Gatherer.COLLECTION_OPENED, true);
216 Gatherer.g_man.updateUI();
217 }
218 });
219
220
221
222 getRootPane().setDefaultButton(save_button);
223 Dimension screen_size = Configuration.screen_size;
224 setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
225 editor.setCaretPosition(0); // start at top of config XML
226
227 // Following has no effect: not sure why on dialog show, with or without requesting focus,
228 // it takes some time for the editor to get focus and only if user's mouse curser is
229 // hovering over ConfigFileEditor and moves about a bit there.
230 // Give editor focus on dialog show, so that Ctrl-F, to shift focus to searchField, will be
231 // responsive too. Not working until mouse moves about over editor, despite the suggestion
232 // at https://stackoverflow.com/questions/17828264/java-swing-jdialog-default-focus
233 editor.requestFocusInWindow();
234 }
235
236
237 public void actionPerformed(ActionEvent e) {
238
239 if(e.getSource() == cancel_button) {
240 this.dispose(); // get rid of this dialog, we're done
241 }
242 else if(e.getSource() == save_button) {
243 // write out the XML to the collectionConfig.xml file and we're done.
244
245 // already know the xml in the textarea is valid, else the save_button would have been inactive
246 Document xml_file_doc = XMLTools.getDOM(editor.getText());
247 // This code from FormatConversionDialog.dispose()
248 // saves the XML, prepending the processing instruction (<?xml ... ?>)
249 String[] nonEscapingTagNames = { StaticStrings.FORMAT_STR, StaticStrings.DISPLAYITEM_STR };
250 XMLTools.writeXMLFile(this.config_file, xml_file_doc, nonEscapingTagNames);
251
252 this.dispose(); // get rid of this dialog, we're done
253
254 // close and reopen the collection in GLI
255 String current_collection_filepath = Gatherer.c_man.getCollection().getCollectionPath();
256 ////Gatherer.c_man.closeCollection(); // explicit save was necessary after all
257 // closeCollection() doesn't auto-save
258 ////Gatherer.c_man.loadCollection(current_collection_filepath);
259
260
261 // Fixing bug: Edit > colcfg wasn't being saved on Save when client-GLI.
262 // And in fact, it wasn't properly saving in GLI for some GLI panes.
263 // That's because save needs to be explicitly called. (Didn't understand how GLI's
264 // "autosave" worked until now. GLI > File > Close a collection saves it, but underneath
265 // it calles SAVEandCloseCollection.
266
267 // Works - but arduous: have to load in the entire collection just because cfg changed?
268 // Especial pain with client-GLI where reloading takes forever.
269 //Gatherer.g_man.saveThenCloseCurrentCollection();
270 //Gatherer.c_man.loadCollection(current_collection_filepath);
271
272 // Better way: don't have to close and reopen the entire collection after editing colCfg.xml
273 // This just updates GLI's interface with the current collCfg.xml.
274 // Fingers crossed this works for client-GLI, because then the coll won't need to be closed
275 // and reopened each time after Edit > collectionCfg.xml gets saved.
276 Gatherer.c_man.saveCollection(); // should save colcfg whether GLI or client-GLI
277 Gatherer.c_man.reloadAfterConfigFileEdited();
278 //Gatherer.g_man.updateUI(); // didn't do what I hoped it would, so is this relevant here?
279 // It's done when a new collection is loaded, so may be useful?
280 // But all my tests concerning GLI > Edit > collConfig.xml worked out without it
281
282 }
283
284 // Find functionality, can extend for Find and Replace
285 // taken from https://github.com/bobbylight/RSyntaxTextArea/wiki/Example:-Using-Find-and-Replace
286 else if(e.getSource() == searchField) {
287 nextButton.doClick(0);
288 }
289 else if(e.getSource() == nextButton || e.getSource() == prevButton) {
290 // "FindNext" => search forward, "FindPrev" => search backward
291 String command = e.getActionCommand();
292 boolean forward = "FindNext".equals(command);
293
294 // Create an object defining our search parameters.
295 SearchContext context = new SearchContext();
296 String text = searchField.getText();
297 if (text.length() == 0) {
298 return;
299 }
300 context.setSearchFor(text);
301 context.setMatchCase(matchCaseCB.isSelected());
302 context.setRegularExpression(regexCB.isSelected());
303 context.setSearchForward(forward);
304 context.setWholeWord(false);
305
306 //boolean found = SearchEngine.find(this.editor, context).wasFound();
307 boolean found = false;
308 try {
309 found = SearchEngine.find(this.editor, context);
310 // if the current search string is a correction to a previous invalid regex,
311 // then return background colour back to normal
312 searchField.setBackground(Color.white);
313 if (!found) {
314 JOptionPane.showMessageDialog(this, "Text not found");
315 }
316 } catch(java.util.regex.PatternSyntaxException regex_exception) {
317 searchField.setBackground(Color.red);
318 JOptionPane.showMessageDialog(this, "Invalid regex");
319 }
320
321 }
322 }
323
324
325 // This method returns a proper processing instruction (starts with <?xml and ends with ?>)
326 // else the empty string is returned
327 public String getProcessingInstruction(String xmlStr) {
328 String processingInstruction = "";
329
330 xmlStr = xmlStr.trim();
331 if(xmlStr.startsWith("<?xml")) {
332 int endIndex = xmlStr.indexOf("?>"); // end of processing instruction
333
334 if(endIndex != -1) {
335 endIndex += 2;
336 processingInstruction = xmlStr.substring(0, endIndex);
337 }
338 }
339 return processingInstruction;
340 }
341
342
343 // THE FOLLOWING FUNCTIONS ARE LARGELY FROM Format4gs3Manager.java.EditorListener
344 public void changedUpdate(DocumentEvent e)
345 {
346 update();
347 }
348
349 public void insertUpdate(DocumentEvent e)
350 {
351 update();
352 updateUndo("insert");
353
354 }
355
356 public void removeUpdate(DocumentEvent e)
357 {
358 update();
359 updateUndo("remove");
360
361 }
362
363 private void updateUndo(String from)
364 {
365
366 editor.undoButton.setEnabled(true);
367 }
368
369 public void update()
370 {
371
372 String xml_str = editor.getText();
373
374 // Processing instructions "<?xml...?>" are no longer displayed in the editor (but
375 // they are written out to the file on save) so we don't need to deal with them here.
376
377 // can't parse with processing instruction <?xml...?>, so remove that
378 ///String processingInstruction = getProcessingInstruction(xml_str);
379 ///xml_str = xml_str.replace(processingInstruction, "");
380 // now can parse the XML portion without the processing instruction,
381 // while the original XML remains unchanged in the editor area
382 // If the processing instruction was incomplete, then xml_str would still
383 // include the incomplete processing instruction, and parsing below will catch the error.
384
385 String msg = XMLTools.parse(xml_str);
386 editor_msgarea.setText(msg);
387
388 if (msg.startsWith(XMLTools.WELLFORMED))
389 {
390 editor_msgarea.setBackground(Color.white);
391 save_button.setEnabled(true);
392 }
393 else
394 {
395 editor_msgarea.setBackground(Color.red);
396 save_button.setEnabled(false);
397 }
398 }
399
400
401 // if Ctrl (or Mac equiv) + F is pressed in the config file editing area,
402 // then move the focus to the searchField, so the user can start typing the searchTerm
403 private class ConfigFileEditorKeyListener
404 extends KeyAdapter
405 {
406 /** Gives notification of key events on the text field */
407 public void keyPressed(KeyEvent key_event)
408 {
409 // Don't hardcode check for Ctrl key "(e.getModifiers() & KeyEvent.CTRL_MASK) != 0)"
410 // because Mac uses another modifier key, Command-F instead of Ctrl-F.
411 // https://stackoverflow.com/questions/5970765/java-detect-ctrlx-key-combination-on-a-jtree
412 if((key_event.getModifiers() == Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())
413 && key_event.getKeyCode() == KeyEvent.VK_F) {
414
415 // Instead of requestFocus() or the more forceful sounding grabFocus(),
416 // use requestFocusInWindow(), see
417 // https://stackoverflow.com/questions/1425392/how-do-you-set-a-focus-on-textfield-in-swing
418 ConfigFileEditor.this.searchField.requestFocusInWindow();
419 //JOptionPane.showMessageDialog(ConfigFileEditor.this, "Got a key", "Got key " + key_event.getKeyCode(), JOptionPane.INFORMATION_MESSAGE);
420 }
421 }
422 }
423}
Note: See TracBrowser for help on using the repository browser.