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

Last change on this file since 3028 was 3028, checked in by Gubaer, 17 years ago

fixed #4484: Tagging preset file does not save if not deselected

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