diff --git a/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java b/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java index 53656aef..6688f4b4 100644 --- a/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java +++ b/src/main/java/org/embl/mobie/lib/bdv/view/SliceViewer.java @@ -70,6 +70,7 @@ public class SliceViewer public static final String UNDO_SEGMENT_SELECTIONS = "Undo Segment Selections [ Ctrl Shift N ]"; public static final String LOAD_ADDITIONAL_VIEWS = "Load Additional Views"; public static final String SAVE_CURRENT_SETTINGS_AS_VIEW = "Save Current View"; + public static final String DELETE_VIEW = "Delete View"; public static final String FRAME_TITLE = "MoBIE BigDataViewer"; public static boolean tileRenderOverlay = false; private final SourceAndConverterBdvDisplayService bdvDisplayService; @@ -142,6 +143,10 @@ private void installContextMenuAndKeyboardShortCuts( ) moBIE.getViewManager().getViewsSaver().saveViewDialog( view ); } ); + sacService.registerAction( DELETE_VIEW, sourceAndConverters -> { + moBIE.getViewManager().getViewsDeleter().deleteViewDialog(); + }); + final Set< String > actionsKeys = sacService.getActionsKeys(); final ArrayList< String > actions = new ArrayList< String >(); actions.add( SourceAndConverterService.getCommandName( SourcesInfoCommand.class ) ); @@ -157,6 +162,7 @@ private void installContextMenuAndKeyboardShortCuts( ) actions.add( UNDO_SEGMENT_SELECTIONS ); actions.add( LOAD_ADDITIONAL_VIEWS ); actions.add( SAVE_CURRENT_SETTINGS_AS_VIEW ); + actions.add( DELETE_VIEW ); if ( projectCommands != null ) { diff --git a/src/main/java/org/embl/mobie/lib/create/ProjectCreatorHelper.java b/src/main/java/org/embl/mobie/lib/create/ProjectCreatorHelper.java index b0afdc85..bd34ef2c 100644 --- a/src/main/java/org/embl/mobie/lib/create/ProjectCreatorHelper.java +++ b/src/main/java/org/embl/mobie/lib/create/ProjectCreatorHelper.java @@ -155,20 +155,7 @@ public static boolean is2D( ImageData< ? > imageData ) { * @return Map of ui selection group names to array of view names */ public static Map> getGroupToViewsMap( Dataset dataset ) { - Map> groupToViewsMap = new HashMap<>(); - for ( String viewName: dataset.views().keySet() ) { - View view = dataset.views().get( viewName ); - String group = view.getUiSelectionGroup(); - if ( !groupToViewsMap.containsKey( group ) ) { - ArrayList views = new ArrayList<>(); - views.add( viewName ); - groupToViewsMap.put( group, views ); - } else { - groupToViewsMap.get( group ).add( viewName ); - } - } - - return groupToViewsMap; + return getGroupToViewsMap( dataset.views() ); } /** @@ -178,9 +165,18 @@ public static Map> getGroupToViewsMap( Dataset dataset * @return Map of ui selection group names to array of view names */ public static Map> getGroupToViewsMap( AdditionalViews additionalViews ) { + return getGroupToViewsMap(additionalViews.views); + } + + /** + * Get mapping of ui selection groups (i.e. MoBIE dropdown menu names) to views for given views + * @param viewNameToView map of view names to view + * @return Map of ui selection group names to array of view names + */ + public static Map> getGroupToViewsMap( Map viewNameToView ) { Map> groupToViewsMap = new HashMap<>(); - for ( String viewName: additionalViews.views.keySet() ) { - View view = additionalViews.views.get( viewName ); + for ( String viewName: viewNameToView.keySet() ) { + View view = viewNameToView.get( viewName ); String group = view.getUiSelectionGroup(); if ( !groupToViewsMap.containsKey( group ) ) { ArrayList views = new ArrayList<>(); diff --git a/src/main/java/org/embl/mobie/lib/view/ViewManager.java b/src/main/java/org/embl/mobie/lib/view/ViewManager.java index 11caa2d7..a3f1ffc9 100644 --- a/src/main/java/org/embl/mobie/lib/view/ViewManager.java +++ b/src/main/java/org/embl/mobie/lib/view/ViewManager.java @@ -60,6 +60,7 @@ import org.embl.mobie.lib.transform.TransformHelper; import org.embl.mobie.lib.transform.ImageTransformer; import org.embl.mobie.lib.transform.viewer.*; +import org.embl.mobie.lib.view.delete.ViewDeleter; import org.embl.mobie.ui.UserInterface; import org.embl.mobie.ui.WindowArrangementHelper; import org.embl.mobie.lib.view.save.ViewSaver; @@ -88,6 +89,7 @@ public class ViewManager private final UniverseManager universeManager; private final AdditionalViewsLoader additionalViewsLoader; private final ViewSaver viewSaver; + private final ViewDeleter viewDeleter; public ViewManager( MoBIE moBIE, UserInterface userInterface, boolean is2D ) { @@ -98,6 +100,7 @@ public ViewManager( MoBIE moBIE, UserInterface userInterface, boolean is2D ) universeManager = new UniverseManager(); additionalViewsLoader = new AdditionalViewsLoader( moBIE ); viewSaver = new ViewSaver( moBIE ); + viewDeleter = new ViewDeleter( moBIE ); sacService = ( SourceAndConverterService ) SourceAndConverterServices.getSourceAndConverterService(); } @@ -192,6 +195,8 @@ public SliceViewer getSliceViewer() public ViewSaver getViewsSaver() { return viewSaver; } + public ViewDeleter getViewsDeleter() { return viewDeleter; } + private void addImageTransforms( List< Transformation > transformations, List< ? extends Image< ? > > images ) { diff --git a/src/main/java/org/embl/mobie/lib/view/delete/ViewDeleter.java b/src/main/java/org/embl/mobie/lib/view/delete/ViewDeleter.java new file mode 100644 index 00000000..51119f5d --- /dev/null +++ b/src/main/java/org/embl/mobie/lib/view/delete/ViewDeleter.java @@ -0,0 +1,136 @@ +package org.embl.mobie.lib.view.delete; + +import ij.IJ; +import org.apache.commons.lang.NotImplementedException; +import org.embl.mobie.MoBIE; +import org.embl.mobie.lib.io.FileLocation; +import org.embl.mobie.lib.serialize.AdditionalViewsJsonParser; +import org.embl.mobie.lib.serialize.Dataset; +import org.embl.mobie.lib.serialize.DatasetJsonParser; +import org.embl.mobie.lib.serialize.View; +import org.embl.mobie.lib.view.AdditionalViews; +import org.embl.mobie.lib.view.save.SelectExistingViewDialog; +import org.embl.mobie.ui.UserInterfaceHelper; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import static org.embl.mobie.io.github.GitHubUtils.isGithub; +import static org.embl.mobie.io.util.S3Utils.isS3; + +public class ViewDeleter { + + static { net.imagej.patcher.LegacyInjector.preinit(); } + + private MoBIE moBIE; + + /** + * Delete views from the current dataset, or an external file + */ + public ViewDeleter( MoBIE moBIE ) { + this.moBIE = moBIE; + } + + public void deleteViewDialog() + { + new Thread( () -> { + try { + FileLocation fileLocation = UserInterfaceHelper.loadFromProjectOrFileSystemDialog("Delete from"); + + if ( fileLocation == FileLocation.CurrentProject ) { + removeViewsFromCurrentProject(); + } else if ( fileLocation == FileLocation.ExternalFile ) { + removeViewsFromExternalFile(); + } + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); + } + + private void removeViewsFromCurrentProject() throws IOException { + String datasetJson = moBIE.absolutePath( "dataset.json"); + + if ( isGithub(datasetJson) || isS3(datasetJson) ) { + throw new NotImplementedException("View deletion is only implemented for local projects"); + } + + // Read views directly from dataset json rather than from MoBIE.getViews() (otherwise could include + // views loaded from external files via Load Additional Views) + Dataset dataset = new DatasetJsonParser().parseDataset( datasetJson ); + Map views = dataset.views(); + // Remove default view, as this shouldn't be deleted + views.remove( View.DEFAULT ); + + if ( views.isEmpty() ) { + IJ.log("No valid views in dataset - can't remove default view"); + return; + } + + Map viewsToRemove = selectViewsToRemove( views ); + if ( viewsToRemove == null ) { + return; + } + removeViewsFromDatasetJson( viewsToRemove, datasetJson ); + removeViewsFromUI( viewsToRemove ); + } + + private void removeViewsFromExternalFile() throws IOException { + String selectedFilePath = UserInterfaceHelper.selectFilePath( "json", "View", true ); + if ( selectedFilePath == null ) { + return; + } + + Map views = new AdditionalViewsJsonParser().getViews( selectedFilePath ).views; + if ( views.isEmpty() ) { + IJ.log("No valid views in file"); + return; + } + + Map viewsToRemove = selectViewsToRemove( views ); + if ( viewsToRemove == null ) { + return; + } + removeViewsFromAdditionalViewsJson( viewsToRemove, selectedFilePath ); + removeViewsFromUI( viewsToRemove ); + } + + private Map selectViewsToRemove( Map views ) { + String selectedView = new SelectExistingViewDialog( views ).getSelectedView( + "Choose a view to delete..." + ); + if ( selectedView == null ) { + return null; + } + + Map viewsToRemove = new HashMap<>(); + viewsToRemove.put( selectedView, views.get(selectedView) ); + + return viewsToRemove; + } + + public void removeViewsFromDatasetJson( Map views, String datasetJsonPath) throws IOException { + Dataset dataset = new DatasetJsonParser().parseDataset( datasetJsonPath ); + dataset.views().keySet().removeAll( views.keySet() ); + + new DatasetJsonParser().saveDataset( dataset, datasetJsonPath ); + IJ.log( "Views \"" + views.keySet() + "\" removed from dataset.json" ); + } + + public void removeViewsFromAdditionalViewsJson( Map views, String jsonPath ) throws IOException + { + AdditionalViews additionalViews = new AdditionalViewsJsonParser().getViews( jsonPath ); + additionalViews.views.keySet().removeAll( views.keySet() ); + + new AdditionalViewsJsonParser().saveViews( additionalViews, jsonPath ); + IJ.log( "Views \"" + views.keySet() + "\" removed from " + jsonPath ); + } + + public void removeViewsFromUI( Map views ) + { + moBIE.getViews().keySet().removeAll( views.keySet() ); + moBIE.getUserInterface().removeViews( views ); + IJ.log( "The following views were removed:\n" + views.keySet() ); + } +} diff --git a/src/main/java/org/embl/mobie/lib/view/save/SelectExistingViewDialog.java b/src/main/java/org/embl/mobie/lib/view/save/SelectExistingViewDialog.java index c0c50b71..c6b511a5 100644 --- a/src/main/java/org/embl/mobie/lib/view/save/SelectExistingViewDialog.java +++ b/src/main/java/org/embl/mobie/lib/view/save/SelectExistingViewDialog.java @@ -30,6 +30,7 @@ import org.embl.mobie.lib.create.ProjectCreatorHelper; import org.embl.mobie.lib.serialize.Dataset; +import org.embl.mobie.lib.serialize.View; import org.embl.mobie.ui.MoBIELaf; import org.embl.mobie.ui.SwingHelper; import org.embl.mobie.ui.UserInterfaceHelper; @@ -54,6 +55,7 @@ public class SelectExistingViewDialog { private Map> groupToViewsMap; private String selectedView; + private String title = "Choose an existing view..."; // writing to dataset json public SelectExistingViewDialog( Dataset dataset ) { @@ -65,6 +67,15 @@ public SelectExistingViewDialog( AdditionalViews additionalViews ) { groupToViewsMap = ProjectCreatorHelper.getGroupToViewsMap(additionalViews); } + public SelectExistingViewDialog( Map views ) { + groupToViewsMap = ProjectCreatorHelper.getGroupToViewsMap(views); + } + + public String getSelectedView( String title ) { + this.title = title; + return getSelectedView(); + } + public String getSelectedView() { showViewSelectionUI(); return selectedView; @@ -73,7 +84,7 @@ public String getSelectedView() { private void showViewSelectionUI() { MoBIELaf.MoBIELafOn(); dialog = new JDialog((Frame)null, true); - dialog.setTitle( "Choose an existing view..." ); + dialog.setTitle( title ); dialog.getContentPane().setLayout( new BoxLayout(dialog.getContentPane(), BoxLayout.Y_AXIS ) ); dialog.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE ); createComboBoxes(); diff --git a/src/main/java/org/embl/mobie/ui/UserInterface.java b/src/main/java/org/embl/mobie/ui/UserInterface.java index 09ae4fd5..8ae755be 100644 --- a/src/main/java/org/embl/mobie/ui/UserInterface.java +++ b/src/main/java/org/embl/mobie/ui/UserInterface.java @@ -152,6 +152,11 @@ public void addViews( Map views ) MoBIELaf.MoBIELafOff(); } + public void removeViews( Map views ) { + userInterfaceHelper.removeViewsFromViewSelectionPanel( views ); + refreshSelection(); + } + public Map< String, Map< String, View > > getGroupingsToViews() { return userInterfaceHelper.getGroupingsToViews(); diff --git a/src/main/java/org/embl/mobie/ui/UserInterfaceHelper.java b/src/main/java/org/embl/mobie/ui/UserInterfaceHelper.java index 08592869..550df704 100644 --- a/src/main/java/org/embl/mobie/ui/UserInterfaceHelper.java +++ b/src/main/java/org/embl/mobie/ui/UserInterfaceHelper.java @@ -113,6 +113,7 @@ public class UserInterfaceHelper private JPanel viewSelectionPanel; private Map< String, Map< String, View > > groupingsToViews; private Map< String, JComboBox > groupingsToComboBox; + private Map< String, JPanel > groupingsToPanels; private JCheckBox overlayNamesCheckbox; public UserInterfaceHelper( MoBIE moBIE ) @@ -152,8 +153,12 @@ public static void closeWindowByName(String windowTitle) { } public static FileLocation loadFromProjectOrFileSystemDialog() { + return loadFromProjectOrFileSystemDialog("Load from"); + } + + public static FileLocation loadFromProjectOrFileSystemDialog( String dialogText ) { final GenericDialog gd = new GenericDialog("Choose source"); - gd.addChoice("Load from", new String[]{ FileLocation.CurrentProject.toString(), FileLocation.ExternalFile.toString()}, FileLocation.CurrentProject.toString()); + gd.addChoice(dialogText, new String[]{ FileLocation.CurrentProject.toString(), FileLocation.ExternalFile.toString()}, FileLocation.CurrentProject.toString()); gd.showDialog(); if ( gd.wasCanceled() ) return null; return FileLocation.fromString( gd.getNextChoice() ); @@ -795,6 +800,7 @@ public JPanel createViewsSelectionPanel( Map< String, View > views ) { groupingsToViews = new HashMap<>( ); groupingsToComboBox = new HashMap<>( ); + groupingsToPanels = new HashMap<>(); viewSelectionPanel = new JPanel( new BorderLayout() ); viewSelectionPanel.setLayout( new BoxLayout( viewSelectionPanel, BoxLayout.Y_AXIS ) ); @@ -824,40 +830,76 @@ public int compare(String s1, String s2) { }); // If it's the first time, just add all the panels in order - if ( groupingsToComboBox.keySet().size() == 0 ) { + if ( groupingsToComboBox.keySet().isEmpty() ) { for (String uiSelectionGroup : uiSelectionGroups) { final JPanel selectionPanel = createViewSelectionPanel(moBIE, uiSelectionGroup, groupingsToViews.get(uiSelectionGroup)); viewSelectionPanel.add(selectionPanel); } - } else { - // If there are already panels, then add new ones at the correct index to maintain alphabetical order - Map< Integer, JPanel > indexToPanel = new HashMap<>(); - for ( String viewName : views.keySet() ) { - String uiSelectionGroup = views.get( viewName ).getUiSelectionGroup(); - if ( groupingsToComboBox.containsKey( uiSelectionGroup ) ) { - JComboBox comboBox = groupingsToComboBox.get( uiSelectionGroup ); - // check if a view of that name already exists: -1 means it doesn't exist - int index = ( (DefaultComboBoxModel) comboBox.getModel() ).getIndexOf( viewName ); - if ( index == -1 ) { - comboBox.addItem(viewName); - } - } else { - final JPanel selectionPanel = createViewSelectionPanel(moBIE, uiSelectionGroup, groupingsToViews.get(uiSelectionGroup)); - int alphabeticalIndex = uiSelectionGroups.indexOf( uiSelectionGroup ); - indexToPanel.put( alphabeticalIndex, selectionPanel ); + + refreshViewsSelectionPanelHeight(); + return; + } + + // If there are already panels, then add new ones at the correct index to maintain alphabetical order + Map< Integer, JPanel > indexToPanel = new HashMap<>(); + for ( String viewName : views.keySet() ) { + String uiSelectionGroup = views.get( viewName ).getUiSelectionGroup(); + if ( groupingsToComboBox.containsKey( uiSelectionGroup ) ) { + JComboBox comboBox = groupingsToComboBox.get( uiSelectionGroup ); + // check if a view of that name already exists: -1 means it doesn't exist + int index = ( (DefaultComboBoxModel) comboBox.getModel() ).getIndexOf( viewName ); + if ( index == -1 ) { + comboBox.addItem(viewName); } + } else { + final JPanel selectionPanel = createViewSelectionPanel(moBIE, uiSelectionGroup, groupingsToViews.get(uiSelectionGroup)); + int alphabeticalIndex = uiSelectionGroups.indexOf( uiSelectionGroup ); + indexToPanel.put( alphabeticalIndex, selectionPanel ); } + } - if ( indexToPanel.keySet().size() > 0 ) { - // add panels in ascending index order - final ArrayList< Integer > sortedIndices = new ArrayList<>( indexToPanel.keySet() ); - Collections.sort( sortedIndices ); - for ( Integer index: sortedIndices ) { - viewSelectionPanel.add( indexToPanel.get(index), index.intValue() ); + if ( !indexToPanel.keySet().isEmpty() ) { + // add panels in ascending index order + final ArrayList sortedIndices = new ArrayList<>(indexToPanel.keySet()); + Collections.sort(sortedIndices); + for (Integer index : sortedIndices) { + viewSelectionPanel.add(indexToPanel.get(index), index.intValue()); + } + } + refreshViewsSelectionPanelHeight(); + } + + public void removeViewsFromViewSelectionPanel( Map< String, View > views ) + { + for ( String viewName : views.keySet() ) + { + final View view = views.get( viewName ); + final String uiSelectionGroup = view.getUiSelectionGroup(); + if ( groupingsToViews.containsKey( uiSelectionGroup ) ) { + Map groupViews = groupingsToViews.get( uiSelectionGroup ); + groupViews.remove( viewName ); + + if ( groupViews.isEmpty() ) { + groupingsToViews.remove( uiSelectionGroup ); + } + } + + if ( groupingsToComboBox.containsKey( uiSelectionGroup ) ) { + JComboBox comboBox = groupingsToComboBox.get( uiSelectionGroup ); + comboBox.removeItem( viewName ); + + if ( comboBox.getItemCount() == 0 ) { + groupingsToComboBox.remove( uiSelectionGroup ); + viewSelectionPanel.remove( groupingsToPanels.get(uiSelectionGroup) ); + groupingsToPanels.remove(uiSelectionGroup); } } } + refreshViewsSelectionPanelHeight(); + } + + private void refreshViewsSelectionPanelHeight() { viewsSelectionPanelHeight = groupingsToViews.keySet().size() * 40; } @@ -932,6 +974,7 @@ private JPanel createViewSelectionPanel( MoBIE moBIE, String panelName, Map< Str horizontalLayoutPanel.add( button ); groupingsToComboBox.put( panelName, comboBox ); + groupingsToPanels.put( panelName, horizontalLayoutPanel ); return horizontalLayoutPanel; }