/* * #%L * gitools-ui-app * %% * Copyright (C) 2013 Universitat Pompeu Fabra - Biomedical Genomics group * %% * This program 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 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 General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program. If not, see * <http://www.gnu.org/licenses/gpl-3.0.html>. * #L% */ package org.gitools.ui.app.heatmap.editor; import com.alee.extended.panel.GroupPanel; import com.alee.extended.panel.GroupingType; import com.alee.laf.panel.WebPanel; import com.alee.laf.splitpane.WebSplitPane; import com.google.common.base.Strings; import org.gitools.api.ApplicationContext; import org.gitools.api.PersistenceException; import org.gitools.api.analysis.IProgressMonitor; import org.gitools.api.matrix.IMatrix; import org.gitools.api.matrix.IMatrixDimension; import org.gitools.api.persistence.FileFormat; import org.gitools.api.resource.IResourceLocator; import org.gitools.api.resource.ResourceReference; import org.gitools.heatmap.Heatmap; import org.gitools.heatmap.HeatmapDimension; import org.gitools.heatmap.format.HeatmapFormat; import org.gitools.matrix.model.MatrixWrapper; import org.gitools.persistence.locators.UrlResourceLocator; import org.gitools.ui.app.commands.CommandLoadFile; import org.gitools.ui.app.heatmap.panel.ColorScalePanel; import org.gitools.ui.app.heatmap.panel.HeatmapMouseListener; import org.gitools.ui.app.heatmap.panel.HeatmapPanel; import org.gitools.ui.app.heatmap.panel.details.DetailsPanel; import org.gitools.ui.app.heatmap.panel.search.HeatmapSearchPanel; import org.gitools.ui.core.Application; import org.gitools.ui.core.commands.Command; import org.gitools.ui.core.components.boxes.Box; import org.gitools.ui.core.components.editor.AbstractEditor; import org.gitools.ui.core.components.editor.EditorsPanel; import org.gitools.ui.core.components.wizard.SaveFileWizard; import org.gitools.ui.core.pages.common.SaveHeatmapFilePage; import org.gitools.ui.platform.IconUtils; import org.gitools.ui.platform.icons.IconNames; import org.gitools.ui.platform.progress.JobRunnable; import org.gitools.ui.platform.progress.JobThread; import org.gitools.ui.platform.settings.Settings; import org.gitools.ui.platform.wizard.WizardDialog; import org.gitools.utils.MemoryUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; import javax.swing.border.CompoundBorder; import java.awt.*; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.net.URISyntaxException; import java.util.Collection; import java.util.Date; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.CancellationException; public class HeatmapEditor extends AbstractEditor { private static final Logger LOGGER = LoggerFactory.getLogger(HeatmapEditor.class); private static final int DEFAULT_ACCORDION_WIDTH = 320; private static final int MINIMUM_AVAILABLE_MEMORY_THRESHOLD = (int) (3 * Runtime.getRuntime().maxMemory() / 10); private Heatmap heatmap; private HeatmapPanel heatmapPanel; private ColorScalePanel colorScalePanel; private HeatmapSearchPanel searchPanel; private DetailsPanel detailsPanel; private int lastMouseRow = -1; private int lastMouseCol = -1; private Timer timer; public HeatmapEditor() { //weld requirement } public HeatmapEditor(final Heatmap heatmap) { IResourceLocator locator = heatmap.getLocator(); if (locator != null && locator.getURL().getProtocol().equals("file")) { try { File file = new File(locator.getURL().toURI()); setFile(file); } catch (URISyntaxException e) { } } // Initialize and create heatmap model heatmap.init(); this.heatmap = heatmap; setIcon(IconUtils.getIconResource(IconNames.heatmap16)); if (!Strings.isNullOrEmpty(heatmap.getTitle())) { setName(heatmap.getTitle()); } else { heatmap.setTitle(getName()); } createComponents(this); setSaveAllowed(true); setSaveAsAllowed(true); setBackground(Color.WHITE); // Add change listeners PropertyChangeListener dirtyListener = new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { HeatmapEditor.this.setDirty(true); } }; heatmap.addPropertyChangeListener(dirtyListener); heatmap.getRows().addPropertyChangeListener(dirtyListener); heatmap.getColumns().addPropertyChangeListener(dirtyListener); heatmap.getLayers().addPropertyChangeListener(dirtyListener); heatmap.getLayers().getTopLayer().getDecorator().addPropertyChangeListener(dirtyListener); heatmap.addPropertyChangeListener(Heatmap.PROPERTY_TITLE, new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { setName(heatmap.getTitle()); } }); // Create a timer that watches every 5 seconds the available memory // and detach the heatmap if it is below a minimum threshold. timer = new java.util.Timer(); timer.scheduleAtFixedRate(new CleanCacheTimer(), 5000, 5000); } @Override public void setName(String name) { if (this.heatmap != null && (this.heatmap.getTitle() == null || this.heatmap.getTitle().equals(""))) { heatmap.setTitle(name); } super.setName(name); } private void createComponents(JComponent container) { Dimension minimumSize = new Dimension(DEFAULT_ACCORDION_WIDTH, 100); detailsPanel = new DetailsPanel(heatmap); detailsPanel.setMinimumSize(minimumSize); colorScalePanel = new ColorScalePanel(heatmap); colorScalePanel.setMinimumSize(minimumSize); WebPanel emptyPanel = new WebPanel(); emptyPanel.setBackground(Color.WHITE); GroupPanel leftPanel = new GroupPanel(GroupingType.fillMiddle, false, detailsPanel, emptyPanel, colorScalePanel); leftPanel.setUndecorated(true); leftPanel.setBackground(Color.WHITE); leftPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 0)); leftPanel.setMinimumSize(minimumSize); heatmapPanel = new HeatmapPanel(heatmap); heatmapPanel.requestFocusInWindow(); heatmapPanel.addHeatmapMouseListener(new HeatmapMouseListener() { @Override public void mouseMoved(int row, int col, MouseEvent e) { HeatmapEditor.this.mouseMoved(row, col, e); } @Override public void mouseClicked(int row, int col, MouseEvent e) { HeatmapEditor.this.mouseClicked(row, col, e); } }); searchPanel = new HeatmapSearchPanel(heatmap, heatmapPanel); searchPanel.setVisible(false); JSplitPane splitPane = new WebSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, heatmapPanel); heatmapPanel.setBorder(new CompoundBorder(BorderFactory.createEmptyBorder(20, 10, 20, 20), BorderFactory.createMatteBorder(0, 1, 1, 0, Color.GRAY))); splitPane.setOneTouchExpandable(true); splitPane.setDividerLocation(DEFAULT_ACCORDION_WIDTH); splitPane.setLastDividerLocation(DEFAULT_ACCORDION_WIDTH); splitPane.setContinuousLayout(false); splitPane.setDividerSize(4); splitPane.setBackground(Color.WHITE); splitPane.setForeground(Color.WHITE); container.setLayout(new BorderLayout()); container.add(searchPanel, BorderLayout.NORTH); container.add(splitPane, BorderLayout.CENTER); } @Override public Heatmap getModel() { return heatmap; } @Override public void refresh() { } @Override public void doVisible() { heatmapPanel.requestFocusInWindow(); } @Override public void doSaveAs(IProgressMonitor monitor) { File file = getFile(); if (file != null) { Settings.get().setLastPath(file.getParent()); } String name = getName(); int heatmapExt = name.indexOf(".heatmap"); if (heatmapExt != -1) { name = name.substring(0, heatmapExt); } name = name.replaceAll("\\.", "_"); SaveHeatmapFilePage page = new SaveHeatmapFilePage(); page.setTitle("Save heatmap as"); page.setFileNameWithoutExtension(name); page.setFolder(Settings.get().getLastPath()); page.setFormats(new FileFormat[]{ new FileFormat("Heatmap, single file (*.heatmap.zip)", HeatmapFormat.EXTENSION + ".zip", false, false), new FileFormat("Heatmap, multiple files (*.heatmap)", HeatmapFormat.EXTENSION, false, false) }); if (heatmap.getRows().size() == heatmap.getContents().getRows().size() && heatmap.getColumns().size() == heatmap.getContents().getColumns().size()) { page.enableDiscardHidden(false); } else { page.suggestDiscardHidden(); } SaveFileWizard wiz = SaveFileWizard.createCustom(page); final WizardDialog dlg = new WizardDialog(Application.get(), wiz); try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { dlg.setVisible(true); } }); } catch (InterruptedException e) { throw new CancellationException(); } catch (InvocationTargetException e) { throw new CancellationException(); } if (dlg.isCancelled()) { return; } file = wiz.getPathAsFile(); boolean discardHidden = page.isDiscardHidden(); boolean optimizeData = page.isOptimizeData(); doSaveAsNoUI(monitor, file, discardHidden, optimizeData, true); } public void doSaveAsNoUI(IProgressMonitor monitor, File file, boolean discardHidden, boolean optimizeData, boolean reload) { setFile(file); heatmap.setGitoolsVersion(Application.getGitoolsVersion()); IResourceLocator toLocator; if (heatmap.getLocator() == null) { toLocator = new UrlResourceLocator(file); } else { toLocator = new UrlResourceLocator(heatmap.getLocator().getReadFile(), file); } // Data matrix IMatrix data = heatmap.getData().get(); if (discardHidden) { // Discard hidden data data = new MatrixWrapper(data) { @Override public IMatrixDimension getColumns() { return heatmap.getColumns(); } @Override public IMatrixDimension getRows() { return heatmap.getRows(); } @Override public boolean isChanged() { return true; } }; heatmap.setData(new ResourceReference<>("data", data)); } else { if (optimizeData) { // Optimize mtabix index data = new MatrixWrapper(data) { @Override public boolean isChanged() { return true; } }; heatmap.setData(new ResourceReference<>("data", data)); } else { IResourceLocator locator = heatmap.getData().getLocator(); heatmap.setData(new ResourceReference<>("data", data)); heatmap.getData().setLocator(locator); heatmap.getData().setChanged(data.isChanged()); heatmap.getData().setBaseName(toLocator.getBaseName() + "-data"); } } HeatmapDimension rows = heatmap.getRows(); HeatmapDimension columns = heatmap.getColumns(); rows.setAnnotationsReference(new ResourceReference<>(rows.getId().toString().toLowerCase() + "-annotations", rows.getAnnotations())); columns.setAnnotationsReference(new ResourceReference<>(columns.getId().toString().toLowerCase() + "-annotations", columns.getAnnotations())); Settings.get().setLastPath(file.getAbsoluteFile().getParent()); doSave(toLocator, monitor, reload); } @Override public void doSave(IProgressMonitor monitor) { doSave(heatmap.getLocator(), monitor, true); } public void doSave(IResourceLocator toLocator, IProgressMonitor monitor, boolean reload) { File file = getFile(); if (file == null) { doSaveAs(monitor); return; } heatmap.setGitoolsVersion(Application.getGitoolsVersion()); // Last saved and author info. heatmap.setLastSaved(new Date()); Settings settings = Settings.get(); if (Strings.isNullOrEmpty(heatmap.getAuthorName())) { heatmap.setAuthorName(settings.getAuthorName()); if (!Strings.isNullOrEmpty(settings.getAuthorEmail())) { heatmap.setAuthorEmail(settings.getAuthorEmail()); } } try { ApplicationContext.getPersistenceManager().store(toLocator, heatmap, monitor); } catch (PersistenceException ex) { monitor.exception(ex); } setDirty(false); Settings.get().addRecentFile(file.getAbsolutePath()); Settings.get().save(); // Force to reload the data after save if (reload && (!toLocator.equals(heatmap.getLocator()) || heatmap.getData().get().isChanged())) { monitor.title("Reloading the heatmap..."); EditorsPanel editorPanel = Application.get().getEditorsPanel(); editorPanel.removeEditor(this); CommandLoadFile loadFile = new CommandLoadFile(toLocator.getURL()); try { loadFile.execute(monitor); } catch (Command.CommandException e) { throw new RuntimeException(e); } } } @Override public boolean doClose() { return doClose(true); } public boolean doClose(boolean UI) { if (UI && isDirty()) { int res = JOptionPane.showOptionDialog(Application.get(), "File " + getName() + " is modified.\n" + "Save changes ?", "Close", JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE, null, new String[]{"Cancel", "Discard", "Save"}, "Save"); if (res == -1 || res == 0) { return false; } else if (res == 2) { SaveFileWizard wiz = SaveFileWizard.createSimple("Save heatmap", getName(), Settings.get().getLastPath(), new FileFormat[]{new FileFormat("Heatmap", HeatmapFormat.EXTENSION)}); WizardDialog dlg = new WizardDialog(Application.get(), wiz); dlg.setVisible(true); if (dlg.isCancelled()) { return false; } Settings.get().setLastPath(wiz.getFolder()); setFile(wiz.getPathAsFile()); JobThread.execute(Application.get(), new JobRunnable() { @Override public void run(IProgressMonitor monitor) { doSave(monitor); } }); } } // Force free memory timer.cancel(); timer.purge(); heatmap.detach(null); if (heatmap.getData().getLocator() != null) { heatmap.getData().unload(); } heatmap = null; return true; } public void showSearch(boolean searchColumns) { if (searchPanel.isVisible() && searchPanel.searchRows() != searchColumns) { searchPanel.close(); } else { searchPanel.searchOnColumns(searchColumns); searchPanel.setVisible(true); } } void mouseMoved(int row, int col, MouseEvent e) { } void mouseClicked(int row, int col, MouseEvent e) { } @Override public void detach() { this.heatmap.detach(heatmap.getLayers().getTopLayer()); } @Override public Collection<Box> getBoxes() { return detailsPanel.getBoxes(); } private class CleanCacheTimer extends TimerTask { private boolean trackException = true; private long lastDetach; private CleanCacheTimer() { this.lastDetach = System.currentTimeMillis(); } @Override public void run() { if (MemoryUtils.getAvailableMemory() < MINIMUM_AVAILABLE_MEMORY_THRESHOLD && ((System.currentTimeMillis() - lastDetach)) > 2000) { LOGGER.warn("Memory too low, cleaning cache."); this.lastDetach = System.currentTimeMillis(); if (trackException) { Application.get().trackException("Memory too low"); trackException = false; } detach(); } } } }