Gridarta Editor
GSplitPane.java
Go to the documentation of this file.
1 /*
2  * Gridarta MMORPG map editor for Crossfire, Daimonin and similar games.
3  * Copyright (C) 2000-2023 The Gridarta Developers.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 package net.sf.gridarta.gui.utils;
21 
22 import java.awt.Component;
23 import java.awt.Container;
24 import java.awt.Dimension;
25 import java.awt.event.ActionEvent;
26 import java.awt.event.ActionListener;
27 import java.awt.event.HierarchyEvent;
28 import java.awt.event.HierarchyListener;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.prefs.Preferences;
32 import javax.swing.AbstractButton;
33 import javax.swing.JSplitPane;
34 import javax.swing.border.EmptyBorder;
35 import net.sf.gridarta.MainControl;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38 
50 public class GSplitPane extends JSplitPane {
51 
55  @NotNull
56  private static final String MIN = "min";
57 
61  @NotNull
62  private static final String MAX = "max";
63 
67  private static final long serialVersionUID = 1L;
68 
72  @NotNull
73  private static final Preferences PREFERENCES = Preferences.userNodeForPackage(MainControl.class);
74 
78  @NotNull
79  private final String preferencesKey;
80 
88  @NotNull
89  private final List<ActionListener> actionListeners = new ArrayList<>();
90 
95  @Nullable
96  private final String defaultDividerLocation;
97 
101  @NotNull
102  private final HierarchyListener hierarchyListener = new HierarchyListener() {
103 
104  @Override
105  public void hierarchyChanged(final HierarchyEvent e) {
106  if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0L && isShowing()) {
107  removeHierarchyListener(hierarchyListener);
108  if (defaultDividerLocation == null) { // should not happen
109  // ignore
110  } else if (defaultDividerLocation.equals(MIN)) {
111  if (!actionListeners.isEmpty()) {
112  actionListeners.get(0).actionPerformed(new ActionEvent(GSplitPane.this, 0, ""));
113  }
114  } else if (defaultDividerLocation.equals(MAX)) {
115  if (!actionListeners.isEmpty()) {
116  actionListeners.get(1).actionPerformed(new ActionEvent(GSplitPane.this, 0, ""));
117  }
118  } else {
119  try {
120  setDividerLocation(Integer.parseInt(defaultDividerLocation));
121  } catch (final NumberFormatException ignored) {
122  // ignore
123  }
124  }
125  }
126  }
127  };
128 
143  public GSplitPane(final int newOrientation, @NotNull final Component newLeftComponent, @NotNull final Component newRightComponent, @NotNull final String preferencesKey, final int defaultDividerLocation) {
144  super(newOrientation, newLeftComponent, newRightComponent);
145  this.preferencesKey = preferencesKey;
146  setOneTouchExpandable(true);
147  this.defaultDividerLocation = PREFERENCES.get(preferencesKey, defaultDividerLocation == -1 ? null : Integer.toString(defaultDividerLocation));
148 
149  // Hacks to circumvent undesired behavior when maximizing/de-maximizing
150  // the application's main window: without this code, the minimized or
151  // maximized state (oneTouchExpandable) does not stick and cannot be
152  // restored when the editor is restarted.
153  //
154  // This code walks the component hierarchy trying to find the
155  // oneTouchExpandable buttons. The contained ActionListeners are
156  // replaced with a proxy that shows the left/right component as
157  // appropriate for the action.
158  setBorder(new EmptyBorder(0, 0, 0, 0));
159  for (final Component component : getComponents()) {
160  if (component != newLeftComponent && component != newRightComponent) {
161  if (component instanceof Container) {
162  final Container container = (Container) component;
163  for (final Component component2 : container.getComponents()) {
164  if (component2 instanceof AbstractButton) {
165  final AbstractButton button = (AbstractButton) component2;
166  for (final ActionListener actionListener : button.getActionListeners()) {
167  if (actionListener.getClass().getName().endsWith("$OneTouchActionHandler")) {
168  actionListeners.add(actionListener);
169  button.removeActionListener(actionListener);
170  if (actionListeners.size() == 1) {
171  button.addActionListener(e -> {
172  getRightComponent().setVisible(true);
173  actionListener.actionPerformed(e);
174  });
175  } else {
176  button.addActionListener(e -> {
177  getLeftComponent().setVisible(true);
178  actionListener.actionPerformed(e);
179  });
180  }
181  break;
182  }
183  }
184  }
185  }
186  }
187  }
188  }
189  if (actionListeners.size() != 2) {
190  actionListeners.clear();
191  }
192  //noinspection VariableNotUsedInsideIf
193  if (this.defaultDividerLocation != null) {
194  addHierarchyListener(hierarchyListener);
195  }
196  }
197 
201  public void saveLocation() {
202  final int dividerLocation = getDividerLocation();
203  if (dividerLocation != -1) {
204  final String value;
205  switch (getState(dividerLocation)) {
206  case -1:
207  value = MIN;
208  break;
209 
210  default:
211  value = Integer.toString(dividerLocation);
212  break;
213 
214  case +1:
215  value = MAX;
216  break;
217  }
218  PREFERENCES.put(preferencesKey, value);
219  }
220  }
221 
222  @Override
223  public void setDividerLocation(final int location) {
224  super.setDividerLocation(location);
225  final int state = getState(location);
226  getLeftComponent().setVisible(state != -1);
227  getRightComponent().setVisible(state != +1);
228  }
229 
236  private int getState(final int dividerLocation) {
237  final Dimension leftSize = leftComponent.getMinimumSize();
238  final Dimension rightSize = rightComponent.getMinimumSize();
239  final int minLimit = getOrientation() == HORIZONTAL_SPLIT ? leftSize.width : leftSize.height;
240  final int maxLimit = getOrientation() == HORIZONTAL_SPLIT ? getWidth() - rightSize.width - getDividerSize() : getHeight() - rightSize.height - getDividerSize();
241  if (dividerLocation < minLimit) {
242  return -1;
243  }
244  if (dividerLocation > maxLimit) {
245  return +1;
246  }
247  return 0;
248  }
249 
250 }
net.sf.gridarta.gui.utils.GSplitPane.defaultDividerLocation
final String defaultDividerLocation
The default divider location.
Definition: GSplitPane.java:96
net.sf.gridarta.gui.utils.GSplitPane.MIN
static final String MIN
The preferences value for a minimized divider.
Definition: GSplitPane.java:56
net.sf.gridarta
Base package of all Gridarta classes.
net.sf.gridarta.gui.utils.GSplitPane.GSplitPane
GSplitPane(final int newOrientation, @NotNull final Component newLeftComponent, @NotNull final Component newRightComponent, @NotNull final String preferencesKey, final int defaultDividerLocation)
Creates a new instance.
Definition: GSplitPane.java:143
net.sf
net.sf.gridarta.gui.utils.GSplitPane.preferencesKey
final String preferencesKey
The preferences key for restoring the divider location.
Definition: GSplitPane.java:79
net.sf.gridarta.gui.utils.GSplitPane.saveLocation
void saveLocation()
Saves the current divider location into the preferences.
Definition: GSplitPane.java:201
net.sf.gridarta.gui.utils.GSplitPane.actionListeners
final List< ActionListener > actionListeners
The ActionListeners attached to the "oneTouchExpandable" buttons of the JSplitPane.
Definition: GSplitPane.java:89
net.sf.gridarta.gui.utils.GSplitPane.getState
int getState(final int dividerLocation)
Returns the minimized/maximized state for a given divider location.
Definition: GSplitPane.java:236
net
net.sf.gridarta.gui.utils.GSplitPane.MAX
static final String MAX
The preferences value for a maximized divider.
Definition: GSplitPane.java:62
net.sf.gridarta.gui.utils.GSplitPane.setDividerLocation
void setDividerLocation(final int location)
Definition: GSplitPane.java:223
net.sf.gridarta.gui.utils.GSplitPane
A JSplitPane that keeps its size even upon ancestor layout changes and is restored upon editor restar...
Definition: GSplitPane.java:50
net.sf.gridarta.gui.utils.GSplitPane.serialVersionUID
static final long serialVersionUID
Serial Version UID.
Definition: GSplitPane.java:67
net.sf.gridarta.gui.utils.GSplitPane.PREFERENCES
static final Preferences PREFERENCES
The preferences.
Definition: GSplitPane.java:73
net.sf.gridarta.gui.utils.GSplitPane.hierarchyListener
final HierarchyListener hierarchyListener
The HierarchyListener for updating the initial divider location.
Definition: GSplitPane.java:102
net.sf.gridarta.MainControl
Interface used as preferences location.
Definition: MainControl.java:27