/** * Copyright (C) 2001-2017 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.com * * This program is free software: you can redistribute it and/or modify it under the terms of the * GNU Affero General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * This program 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 * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.gui.tools.ioobjectcache; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Font; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.HierarchyEvent; import java.awt.event.HierarchyListener; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import javax.swing.Action; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.Icon; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JToolBar; import javax.swing.SwingUtilities; import com.rapidminer.gui.MainFrame; import com.rapidminer.gui.look.Colors; import com.rapidminer.gui.renderer.RendererService; import com.rapidminer.gui.tools.ExtendedJScrollPane; import com.rapidminer.gui.tools.ExtendedJToolBar; import com.rapidminer.gui.tools.ResourceDockKey; import com.rapidminer.gui.tools.SwingTools; import com.rapidminer.gui.tools.ioobjectcache.actions.ClearCacheAction; import com.rapidminer.gui.tools.ioobjectcache.actions.OpenCacheEntryAction; import com.rapidminer.gui.tools.ioobjectcache.actions.RemoveCacheEntryAction; import com.rapidminer.operator.IOObject; import com.rapidminer.operator.IOObjectMap; import com.rapidminer.operator.IOObjectMapEvent; import com.rapidminer.tools.I18N; import com.rapidminer.tools.Observable; import com.rapidminer.tools.Observer; import com.vlsolutions.swing.docking.DockKey; import com.vlsolutions.swing.docking.Dockable; /** * Simple viewer for {@link IOObjectMap}s. Allows for the deletion for single entries and the entire * map, as well as to open entries in the results perspective. * * @author Michael Knopf */ public class IOObjectCacheViewer extends JPanel implements Dockable { private static final long serialVersionUID = 1L; public static final String IOOBJECT_CACHE_VIEWER_DOCK_KEY = "ioobject_cache_viewer"; private final DockKey dockKey = new ResourceDockKey(IOOBJECT_CACHE_VIEWER_DOCK_KEY); { dockKey.setDockGroup(MainFrame.DOCK_GROUP_ROOT); } /** * Utility class that manages all updates of the {@link IOObjectCacheViewer}. It ensures that no * more than two updates are scheduled at the same time and that the view is not updated if it * is invisible. FOr this purpose, it observes the {@link IOObjectMap} itself as well as the * Swing hierarchy. * * @author Michael Knopf */ private class UpdateManager implements Observer<IOObjectMapEvent>, HierarchyListener { /** Number of currently scheduled updates (should never exceed two). */ private int scheduledUpdates = 0; /** Indicates whether updates have been skipped while the viewer was invisible. */ private boolean isDirty = false; /** Lock to manage the access to the above counter and flag. */ private final Object updateLock = new Object(); /** Invokes an update of the viewer. */ private final Runnable updateEntries = new Runnable() { @Override public void run() { synchronized (updateLock) { scheduledUpdates--; isDirty = false; } scrollPane.setViewportView(createEntriesPanel()); scrollPane.getViewport().revalidate(); scrollPane.getViewport().repaint(); } }; @Override public void hierarchyChanged(HierarchyEvent e) { // update whenever the component visibility changes, the view is dirty (prior updates // have been ignored), and not more than one update is scheduled synchronized (updateLock) { if ((HierarchyEvent.SHOWING_CHANGED & e.getChangeFlags()) != 0 && isDirty && scheduledUpdates <= 1) { scheduledUpdates++; SwingUtilities.invokeLater(updateEntries); } } } @Override public void update(Observable<IOObjectMapEvent> observable, IOObjectMapEvent event) { synchronized (updateLock) { // only update if the viewer is visible and not more than one update is currently // scheduled if (!IOObjectCacheViewer.this.isShowing()) { // remember that we skipped an update isDirty = true; } else if (scheduledUpdates <= 1) { scheduledUpdates++; SwingUtilities.invokeLater(updateEntries); } } } } private static final Icon UNKNOWN_TYPE = SwingTools.createIcon("16/" + I18N.getGUIMessage("gui.icon.ioobject_viewer.unknown_type.icon")); /** The corresponding {@link IOObjectMap}. */ private final IOObjectMap map; /** The scroll pane containing the list of cache entries. */ private JScrollPane scrollPane; /** Initializes the GUI components of the viewer. */ private void initView() { // the viewer's toolbar JToolBar toolBar = new ExtendedJToolBar(true); toolBar.setBorder(BorderFactory.createMatteBorder(0, 0, 1, 0, Colors.TEXTFIELD_BORDER)); // add actions that clears all entries Action clearAction = new ClearCacheAction(map); toolBar.add(clearAction); // setup the header column (reuse the layout of the entries) JPanel headerPanel = new JPanel(IOObjectCacheEntryPanel.ENTRY_LAYOUT); headerPanel.add(Box.createVerticalStrut(16), IOObjectCacheEntryPanel.ICON_CONSTRAINTS); JLabel typeLabel = new JLabel(I18N.getGUILabel("ioobject_viewer.type")); typeLabel.setFont(getFont().deriveFont(Font.ITALIC)); headerPanel.add(typeLabel, IOObjectCacheEntryPanel.TYPE_CONSTRAINTS); JLabel keyLabel = new JLabel(I18N.getGUILabel("ioobject_viewer.key")); keyLabel.setFont(getFont().deriveFont(Font.ITALIC)); headerPanel.add(keyLabel, IOObjectCacheEntryPanel.KEY_CONSTRAINTS); headerPanel.add(Box.createVerticalStrut(24), IOObjectCacheEntryPanel.REMOVE_BUTTON_CONSTRAINTS); // create entries panel and embed in scroll pane scrollPane = new ExtendedJScrollPane(createEntriesPanel()); scrollPane.setBorder(null); // panel containing the header row and the actual entries JPanel contentPanel = new JPanel(new BorderLayout()); contentPanel.add(headerPanel, BorderLayout.NORTH); contentPanel.add(scrollPane, BorderLayout.CENTER); // put everything together add(toolBar, BorderLayout.NORTH); add(contentPanel, BorderLayout.CENTER); } /** Updates the contents of the entries panel. */ private JPanel createEntriesPanel() { GridBagLayout layout = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 1.0; gbc.fill = GridBagConstraints.HORIZONTAL; JPanel entriesPanel = new JPanel(); entriesPanel.setLayout(layout); List<String> keys = new ArrayList<>(map.getAll().keySet()); Collections.sort(keys); boolean alternatingRow = true; for (String key : keys) { IOObject object = map.get(key); if (object == null) { // do not display empty results continue; } // look up icon Icon icon = RendererService.getIcon(object.getClass()); if (icon == null) { icon = UNKNOWN_TYPE; } // look up name String type = RendererService.getName(object.getClass()); if (type == null) { type = object.getClass().getSimpleName(); } Action removeAction = new RemoveCacheEntryAction(map, key); Action openAction = new OpenCacheEntryAction(map, key); IOObjectCacheEntryPanel entry = new IOObjectCacheEntryPanel(icon, type, openAction, removeAction); if (alternatingRow) { entry.setDefaultBackground(Colors.WHITE); } alternatingRow = !alternatingRow; entriesPanel.add(entry, gbc); gbc.gridy += 1; } gbc.weighty = 1.0; gbc.fill = GridBagConstraints.BOTH; entriesPanel.add(Box.createVerticalGlue(), gbc); return entriesPanel; } /** * Creates a new viewer for the specified {@link IOObjectMap}. * * @param map * The corresponding {@link IOObjectMap}; * * @throws NPE * If the provided map is <code>null</code>. */ public IOObjectCacheViewer(IOObjectMap map) { super(new BorderLayout()); Objects.requireNonNull(map); this.map = map; // initialize GUi elements initView(); // listen to changes of the map and of the GUI hierarchy UpdateManager updateManager = new UpdateManager(); map.addMapObserver(updateManager); addHierarchyListener(updateManager); } @Override public Component getComponent() { return this; } @Override public DockKey getDockKey() { return dockKey; } }