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

Last change on this file since 2400 was 2400, checked in by Gubaer, 15 years ago

fixed #3800: UI: improve mappaint styles and presets tabs in preferences

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