From 1d8c114fc5eec614f35246e3e6b0fa5bc854d826 Mon Sep 17 00:00:00 2001 From: MichaelR Date: Wed, 10 Jul 2024 15:05:44 +0000 Subject: [PATCH] Resolve "Improve shape layer selection panel - Round2" Closes #1910 See merge request main/Sumatra!1871 sumatra-commit: 464d45aaa542f89ffbddc8ad24a271ff938d842e --- .../src/main/resources/icons8-add-50.png | Bin 0 -> 161 bytes .../src/main/resources/icons8-cancel-2-60.png | Bin 0 -> 1010 bytes .../src/main/resources/icons8-collapse-50.png | Bin 0 -> 461 bytes .../src/main/resources/icons8-expand-50.png | Bin 0 -> 445 bytes .../visualizer/VisualizerPresenter.java | 258 ++++++++++++++++-- .../options/ShapeSelectionModel.java | 57 +++- .../options/ShapeSelectionPanel.java | 216 ++++++++++++++- .../tigers/sumatra/model/SumatraModel.java | 37 +++ 8 files changed, 524 insertions(+), 44 deletions(-) create mode 100644 modules/common-gui/src/main/resources/icons8-add-50.png create mode 100644 modules/common-gui/src/main/resources/icons8-cancel-2-60.png create mode 100644 modules/common-gui/src/main/resources/icons8-collapse-50.png create mode 100644 modules/common-gui/src/main/resources/icons8-expand-50.png diff --git a/modules/common-gui/src/main/resources/icons8-add-50.png b/modules/common-gui/src/main/resources/icons8-add-50.png new file mode 100644 index 0000000000000000000000000000000000000000..cbdc5eabe7c5f82733844ba6b3c282725f833fd8 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1|;R|J2nC-&H|6fVg?3oVGw3ym^DWNC>ZSN z;uumf=k4W#oDBgytOvi`FWFNac1PcM0+XC~8bibNw{QElwjI$3TI|uQWZHE_BWSYr zCi(lxJd&D3vS>lw*wl_;OXk;vd$@? F2>^=3GE@Kn literal 0 HcmV?d00001 diff --git a/modules/common-gui/src/main/resources/icons8-cancel-2-60.png b/modules/common-gui/src/main/resources/icons8-cancel-2-60.png new file mode 100644 index 0000000000000000000000000000000000000000..1181afb6c83872e24b4a60189174a000fb41e036 GIT binary patch literal 1010 zcmVn$g5$1jS^jc?v-{32F?^Mi4^4 zOBB(dE=BN$63si9H&7GzAs5gG@U~EQ#z`<^7UnJmB@D&sPNw^GPmBFS!_d?H*ZHT; zRMn|-AV-cIIdc5JXamjx&w&Zx1F!(B0L%JW?!D1xO8U+}g}uNPU<%j(Hfy}(ompTA zD3Dk1DAXl<7-+1w*dxG9$UxV1YTpM=14n@tV22LA zMep?i4}fW4ErjnK&{Z$7CqleB2iyhPBk&f1Tfo-njXF3rbFPEimZp|LC1Kc7Vv_0$5ei)-<9epdd%3Z z_zIdMj?`04-0u)A#lv}$MM3j@wcRH^QbNU$N}jc-m!~f}N>CYdWL!0bWuQ6W*?M?d zg6S7y^Ku-*X+tLEVi@Y-={v@r)b^2=dmCmUu$`cgw&;VV@{=*QMjUpP363?R%`6_)v`L;d(M4a2 z2iGqrI|5`(^gZ*=WSl_^8M1Gl^0BWx-Kzz%h^Mcc=cL)V$9EI2 z4D2IWY{%0@M{y*}#MtQ2=2+TWD`B2)&|F*n-9nsTWif2X)0o;}qHRxFu4_IGH|l0| zwSg7g_Na&GO-E#wO?I#7a%_MvWk9q{<-Gp?@;-p#g?0kS{Qxp3aJt2o$q$0bmP4Ze%)N-*L@}Et7 z8V8|MH=~21=epM854D)+)@k3Qk-wn#`m|`64LCni9gPo>k%MWKEmB9EYe6^myr&)? zM-OdP7Ux=2&r2INvuM%7r86-Omrm-cC-y=hZM5pfmb6^Pf%h7Wmi3y~d*k{{f7ZR3 gBS(%LIY7{>*&}@MtgGE<_<6?WByMcc17Vb4{6>c-2)ig@RO(m?9txRJ9{j1T|$;K*=UYq>79R ztUKk36qSi7o^gm2k%_GU?k9&BGU9qJnUwDn<_2)IZN(6$bGPQMbr~~2#^gwHT!_S+ zj44r(yevq8$Y(x`dVJ>_%}QKB5YOfZtpIg^Y(45AGou00000NkvXXu0mjf DVVS(J literal 0 HcmV?d00001 diff --git a/modules/common-gui/src/main/resources/icons8-expand-50.png b/modules/common-gui/src/main/resources/icons8-expand-50.png new file mode 100644 index 0000000000000000000000000000000000000000..dcbca1307d913205c2d77722163416cd13b817dd GIT binary patch literal 445 zcmV;u0Yd(XP))o?WPd|{Y#vhN9l)YNWC(4{ zD)I;54f<>WoV6pOEwe31G>M!e*G@sgCHfK*d5JVA9hq1pd>^lpB2bpeNkCdeOQs~D zA|frrk!TXRP8s|h4+R;n1ZO1e%x1N92gZ1sNDXL}sk54KI&YTo`BRER zj}j@F6VjY|mAHOnb|D%|b0+;}d=nVI7LG&^) zp2BZkE{HCYqI_oV|LQSCaft+Qlc}?rGie+Fe20H;*+CjVGR6~SDttw3k+`LesW6@- zxvgc*&aMP^r%BR#c1ejE8Ilyykl@OYl!>M~0+C?&gyzDMo@58Il&!mf*^;p^9Q> nh`k-Xk)0vVaE53Y#+`8ie3wwAXKaYU00000NkvXXu0mjfFwnLi literal 0 HcmV?d00001 diff --git a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/VisualizerPresenter.java b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/VisualizerPresenter.java index 9a339c82..95564f3f 100644 --- a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/VisualizerPresenter.java +++ b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/VisualizerPresenter.java @@ -27,20 +27,34 @@ import org.apache.commons.lang.StringUtils; import javax.swing.AbstractButton; +import javax.swing.JSplitPane; import javax.swing.JTextField; import javax.swing.JToggleButton; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; +import javax.swing.event.TreeExpansionEvent; +import javax.swing.event.TreeExpansionListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; +import java.util.stream.Collectors; /** @@ -50,23 +64,21 @@ public class VisualizerPresenter implements ISumatraViewPresenter, IWorldFrameObserver { private static final int VISUALIZATION_FPS = 24; - private final ShapeSelectionModel shapeSelectionModel = new ShapeSelectionModel(); - @Getter private final VisualizerPanel viewPanel = new VisualizerPanel(); - @Getter private final VisualizerFieldPresenter fieldPresenter = new VisualizerFieldPresenter( viewPanel.getFieldPanel() ); - - @Setter - private String propertiesPrefix = VisualizerPresenter.class.getCanonicalName() + "."; - private final BallInteractor ballInteractor = new BallInteractor(); private final MediaRecorder mediaRecorder = new MediaRecorder(); + private final Set defaultVisibilityObtained = new HashSet<>(); + @Setter + private String propertiesPrefix = VisualizerPresenter.class.getCanonicalName() + "."; + private final PropertyChangeListener dividerPositionChangeListener = this::dividerLocationChanged; private Thread updateThread; + private boolean firstUpdate = true; public VisualizerPresenter() @@ -116,9 +128,13 @@ public VisualizerPresenter() viewPanel.getShapeSelectionPanel().getTree().setDigIn(false); viewPanel.getShapeSelectionPanel().getTree().getCheckBoxTreeSelectionModel() .addTreeSelectionListener(this::onSelectionChanged); + viewPanel.getShapeSelectionPanel().getTree().addTreeExpansionListener(new MyTreeExpansionListener()); viewPanel.getShapeSelectionPanel().getTree().setCellRenderer(new ShapeTreeCellRenderer()); - viewPanel.getShapeSelectionPanel().getExpandAll().addActionListener(this::expandAll); - viewPanel.getShapeSelectionPanel().getCollapseAll().addActionListener(this::collapseAll); + viewPanel.getShapeSelectionPanel().getExpandAll().addActionListener(a -> expandAll()); + viewPanel.getShapeSelectionPanel().getCollapseAll().addActionListener(a -> collapseAll()); + viewPanel.getShapeSelectionPanel().addLayerFileSaver(this::saveLayersToFile); + viewPanel.getShapeSelectionPanel().addLayerFileOpener(this::openLayersFromFile); + viewPanel.getShapeSelectionPanel().getPresetDef().addActionListener(a -> showOnlyDefaultLayers()); viewPanel.getToolbar().getTurnCounterClockwise().addActionListener(a -> fieldPresenter.turnCounterClockwise()); viewPanel.getToolbar().getTurnClockwise().addActionListener(a -> fieldPresenter.turnClockwise()); @@ -127,17 +143,27 @@ public VisualizerPresenter() viewPanel.getToolbar().getRecordVideoSelection().addActionListener(this::recordVideoCurrentSelection); viewPanel.getToolbar().getTakeScreenshotFull().addActionListener(this::takeScreenshotFull); viewPanel.getToolbar().getTakeScreenshotSelection().addActionListener(this::takeScreenshotSelection); - - viewPanel.getSplitPane().setDividerLocation(0.8); } private void setShapeSelectionPanelVisibility(boolean visible) { + if (!firstUpdate && !visible) + { + viewPanel.getSplitPane().removePropertyChangeListener( + JSplitPane.DIVIDER_LOCATION_PROPERTY, + dividerPositionChangeListener + ); + } viewPanel.getShapeSelectionPanel().setVisible(visible); - if (visible) + if (!firstUpdate && visible) { - viewPanel.getSplitPane().setDividerLocation(0.8); + var prop = SumatraModel.getInstance().getUserProperty(propertiesPrefix + "dividerLocation", 0.8); + SwingUtilities.invokeLater(() -> viewPanel.getSplitPane().setDividerLocation(prop)); + viewPanel.getSplitPane().addPropertyChangeListener( + JSplitPane.DIVIDER_LOCATION_PROPERTY, + dividerPositionChangeListener + ); } } @@ -255,6 +281,22 @@ private void update() { log.error("Exception in visualizer updater", e); } + + if (firstUpdate) + { + firstUpdate = false; + setShapeSelectionPanelVisibility(getViewPanel().getToolbar().getShapeSelection().isSelected()); + } + } + + + private void dividerLocationChanged(PropertyChangeEvent event) + { + var width = viewPanel.getSplitPane().getWidth(); + var size = viewPanel.getSplitPane().getDividerSize(); + var loc = viewPanel.getSplitPane().getDividerLocation(); + var position = loc / (double) (width - size); + SumatraModel.getInstance().setUserProperty(propertiesPrefix + "dividerLocation", position); } @@ -267,14 +309,26 @@ private void onSelectionChanged(TreeSelectionEvent treeSelectionEvent) private void updateVisibility() { shapeSelectionModel.getSources().forEach((source, node) -> { - boolean visible = isSelected(node); + boolean visible = isSelected(node, true); fieldPresenter.setSourceVisibility(source, visible); }); shapeSelectionModel.getLayers().forEach((layer, node) -> { - boolean visible = isSelected(node); + boolean visible = isSelected(node, true); fieldPresenter.setShapeLayerVisibility(layer.getId(), visible); - SumatraModel.getInstance().setUserProperty(propertiesPrefix + layer.getId(), String.valueOf(visible)); + if (defaultVisibilityObtained.contains(layer)) + { + var noDigIn = isSelected(node, false); + SumatraModel.getInstance().setUserProperty(propertiesPrefix + layer.getId(), String.valueOf(noDigIn)); + } + }); + + shapeSelectionModel.getLayerCategories().forEach((category, node) -> { + if (defaultVisibilityObtained.contains(category)) + { + var noDigIn = isSelected(node, false); + SumatraModel.getInstance().setUserProperty(propertiesPrefix + category.name(), String.valueOf(noDigIn)); + } }); viewPanel.getShapeSelectionPanel().getTree().updateUI(); } @@ -283,7 +337,7 @@ private void updateVisibility() private void newShapeMap(final ShapeMapSource source, final ShapeMap shapeMap) { // select shape map sources by default - shapeSelectionModel.addShapeMapSource(source).ifPresent(this::selectNode); + shapeSelectionModel.addShapeMapSource(source).ifPresent(node -> this.selectShapeMapSource(source, node)); List newNodes = shapeMap.getAllShapeLayers().stream() .map(ShapeMap.ShapeLayer::getIdentifier) @@ -292,6 +346,27 @@ private void newShapeMap(final ShapeMapSource source, final ShapeMap shapeMap) .map(Optional::get) .toList(); newNodes.forEach(this::setDefaultVisibility); + + shapeSelectionModel.getLayerCategories().forEach((category, node) -> { + if (!defaultVisibilityObtained.contains(category)) + { + var visible = SumatraModel.getInstance().getUserProperty(propertiesPrefix + category.name(), false); + setNodeSelection(node, visible); + defaultVisibilityObtained.add(category); + } + }); + } + + + private void selectShapeMapSource(ShapeMapSource source, DefaultMutableTreeNode node) + { + if (source.getParent() != null && node.getParent() instanceof DefaultMutableTreeNode parent) + { + selectNode(parent); + } else + { + selectNode(node); + } } @@ -303,13 +378,103 @@ private void setDefaultVisibility(DefaultMutableTreeNode node) propertiesPrefix + shapeLayer.getId(), shapeLayer.isVisibleByDefault() ); - if (visible) + setNodeSelection(node, visible); + defaultVisibilityObtained.add(shapeLayer); + expandCollapseDependingOnUserProperty(node); + } + } + + + private void expandCollapseDependingOnUserProperty(DefaultMutableTreeNode node) + { + var tree = viewPanel.getShapeSelectionPanel().getTree(); + var path = new TreePath(node.getPath()).getParentPath(); + while (path != null) + { + var expanded = SumatraModel.getInstance().getUserProperty(propertiesPrefix + path, false); + if (expanded) { - selectNode(node); + if (!tree.isExpanded(path)) + { + var constPath = path; + SwingUtilities.invokeLater(() -> tree.expandPath(constPath)); + } } else { - deselectNode(node); + if (tree.isExpanded(path)) + { + var constPath = path; + SwingUtilities.invokeLater(() -> tree.collapsePath(constPath)); + } } + path = path.getParentPath(); + } + } + + + private void showOnlyDefaultLayers() + { + shapeSelectionModel.getAllLayerNonLeafNodes().forEach(this::deselectNode); + for (var entry : shapeSelectionModel.getLayers().entrySet()) + { + var layer = entry.getKey(); + var node = entry.getValue(); + + var visible = layer.isVisibleByDefault(); + SumatraModel.getInstance().setUserProperty(propertiesPrefix + layer.getId(), visible); + setNodeSelection(node, visible); + } + + } + + + private void saveLayersToFile(File file) + { + var lines = shapeSelectionModel.getAllLayerNodes() + .filter(node -> isSelected(node, false)) + .map(node -> new TreePath(node.getPath()).toString()) + .toList(); + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) + { + for (var line : lines) + { + writer.write(line); + writer.newLine(); + } + } catch (IOException e) + { + log.error(e); + } + } + + + private void openLayersFromFile(File file) + { + Set paths; + try (BufferedReader reader = new BufferedReader(new FileReader(file))) + { + paths = reader.lines().collect(Collectors.toSet()); + } catch (IOException e) + { + log.error(e); + return; + } + for (var node : shapeSelectionModel.getAllLayerNodes().toList()) + { + var path = new TreePath(node.getPath()).toString(); + setNodeSelection(node, paths.contains(path)); + } + } + + + private void setNodeSelection(DefaultMutableTreeNode node, boolean select) + { + if (select) + { + selectNode(node); + } else + { + deselectNode(node); } } @@ -330,25 +495,31 @@ private void deselectNode(DefaultMutableTreeNode node) } - private void expandAll(ActionEvent e) + private void expandAll() { var tree = viewPanel.getShapeSelectionPanel().getTree(); - shapeSelectionModel.getAllNonLeafPaths().forEach(tree::expandPath); + shapeSelectionModel.getAllNonLeafPaths().forEach(path -> SwingUtilities.invokeLater(() -> tree.expandPath(path))); } - private void collapseAll(ActionEvent e) + private void collapseAll() { var tree = viewPanel.getShapeSelectionPanel().getTree(); - shapeSelectionModel.getAllNonLeafPaths().forEach(tree::collapsePath); + shapeSelectionModel.getAllNonLeafNodes() + .filter(node -> node.getPath().length > 1) + .filter(node -> !(shapeSelectionModel.isLayer(node) && node.getPath().length < 3)) + .filter(node -> !(shapeSelectionModel.isSource(node) && node.getPath().length < 2)) + .map(DefaultMutableTreeNode::getPath) + .map(TreePath::new) + .forEach(path -> SwingUtilities.invokeLater(() -> tree.collapsePath(path))); } - private boolean isSelected(DefaultMutableTreeNode node) + private boolean isSelected(DefaultMutableTreeNode node, boolean digIn) { var treePath = new TreePath(node.getPath()); return viewPanel.getShapeSelectionPanel().getTree().getCheckBoxTreeSelectionModel() - .isPathSelected(treePath, true); + .isPathSelected(treePath, digIn); } @@ -470,4 +641,39 @@ private void toggleShapeSelection() } } } + + + private class MyTreeExpansionListener implements TreeExpansionListener + { + @Override + public void treeCollapsed(TreeExpansionEvent event) + { + var obj = event.getPath().getLastPathComponent(); + if (obj instanceof DefaultMutableTreeNode node) + { + iterateChildrenToCollapse(node); + } + SumatraModel.getInstance().setUserProperty(propertiesPrefix + event.getPath(), null); + } + + + private void iterateChildrenToCollapse(DefaultMutableTreeNode node) + { + var children = node.children(); + while (children.hasMoreElements()) + { + var child = (DefaultMutableTreeNode) children.nextElement(); + var path = new TreePath(child.getPath()); + SumatraModel.getInstance().setUserProperty(propertiesPrefix + path, null); + iterateChildrenToCollapse(child); + } + } + + + @Override + public void treeExpanded(TreeExpansionEvent event) + { + SumatraModel.getInstance().setUserProperty(propertiesPrefix + event.getPath(), true); + } + } } diff --git a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/options/ShapeSelectionModel.java b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/options/ShapeSelectionModel.java index 2dd3721e..416e7126 100644 --- a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/options/ShapeSelectionModel.java +++ b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/options/ShapeSelectionModel.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; @Log4j2 @@ -33,6 +34,7 @@ public class ShapeSelectionModel extends DefaultTreeModel private final Map sourceCategories = new HashMap<>(); @Getter private final Map layers = new HashMap<>(); + @Getter private final Map layerCategories = new HashMap<>(); @@ -94,27 +96,60 @@ public Optional addShapeLayer(IShapeLayerIdentifier shap } - private List getAllPaths() + private Stream getAllNodes() + { + return iterateNode((DefaultMutableTreeNode) root).stream(); + } + + + public Stream getAllNonLeafPaths() + { + return getAllNonLeafNodes() + .map(DefaultMutableTreeNode::getPath) + .map(TreePath::new); + } + + + public Stream getAllNonLeafNodes() + { + return getAllNodes() + .filter(node -> !node.isLeaf()) + .sorted(Comparator.comparingInt(node -> -node.getPath().length)); + } + + + public Stream getAllLayerNodes() + { + return getAllNodes() + .filter(this::isLayer); + } + + + public boolean isSource(DefaultMutableTreeNode node) + { + return sourcesNode.isNodeDescendant(node); + } + + + public boolean isLayer(DefaultMutableTreeNode node) { - return iterateNode((DefaultMutableTreeNode) root).stream() - .sorted(Comparator.comparingInt(TreePath::getPathCount).reversed()) - .toList(); + return layersNode.isNodeDescendant(node); } - public List getAllNonLeafPaths() + public Stream getAllLayerNonLeafNodes() { - return getAllPaths().stream() - .filter(p -> !isLeaf(p.getLastPathComponent())) - .toList(); + return getAllNodes() + .filter(node -> !node.isLeaf()) + .filter(this::isLayer); } - private List iterateNode(DefaultMutableTreeNode node) + private List iterateNode(DefaultMutableTreeNode node) { var children = node.children(); - List paths = new ArrayList<>(); - paths.add(new TreePath(node.getPath())); + List paths = new ArrayList<>(); + paths.add(node); while (children.hasMoreElements()) { paths.addAll(iterateNode((DefaultMutableTreeNode) children.nextElement())); diff --git a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/options/ShapeSelectionPanel.java b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/options/ShapeSelectionPanel.java index 9dc0345e..d2537cb3 100644 --- a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/options/ShapeSelectionPanel.java +++ b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/options/ShapeSelectionPanel.java @@ -7,46 +7,248 @@ import com.jidesoft.swing.CheckBoxTree; import edu.tigers.sumatra.components.BetterScrollPane; +import edu.tigers.sumatra.util.ImageScaler; import lombok.Getter; import lombok.extern.log4j.Log4j2; +import javax.swing.Box; import javax.swing.JButton; +import javax.swing.JFileChooser; import javax.swing.JPanel; +import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.ScrollPaneConstants; import java.awt.BorderLayout; +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; @Log4j2 public class ShapeSelectionPanel extends JPanel { + private static final String PRESET_QUICK_FILE_PATH_FORMAT = "preset_quick%d.layers"; + private static final String CONFIG_DIR = "config/shape_layer"; + private static final int NUM_PRESET_QUICK_SLOTS = 5; + + @Getter + private final JToolBar toolBarNorth = new JToolBar(); @Getter - private final JToolBar toolBar = new JToolBar(); + private final JToolBar toolBarSouth = new JToolBar(); @Getter private final CheckBoxTree tree = new CheckBoxTree(); @Getter - private final JButton expandAll = new JButton("Exp"); + private final JButton expandAll = new JButton(); @Getter - private final JButton collapseAll = new JButton("Col"); + private final JButton collapseAll = new JButton(); + @Getter + private final JButton presetDef = new JButton("Def"); + private final List presetQuick = new ArrayList<>(); + private final JToggleButton add = new JToggleButton(); + private final JButton open = new JButton(); + private final JButton save = new JButton(); + + private final List layerFileSavers = new ArrayList<>(); + private final List layerFileOpeners = new ArrayList<>(); public ShapeSelectionPanel() { setLayout(new BorderLayout()); - toolBar.setFloatable(false); - toolBar.add(expandAll); - toolBar.add(collapseAll); + expandAll.setIcon(ImageScaler.scaleSmallButtonImageIcon("/icons8-expand-50.png")); + expandAll.setToolTipText("Expand all"); + collapseAll.setIcon(ImageScaler.scaleSmallButtonImageIcon("/icons8-collapse-50.png")); + collapseAll.setToolTipText("Collapse all"); + + + open.setIcon(ImageScaler.scaleSmallButtonImageIcon("/open.png")); + open.setToolTipText("Open from file"); + open.addActionListener(a -> pressOpenButton()); + save.setIcon(ImageScaler.scaleSmallButtonImageIcon("/save.png")); + save.setToolTipText("Save to file"); + save.addActionListener(a -> pressSaveButton()); + + presetDef.setToolTipText("Switch layers to default"); + for (int i = 0; i < NUM_PRESET_QUICK_SLOTS; i++) + { + var num = i + 1; + var button = new JButton(String.valueOf(num)); + button.addActionListener(a -> pressQuickPreset(num)); + presetQuick.add(button); + } + add.addActionListener(a -> pressAddButton()); + add.setSelected(false); + pressAddButton(); + var scrollPane = new BetterScrollPane(tree); scrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED); scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); - add(toolBar, BorderLayout.NORTH); + + toolBarNorth.setFloatable(false); + toolBarNorth.add(expandAll); + toolBarNorth.add(collapseAll); + + toolBarSouth.setFloatable(false); + toolBarSouth.add(open); + toolBarSouth.add(save); + toolBarSouth.add(Box.createHorizontalGlue()); + toolBarSouth.add(presetDef); + presetQuick.forEach(toolBarSouth::add); + toolBarSouth.add(add); + + add(toolBarNorth, BorderLayout.NORTH); add(scrollPane, BorderLayout.CENTER); + add(toolBarSouth, BorderLayout.SOUTH); + } + + + public void addLayerFileSaver(LayerFileSaver saver) + { + layerFileSavers.add(saver); + } + + + public void addLayerFileOpener(LayerFileOpener opener) + { + layerFileOpeners.add(opener); + } + + + private void pressSaveButton() + { + getPresetFilePathFromUser(true).map(File::new).ifPresent(this::saveToFile); + } + + + private void pressOpenButton() + { + getPresetFilePathFromUser(false).map(File::new).ifPresent(this::openFromFile); + } + + + private void saveToFile(File file) + { + layerFileSavers.forEach(saver -> saver.onSaveToFile(file)); } + private void openFromFile(File file) + { + if (file.exists()) + { + layerFileOpeners.forEach(saver -> saver.onOpenFromFile(file)); + } + } + + + private void pressAddButton() + { + if (add.isSelected()) + { + presetDef.setEnabled(false); + + for (int i = 0; i < presetQuick.size(); ++i) + { + var num = i + 1; + var button = presetQuick.get(i); + button.setEnabled(true); + button.setToolTipText(String.format("Save current layers to quick select %d", num)); + } + presetQuick.forEach(button -> button.setEnabled(true)); + + open.setEnabled(false); + save.setEnabled(false); + add.setIcon(ImageScaler.scaleSmallButtonImageIcon("/icons8-cancel-2-60.png")); + add.setToolTipText("Cancel setting quick access"); + } else + { + presetDef.setEnabled(true); + for (int i = 0; i < presetQuick.size(); ++i) + { + var num = i + 1; + var button = presetQuick.get(i); + button.setEnabled(getQuickPresetFile(num).exists()); + button.setToolTipText(String.format("Switch layers to quick select %d", num)); + } + + open.setEnabled(true); + save.setEnabled(true); + + add.setIcon(ImageScaler.scaleSmallButtonImageIcon("/icons8-add-50.png")); + add.setToolTipText("Set current layers to quick access"); + } + } + + + private void pressQuickPreset(int presetNumber) + { + if (add.isSelected()) + { + saveToFile(getQuickPresetFile(presetNumber)); + add.setSelected(false); + pressAddButton(); + } else + { + openFromFile(getQuickPresetFile(presetNumber)); + } + } + + + private File getQuickPresetFile(int presetNumber) + { + Paths.get(CONFIG_DIR).toFile().mkdirs(); + return Paths.get(CONFIG_DIR, String.format(PRESET_QUICK_FILE_PATH_FORMAT, presetNumber)).toFile(); + } + + + private Optional getPresetFilePathFromUser(boolean useSaveDialog) + { + Paths.get(CONFIG_DIR).toFile().mkdirs(); + File savedLayers = Paths.get(CONFIG_DIR, "preset.layers").toFile(); + var lastConfigDir = savedLayers.getParentFile(); + if (lastConfigDir.mkdirs()) + { + log.info("New directory created: {}", lastConfigDir); + } + + var fcOpenSnapshot = new JFileChooser(lastConfigDir); + fcOpenSnapshot.setSelectedFile(savedLayers); + + int returnVal = useSaveDialog + ? fcOpenSnapshot.showSaveDialog(this) + : fcOpenSnapshot.showOpenDialog(this); + + if (returnVal == JFileChooser.APPROVE_OPTION) + { + try + { + return Optional.of(fcOpenSnapshot.getSelectedFile().getCanonicalPath()); + } catch (IOException e) + { + log.error("Could not load snapshot", e); + } + } + return Optional.empty(); + } + + + @FunctionalInterface + public interface LayerFileSaver + { + void onSaveToFile(File file); + } + + @FunctionalInterface + public interface LayerFileOpener + { + void onOpenFromFile(File file); + } } diff --git a/modules/sumatra-model/src/main/java/edu/tigers/sumatra/model/SumatraModel.java b/modules/sumatra-model/src/main/java/edu/tigers/sumatra/model/SumatraModel.java index d57bf001..c9c9b46d 100644 --- a/modules/sumatra-model/src/main/java/edu/tigers/sumatra/model/SumatraModel.java +++ b/modules/sumatra-model/src/main/java/edu/tigers/sumatra/model/SumatraModel.java @@ -352,6 +352,43 @@ public boolean getUserProperty(Class type, String key, boolean def) } + /** + * Calls {@link Properties#getProperty(String)} and parses value to double + * + * @param key + * @param def + * @return The double associated with the given key + */ + public double getUserProperty(String key, double def) + { + String val = getUserProperty(key); + if (val == null) + { + return def; + } + return Double.parseDouble(val); + } + + + /** + * Calls {@link Properties#getProperty(String)} and parses value to double + * + * @param type + * @param key + * @param def + * @return The double associated with the given key + */ + public double getUserProperty(Class type, String key, double def) + { + String val = getUserProperty(type, key); + if (val == null) + { + return def; + } + return Double.parseDouble(val); + } + + /** * Sumatra version *