# HG changeset patch # User Mario Torre # Date 1485270838 -3600 # Node ID 027f92962a8affeeefe55a62402b0f283e65d24a # Parent 0c7e9a8854c6e75a1d3bbc39f5d99f0ec783f47c New tabs for Thermostat review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-January/022010.html reviewed-by: almac diff -r 0c7e9a8854c6 -r 027f92962a8a client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/Tab.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/Tab.java Tue Jan 24 16:13:58 2017 +0100 @@ -0,0 +1,57 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.client.swing.internal; + +import com.redhat.thermostat.shared.locale.LocalizedString; + +import javax.swing.JComponent; + +/** + */ +public class Tab extends TabUI { + + private JComponent component; + + public Tab(JComponent component, LocalizedString name) { + super(name); + this.component = component; + } + + public JComponent getContent() { + return component; + } +} diff -r 0c7e9a8854c6 -r 027f92962a8a client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/TabModel.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/TabModel.java Tue Jan 24 16:13:58 2017 +0100 @@ -0,0 +1,82 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.client.swing.internal; + +import com.redhat.thermostat.client.swing.UIDefaults; +import com.redhat.thermostat.client.swing.internal.vmlist.UIDefaultsImpl; + +import java.awt.Color; + +/** + */ +class TabModel { + private TabUI tab; + private boolean selected; + private boolean hover; + + private UIDefaults defaults = UIDefaultsImpl.getInstance(); + + public TabModel(TabUI tab) { + this.tab = tab; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + if (selected || this.hover) { + tab.getTitle().setForeground((Color) defaults.getSelectedComponentFGColor()); + } else { + tab.getTitle().setForeground((Color) defaults.getSelectedComponentBGColor()); + } + this.selected = selected; + } + + public boolean isHover() { + return hover; + } + + public void setHover(boolean hover) { + if (this.selected || hover) { + tab.getTitle().setForeground((Color) defaults.getSelectedComponentFGColor()); + } else { + tab.getTitle().setForeground((Color) defaults.getSelectedComponentBGColor()); + } + this.hover = hover; + } +} diff -r 0c7e9a8854c6 -r 027f92962a8a client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/TabUI.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/TabUI.java Tue Jan 24 16:13:58 2017 +0100 @@ -0,0 +1,105 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.client.swing.internal; + +import com.redhat.thermostat.client.swing.GraphicsUtils; +import com.redhat.thermostat.client.swing.UIDefaults; +import com.redhat.thermostat.client.swing.components.ShadowLabel; +import com.redhat.thermostat.client.swing.internal.vmlist.UIDefaultsImpl; +import com.redhat.thermostat.shared.locale.LocalizedString; + +import javax.swing.JComponent; +import javax.swing.SwingConstants; +import javax.swing.border.EmptyBorder; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Graphics; +import java.awt.Graphics2D; + +/** + */ +class TabUI extends JComponent { + + private UIDefaults defaults = UIDefaultsImpl.getInstance(); + private LocalizedString name; + protected TabModel model; + private ShadowLabel label; + + TabUI(LocalizedString name) { + this.name = name; + model = new TabModel(this); + + setBorder(new EmptyBorder(5, 5, 5, 5)); + + setLayout(new BorderLayout()); + label = new ShadowLabel(name); + label.setFont(defaults.getDefaultFont()); + label.setForeground((Color) defaults.getSelectedComponentBGColor()); + label.setHorizontalAlignment(SwingConstants.CENTER); + add(label); + } + + public LocalizedString getTabName() { + return name; + } + + TabModel getModel() { + return model; + } + + ShadowLabel getTitle() { + return label; + } + + @Override + protected void paintComponent(Graphics g) { + + GraphicsUtils utils = GraphicsUtils.getInstance(); + Graphics2D graphics = utils.createAAGraphics(g); + + if (getModel().isSelected()) { + graphics.setPaint(defaults.getSelectedComponentBGColor()); + graphics.fillRoundRect(0, 0, getWidth(), getHeight(), 5, 5); + + } else if (getModel().isHover()) { + graphics.setPaint(defaults.getDecorationIconColor()); + graphics.fillRoundRect(0, 0, getWidth(), getHeight(), 5, 5); + } + + graphics.dispose(); + } +} diff -r 0c7e9a8854c6 -r 027f92962a8a client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/TabbedPane.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/TabbedPane.java Tue Jan 24 16:13:58 2017 +0100 @@ -0,0 +1,360 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.client.swing.internal; + +import com.redhat.thermostat.client.swing.UIDefaults; +import com.redhat.thermostat.client.swing.components.FontAwesomeIcon; +import com.redhat.thermostat.client.swing.components.GradientPanel; +import com.redhat.thermostat.client.swing.components.ThermostatPopupMenu; +import com.redhat.thermostat.client.swing.internal.vmlist.UIDefaultsImpl; +import com.redhat.thermostat.client.ui.Palette; + +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.SwingConstants; +import java.awt.BorderLayout; +import java.awt.CardLayout; +import java.awt.Color; +import java.awt.GridLayout; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + */ +public class TabbedPane extends JComponent { + + public static final String TABBED_PANE_ID = "_TabbedPane_"; + public static final String CONTROLS_ID = "_TabbedPane_Controls_"; + public static final String CONTENT_ID = "_TabbedPane_Content_"; + + private GradientPanel controls; + private JPanel contentPane; + + private List tabs; + private Map hiddenControls; + + private CardLayout contentPaneLayout; + + private TabController controller; + + private JLabel hiddenTabsControl; + private UIDefaults defaults = UIDefaultsImpl.getInstance(); + + @Override + public boolean isOptimizedDrawingEnabled() { + return false; + } + + public TabbedPane() { + + setName(TABBED_PANE_ID); + + controller = new TabController(); + + tabs = new ArrayList<>(); + hiddenControls = new HashMap<>(); + + setLayout(new BorderLayout()); + + this.controls = new GradientPanel(Color.WHITE, Palette.LIGHT_GRAY.getColor()); + controls.setLayout(new TabbedPaneControlPanelLayout()); + controls.setName(CONTROLS_ID); + + hiddenTabsControl = new JLabel(new FontAwesomeIcon('\uf0d7', 12, defaults.getSelectedComponentBGColor())); + hiddenTabsControl.setHorizontalAlignment(SwingConstants.CENTER); + hiddenTabsControl.setVerticalAlignment(SwingConstants.CENTER); + hiddenTabsControl.addMouseListener(controller.getHiddenTabsListener()); + controls.add(hiddenTabsControl); + + add(controls, BorderLayout.NORTH); + + contentPaneLayout = new CardLayout(); + + this.contentPane = new JPanel(); + this.contentPane.setBackground(Color.WHITE); + this.contentPane.setLayout(contentPaneLayout); + contentPane.setName(CONTENT_ID); + + add(contentPane, BorderLayout.CENTER); + } + + public void add(Tab content) { + add(content, tabs.size()); + } + + @Override + public void removeAll() { + controls.removeAll(); + controls.add(hiddenTabsControl); + + contentPane.removeAll(); + + for (Tab tab : tabs) { + tab.removeMouseListener(controller.getListener()); + tab.removeMouseMotionListener(controller.getListener()); + } + tabs.clear(); + hiddenControls.clear(); + + revalidate(); + repaint(); + } + + public void add(Tab content, int position) { + addDeferValidation(content, position); + setSelectedIndex(getSelectedIndex()); + + revalidate(); + repaint(); + } + + private void addDeferValidation(Tab tab, int position) { + tabs.add(position, tab); + + tab.addMouseListener(controller.getListener()); + tab.addMouseMotionListener(controller.getListener()); + + TabUI hiddenControl = new TabUI(tab.getTabName()); + hiddenControl.addMouseListener(controller.getListener()); + hiddenControl.addMouseMotionListener(controller.getListener()); + + hiddenControls.put(tab.getTabName().getContents(), hiddenControl); + + controls.add(tab); + contentPane.add(tab.getContent(), "" + position); + } + + public void add(List tabs) { + int position = tabs.size(); + for (Tab tab : tabs) { + addDeferValidation(tab, position++); + } + + revalidate(); + repaint(); + } + + public void remove(Tab tab) { + + tabs.remove(tab); + contentPane.remove(tab); + controls.remove(tab.getContent()); + + tab.removeMouseListener(controller.getListener()); + tab.removeMouseMotionListener(controller.getListener()); + + TabUI hiddenControl = hiddenControls.remove(tab.getTabName().getContents()); + hiddenControl.removeMouseListener(controller.getListener()); + hiddenControl.removeMouseMotionListener(controller.getListener()); + + revalidate(); + repaint(); + } + + public List getTabs() { + return tabs; + } + + public int getSelectedIndex() { + return controller.getSelected(); + } + + public void setSelectedIndex(int index) { + this.controller.setSelected(index); + } + + TabController getController() { + return controller; + } + + JComponent getHiddenTabsControl() { + return hiddenTabsControl; + } + + class TabController { + + private List hiddenTabs; + private int selected; + + private MouseAdapter adapter; + private MouseAdapter hiddenTabsAdapter; + private ThermostatPopupMenu menu; + + public TabController() { + + menu = new ThermostatPopupMenu(); + hiddenTabs = new ArrayList<>(); + + selected = -1; + + adapter = new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + + menu.setVisible(false); + + TabUI selectedTab = (TabUI) e.getSource(); + String name = selectedTab.getTabName().getContents(); + + for (TabUI tab : hiddenControls.values()) { + if (name.equals(tab.getTabName().getContents())) { + tab.getModel().setHover(false); + tab.getModel().setSelected(true); + tab.repaint(); + continue; + } + tab.getModel().setHover(false); + tab.getModel().setSelected(false); + tab.repaint(); + } + + int index = 0; + + for (TabUI tab : tabs) { + if (name.equals(tab.getTabName().getContents())) { + tab.getModel().setHover(false); + tab.getModel().setSelected(true); + tab.repaint(); + + selected = index; + contentPaneLayout.show(contentPane, "" + index); + continue; + } + index++; + tab.getModel().setHover(false); + tab.getModel().setSelected(false); + tab.repaint(); + } + } + + @Override + public void mouseEntered(MouseEvent e) { + TabUI hoveredTab = (TabUI) e.getSource(); + hoveredTab.getModel().setHover(true); + hoveredTab.repaint(); + + for (TabUI tab : tabs) { + if (hoveredTab.equals(tab)) { + continue; + } + tab.getModel().setHover(false); + tab.repaint(); + } + for (TabUI tab : hiddenControls.values()) { + if (hoveredTab.equals(tab)) { + continue; + } + tab.getModel().setHover(false); + tab.repaint(); + } + } + + @Override + public void mouseExited(MouseEvent e) { + TabUI tab = (TabUI) e.getSource(); + tab.getModel().setHover(false); + tab.repaint(); + } + }; + + hiddenTabsAdapter = new MouseAdapter() { + + @Override + public void mouseClicked(MouseEvent e) { + JPanel panel = new JPanel(); + panel.setLayout(new GridLayout(0, 1)); + for (Tab tab : hiddenTabs) { + panel.add(hiddenControls.get(tab.getTabName().getContents())); + } + menu.removeAll(); + menu.add(panel); + menu.show(hiddenTabsControl, e.getX(), e.getY()); + } + }; + } + + public List getHiddenTabs() { + return hiddenTabs; + } + + public void setSelected(int selected) { + + if (tabs.isEmpty()) { + return; + } + + if (selected < 0) { + selected = 0; + } + + int index = 0; + this.selected = selected; + for (Tab tab : tabs) { + if (index == selected) { + tab.getModel().setHover(false); + tab.getModel().setSelected(true); + tab.repaint(); + contentPaneLayout.show(contentPane, "" + index); + } else { + tab.getModel().setHover(false); + tab.getModel().setSelected(false); + tab.repaint(); + } + index++; + } + revalidate(); + repaint(); + } + + public int getSelected() { + return selected; + } + + public MouseAdapter getListener() { + return adapter; + } + + public MouseAdapter getHiddenTabsListener() { + return hiddenTabsAdapter; + } + } +} diff -r 0c7e9a8854c6 -r 027f92962a8a client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/TabbedPaneControlPanelLayout.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/TabbedPaneControlPanelLayout.java Tue Jan 24 16:13:58 2017 +0100 @@ -0,0 +1,135 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.client.swing.internal; + +import com.redhat.thermostat.client.swing.components.AbstractLayout; +import com.redhat.thermostat.client.swing.internal.TabbedPane.TabController; + +import javax.swing.JComponent; +import java.awt.Container; +import java.awt.Dimension; +import java.util.List; + +/** + */ +class TabbedPaneControlPanelLayout extends AbstractLayout { + + private int hgap; + private int vgap; + + public TabbedPaneControlPanelLayout() { + hgap = 5; + vgap = 5; + } + + @Override + public Dimension preferredLayoutSize(Container tabControls) { + TabbedPane tabbedPane = (TabbedPane) tabControls.getParent(); + int totalWidth = tabbedPane.getWidth(); + + int height = 0; + for (Tab tab : tabbedPane.getTabs()) { + Dimension tabSize = tab.getPreferredSize(); + height = Math.max(height, tabSize.height); + } + + return new Dimension(totalWidth, height + vgap + vgap); + } + + @Override + protected void doLayout(Container tabControls) { + TabbedPane tabbedPane = (TabbedPane) tabControls.getParent(); + + TabController controller = tabbedPane.getController(); + List hiddenTabsList = controller.getHiddenTabs(); + hiddenTabsList.clear(); + + JComponent hiddenTabs = tabbedPane.getHiddenTabsControl(); + hiddenTabs.setBounds(-5, 0, 0, 0); + + int totalWidth = tabbedPane.getWidth(); + + int height = 0; + int tabsSpan = 0; + for (Tab tab : tabbedPane.getTabs()) { + Dimension tabSize = tab.getPreferredSize(); + height = Math.max(height, tabSize.height); + + tabsSpan += tabSize.width + hgap; + } + + tabControls.setBounds(0, 0, totalWidth, height + vgap + vgap); + + if (tabsSpan <= totalWidth) { + int x = totalWidth / 2 - tabsSpan / 2; + for (Tab tab : tabbedPane.getTabs()) { + Dimension tabSize = tab.getPreferredSize(); + tab.setBounds(x, vgap, tabSize.width, tabSize.height); + x += tabSize.width + hgap; + } + + } else { + + int farSide = height + vgap + vgap; + hiddenTabs.setBounds(totalWidth - height, 0, height, farSide); + + boolean hideRest = false; + int x = hgap; + for (Tab tab : tabbedPane.getTabs()) { + + if (hideRest) { + tab.setBounds(-5, 0, 0, 0); + hiddenTabsList.add(tab); + continue; + } + + Dimension tabSize = tab.getPreferredSize(); + int nextX = x + tabSize.width + hgap; + if (nextX > (totalWidth - farSide - hgap)) { + tab.setBounds(-5, 0, 0, 0); + hiddenTabsList.add(tab); + + hideRest = true; + continue; + } + + tab.setBounds(x, vgap, tabSize.width, tabSize.height); + x = nextX; + } + } + } +} diff -r 0c7e9a8854c6 -r 027f92962a8a client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/views/HostInformationPanel.java --- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/views/HostInformationPanel.java Mon Jan 23 14:57:27 2017 -0500 +++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/views/HostInformationPanel.java Tue Jan 24 16:13:58 2017 +0100 @@ -41,15 +41,16 @@ import java.lang.reflect.InvocationTargetException; import java.util.logging.Logger; +import javax.swing.JComponent; import javax.swing.JPanel; -import javax.swing.JTabbedPane; -import javax.swing.SwingUtilities; import com.redhat.thermostat.client.core.views.HostInformationView; import com.redhat.thermostat.client.core.views.UIComponent; import com.redhat.thermostat.client.swing.EdtHelper; import com.redhat.thermostat.client.swing.OverlayContainer; import com.redhat.thermostat.client.swing.SwingComponent; +import com.redhat.thermostat.client.swing.internal.Tab; +import com.redhat.thermostat.client.swing.internal.TabbedPane; import com.redhat.thermostat.common.utils.LoggingUtils; import com.redhat.thermostat.shared.locale.LocalizedString; @@ -59,16 +60,37 @@ private static final EdtHelper edtHelper = new EdtHelper(); private JPanel visiblePanel; - private final JTabbedPane tabPane; + private final TabbedPane tabPane; public HostInformationPanel() { super(); visiblePanel = new JPanel(); visiblePanel.setLayout(new BorderLayout()); - tabPane = new JTabbedPane(); + tabPane = new TabbedPane(); visiblePanel.add(tabPane); } - + + TabbedPane __test__getTabPane() { + return tabPane; + } + + private Tab makeTab(SwingComponent component, LocalizedString title) { + Tab tab = null; + + JComponent tabContent = null; + Component comp = component.getUiComponent(); + if (comp instanceof JComponent) { + tabContent = (JComponent) comp; + } else { + tabContent = new JPanel(); + tabContent.setLayout(new BorderLayout()); + tabContent.add(comp); + } + + tab = new Tab(tabContent, title); + return tab; + } + @Override public void addChildView(final LocalizedString title, final UIComponent view) { if (view instanceof SwingComponent) { @@ -77,7 +99,9 @@ edtHelper.callAndWait(new Runnable() { @Override public void run() { - tabPane.addTab(title.getContents(), null, component.getUiComponent(), null); + Tab tab = makeTab(component, title); + + tabPane.add(tab); if (view instanceof OverlayContainer) { OverlayContainer overlayContainer = (OverlayContainer) view; tabPane.addMouseListener(overlayContainer.getOverlay().getClickOutCloseListener(tabPane)); @@ -102,17 +126,26 @@ @Override public void removeChildView(final LocalizedString title) { - SwingUtilities.invokeLater(new Runnable() { - @Override - public void run() { - for (int i = 0; i < tabPane.getComponentCount(); i++) { - if (tabPane.getTitleAt(i).equals(title.getContents())) { - tabPane.remove(i); - break; + try { + edtHelper.callAndWait(new Runnable() { + @Override + public void run() { + Tab toRemove = null; + for (Tab tab : tabPane.getTabs()) { + if (tab.getTabName().getContents().equals(title.getContents())) { + toRemove = tab; + break; + } + } + + if (toRemove != null) { + tabPane.remove(toRemove); } } - } - }); + }); + } catch (Exception e) { + logger.severe(e.getLocalizedMessage()); + } } @Override @@ -122,7 +155,7 @@ @Override public int getNumChildren() { - return tabPane.getComponentCount(); + return tabPane.getTabs().size(); } } diff -r 0c7e9a8854c6 -r 027f92962a8a client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/views/VmInformationPanel.java --- a/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/views/VmInformationPanel.java Mon Jan 23 14:57:27 2017 -0500 +++ b/client/swing/src/main/java/com/redhat/thermostat/client/swing/internal/views/VmInformationPanel.java Tue Jan 24 16:13:58 2017 +0100 @@ -40,10 +40,12 @@ import java.awt.Component; import java.lang.reflect.InvocationTargetException; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.FutureTask; import java.util.logging.Logger; +import javax.swing.JComponent; import javax.swing.JPanel; -import javax.swing.JTabbedPane; import com.redhat.thermostat.client.core.views.UIComponent; import com.redhat.thermostat.client.core.views.UIPluginInfo; @@ -51,6 +53,8 @@ import com.redhat.thermostat.client.swing.EdtHelper; import com.redhat.thermostat.client.swing.OverlayContainer; import com.redhat.thermostat.client.swing.SwingComponent; +import com.redhat.thermostat.client.swing.internal.Tab; +import com.redhat.thermostat.client.swing.internal.TabbedPane; import com.redhat.thermostat.common.utils.LoggingUtils; import com.redhat.thermostat.shared.locale.LocalizedString; @@ -67,7 +71,7 @@ private static final Logger logger = LoggingUtils.getLogger(VmInformationPanel.class); private static final EdtHelper edtHelper = new EdtHelper(); - private final JTabbedPane tabPane = new JTabbedPane(); + private final TabbedPane tabPane = new TabbedPane(); private JPanel visiblePanel; public VmInformationPanel() { @@ -78,6 +82,10 @@ visiblePanel.add(tabPane); } + TabbedPane __test__getTabPane() { + return tabPane; + } + @Override public void clear() { try { @@ -93,6 +101,23 @@ } } + private Tab makeTab(SwingComponent component, LocalizedString title) { + Tab tab = null; + + JComponent tabContent = null; + Component comp = component.getUiComponent(); + if (comp instanceof JComponent) { + tabContent = (JComponent) comp; + } else { + tabContent = new JPanel(); + tabContent.setLayout(new BorderLayout()); + tabContent.add(comp); + } + + tab = new Tab(tabContent, title); + return tab; + } + @Override public void addChildViews(final List plugins) { try { @@ -119,7 +144,8 @@ } private void addViewImpl(final LocalizedString title, final SwingComponent view) { - tabPane.addTab(title.getContents(), null, view.getUiComponent(), null); + Tab tabContent = makeTab(view, title); + tabPane.add(tabContent); if (view instanceof OverlayContainer) { OverlayContainer overlayContainer = (OverlayContainer) view; tabPane.addMouseListener(overlayContainer.getOverlay().getClickOutCloseListener(tabPane)); @@ -152,21 +178,60 @@ @Override public int getSelectedChildID() { - return tabPane.getSelectedIndex(); + FutureTask task = new FutureTask<>(new Callable() { + @Override + public Integer call() throws Exception { + return tabPane.getSelectedIndex(); + } + }); + + try { + edtHelper.callAndWait(task); + return task.get(); + + } catch (Exception e) { + return 0; + } } @Override - public boolean selectChildID(int id) { - if (tabPane.getComponentCount() > id) { - tabPane.setSelectedIndex(id); - return true; + public boolean selectChildID(final int id) { + FutureTask task = new FutureTask<>(new Callable() { + @Override + public Boolean call() throws Exception { + if (tabPane.getTabs().size() > id) { + tabPane.setSelectedIndex(id); + return true; + } + return false; + } + }); + + try { + edtHelper.callAndWait(task); + return task.get(); + + } catch (Exception e) { + return false; } - return false; } @Override public int getNumChildren() { - return tabPane.getComponentCount(); + FutureTask task = new FutureTask<>(new Callable() { + @Override + public Integer call() throws Exception { + return tabPane.getTabs().size(); + } + }); + + try { + edtHelper.callAndWait(task); + return task.get(); + + } catch (Exception e) { + return 0; + } } } diff -r 0c7e9a8854c6 -r 027f92962a8a client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/TabbedPaneTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/TabbedPaneTest.java Tue Jan 24 16:13:58 2017 +0100 @@ -0,0 +1,90 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.client.swing.internal; + +import com.redhat.thermostat.shared.locale.LocalizedString; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Dimension; + +/** + */ +public class TabbedPaneTest { + public static void main(String[] args) { + JFrame frame = new JFrame(); + frame.setMinimumSize(new Dimension(300, 300)); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + TabbedPane pane = new TabbedPane(); + pane.add(createContent("Tab #1")); + pane.add(createContent("Tab #2")); + pane.add(createContent("Tab #3")); + + frame.add(pane); + + pane.removeAll(); + + pane.add(createContent("Tab #5")); + pane.add(createContent("Tab #6")); + pane.add(createContent("Tab #7")); + + pane.add(createContent("Tab #52")); + pane.add(createContent("Tab #6&&&&&&&88888888888888888&&&&&&&&&&&")); + pane.add(createContent("Tab #733")); + + frame.setVisible(true); + } + + static private Tab createContent(final String name) { + + JPanel contentPane = new JPanel() { + { + setBackground(Color.WHITE); + setLayout(new BorderLayout()); + + JLabel label = new JLabel(name); + add(label); + } + }; + + return new Tab(contentPane, new LocalizedString(name)); + } +} \ No newline at end of file diff -r 0c7e9a8854c6 -r 027f92962a8a client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/TabbedPaneUnitTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/TabbedPaneUnitTest.java Tue Jan 24 16:13:58 2017 +0100 @@ -0,0 +1,171 @@ +/* + * Copyright 2012-2017 Red Hat, Inc. + * + * This file is part of Thermostat. + * + * Thermostat is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published + * by the Free Software Foundation; either version 2, or (at your + * option) any later version. + * + * Thermostat is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Thermostat; see the file COPYING. If not see + * . + * + * Linking this code with other modules is making a combined work + * based on this code. Thus, the terms and conditions of the GNU + * General Public License cover the whole combination. + * + * As a special exception, the copyright holders of this code give + * you permission to link this code with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also + * meet, for each linked independent module, the terms and conditions + * of the license of that module. An independent module is a module + * which is not derived from or based on this code. If you modify + * this code, you may extend this exception to your version of the + * library, but you are not obligated to do so. If you do not wish + * to do so, delete this exception statement from your version. + */ + +package com.redhat.thermostat.client.swing.internal; + +import com.redhat.thermostat.annotations.internal.CacioTest; +import com.redhat.thermostat.shared.locale.LocalizedString; +import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner; +import org.fest.swing.annotation.GUITest; +import org.fest.swing.annotation.RunsInEDT; +import org.fest.swing.edt.FailOnThreadViolationRepaintManager; +import org.fest.swing.edt.GuiActionRunner; +import org.fest.swing.edt.GuiTask; +import org.fest.swing.fixture.FrameFixture; +import org.fest.swing.fixture.JLabelFixture; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import java.awt.BorderLayout; +import java.awt.Color; + +import static junit.framework.Assert.assertEquals; + +@Category(CacioTest.class) +@RunWith(CacioFESTRunner.class) +public class TabbedPaneUnitTest { + + private JFrame frame; + private FrameFixture frameFixture; + + @BeforeClass + public static void setUpOnce() { + FailOnThreadViolationRepaintManager.install(); + } + + private TabbedPane pane; + + @Before + public void setUp() { + GuiActionRunner.execute(new GuiTask() { + @Override + protected void executeInEDT() throws Throwable { + frame = new JFrame(); + frame.getContentPane().setLayout(new BorderLayout()); + pane = new TabbedPane(); + frame.add(pane); + frame.setSize(500, 500); + } + }); + frameFixture = new FrameFixture(frame); + } + + @After + public void tearDown() { + frameFixture.cleanUp(); + frameFixture = null; + } + + @Test + @GUITest + @RunsInEDT + public void testAddTabs() { + + frameFixture.show(); + + GuiActionRunner.execute(new GuiTask() { + @Override + protected void executeInEDT() throws Throwable { + pane.add(createContent("__test_1__")); + pane.add(createContent("__test_2__")); + } + }); + + assertEquals("__test_1__", pane.getTabs().get(0).getTabName().getContents()); + assertEquals("__test_2__", pane.getTabs().get(1).getTabName().getContents()); + assertEquals(2, pane.getTabs().size()); + + JLabelFixture labelFixture = frameFixture.label("__test_1__"); + labelFixture.requireVisible(); + + final int[] selected = new int[] { -1 }; + + GuiActionRunner.execute(new GuiTask() { + @Override + protected void executeInEDT() throws Throwable { + selected[0] = pane.getSelectedIndex(); + } + }); + + assertEquals(0, selected[0]); + + GuiActionRunner.execute(new GuiTask() { + @Override + protected void executeInEDT() throws Throwable { + pane.setSelectedIndex(1); + } + }); + + JLabelFixture labelFixture2 = frameFixture.label("__test_2__"); + labelFixture2.requireVisible(); + + GuiActionRunner.execute(new GuiTask() { + @Override + protected void executeInEDT() throws Throwable { + selected[0] = pane.getSelectedIndex(); + } + }); + + // be sure order didn't change + assertEquals(1, selected[0]); + assertEquals("__test_1__", pane.getTabs().get(0).getTabName().getContents()); + assertEquals("__test_2__", pane.getTabs().get(1).getTabName().getContents()); + assertEquals(2, pane.getTabs().size()); + } + + private Tab createContent(final String name) { + + JPanel contentPane = new JPanel() { + { + setBackground(Color.WHITE); + setLayout(new BorderLayout()); + + JLabel label = new JLabel(name); + label.setName(name); + add(label); + } + }; + + return new Tab(contentPane, new LocalizedString(name)); + } +} \ No newline at end of file diff -r 0c7e9a8854c6 -r 027f92962a8a client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/views/HostInformationPanelTest.java --- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/views/HostInformationPanelTest.java Mon Jan 23 14:57:27 2017 -0500 +++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/views/HostInformationPanelTest.java Tue Jan 24 16:13:58 2017 +0100 @@ -37,13 +37,13 @@ package com.redhat.thermostat.client.swing.internal.views; import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import java.lang.reflect.InvocationTargetException; -import javax.swing.JTabbedPane; - +import com.redhat.thermostat.client.swing.internal.TabbedPane; import com.redhat.thermostat.common.internal.test.Bug; import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner; @@ -58,7 +58,6 @@ import com.redhat.thermostat.annotations.internal.CacioTest; import com.redhat.thermostat.client.core.views.UIComponent; import com.redhat.thermostat.client.swing.FrameWithPanelTest; -import com.redhat.thermostat.client.swing.TabbedPaneMatcher; import com.redhat.thermostat.shared.locale.LocalizedString; @Category(CacioTest.class) @@ -81,14 +80,14 @@ panel.addChildView(new LocalizedString("foo1"), mock1); - // The panel in test has no views added so the matcher with a tab count > 0 works - // in order to select the right panel. - window.panel("panel").tabbedPane(new TabbedPaneMatcher(JTabbedPane.class)).requireTabTitles("foo1"); + TabbedPane tabbedPane = panel.__test__getTabPane(); + + assertEquals(1, tabbedPane.getTabs().size()); + assertEquals("foo1", tabbedPane.getTabs().get(0).getTabName().getContents()); UIComponent mock2 = createPanel(); panel.addChildView(new LocalizedString("foo2"), mock2); - - window.panel("panel").tabbedPane(new TabbedPaneMatcher(JTabbedPane.class)).requireTabTitles("foo1", "foo2"); + assertEquals("foo1", tabbedPane.getTabs().get(0).getTabName().getContents()); } @Test @@ -99,13 +98,14 @@ panel.addChildView(new LocalizedString("test1"), test1); panel.addChildView(new LocalizedString("test2"), test2); - // The panel in test has no views added so the matcher with a tab count > 0 works - // in order to select the right panel. - window.panel("panel").tabbedPane(new TabbedPaneMatcher(JTabbedPane.class)).requireTabTitles("test1", "test2"); + TabbedPane tabbedPane = panel.__test__getTabPane(); + assertEquals(2, tabbedPane.getTabs().size()); + assertEquals("test1", tabbedPane.getTabs().get(0).getTabName().getContents()); + assertEquals("test2", tabbedPane.getTabs().get(1).getTabName().getContents()); panel.removeChildView(new LocalizedString("test1")); - - window.panel("panel").tabbedPane(new TabbedPaneMatcher(JTabbedPane.class)).requireTabTitles("test2"); + assertEquals(1, tabbedPane.getTabs().size()); + assertEquals("test2", tabbedPane.getTabs().get(0).getTabName().getContents()); } @GUITest diff -r 0c7e9a8854c6 -r 027f92962a8a client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/views/VmInformationPanelTest.java --- a/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/views/VmInformationPanelTest.java Mon Jan 23 14:57:27 2017 -0500 +++ b/client/swing/src/test/java/com/redhat/thermostat/client/swing/internal/views/VmInformationPanelTest.java Tue Jan 24 16:13:58 2017 +0100 @@ -42,8 +42,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.redhat.thermostat.client.core.views.UIPluginInfo; -import com.redhat.thermostat.client.swing.TabbedPaneMatcher; +import com.redhat.thermostat.client.swing.internal.TabbedPane; import com.redhat.thermostat.common.internal.test.Bug; import net.java.openjdk.cacio.ctc.junit.CacioFESTRunner; @@ -60,12 +59,8 @@ import com.redhat.thermostat.client.swing.FrameWithPanelTest; import com.redhat.thermostat.shared.locale.LocalizedString; -import javax.swing.JTabbedPane; import java.awt.AWTException; -import java.awt.Robot; import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; @Category(CacioTest.class) @RunWith(CacioFESTRunner.class) @@ -87,14 +82,14 @@ panel.addChildView(new LocalizedString("foo1"), mock1); - // The panel in test has no views added so the matcher with a tab count > 0 works - // in order to select the right panel. - window.panel("panel").tabbedPane(new TabbedPaneMatcher(JTabbedPane.class)).requireTabTitles("foo1"); + TabbedPane tabbedPane = panel.__test__getTabPane(); + + assertEquals(1, tabbedPane.getTabs().size()); + assertEquals("foo1", tabbedPane.getTabs().get(0).getTabName().getContents()); UIComponent mock2 = createPanel(); panel.addChildView(new LocalizedString("foo2"), mock2); - - window.panel("panel").tabbedPane(new TabbedPaneMatcher(JTabbedPane.class)).requireTabTitles("foo1", "foo2"); + assertEquals("foo1", tabbedPane.getTabs().get(0).getTabName().getContents()); } @GUITest