/** *######################################################################### * * A component of the Gatherer application, part of the Greenstone digital * library suite from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * * Copyright (C) 1999 New Zealand Digital Library Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *######################################################################## */ package org.greenstone.gatherer.gui; // for RSyntaxTextArea editor's search functionality: import org.fife.ui.rtextarea.SearchEngine; import org.fife.ui.rtextarea.SearchContext; import org.greenstone.gatherer.Configuration; import org.greenstone.gatherer.Dictionary; import org.greenstone.gatherer.Gatherer; import org.greenstone.gatherer.util.StaticStrings; import org.greenstone.gatherer.util.Utility; import org.greenstone.gatherer.util.XMLTools; import org.w3c.dom.*; import java.io.File; import java.io.IOException; import java.awt.*; import java.awt.event.*; import java.util.*; import javax.swing.*; import javax.swing.border.*; import javax.swing.event.*; public class ConfigFileEditor extends ModalDialog implements ActionListener, DocumentListener { public final File config_file; private GLIButton cancel_button = null; private GLIButton save_button = null; private NumberedJTextArea editor = null; private JTextArea editor_msgarea; // https://github.com/bobbylight/RSyntaxTextArea/wiki/Example:-Using-Find-and-Replace final private JTextField searchField; private JCheckBox regexCB; private JCheckBox matchCaseCB; private JButton nextButton; private JButton prevButton; //private final String DEFAULT_PROCESSING_INSTRUCTION = ""; private static final Dimension SIZE = new Dimension(850,550); public ConfigFileEditor() { super(Gatherer.g_man, true); setModal(true); setSize(SIZE); String collection_folder_path = Gatherer.getCollectDirectoryPath(); String collectionName = Gatherer.c_man.getCollection().getName(); // we won't be in this constructor if collection is null. this.config_file = new File(collection_folder_path + File.separator + collectionName, Utility.CONFIG_GS3_FILE); // col config editing is a GS3 feature // Most of the validation_msg_panel code is from Format4gs3Manager.java JPanel validation_msg_panel = new JPanel(); JLabel validation_label = new JLabel(Dictionary.get("CDM.FormatManager.MessageBox")); validation_label.setBorder(new EmptyBorder(10,10,10,10)); // add some margin editor_msgarea = new JTextArea(); editor_msgarea.setCaretPosition(0); editor_msgarea.setLineWrap(true); editor_msgarea.setRows(3); editor_msgarea.setWrapStyleWord(false); editor_msgarea.setEditable(false); editor_msgarea.setToolTipText(Dictionary.get("CDM.FormatManager.MessageBox_Tooltip")); validation_msg_panel.setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0)); validation_msg_panel.setLayout(new BorderLayout(5, 0)); validation_msg_panel.add(validation_label, BorderLayout.WEST); validation_msg_panel.add(new JScrollPane(editor_msgarea), BorderLayout.CENTER); // Find functionality - can extend for Find and Replace // taken from https://github.com/bobbylight/RSyntaxTextArea/wiki/Example:-Using-Find-and-Replace // Create a toolbar with searching options. JToolBar toolBar = new JToolBar(); searchField = new JTextField(30); toolBar.add(searchField); nextButton = new JButton("Find Next"); nextButton.setActionCommand("FindNext"); nextButton.addActionListener(this); toolBar.add(nextButton); searchField.addActionListener(this); prevButton = new JButton("Find Previous"); prevButton.setActionCommand("FindPrev"); prevButton.addActionListener(this); toolBar.add(prevButton); regexCB = new JCheckBox("Regex"); toolBar.add(regexCB); matchCaseCB = new JCheckBox("Match Case"); toolBar.add(matchCaseCB); // the all important XML editor editor = new NumberedJTextArea(Dictionary.get("ConfigFileEditor.Tooltip")); editor.getDocument().addDocumentListener(this); // if Ctrl (or Mac equiv) + F is pressed in the config file editing area, // then move the focus to the searchField, so the user can start typing the searchTerm editor.addKeyListener(new ConfigFileEditorKeyListener()); save_button = new GLIButton(Dictionary.get("General.Save"), Dictionary.get("ConfigFileEditor.Save_Tooltip")); cancel_button = new GLIButton(Dictionary.get("General.Cancel"), Dictionary.get("ConfigFileEditor.Cancel_Tooltip")); // tooltip is the same cancel_button.addActionListener(this); save_button.addActionListener(this); // LAYOUT JPanel button_panel = new JPanel(new FlowLayout()); button_panel.setComponentOrientation(Dictionary.getOrientation()); button_panel.add(editor.undoButton); button_panel.add(editor.redoButton); button_panel.add(cancel_button); button_panel.add(save_button); // toolbars_panel to contain find-and-replace toolbar and undo/redo/cancel/save button panel JPanel toolbars_panel = new JPanel(new BorderLayout()); toolbars_panel.add(toolBar, BorderLayout.NORTH); toolbars_panel.add(button_panel, BorderLayout.CENTER); JPanel content_pane = (JPanel) getContentPane(); content_pane.setComponentOrientation(Dictionary.getOrientation()); content_pane.setLayout(new BorderLayout()); content_pane.add(validation_msg_panel, BorderLayout.NORTH); content_pane.add(new JScrollPane(editor), BorderLayout.CENTER); // make editor scrollable content_pane.add(toolbars_panel, BorderLayout.SOUTH); // NOW WE CAN PREPARE THE ACTUAL CONTENT TO GO INTO THE XML EDITOR TEXTAREA Document xmlDoc = null; xmlDoc = XMLTools.parseXMLFile(config_file); if(xmlDoc != null) { // Save the collectionConfig.xml before loading it. String[] nonEscapingTagNames = { StaticStrings.FORMAT_STR, StaticStrings.DISPLAYITEM_STR }; XMLTools.writeXMLFile(this.config_file, xmlDoc, nonEscapingTagNames); //prepends proc instruction // now load the contents editor_msgarea.setBackground(Color.white); StringBuffer sb = new StringBuffer(); XMLTools.xmlNodeToString(sb, xmlDoc.getDocumentElement(), true, " ", 0); editor.setText(sb.toString()); //editor.setText(elementToString(xmlDoc.getDocumentElement(), true)); // reading in file this way adds processing instruction, which the parser doesn't like. } else { // parsing failed // so read in file as text and display as text and show the validation box in red // Won't re-save file that is already invalid before it's even been edited through this Editor System.err.println("*** Warning: Parsing error in file " + config_file + "."); System.err.println("*** Not saving ahead of editing."); String xml_str = Utility.readFile(config_file); // re-parse to get error msg to display in validation field // but first get rid of the preprocessing instruction, as this will fail parsing String processingInstruction = getProcessingInstruction(xml_str); xml_str = xml_str.replace(processingInstruction, "").trim(); // also trim newline at start if(!xml_str.equals("")) { String msg = XMLTools.parse(xml_str); // re-parse editor_msgarea.setBackground(Color.red); editor_msgarea.setText(msg); // display the parsing error save_button.setEnabled(false); editor.setText(xml_str); // display the xml contents with error and all } else { editor.setText(""); editor_msgarea.setText("Empty collection config file"); } } // Final dialog setup & positioning. setDefaultCloseOperation(DISPOSE_ON_CLOSE); // get rid of this dialog when it's closed (on dispose()) getRootPane().setDefaultButton(save_button); Dimension screen_size = Configuration.screen_size; setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2); editor.setCaretPosition(0); // start at top of config XML // Following has no effect: not sure why on dialog show, with or without requesting focus, // it takes some time for the editor to get focus and only if user's mouse curser is // hovering over ConfigFileEditor and moves about a bit there. // Give editor focus on dialog show, so that Ctrl-F, to shift focus to searchField, will be // responsive too. Not working until mouse moves about over editor, despite the suggestion // at https://stackoverflow.com/questions/17828264/java-swing-jdialog-default-focus editor.requestFocusInWindow(); } public void actionPerformed(ActionEvent e) { if(e.getSource() == cancel_button) { this.dispose(); // get rid of this dialog, we're done } else if(e.getSource() == save_button) { // write out the XML to the collectionConfig.xml file and we're done. // already know the xml in the textarea is valid, else the save_button would have been inactive Document xml_file_doc = XMLTools.getDOM(editor.getText()); // This code from FormatConversionDialog.dispose() // saves the XML, prepending the processing instruction () String[] nonEscapingTagNames = { StaticStrings.FORMAT_STR, StaticStrings.DISPLAYITEM_STR }; XMLTools.writeXMLFile(this.config_file, xml_file_doc, nonEscapingTagNames); this.dispose(); // get rid of this dialog, we're done // close and reopen the collection in GLI String current_collection_filepath = Gatherer.c_man.getCollection().getCollectionPath(); Gatherer.c_man.closeCollection(); Gatherer.c_man.loadCollection(current_collection_filepath); } // Find functionality, can extend for Find and Replace // taken from https://github.com/bobbylight/RSyntaxTextArea/wiki/Example:-Using-Find-and-Replace else if(e.getSource() == searchField) { nextButton.doClick(0); } else if(e.getSource() == nextButton || e.getSource() == prevButton) { // "FindNext" => search forward, "FindPrev" => search backward String command = e.getActionCommand(); boolean forward = "FindNext".equals(command); // Create an object defining our search parameters. SearchContext context = new SearchContext(); String text = searchField.getText(); if (text.length() == 0) { return; } context.setSearchFor(text); context.setMatchCase(matchCaseCB.isSelected()); context.setRegularExpression(regexCB.isSelected()); context.setSearchForward(forward); context.setWholeWord(false); //boolean found = SearchEngine.find(this.editor, context).wasFound(); boolean found = false; try { found = SearchEngine.find(this.editor, context); // if the current search string is a correction to a previous invalid regex, // then return background colour back to normal searchField.setBackground(Color.white); if (!found) { JOptionPane.showMessageDialog(this, "Text not found"); } } catch(java.util.regex.PatternSyntaxException regex_exception) { searchField.setBackground(Color.red); JOptionPane.showMessageDialog(this, "Invalid regex"); } } } // This method returns a proper processing instruction (starts with ) // else the empty string is returned public String getProcessingInstruction(String xmlStr) { String processingInstruction = ""; xmlStr = xmlStr.trim(); if(xmlStr.startsWith(""); // end of processing instruction if(endIndex != -1) { endIndex += 2; processingInstruction = xmlStr.substring(0, endIndex); } } return processingInstruction; } // THE FOLLOWING FUNCTIONS ARE LARGELY FROM Format4gs3Manager.java.EditorListener public void changedUpdate(DocumentEvent e) { update(); } public void insertUpdate(DocumentEvent e) { update(); updateUndo("insert"); } public void removeUpdate(DocumentEvent e) { update(); updateUndo("remove"); } private void updateUndo(String from) { editor.undoButton.setEnabled(true); } public void update() { String xml_str = editor.getText(); // Processing instructions "" are no longer displayed in the editor (but // they are written out to the file on save) so we don't need to deal with them here. // can't parse with processing instruction , so remove that ///String processingInstruction = getProcessingInstruction(xml_str); ///xml_str = xml_str.replace(processingInstruction, ""); // now can parse the XML portion without the processing instruction, // while the original XML remains unchanged in the editor area // If the processing instruction was incomplete, then xml_str would still // include the incomplete processing instruction, and parsing below will catch the error. String msg = XMLTools.parse(xml_str); editor_msgarea.setText(msg); if (msg.startsWith(XMLTools.WELLFORMED)) { editor_msgarea.setBackground(Color.white); save_button.setEnabled(true); } else { editor_msgarea.setBackground(Color.red); save_button.setEnabled(false); } } // if Ctrl (or Mac equiv) + F is pressed in the config file editing area, // then move the focus to the searchField, so the user can start typing the searchTerm private class ConfigFileEditorKeyListener extends KeyAdapter { /** Gives notification of key events on the text field */ public void keyPressed(KeyEvent key_event) { // Don't hardcode check for Ctrl key "(e.getModifiers() & KeyEvent.CTRL_MASK) != 0)" // because Mac uses another modifier key, Command-F instead of Ctrl-F. // https://stackoverflow.com/questions/5970765/java-detect-ctrlx-key-combination-on-a-jtree if((key_event.getModifiers() == Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) && key_event.getKeyCode() == KeyEvent.VK_F) { // Instead of requestFocus() or the more forceful sounding grabFocus(), // use requestFocusInWindow(), see // https://stackoverflow.com/questions/1425392/how-do-you-set-a-focus-on-textfield-in-swing ConfigFileEditor.this.searchField.requestFocusInWindow(); //JOptionPane.showMessageDialog(ConfigFileEditor.this, "Got a key", "Got key " + key_event.getKeyCode(), JOptionPane.INFORMATION_MESSAGE); } } } }