Merge branch 'JAL-3927_ws_preferences_dialog_fix' into alpha/JAL-3066_Jalview_212_sli...
[jalview.git] / src / jalview / gui / SlivkaPreferences.java
1 package jalview.gui;
2
3 import jalview.bin.Cache;
4 import jalview.util.MessageManager;
5 import jalview.ws.WSDiscovererI;
6 import jalview.ws.slivkaws.SlivkaWSDiscoverer;
7
8 import java.awt.BorderLayout;
9 import java.awt.Color;
10 import java.awt.Component;
11 import java.awt.Dimension;
12 import java.awt.Font;
13 import java.awt.event.ActionEvent;
14 import java.awt.event.ActionListener;
15 import java.awt.event.MouseAdapter;
16 import java.awt.event.MouseEvent;
17 import java.awt.event.MouseListener;
18 import java.net.MalformedURLException;
19 import java.net.URL;
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.Map;
23 import java.util.NoSuchElementException;
24 import java.util.concurrent.CompletableFuture;
25
26 import javax.swing.BorderFactory;
27 import javax.swing.Box;
28 import javax.swing.BoxLayout;
29 import javax.swing.JButton;
30 import javax.swing.JOptionPane;
31 import javax.swing.JPanel;
32 import javax.swing.JProgressBar;
33 import javax.swing.JScrollPane;
34 import javax.swing.JTable;
35 import javax.swing.SwingUtilities;
36 import javax.swing.UIManager;
37 import javax.swing.table.AbstractTableModel;
38 import javax.swing.table.DefaultTableCellRenderer;
39
40 @SuppressWarnings("serial")
41 public class SlivkaPreferences extends JPanel
42 {
43   {
44     setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
45     setPreferredSize(new Dimension(500, 450));
46   }
47
48   WSDiscovererI discoverer;
49
50   private final ArrayList<String> urls = new ArrayList<>();
51
52   private final Map<String, Integer> statuses = new HashMap<>();
53
54   private final AbstractTableModel urlTableModel = new AbstractTableModel()
55   {
56     final String[] columnNames = { "Service URL", "Status" };
57
58     @Override
59     public String getColumnName(int col)
60     {
61       return columnNames[col];
62     }
63
64     @Override
65     public Object getValueAt(int rowIndex, int columnIndex)
66     {
67       switch (columnIndex)
68       {
69       case 0:
70         return urls.get(rowIndex);
71       case 1:
72         return statuses.getOrDefault(urls.get(rowIndex), WSDiscovererI.STATUS_UNKNOWN);
73       default:
74         throw new NoSuchElementException();
75       }
76     }
77
78     @Override
79     public int getRowCount()
80     {
81       return urls.size();
82     }
83
84     @Override
85     public int getColumnCount()
86     {
87       return 2;
88     }
89   };
90
91   private class WSStatusCellRenderer extends DefaultTableCellRenderer
92   {
93     @Override
94     public Component getTableCellRendererComponent(JTable table,
95         Object value, boolean isSelected, boolean hasFocus, int row,
96         int column)
97     {
98       setHorizontalAlignment(CENTER);
99       super.getTableCellRendererComponent(table, "\u25CF", isSelected,
100           hasFocus, row, column);
101       switch ((Integer) value)
102       {
103       case WSDiscovererI.STATUS_NO_SERVICES:
104         setForeground(Color.ORANGE);
105         break;
106       case WSDiscovererI.STATUS_OK:
107         setForeground(Color.GREEN);
108         break;
109       case WSDiscovererI.STATUS_INVALID:
110         setForeground(Color.RED);
111         break;
112       case WSDiscovererI.STATUS_UNKNOWN:
113       default:
114         setForeground(Color.LIGHT_GRAY);
115       }
116       return this;
117     }
118   }
119
120   private JTable urlListTable = new JTable(urlTableModel);
121   {
122     urlListTable.getColumnModel().getColumn(1).setMaxWidth(60);
123     urlListTable.getColumnModel().getColumn(1)
124         .setCellRenderer(new WSStatusCellRenderer());
125   }
126
127   // URL control panel buttons
128   JButton newWsUrl = new JButton(
129       MessageManager.getString("label.new_service_url"));
130
131   JButton editWsUrl = new JButton(
132       MessageManager.getString("label.edit_service_url"));
133
134   JButton deleteWsUrl = new JButton(
135       MessageManager.getString("label.delete_service_url"));
136
137   JButton moveUrlUp = new JButton(
138       MessageManager.getString("action.move_up"));
139
140   JButton moveUrlDown = new JButton(
141       MessageManager.getString("action.move_down"));
142
143   private String showEditUrlDialog(String oldUrl)
144   {
145     String input = (String) JvOptionPane
146         .showInternalInputDialog(
147             this,
148             MessageManager.getString("label.url:"),
149             UIManager.getString("OptionPane.inputDialogTitle", MessageManager.getLocale()),
150             JOptionPane.QUESTION_MESSAGE,
151             null,
152             null,
153             oldUrl);
154     if (input == null)
155     {
156       return null;
157     }
158     try
159     {
160       new URL(input);
161     } catch (MalformedURLException ex)
162     {
163       JvOptionPane.showInternalMessageDialog(this,
164           MessageManager.getString("label.invalid_url"),
165           UIManager.getString("OptionPane.messageDialogTitle",
166               MessageManager.getLocale()),
167           JOptionPane.WARNING_MESSAGE);
168       return null;
169     }
170     return input;
171   }
172
173   // Button Action Listeners
174   private ActionListener newUrlAction = (ActionEvent e) -> {
175     final String input = showEditUrlDialog("");
176     if (input != null)
177     {
178       urls.add(input);
179       reloadStatusForUrl(input);
180       urlTableModel.fireTableRowsInserted(urls.size(), urls.size());
181       discoverer.setServiceUrls(urls);
182     }
183   };
184
185   private ActionListener editUrlAction = (ActionEvent e) -> {
186     final int i = urlListTable.getSelectedRow();
187     if (i >= 0)
188     {
189       final String input = showEditUrlDialog(urls.get(i));
190       if (input != null)
191       {
192         urls.set(i, input);
193         statuses.remove(input);
194         reloadStatusForUrl(input);
195         urlTableModel.fireTableRowsUpdated(i, i);
196         discoverer.setServiceUrls(urls);
197       }
198     }
199   };
200
201   private ActionListener deleteUrlAction = (ActionEvent e) -> {
202     final int i = urlListTable.getSelectedRow();
203     if (i >= 0)
204     {
205       urls.remove(i);
206       statuses.remove(i);
207       urlTableModel.fireTableRowsDeleted(i, i);
208       discoverer.setServiceUrls(urls);
209     }
210   };
211
212   private ActionListener moveUrlUpAction = (ActionEvent e) -> {
213     final int i = urlListTable.getSelectedRow();
214     if (i > 0)
215     {
216       moveTableRow(i, i - 1);
217       discoverer.setServiceUrls(urls);
218     }
219   };
220
221   private ActionListener moveUrlDownAction = (ActionEvent e) -> {
222     final int i = urlListTable.getSelectedRow();
223     if (i >= 0 && i < urls.size() - 1)
224     {
225       moveTableRow(i, i + 1);
226       discoverer.setServiceUrls(urls);
227     }
228   };
229
230   private MouseListener tableClickListener = new MouseAdapter()
231   {
232     final ActionEvent actionEvent = new ActionEvent(urlListTable,
233         ActionEvent.ACTION_PERFORMED, "edit");
234
235     @Override
236     public void mouseClicked(MouseEvent e)
237     {
238       if (e.getClickCount() > 1)
239       {
240         editUrlAction.actionPerformed(actionEvent);
241       }
242     }
243   };
244
245   // Setting up URL list Pane
246   {
247     Font font = new Font("Verdana", Font.PLAIN, 10);
248     JPanel urlPaneContainer = new JPanel(new BorderLayout(5, 5));
249     urlPaneContainer.setBorder(BorderFactory.createCompoundBorder(
250         BorderFactory.createTitledBorder(BorderFactory.createEtchedBorder(),
251             "Slivka Web Services"),
252         BorderFactory.createEmptyBorder(10, 5, 5, 5)));
253
254     newWsUrl.setFont(font);
255     editWsUrl.setFont(font);
256     deleteWsUrl.setFont(font);
257     moveUrlUp.setFont(font);
258     moveUrlDown.setFont(font);
259     JPanel editContainer = new JPanel();
260     editContainer.add(newWsUrl);
261     editContainer.add(editWsUrl);
262     editContainer.add(deleteWsUrl);
263     urlPaneContainer.add(editContainer, BorderLayout.PAGE_END);
264
265     JPanel moveContainer = new JPanel();
266     moveContainer
267         .setLayout(new BoxLayout(moveContainer, BoxLayout.PAGE_AXIS));
268     moveContainer.add(moveUrlUp);
269     moveContainer.add(Box.createRigidArea(new Dimension(0, 5)));
270     moveContainer.add(moveUrlDown);
271     urlPaneContainer.add(moveContainer, BorderLayout.LINE_START);
272
273     urlPaneContainer.add(new JScrollPane(urlListTable),
274         BorderLayout.CENTER);
275     this.add(urlPaneContainer);
276
277     // Connecting action listeners
278     urlListTable.addMouseListener(tableClickListener);
279     newWsUrl.addActionListener(newUrlAction);
280     editWsUrl.addActionListener(editUrlAction);
281     deleteWsUrl.addActionListener(deleteUrlAction);
282     moveUrlUp.addActionListener(moveUrlUpAction);
283     moveUrlDown.addActionListener(moveUrlDownAction);
284   }
285
286   private void moveTableRow(int fromIndex, int toIndex)
287   {
288     String url = urls.get(fromIndex);
289     int status = statuses.get(fromIndex);
290     urls.set(fromIndex, urls.get(toIndex));
291     urls.set(toIndex, url);
292     if (urlListTable.getSelectedRow() == fromIndex)
293     {
294       urlListTable.setRowSelectionInterval(toIndex, toIndex);
295     }
296     int firstRow = Math.min(toIndex, fromIndex);
297     int lastRow = Math.max(fromIndex, toIndex);
298     urlTableModel.fireTableRowsUpdated(firstRow, lastRow);
299   }
300
301   // Discoverer reloading buttons
302   JButton refreshServices = new JButton(
303       MessageManager.getString("action.refresh_services"));
304
305   JButton resetServices = new JButton(
306       MessageManager.getString("action.reset_services"));
307
308   JProgressBar progressBar = new JProgressBar();
309
310   // Discoverer buttons action listeners
311   private ActionListener refreshServicesAction = (ActionEvent e) -> {
312     progressBar.setVisible(true);
313     Cache.log.info("Requesting service reload");
314     discoverer.startDiscoverer().handle((_discoverer, exception) -> {
315       if (exception == null)
316       {
317         Cache.log.info("Reloading done");
318       }
319       else
320       {
321         Cache.log.error("Reloading failed", exception);
322       }
323       SwingUtilities.invokeLater(() -> progressBar.setVisible(false));
324       return null;
325     });
326   };
327
328   private ActionListener resetServicesAction = (ActionEvent e) -> {
329     discoverer.setServiceUrls(null);
330     urls.clear();
331     statuses.clear();
332     urls.addAll(discoverer.getServiceUrls());
333     for (String url : urls)
334     {
335       reloadStatusForUrl(url);
336     }
337     urlTableModel.fireTableDataChanged();
338   };
339
340   {
341     Font font = new Font("Verdana", Font.PLAIN, 11);
342     refreshServices.setFont(font);
343     resetServices.setFont(font);
344     JPanel container = new JPanel();
345     container.add(refreshServices);
346     container.add(resetServices);
347     this.add(container);
348
349     // Connecting action listeners
350     refreshServices.addActionListener(refreshServicesAction);
351     resetServices.addActionListener(resetServicesAction);
352   }
353
354   {
355     progressBar.setVisible(false);
356     progressBar.setIndeterminate(true);
357     add(progressBar);
358   }
359
360   SlivkaPreferences()
361   {
362     // Initial URLs loading
363     discoverer = SlivkaWSDiscoverer.getInstance();
364     urls.addAll(discoverer.getServiceUrls());
365     for (String url : urls)
366     {
367       reloadStatusForUrl(url);
368     }
369   }
370
371   private void reloadStatusForUrl(String url)
372   {
373     CompletableFuture.supplyAsync(() -> discoverer.getServerStatusFor(url))
374         .thenAccept((status) -> {
375           statuses.put(url, status);
376           int row = urls.indexOf(url);
377           if (row >= 0)
378             urlTableModel.fireTableCellUpdated(row, 1);
379         });
380   }
381 }