22 package org.matsim.run.gui;
24 import java.awt.Color;
25 import java.awt.Graphics;
26 import java.awt.event.ActionEvent;
27 import java.awt.event.InputEvent;
28 import java.awt.event.KeyEvent;
29 import java.io.BufferedReader;
30 import java.io.BufferedWriter;
32 import java.io.IOException;
33 import java.util.HashMap;
35 import java.util.SortedMap;
36 import java.util.TreeMap;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
40 import javax.swing.AbstractAction;
41 import javax.swing.GroupLayout;
42 import javax.swing.GroupLayout.Alignment;
43 import javax.swing.JButton;
44 import javax.swing.JDialog;
45 import javax.swing.JFileChooser;
46 import javax.swing.JFrame;
47 import javax.swing.JScrollPane;
48 import javax.swing.JTextPane;
49 import javax.swing.KeyStroke;
50 import javax.swing.LayoutStyle.ComponentPlacement;
51 import javax.swing.event.DocumentEvent;
52 import javax.swing.event.DocumentListener;
53 import javax.swing.text.BadLocationException;
54 import javax.swing.text.Document;
55 import javax.swing.text.Element;
56 import javax.swing.text.PlainDocument;
57 import javax.swing.text.PlainView;
58 import javax.swing.text.Segment;
59 import javax.swing.text.StyledEditorKit;
60 import javax.swing.text.Utilities;
61 import javax.swing.text.ViewFactory;
62 import javax.swing.undo.CannotRedoException;
63 import javax.swing.undo.CannotUndoException;
64 import javax.swing.undo.UndoManager;
71 class ConfigEditor
extends JDialog {
73 private static final boolean IS_MAC = System.getProperty(
"os.name").startsWith(
"Mac");
75 private JButton btnSave;
76 private File configFile;
77 private JTextPane xmlPane;
78 private ConfigChangeListener configChangeListener;
80 ConfigEditor(JFrame parent, File configFile, ConfigChangeListener configChangeListener) {
82 setTitle(
"Config Editor");
83 this.configChangeListener = configChangeListener;
84 this.configFile = configFile;
86 this.btnSave =
new JButton(
"Save");
87 this.btnSave.addActionListener(e -> this.save());
89 JButton btnSaveAs =
new JButton(
"Save as…");
90 btnSaveAs.addActionListener(e -> this.saveAs());
92 JScrollPane scrollPane =
new JScrollPane();
94 GroupLayout groupLayout =
new GroupLayout(getContentPane());
95 groupLayout.setHorizontalGroup(groupLayout.createParallelGroup(Alignment.LEADING)
96 .addGroup(groupLayout.createSequentialGroup()
98 .addComponent(this.btnSave)
99 .addPreferredGap(ComponentPlacement.RELATED)
100 .addComponent(btnSaveAs)
101 .addContainerGap(471, Short.MAX_VALUE))
102 .addComponent(scrollPane, Alignment.TRAILING, GroupLayout.DEFAULT_SIZE, 661, Short.MAX_VALUE));
103 groupLayout.setVerticalGroup(groupLayout.createParallelGroup(Alignment.LEADING)
104 .addGroup(groupLayout.createSequentialGroup()
106 .addGroup(groupLayout.createParallelGroup(Alignment.BASELINE)
107 .addComponent(this.btnSave)
108 .addComponent(btnSaveAs))
109 .addPreferredGap(ComponentPlacement.RELATED)
110 .addComponent(scrollPane, GroupLayout.DEFAULT_SIZE, 456, Short.MAX_VALUE)));
112 this.xmlPane =
new JTextPane();
113 this.xmlPane.setContentType(
"text/xml");
114 this.xmlPane.setEditorKit(
new XmlEditorKit());
115 scrollPane.setViewportView(this.xmlPane);
116 getContentPane().setLayout(groupLayout);
118 UndoManager undoManager = this.addUndoFunctionality(this.xmlPane);
119 addSaveFunctionality();
120 addCloseFunctionality();
122 this.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
126 undoManager.discardAllEdits();
127 this.btnSave.setEnabled(
false);
129 this.xmlPane.getDocument().addDocumentListener(
new DocumentListener() {
131 public void insertUpdate(DocumentEvent e) {
132 ConfigEditor.this.btnSave.setEnabled(
true);
136 public void removeUpdate(DocumentEvent e) {
137 ConfigEditor.this.btnSave.setEnabled(
true);
141 public void changedUpdate(DocumentEvent e) {
142 ConfigEditor.this.btnSave.setEnabled(
true);
149 this.xmlPane.requestFocus();
156 private void saveAs() {
157 SaveFileSaver chooser =
new SaveFileSaver();
158 chooser.setSelectedFile(this.configFile);
159 int saveResult = chooser.showSaveDialog(null);
160 if (saveResult == JFileChooser.APPROVE_OPTION) {
161 this.configFile = chooser.getSelectedFile();
166 private void save() {
167 String fullXml = this.xmlPane.getText();
168 try (BufferedWriter writer = IOUtils.getBufferedWriter(
this.configFile.getAbsolutePath())) {
169 writer.write(fullXml);
170 }
catch (IOException e) {
173 this.configChangeListener.configChanged(this.configFile);
174 this.btnSave.setEnabled(
false);
177 private UndoManager addUndoFunctionality(JTextPane textPane) {
178 String UNDO_ACTION =
"__UNDO__";
179 String REDO_ACTION =
"__REDO__";
181 final UndoManager undoManager =
new UndoManager();
184 textPane.getDocument().addUndoableEditListener(pEvt -> undoManager.addEdit(pEvt.getEdit()));
187 textPane.getActionMap().put(UNDO_ACTION,
new AbstractAction(UNDO_ACTION) {
188 public void actionPerformed(ActionEvent pEvt) {
190 if (undoManager.canUndo()) {
193 }
catch (CannotUndoException e) {
198 textPane.getActionMap().put(REDO_ACTION,
new AbstractAction(REDO_ACTION) {
199 public void actionPerformed(ActionEvent pEvt) {
201 if (undoManager.canRedo()) {
204 }
catch (CannotRedoException e) {
211 textPane.getInputMap()
212 .put(KeyStroke.getKeyStroke(KeyEvent.VK_Z,
213 IS_MAC ? InputEvent.META_DOWN_MASK : InputEvent.CTRL_DOWN_MASK), UNDO_ACTION);
214 textPane.getInputMap()
215 .put(KeyStroke.getKeyStroke(KeyEvent.VK_Y,
216 IS_MAC ? InputEvent.META_DOWN_MASK : InputEvent.CTRL_DOWN_MASK), REDO_ACTION);
221 private void addSaveFunctionality() {
222 String SAVE_ACTION =
"__SAVE__";
224 this.xmlPane.getActionMap().put(SAVE_ACTION,
new AbstractAction(SAVE_ACTION) {
225 public void actionPerformed(ActionEvent pEvt) {
229 this.xmlPane.getInputMap()
230 .put(KeyStroke.getKeyStroke(KeyEvent.VK_S,
231 IS_MAC ? InputEvent.META_DOWN_MASK : InputEvent.CTRL_DOWN_MASK), SAVE_ACTION);
234 private void addCloseFunctionality() {
235 String CLOSE_ACTION =
"__CLOSE__";
237 this.xmlPane.getActionMap().put(CLOSE_ACTION,
new AbstractAction(CLOSE_ACTION) {
238 public void actionPerformed(ActionEvent pEvt) {
239 ConfigEditor.this.setVisible(
false);
242 this.xmlPane.getInputMap()
243 .put(KeyStroke.getKeyStroke(KeyEvent.VK_W,
244 IS_MAC ? InputEvent.META_DOWN_MASK : InputEvent.CTRL_DOWN_MASK), CLOSE_ACTION);
247 private void loadConfig() {
248 StringBuilder buffer =
new StringBuilder(1024);
249 try (BufferedReader in = IOUtils.getBufferedReader(
this.configFile.getAbsolutePath())) {
251 while ((line = in.readLine()) != null) {
253 buffer.append(IOUtils.NATIVE_NEWLINE);
255 }
catch (IOException e) {
258 this.xmlPane.setText(buffer.toString());
259 this.xmlPane.setCaretPosition(0);
262 interface ConfigChangeListener {
263 void configChanged(File newConfigFile);
275 private static final long serialVersionUID = 2969169649596107757L;
279 xmlViewFactory = XmlView::new;
284 return xmlViewFactory;
303 private static class XmlView extends PlainView {
306 private static String TAG_PATTERN =
"(</?[a-z\\-]*)\\s?>?";
307 private static String TAG_END_PATTERN =
"(/>)";
308 private static String TAG_ATTRIBUTE_PATTERN =
"\\s(\\w*)\\=";
309 private static String TAG_ATTRIBUTE_VALUE =
"[a-z-]*\\=(\"[^\"]*\")";
310 private static String TAG_COMMENT =
"(<!--.*-->)";
311 private static String TAG_CDATA_START =
"(\\<!\\[CDATA\\[).*";
312 private static String TAG_CDATA_END =
".*(]]>)";
316 patternColors =
new HashMap<>();
317 patternColors.put(Pattern.compile(TAG_CDATA_START),
new Color(128, 128, 128));
318 patternColors.put(Pattern.compile(TAG_CDATA_END),
new Color(128, 128, 128));
319 patternColors.put(Pattern.compile(TAG_PATTERN),
new Color(63, 127, 127));
320 patternColors.put(Pattern.compile(TAG_ATTRIBUTE_PATTERN),
new Color(127, 0, 127));
321 patternColors.put(Pattern.compile(TAG_END_PATTERN),
new Color(63, 127, 127));
322 patternColors.put(Pattern.compile(TAG_ATTRIBUTE_VALUE),
new Color(42, 0, 255));
323 patternColors.put(Pattern.compile(TAG_COMMENT),
new Color(63, 95, 191));
331 getDocument().putProperty(PlainDocument.tabSizeAttribute, 4);
335 protected int drawUnselectedText(Graphics graphics,
int x,
int y,
int p0,
int p1)
throws BadLocationException {
337 Document doc = getDocument();
338 String text = doc.getText(p0, p1 - p0);
340 Segment segment = getLineBuffer();
342 SortedMap<Integer, Integer> startMap =
new TreeMap<>();
343 SortedMap<Integer, Color> colorMap =
new TreeMap<>();
346 for (Map.Entry<Pattern, Color> entry : patternColors.entrySet()) {
348 Matcher matcher = entry.getKey().matcher(text);
350 while (matcher.find()) {
351 startMap.put(matcher.start(1), matcher.end());
352 colorMap.put(matcher.start(1), entry.getValue());
359 for (Map.Entry<Integer, Integer> entry : startMap.entrySet()) {
360 int start = entry.getKey();
361 int end = entry.getValue();
364 graphics.setColor(Color.black);
365 doc.getText(p0 + i, start - i, segment);
366 x = Utilities.drawTabbedText(segment, x, y, graphics,
this, i);
369 graphics.setColor(colorMap.get(start));
371 doc.getText(p0 + start, i - start, segment);
372 x = Utilities.drawTabbedText(segment, x, y, graphics,
this, start);
376 if (i < text.length()) {
377 graphics.setColor(Color.black);
378 doc.getText(p0 + i, text.length() - i, segment);
379 x = Utilities.drawTabbedText(segment, x, y, graphics,
this, i);
ViewFactory getViewFactory()
ViewFactory xmlViewFactory
int drawUnselectedText(Graphics graphics, int x, int y, int p0, int p1)
static HashMap< Pattern, Color > patternColors