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

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

Collection ConfigFileEditor related changes, bugfixes and improvements: 1. In investigating why clientGLI > Edit > collectionConfig.xml > Save button didn't save and get reflected in the interface, found that regular GLI had the same problem. A simple all to c_man.saveCollection() fixed it for GLI, apparently should do so for client-GLI too but still to test. 2. A larger change is to make sure edits in GLI that affect collCfg.xml get saved to collCfg.xml before Edit > collCfg.xml loads the config file. This includes user values entered in controls being edited in the currently selected GLI pane. GLI code gave me a massive run-around to figure how its magic auto-save feature was working but I think I've figured it out now: It's that in GLI Panes which affect colCfg.xml (like Format pane), the loseFocus() on a GLI Pane triggers loseFocus() on GUI controls in that Pane (e.g. Format Pane's General field controls). These controls then save their current values to the collConfig in memory. Then an explicit call to c_man.saveCollection() happens. So just calling c_man.saveCollection before an Edit > collCfg.xml operation won't save the active edits in the current controls of the selected GLI Pane. Based on this understanding, I've forced a loseFocus() on the current pane, to force it to save the current state of its controls, before GLI loads the CollConfigEditor. It works. 3. ConfigFileEditor in GLI (and therefore hopefully in client-GLI too) no longer closes and reopens the collection on saving colcfg edits. This was a quick fix in the past to the problem of getting GLI to immediately reflect changes made to the currently open collection's colcfg.xml file. With the fixes in (1) and (2) this commit is able make a useful improvement to this behaviour, as the changes in this commit avoid having to close and reopen a collection after saving changes in CollConfigEditor, while aiming for the same effect of getting the interface to immediately reflect the edits. Great gains could especially be had with client-GLI, assuming this commit works for it too, as the close then reloading collection operation can take such a long time with client-GLI. Fingers crossed.

File size: 16.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
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 // Final dialog setup & positioning.
208 setDefaultCloseOperation(DISPOSE_ON_CLOSE); // get rid of this dialog when it's closed (on dispose())
209 this.addWindowListener(new WindowAdapter() { // called on dispose. Return focus to any curr GLI Pane
210 public void windowClose(WindowEvent we) {
211 Gatherer.g_man.doRegainFocus();
212 }
213 });
214
215
216
217 getRootPane().setDefaultButton(save_button);
218 Dimension screen_size = Configuration.screen_size;
219 setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
220 editor.setCaretPosition(0); // start at top of config XML
221
222 // Following has no effect: not sure why on dialog show, with or without requesting focus,
223 // it takes some time for the editor to get focus and only if user's mouse curser is
224 // hovering over ConfigFileEditor and moves about a bit there.
225 // Give editor focus on dialog show, so that Ctrl-F, to shift focus to searchField, will be
226 // responsive too. Not working until mouse moves about over editor, despite the suggestion
227 // at https://stackoverflow.com/questions/17828264/java-swing-jdialog-default-focus
228 editor.requestFocusInWindow();
229 }
230
231 public void actionPerformed(ActionEvent e) {
232
233 if(e.getSource() == cancel_button) {
234 this.dispose(); // get rid of this dialog, we're done
235 }
236 else if(e.getSource() == save_button) {
237 // write out the XML to the collectionConfig.xml file and we're done.
238
239 // already know the xml in the textarea is valid, else the save_button would have been inactive
240 Document xml_file_doc = XMLTools.getDOM(editor.getText());
241 // This code from FormatConversionDialog.dispose()
242 // saves the XML, prepending the processing instruction (<?xml ... ?>)
243 String[] nonEscapingTagNames = { StaticStrings.FORMAT_STR, StaticStrings.DISPLAYITEM_STR };
244 XMLTools.writeXMLFile(this.config_file, xml_file_doc, nonEscapingTagNames);
245
246 this.dispose(); // get rid of this dialog, we're done
247
248 // close and reopen the collection in GLI
249 String current_collection_filepath = Gatherer.c_man.getCollection().getCollectionPath();
250 ////Gatherer.c_man.closeCollection(); // explicit save was necessary after all
251 // closeCollection() doesn't auto-save
252 ////Gatherer.c_man.loadCollection(current_collection_filepath);
253
254
255 // Fixing bug: Edit > colcfg wasn't being saved on Save when client-GLI.
256 // And in fact, it wasn't properly saving in GLI for some GLI panes.
257 // That's because save needs to be explicitly called. (Didn't understand how GLI's
258 // "autosave" worked until now. GLI > File > Close a collection saves it, but underneath
259 // it calles SAVEandCloseCollection.
260
261 // Works - but arduous: have to load in the entire collection just because cfg changed?
262 // Especial pain with client-GLI where reloading takes forever.
263 //Gatherer.g_man.saveThenCloseCurrentCollection();
264 //Gatherer.c_man.loadCollection(current_collection_filepath);
265
266 // Better way: don't have to close and reopen the entire collection after editing colCfg.xml
267 // This just updates GLI's interface with the current collCfg.xml.
268 // Fingers crossed this works for client-GLI, because then the coll won't need to be closed
269 // and reopened each time after Edit > collectionCfg.xml gets saved.
270 Gatherer.c_man.saveCollection(); // should save colcfg whether GLI or client-GLI
271 Gatherer.c_man.reloadAfterConfigFileEdited();
272 //Gatherer.g_man.updateUI(); // didn't do what I hoped it would, so is this relevant here?
273 // It's done when a new collection is loaded, so may be useful?
274 // But all my tests concerning GLI > Edit > collConfig.xml worked out without it
275
276 }
277
278 // Find functionality, can extend for Find and Replace
279 // taken from https://github.com/bobbylight/RSyntaxTextArea/wiki/Example:-Using-Find-and-Replace
280 else if(e.getSource() == searchField) {
281 nextButton.doClick(0);
282 }
283 else if(e.getSource() == nextButton || e.getSource() == prevButton) {
284 // "FindNext" => search forward, "FindPrev" => search backward
285 String command = e.getActionCommand();
286 boolean forward = "FindNext".equals(command);
287
288 // Create an object defining our search parameters.
289 SearchContext context = new SearchContext();
290 String text = searchField.getText();
291 if (text.length() == 0) {
292 return;
293 }
294 context.setSearchFor(text);
295 context.setMatchCase(matchCaseCB.isSelected());
296 context.setRegularExpression(regexCB.isSelected());
297 context.setSearchForward(forward);
298 context.setWholeWord(false);
299
300 //boolean found = SearchEngine.find(this.editor, context).wasFound();
301 boolean found = false;
302 try {
303 found = SearchEngine.find(this.editor, context);
304 // if the current search string is a correction to a previous invalid regex,
305 // then return background colour back to normal
306 searchField.setBackground(Color.white);
307 if (!found) {
308 JOptionPane.showMessageDialog(this, "Text not found");
309 }
310 } catch(java.util.regex.PatternSyntaxException regex_exception) {
311 searchField.setBackground(Color.red);
312 JOptionPane.showMessageDialog(this, "Invalid regex");
313 }
314
315 }
316 }
317
318
319 // This method returns a proper processing instruction (starts with <?xml and ends with ?>)
320 // else the empty string is returned
321 public String getProcessingInstruction(String xmlStr) {
322 String processingInstruction = "";
323
324 xmlStr = xmlStr.trim();
325 if(xmlStr.startsWith("<?xml")) {
326 int endIndex = xmlStr.indexOf("?>"); // end of processing instruction
327
328 if(endIndex != -1) {
329 endIndex += 2;
330 processingInstruction = xmlStr.substring(0, endIndex);
331 }
332 }
333 return processingInstruction;
334 }
335
336
337 // THE FOLLOWING FUNCTIONS ARE LARGELY FROM Format4gs3Manager.java.EditorListener
338 public void changedUpdate(DocumentEvent e)
339 {
340 update();
341 }
342
343 public void insertUpdate(DocumentEvent e)
344 {
345 update();
346 updateUndo("insert");
347
348 }
349
350 public void removeUpdate(DocumentEvent e)
351 {
352 update();
353 updateUndo("remove");
354
355 }
356
357 private void updateUndo(String from)
358 {
359
360 editor.undoButton.setEnabled(true);
361 }
362
363 public void update()
364 {
365
366 String xml_str = editor.getText();
367
368 // Processing instructions "<?xml...?>" are no longer displayed in the editor (but
369 // they are written out to the file on save) so we don't need to deal with them here.
370
371 // can't parse with processing instruction <?xml...?>, so remove that
372 ///String processingInstruction = getProcessingInstruction(xml_str);
373 ///xml_str = xml_str.replace(processingInstruction, "");
374 // now can parse the XML portion without the processing instruction,
375 // while the original XML remains unchanged in the editor area
376 // If the processing instruction was incomplete, then xml_str would still
377 // include the incomplete processing instruction, and parsing below will catch the error.
378
379 String msg = XMLTools.parse(xml_str);
380 editor_msgarea.setText(msg);
381
382 if (msg.startsWith(XMLTools.WELLFORMED))
383 {
384 editor_msgarea.setBackground(Color.white);
385 save_button.setEnabled(true);
386 }
387 else
388 {
389 editor_msgarea.setBackground(Color.red);
390 save_button.setEnabled(false);
391 }
392 }
393
394
395 // if Ctrl (or Mac equiv) + F is pressed in the config file editing area,
396 // then move the focus to the searchField, so the user can start typing the searchTerm
397 private class ConfigFileEditorKeyListener
398 extends KeyAdapter
399 {
400 /** Gives notification of key events on the text field */
401 public void keyPressed(KeyEvent key_event)
402 {
403 // Don't hardcode check for Ctrl key "(e.getModifiers() & KeyEvent.CTRL_MASK) != 0)"
404 // because Mac uses another modifier key, Command-F instead of Ctrl-F.
405 // https://stackoverflow.com/questions/5970765/java-detect-ctrlx-key-combination-on-a-jtree
406 if((key_event.getModifiers() == Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())
407 && key_event.getKeyCode() == KeyEvent.VK_F) {
408
409 // Instead of requestFocus() or the more forceful sounding grabFocus(),
410 // use requestFocusInWindow(), see
411 // https://stackoverflow.com/questions/1425392/how-do-you-set-a-focus-on-textfield-in-swing
412 ConfigFileEditor.this.searchField.requestFocusInWindow();
413 //JOptionPane.showMessageDialog(ConfigFileEditor.this, "Got a key", "Got key " + key_event.getKeyCode(), JOptionPane.INFORMATION_MESSAGE);
414 }
415 }
416 }
417}
Note: See TracBrowser for help on using the repository browser.