source: josm/trunk/src/org/openstreetmap/josm/gui/preferences/StyleSourceEditor.java@ 2655

Last change on this file since 2655 was 2655, checked in by jttt, 17 years ago

Fixed #4161 Major slowdown in recent versions, used correct pattern for listeners realized using CopyOnWriteArrayList

File size: 34.5 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.gui.preferences;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.Component;
8import java.awt.FlowLayout;
9import java.awt.GridBagConstraints;
10import java.awt.GridBagLayout;
11import java.awt.event.ActionEvent;
12import java.awt.event.FocusAdapter;
13import java.awt.event.FocusEvent;
14import java.awt.event.KeyEvent;
15import java.awt.event.MouseEvent;
16import java.io.BufferedReader;
17import java.io.File;
18import java.io.IOException;
19import java.io.InputStreamReader;
20import java.io.UnsupportedEncodingException;
21import java.net.MalformedURLException;
22import java.net.URL;
23import java.util.ArrayList;
24import java.util.Collection;
25import java.util.Collections;
26import java.util.Comparator;
27import java.util.EventObject;
28import java.util.Iterator;
29import java.util.LinkedList;
30import java.util.List;
31import java.util.concurrent.CopyOnWriteArrayList;
32import java.util.regex.Matcher;
33import java.util.regex.Pattern;
34
35import javax.swing.AbstractAction;
36import javax.swing.BorderFactory;
37import javax.swing.DefaultListModel;
38import javax.swing.DefaultListSelectionModel;
39import javax.swing.JButton;
40import javax.swing.JComponent;
41import javax.swing.JFileChooser;
42import javax.swing.JLabel;
43import javax.swing.JList;
44import javax.swing.JOptionPane;
45import javax.swing.JPanel;
46import javax.swing.JScrollPane;
47import javax.swing.JTable;
48import javax.swing.JTextField;
49import javax.swing.KeyStroke;
50import javax.swing.ListCellRenderer;
51import javax.swing.ListSelectionModel;
52import javax.swing.event.CellEditorListener;
53import javax.swing.event.ChangeEvent;
54import javax.swing.event.ListSelectionEvent;
55import javax.swing.event.ListSelectionListener;
56import javax.swing.table.AbstractTableModel;
57import javax.swing.table.TableCellEditor;
58
59import org.openstreetmap.josm.Main;
60import org.openstreetmap.josm.gui.HelpAwareOptionPane;
61import org.openstreetmap.josm.gui.PleaseWaitRunnable;
62import org.openstreetmap.josm.io.MirroredInputStream;
63import org.openstreetmap.josm.io.OsmTransferException;
64import org.openstreetmap.josm.tools.GBC;
65import org.openstreetmap.josm.tools.ImageProvider;
66import org.openstreetmap.josm.tools.LanguageInfo;
67import org.xml.sax.SAXException;
68
69public class StyleSourceEditor extends JPanel {
70 private JTable tblActiveStyles;
71 private ActiveStylesModel activeStylesModel;
72 private JList lstAvailableStyles;
73 private AvailableStylesListModel availableStylesModel;
74 private JTable tblIconPaths = null;
75 private IconPathTableModel iconPathsModel;
76 private String pref;
77 private String iconpref;
78 private boolean stylesInitiallyLoaded;
79 private String availableStylesUrl;
80
81 /**
82 *
83 * @param stylesPreferencesKey the preferences key with the list of active style sources (filenames and URLs)
84 * @param iconsPreferenceKey the preference key with the list of icon sources (can be null)
85 * @param availableStylesUrl the URL to the list of available style sources
86 */
87 public StyleSourceEditor(String stylesPreferencesKey, String iconsPreferenceKey, final String availableStylesUrl) {
88
89 DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
90 tblActiveStyles = new JTable(activeStylesModel = new ActiveStylesModel(selectionModel));
91 tblActiveStyles.setSelectionModel(selectionModel);
92 tblActiveStyles.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
93 tblActiveStyles.setTableHeader(null);
94 tblActiveStyles.getColumnModel().getColumn(0).setCellEditor(new FileOrUrlCellEditor());
95 tblActiveStyles.setRowHeight(20);
96 activeStylesModel.setActiveStyles(Main.pref.getCollection(stylesPreferencesKey, null));
97
98 selectionModel = new DefaultListSelectionModel();
99 lstAvailableStyles = new JList(availableStylesModel =new AvailableStylesListModel(selectionModel));
100 lstAvailableStyles.setSelectionModel(selectionModel);
101 lstAvailableStyles.setCellRenderer(new StyleSourceCellRenderer());
102 //availableStylesModel.setStyleSources(reloadAvailableStyles(availableStylesUrl));
103 this.availableStylesUrl = availableStylesUrl;
104
105 this.pref = stylesPreferencesKey;
106 this.iconpref = iconsPreferenceKey;
107
108 JButton iconadd = null;
109 JButton iconedit = null;
110 JButton icondelete = null;
111
112 if (iconsPreferenceKey != null) {
113 selectionModel = new DefaultListSelectionModel();
114 tblIconPaths = new JTable(iconPathsModel = new IconPathTableModel(selectionModel));
115 tblIconPaths.setSelectionModel(selectionModel);
116 tblIconPaths.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
117 tblIconPaths.setTableHeader(null);
118 tblIconPaths.getColumnModel().getColumn(0).setCellEditor(new FileOrUrlCellEditor());
119 tblIconPaths.setRowHeight(20);
120 iconPathsModel.setIconPaths(Main.pref.getCollection(iconsPreferenceKey, null));
121
122 iconadd = new JButton(new NewIconPathAction());
123
124 EditIconPathAction editIconPathAction = new EditIconPathAction();
125 tblIconPaths.getSelectionModel().addListSelectionListener(editIconPathAction);
126 iconedit = new JButton(editIconPathAction);
127
128 RemoveIconPathAction removeIconPathAction = new RemoveIconPathAction();
129 tblIconPaths.getSelectionModel().addListSelectionListener(removeIconPathAction);
130 icondelete = new JButton(removeIconPathAction);
131 tblIconPaths.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0), "delete");
132 tblIconPaths.getActionMap().put("delete", removeIconPathAction);
133 }
134
135 JButton add = new JButton(new NewActiveStyleAction());
136
137 EditActiveStyleAction editActiveStyleAction = new EditActiveStyleAction();
138 tblActiveStyles.getSelectionModel().addListSelectionListener(editActiveStyleAction);
139 JButton edit = new JButton(editActiveStyleAction);
140
141 RemoveActiveStylesAction removeActiveStylesAction = new RemoveActiveStylesAction();
142 tblActiveStyles.getSelectionModel().addListSelectionListener(removeActiveStylesAction);
143 tblActiveStyles.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE,0), "delete");
144 tblActiveStyles.getActionMap().put("delete", removeActiveStylesAction);
145 JButton delete = new JButton(removeActiveStylesAction);
146
147 ActivateStylesAction activateStylesAction = new ActivateStylesAction();
148 lstAvailableStyles.addListSelectionListener(activateStylesAction);
149 JButton copy = new JButton(activateStylesAction);
150
151 JButton update = new JButton(new ReloadStylesAction(availableStylesUrl));
152
153 setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
154 setLayout(new GridBagLayout());
155 add(new JLabel(tr("Active styles")), GBC.eol().insets(5, 5, 5, 0));
156 JScrollPane sp;
157 add(sp = new JScrollPane(tblActiveStyles), GBC.eol().insets(5, 0, 5, 0).fill(GBC.BOTH));
158 sp.setColumnHeaderView(null);
159 JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
160 add(buttonPanel, GBC.eol().insets(5, 0, 5, 5).fill(GBC.HORIZONTAL));
161 buttonPanel.add(add, GBC.std().insets(0, 5, 0, 0));
162 buttonPanel.add(edit, GBC.std().insets(5, 5, 5, 0));
163 buttonPanel.add(delete, GBC.std().insets(0, 5, 5, 0));
164 buttonPanel.add(copy, GBC.std().insets(0, 5, 5, 0));
165 add(new JLabel(tr("Available styles (from {0})", availableStylesUrl)), GBC.eol().insets(5, 5, 5, 0));
166 add(new JScrollPane(lstAvailableStyles), GBC.eol().insets(5, 0, 5, 0).fill(GBC.BOTH));
167 buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
168 add(buttonPanel, GBC.eol().insets(5, 0, 5, 5).fill(GBC.HORIZONTAL));
169 buttonPanel.add(update, GBC.std().insets(0, 5, 0, 0));
170 if (tblIconPaths != null) {
171 add(new JLabel(tr("Icon paths")), GBC.eol().insets(5, -5, 5, 0));
172 add(sp = new JScrollPane(tblIconPaths), GBC.eol().insets(5, 0, 5, 0).fill(GBC.BOTH));
173 sp.setColumnHeaderView(null);
174 buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
175 add(buttonPanel, GBC.eol().insets(5, 0, 5, 5).fill(GBC.HORIZONTAL));
176 buttonPanel.add(iconadd);
177 buttonPanel.add(iconedit);
178 buttonPanel.add(icondelete);
179 }
180 }
181
182 public boolean finish() {
183 boolean changed = false;
184 List<String> activeStyles = activeStylesModel.getStyles();
185
186 if (activeStyles.size() > 0) {
187 if (Main.pref.putCollection(pref, activeStyles)) {
188 changed = true;
189 }
190 } else if (Main.pref.putCollection(pref, null)) {
191 changed = true;
192 }
193
194 if (tblIconPaths != null) {
195 List<String> iconPaths = iconPathsModel.getIconPaths();
196
197 if (!iconPaths.isEmpty()) {
198 if (Main.pref.putCollection(iconpref, iconPaths)) {
199 changed = true;
200 }
201 } else if (Main.pref.putCollection(iconpref, null)) {
202 changed = true;
203 }
204 }
205 return changed;
206 }
207
208 protected void reloadAvailableStyles(String url) {
209 Main.worker.submit(new StyleSourceLoader(url));
210 }
211
212 public void initiallyLoadAvailableStyles() {
213 if (!stylesInitiallyLoaded) {
214 reloadAvailableStyles(this.availableStylesUrl);
215 }
216 stylesInitiallyLoaded = true;
217 }
218
219 static class AvailableStylesListModel extends DefaultListModel {
220 private ArrayList<StyleSourceInfo> data;
221 private DefaultListSelectionModel selectionModel;
222
223 public AvailableStylesListModel(DefaultListSelectionModel selectionModel) {
224 data = new ArrayList<StyleSourceInfo>();
225 this.selectionModel = selectionModel;
226 }
227
228 public void setStyleSources(List<StyleSourceInfo> styleSources) {
229 data.clear();
230 if (styleSources != null) {
231 data.addAll(styleSources);
232 }
233 fireContentsChanged(this, 0, data.size());
234 }
235
236 @Override
237 public Object getElementAt(int index) {
238 return data.get(index);
239 }
240
241 @Override
242 public int getSize() {
243 if (data == null) return 0;
244 return data.size();
245 }
246
247 public void deleteSelected() {
248 Iterator<StyleSourceInfo> it = data.iterator();
249 int i=0;
250 while(it.hasNext()) {
251 it.next();
252 if (selectionModel.isSelectedIndex(i)) {
253 it.remove();
254 }
255 i++;
256 }
257 fireContentsChanged(this, 0, data.size());
258 }
259
260 public List<StyleSourceInfo> getSelected() {
261 ArrayList<StyleSourceInfo> ret = new ArrayList<StyleSourceInfo>();
262 for(int i=0; i<data.size();i++) {
263 if (selectionModel.isSelectedIndex(i)) {
264 ret.add(data.get(i));
265 }
266 }
267 return ret;
268 }
269 }
270
271 static class ActiveStylesModel extends AbstractTableModel {
272 private ArrayList<String> data;
273 private DefaultListSelectionModel selectionModel;
274
275 public ActiveStylesModel(DefaultListSelectionModel selectionModel) {
276 this.selectionModel = selectionModel;
277 this.data = new ArrayList<String>();
278 }
279
280 public int getColumnCount() {
281 return 1;
282 }
283
284 public int getRowCount() {
285 return data == null ? 0 : data.size();
286 }
287
288 public Object getValueAt(int rowIndex, int columnIndex) {
289 return data.get(rowIndex);
290 }
291
292 @Override
293 public boolean isCellEditable(int rowIndex, int columnIndex) {
294 return true;
295 }
296
297 @Override
298 public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
299 updateStyle(rowIndex, (String)aValue);
300 }
301
302 public void setActiveStyles(Collection<String> styles) {
303 data.clear();
304 if (styles !=null) {
305 data.addAll(styles);
306 }
307 sort();
308 fireTableDataChanged();
309 }
310
311 public void addStyle(String style) {
312 if (style == null) return;
313 data.add(style);
314 sort();
315 fireTableDataChanged();
316 int idx = data.indexOf(style);
317 if (idx >= 0) {
318 selectionModel.setSelectionInterval(idx, idx);
319 }
320 }
321
322 public void updateStyle(int pos, String style) {
323 if (style == null) return;
324 if (pos < 0 || pos >= getRowCount()) return;
325 data.set(pos, style);
326 sort();
327 fireTableDataChanged();
328 int idx = data.indexOf(style);
329 if (idx >= 0) {
330 selectionModel.setSelectionInterval(idx, idx);
331 }
332 }
333
334 public void removeSelected() {
335 Iterator<String> it = data.iterator();
336 int i=0;
337 while(it.hasNext()) {
338 it.next();
339 if (selectionModel.isSelectedIndex(i)) {
340 it.remove();
341 }
342 i++;
343 }
344 fireTableDataChanged();
345 }
346
347 protected void sort() {
348 Collections.sort(
349 data,
350 new Comparator<String>() {
351 public int compare(String o1, String o2) {
352 if (o1.equals("") && o2.equals(""))
353 return 0;
354 if (o1.equals("")) return 1;
355 if (o2.equals("")) return -1;
356 return o1.compareTo(o2);
357 }
358 }
359 );
360 }
361
362 public void addStylesFromSources(List<StyleSourceInfo> sources) {
363 if (sources == null) return;
364 for (StyleSourceInfo info: sources) {
365 data.add(info.url);
366 }
367 sort();
368 fireTableDataChanged();
369 selectionModel.clearSelection();
370 for (StyleSourceInfo info: sources) {
371 int pos = data.indexOf(info.url);
372 if (pos >=0) {
373 selectionModel.addSelectionInterval(pos, pos);
374 }
375 }
376 }
377
378 public List<String> getStyles() {
379 return new ArrayList<String>(data);
380 }
381
382 public String getStyle(int pos) {
383 return data.get(pos);
384 }
385 }
386
387 public static class StyleSourceInfo {
388 String version;
389 String name;
390 String url;
391 String author;
392 String link;
393 String description;
394 String shortdescription;
395
396 public StyleSourceInfo(String name, String url) {
397 this.name = name;
398 this.url = url;
399 version = author = link = description = shortdescription = null;
400 }
401
402 public String getName() {
403 return shortdescription == null ? name : shortdescription;
404 }
405
406 public String getTooltip() {
407 String s = tr("Short Description: {0}", getName()) + "<br>" + tr("URL: {0}", url);
408 if (author != null) {
409 s += "<br>" + tr("Author: {0}", author);
410 }
411 if (link != null) {
412 s += "<br>" + tr("Webpage: {0}", link);
413 }
414 if (description != null) {
415 s += "<br>" + tr("Description: {0}", description);
416 }
417 if (version != null) {
418 s += "<br>" + tr("Version: {0}", version);
419 }
420 return "<html>" + s + "</html>";
421 }
422
423 @Override
424 public String toString() {
425 return getName() + " (" + url + ")";
426 }
427 }
428
429 class NewActiveStyleAction extends AbstractAction {
430 public NewActiveStyleAction() {
431 putValue(NAME, tr("New"));
432 putValue(SHORT_DESCRIPTION, tr("Add a filename or an URL of an active style"));
433 putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
434 }
435
436 public void actionPerformed(ActionEvent e) {
437 activeStylesModel.addStyle("");
438 tblActiveStyles.requestFocusInWindow();
439 tblActiveStyles.editCellAt(activeStylesModel.getRowCount()-1, 0);
440 }
441 }
442
443 class RemoveActiveStylesAction extends AbstractAction implements ListSelectionListener {
444
445 public RemoveActiveStylesAction() {
446 putValue(NAME, tr("Remove"));
447 putValue(SHORT_DESCRIPTION, tr("Remove the selected styles from the list of active styles"));
448 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
449 updateEnabledState();
450 }
451
452 protected void updateEnabledState() {
453 setEnabled(tblActiveStyles.getSelectedRowCount() > 0);
454 }
455
456 public void valueChanged(ListSelectionEvent e) {
457 updateEnabledState();
458 }
459
460 public void actionPerformed(ActionEvent e) {
461 activeStylesModel.removeSelected();
462 }
463 }
464
465 class EditActiveStyleAction extends AbstractAction implements ListSelectionListener {
466 public EditActiveStyleAction() {
467 putValue(NAME, tr("Edit"));
468 putValue(SHORT_DESCRIPTION, tr("Edit the filename or URL for the selected active style"));
469 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
470 updateEnabledState();
471 }
472
473 protected void updateEnabledState() {
474 setEnabled(tblActiveStyles.getSelectedRowCount() == 1);
475 }
476
477 public void valueChanged(ListSelectionEvent e) {
478 updateEnabledState();
479 }
480
481 public void actionPerformed(ActionEvent e) {
482 int pos = tblActiveStyles.getSelectedRow();
483 tblActiveStyles.editCellAt(pos, 0);
484 }
485 }
486
487 class ActivateStylesAction extends AbstractAction implements ListSelectionListener {
488 public ActivateStylesAction() {
489 putValue(NAME, tr("Activate"));
490 putValue(SHORT_DESCRIPTION, tr("Add the selected available styles to the list of active styles"));
491 putValue(SMALL_ICON, ImageProvider.get("preferences", "activatestyle"));
492 updateEnabledState();
493 }
494
495 protected void updateEnabledState() {
496 setEnabled(lstAvailableStyles.getSelectedIndices().length > 0);
497 }
498
499 public void valueChanged(ListSelectionEvent e) {
500 updateEnabledState();
501 }
502
503 public void actionPerformed(ActionEvent e) {
504 List<StyleSourceInfo> styleSources = availableStylesModel.getSelected();
505 activeStylesModel.addStylesFromSources(styleSources);
506 }
507 }
508
509 class ReloadStylesAction extends AbstractAction {
510 private String url;
511 public ReloadStylesAction(String url) {
512 putValue(NAME, tr("Reload"));
513 putValue(SHORT_DESCRIPTION, tr("Reloads the list of available styles from ''{0}''", url));
514 putValue(SMALL_ICON, ImageProvider.get("download"));
515 this.url = url;
516 }
517
518 public void actionPerformed(ActionEvent e) {
519 MirroredInputStream.cleanup(url);
520 reloadAvailableStyles(url);
521 }
522 }
523
524 static class IconPathTableModel extends AbstractTableModel {
525 private ArrayList<String> data;
526 private DefaultListSelectionModel selectionModel;
527
528 public IconPathTableModel(DefaultListSelectionModel selectionModel) {
529 this.selectionModel = selectionModel;
530 this.data = new ArrayList<String>();
531 }
532
533 public int getColumnCount() {
534 return 1;
535 }
536
537 public int getRowCount() {
538 return data == null ? 0 : data.size();
539 }
540
541 public Object getValueAt(int rowIndex, int columnIndex) {
542 return data.get(rowIndex);
543 }
544
545 @Override
546 public boolean isCellEditable(int rowIndex, int columnIndex) {
547 return true;
548 }
549
550 @Override
551 public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
552 updatePath(rowIndex, (String)aValue);
553 }
554
555 public void setIconPaths(Collection<String> styles) {
556 data.clear();
557 if (styles !=null) {
558 data.addAll(styles);
559 }
560 sort();
561 fireTableDataChanged();
562 }
563
564 public void addPath(String path) {
565 if (path == null) return;
566 data.add(path);
567 sort();
568 fireTableDataChanged();
569 int idx = data.indexOf(path);
570 if (idx >= 0) {
571 selectionModel.setSelectionInterval(idx, idx);
572 }
573 }
574
575 public void updatePath(int pos, String path) {
576 if (path == null) return;
577 if (pos < 0 || pos >= getRowCount()) return;
578 data.set(pos, path);
579 sort();
580 fireTableDataChanged();
581 int idx = data.indexOf(path);
582 if (idx >= 0) {
583 selectionModel.setSelectionInterval(idx, idx);
584 }
585 }
586
587 public void removeSelected() {
588 Iterator<String> it = data.iterator();
589 int i=0;
590 while(it.hasNext()) {
591 it.next();
592 if (selectionModel.isSelectedIndex(i)) {
593 it.remove();
594 }
595 i++;
596 }
597 fireTableDataChanged();
598 selectionModel.clearSelection();
599 }
600
601 protected void sort() {
602 Collections.sort(
603 data,
604 new Comparator<String>() {
605 public int compare(String o1, String o2) {
606 if (o1.equals("") && o2.equals(""))
607 return 0;
608 if (o1.equals("")) return 1;
609 if (o2.equals("")) return -1;
610 return o1.compareTo(o2);
611 }
612 }
613 );
614 }
615
616 public List<String> getIconPaths() {
617 return new ArrayList<String>(data);
618 }
619 }
620
621 class NewIconPathAction extends AbstractAction {
622 public NewIconPathAction() {
623 putValue(NAME, tr("New"));
624 putValue(SHORT_DESCRIPTION, tr("Add a new icon path"));
625 putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
626 }
627
628 public void actionPerformed(ActionEvent e) {
629 iconPathsModel.addPath("");
630 tblIconPaths.editCellAt(iconPathsModel.getRowCount() -1,0);
631 }
632 }
633
634 class RemoveIconPathAction extends AbstractAction implements ListSelectionListener {
635 public RemoveIconPathAction() {
636 putValue(NAME, tr("Remove"));
637 putValue(SHORT_DESCRIPTION, tr("Remove the selected icon paths"));
638 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
639 updateEnabledState();
640 }
641
642 protected void updateEnabledState() {
643 setEnabled(tblIconPaths.getSelectedRowCount() > 0);
644 }
645
646 public void valueChanged(ListSelectionEvent e) {
647 updateEnabledState();
648 }
649
650 public void actionPerformed(ActionEvent e) {
651 iconPathsModel.removeSelected();
652 }
653 }
654
655 class EditIconPathAction extends AbstractAction implements ListSelectionListener {
656 public EditIconPathAction() {
657 putValue(NAME, tr("Edit"));
658 putValue(SHORT_DESCRIPTION, tr("Edit the selected icon path"));
659 putValue(SMALL_ICON, ImageProvider.get("dialogs", "edit"));
660 updateEnabledState();
661 }
662
663 protected void updateEnabledState() {
664 setEnabled(tblIconPaths.getSelectedRowCount() == 1);
665 }
666
667 public void valueChanged(ListSelectionEvent e) {
668 updateEnabledState();
669 }
670
671 public void actionPerformed(ActionEvent e) {
672 int row = tblIconPaths.getSelectedRow();
673 tblIconPaths.editCellAt(row, 0);
674 }
675 }
676
677 static class StyleSourceCellRenderer extends JLabel implements ListCellRenderer {
678 public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected,
679 boolean cellHasFocus) {
680 String s = value.toString();
681 setText(s);
682 if (isSelected) {
683 setBackground(list.getSelectionBackground());
684 setForeground(list.getSelectionForeground());
685 } else {
686 setBackground(list.getBackground());
687 setForeground(list.getForeground());
688 }
689 setEnabled(list.isEnabled());
690 setFont(list.getFont());
691 setOpaque(true);
692 setToolTipText(((StyleSourceInfo) value).getTooltip());
693 return this;
694 }
695 }
696
697 class StyleSourceLoader extends PleaseWaitRunnable {
698 private String url;
699 private BufferedReader reader;
700 private boolean canceled;
701
702 public StyleSourceLoader(String url) {
703 super(tr("Loading style sources from ''{0}''", url));
704 this.url = url;
705 }
706
707 @Override
708 protected void cancel() {
709 canceled = true;
710 if (reader!= null) {
711 try {
712 reader.close();
713 } catch(IOException e) {
714 // ignore
715 }
716 }
717 }
718
719 @Override
720 protected void finish() {}
721
722 protected void warn(Exception e) {
723 String emsg = e.getMessage() != null ? e.getMessage() : e.toString();
724 emsg = emsg.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
725 String msg = tr("<html>Failed to load the list of style sources from<br>"
726 + "''{0}''.<br>"
727 + "<br>"
728 + "Details (untranslated):<br>{1}</html>",
729 url, emsg
730 );
731
732 HelpAwareOptionPane.showOptionDialog(
733 Main.parent,
734 msg,
735 tr("Error"),
736 JOptionPane.ERROR_MESSAGE,
737 ht("Preferences/Styles#FailedToLoadStyleSources")
738 );
739 }
740
741 @Override
742 protected void realRun() throws SAXException, IOException, OsmTransferException {
743 LinkedList<StyleSourceInfo> styles = new LinkedList<StyleSourceInfo>();
744 String lang = LanguageInfo.getLanguageCodeXML();
745 try {
746 MirroredInputStream stream = new MirroredInputStream(url);
747 InputStreamReader r;
748 try {
749 r = new InputStreamReader(stream, "UTF-8");
750 } catch (UnsupportedEncodingException e) {
751 r = new InputStreamReader(stream);
752 }
753 BufferedReader reader = new BufferedReader(r);
754
755 String line;
756 StyleSourceInfo last = null;
757
758 while ((line = reader.readLine()) != null && !canceled) {
759 if (line.trim().equals("")) {
760 continue; // skip empty lines
761 }
762 if (line.startsWith("\t")) {
763 Matcher m = Pattern.compile("^\t([^:]+): *(.+)$").matcher(line);
764 if (! m.matches()) {
765 System.err.println(tr("Warning: illegal format of entry in style list ''{0}''. Got ''{1}''", url, line));
766 continue;
767 }
768 if (last != null) {
769 String key = m.group(1);
770 String value = m.group(2);
771 if ("author".equals(key) && last.author == null) {
772 last.author = value;
773 } else if ("version".equals(key)) {
774 last.version = value;
775 } else if ("link".equals(key) && last.link == null) {
776 last.link = value;
777 } else if ("description".equals(key) && last.description == null) {
778 last.description = value;
779 } else if ("shortdescription".equals(key) && last.shortdescription == null) {
780 last.shortdescription = value;
781 } else if ((lang + "author").equals(key)) {
782 last.author = value;
783 } else if ((lang + "link").equals(key)) {
784 last.link = value;
785 } else if ((lang + "description").equals(key)) {
786 last.description = value;
787 } else if ((lang + "shortdescription").equals(key)) {
788 last.shortdescription = value;
789 }
790 }
791 } else {
792 last = null;
793 Matcher m = Pattern.compile("^(.+);(.+)$").matcher(line);
794 if (m.matches()) {
795 styles.add(last = new StyleSourceInfo(m.group(1), m.group(2)));
796 } else {
797 System.err.println(tr("Warning: illegal format of entry in style list ''{0}''. Got ''{1}''", url, line));
798 }
799 }
800 }
801 } catch (Exception e) {
802 if (canceled)
803 // ignore the exception and return
804 return;
805 warn(e);
806 return;
807 }
808 availableStylesModel.setStyleSources(styles);
809 }
810 }
811
812 class FileOrUrlCellEditor extends JPanel implements TableCellEditor {
813 private JTextField tfFileName;
814 private CopyOnWriteArrayList<CellEditorListener> listeners;
815 private String value;
816 private JFileChooser fileChooser;
817
818 protected JFileChooser getFileChooser() {
819 if (fileChooser == null) {
820 this.fileChooser = new JFileChooser();
821 }
822 return fileChooser;
823 }
824
825 /**
826 * build the GUI
827 */
828 protected void build() {
829 setLayout(new GridBagLayout());
830 GridBagConstraints gc = new GridBagConstraints();
831 gc.gridx = 0;
832 gc.gridy = 0;
833 gc.fill = GridBagConstraints.BOTH;
834 gc.weightx = 1.0;
835 gc.weighty = 1.0;
836 add(tfFileName = new JTextField(), gc);
837
838 gc.gridx = 1;
839 gc.gridy = 0;
840 gc.fill = GridBagConstraints.BOTH;
841 gc.weightx = 0.0;
842 gc.weighty = 1.0;
843 add(new JButton(new LaunchFileChooserAction()));
844
845 tfFileName.addFocusListener(
846 new FocusAdapter() {
847 @Override
848 public void focusGained(FocusEvent e) {
849 tfFileName.selectAll();
850 }
851 }
852 );
853 }
854
855 public FileOrUrlCellEditor() {
856 listeners = new CopyOnWriteArrayList<CellEditorListener>();
857 build();
858 }
859
860 public void addCellEditorListener(CellEditorListener l) {
861 if (l != null) {
862 listeners.addIfAbsent(l);
863 }
864 }
865
866 protected void fireEditingCanceled() {
867 for (CellEditorListener l: listeners) {
868 l.editingCanceled(new ChangeEvent(this));
869 }
870 }
871
872 protected void fireEditingStopped() {
873 for (CellEditorListener l: listeners) {
874 l.editingStopped(new ChangeEvent(this));
875 }
876 }
877
878 public void cancelCellEditing() {
879 fireEditingCanceled();
880 }
881
882 public Object getCellEditorValue() {
883 return value;
884 }
885
886 public boolean isCellEditable(EventObject anEvent) {
887 if (anEvent instanceof MouseEvent)
888 return ((MouseEvent)anEvent).getClickCount() >= 2;
889 return true;
890 }
891
892 public void removeCellEditorListener(CellEditorListener l) {
893 listeners.remove(l);
894 }
895
896 public boolean shouldSelectCell(EventObject anEvent) {
897 return true;
898 }
899
900 public boolean stopCellEditing() {
901 value = tfFileName.getText();
902 fireEditingStopped();
903 return true;
904 }
905
906 public void setInitialValue(String initialValue) {
907 this.value = initialValue;
908 if (initialValue == null) {
909 this.tfFileName.setText("");
910 } else {
911 this.tfFileName.setText(initialValue);
912 }
913 }
914
915 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
916 setInitialValue((String)value);
917 tfFileName.selectAll();
918 return this;
919 }
920
921 class LaunchFileChooserAction extends AbstractAction {
922 public LaunchFileChooserAction() {
923 putValue(NAME, "...");
924 putValue(SHORT_DESCRIPTION, tr("Launch a file chooser to select a file"));
925 }
926
927 protected void prepareFileChooser(String url, JFileChooser fc) {
928 if (url == null || url.trim().length() == 0) return;
929 URL sourceUrl = null;
930 try {
931 sourceUrl = new URL(url);
932 } catch(MalformedURLException e) {
933 File f = new File(url);
934 if (f.isFile()) {
935 f = f.getParentFile();
936 }
937 if (f != null) {
938 fc.setCurrentDirectory(f);
939 }
940 return;
941 }
942 if (sourceUrl.getProtocol().startsWith("file")) {
943 File f = new File(sourceUrl.getPath());
944 if (f.isFile()) {
945 f = f.getParentFile();
946 }
947 if (f != null) {
948 fc.setCurrentDirectory(f);
949 }
950 }
951 }
952
953 public void actionPerformed(ActionEvent e) {
954 JFileChooser fc = getFileChooser();
955 prepareFileChooser(tfFileName.getText(), fc);
956 int ret = fc.showOpenDialog(JOptionPane.getFrameForComponent(StyleSourceEditor.this));
957 if (ret != JFileChooser.APPROVE_OPTION)
958 return;
959 tfFileName.setText(fc.getSelectedFile().toString());
960 }
961 }
962 }
963}
Note: See TracBrowser for help on using the repository browser.