-
Notifications
You must be signed in to change notification settings - Fork 137
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow opening multiple units in tabs #1673
Merged
+836
−75
Merged
Changes from 1 commit
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
05a9996
Open multiple units in tabs
pavelbraginskiy 1c05c57
Make tabs closable, and set tab name when unit name changes
pavelbraginskiy 8751855
Missing license file
pavelbraginskiy f369abd
tooltip
pavelbraginskiy d550d44
Properly rename tab
pavelbraginskiy 546a444
Redirect certain menu bar operations (e.g. saving) to the currently s…
pavelbraginskiy b62d04c
Add comments to TabbedUI
pavelbraginskiy 8d8ecf5
Restore tabs
pavelbraginskiy fdf0bcd
Fix load from file to not destroy all tabs
pavelbraginskiy 4f1ec42
Don't save tab state when startup behaviour isn't Restore Tabs
pavelbraginskiy fd82a32
Set font to Symbola for symbols
pavelbraginskiy 93b1b61
Prevent keyboard navigation to "new tab" button
pavelbraginskiy a6a39dc
File menu actions and keyboard shortcuts for new/close tab
pavelbraginskiy 2e5970b
Address review comments
pavelbraginskiy a2abcdd
reopen closed tab functionality
pavelbraginskiy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Add comments to TabbedUI
commit b62d04c4691fa94f4a4c4f5680f91a5ef1334875
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,84 +40,86 @@ | |
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
/** | ||
* Replaces {@link MegaMekLabMainUI} as the top-level window for MML. | ||
* Holds several {@link MegaMekLabMainUI}s as tabs, allowing many units to be open at once. | ||
*/ | ||
public class MegaMekLabTabbedUI extends JFrame implements MenuBarOwner, ChangeListener { | ||
private List<MegaMekLabMainUI> editors = new ArrayList<>(); | ||
|
||
private JTabbedPane tabs = new JTabbedPane(); | ||
|
||
private MenuBar menuBar; | ||
|
||
/** | ||
* Constructs a new MegaMekLabTabbedUI instance, which serves as the main tabbed UI | ||
* for managing multiple MegaMekLabMainUI editors. Automatically initializes a default | ||
* BMMainUI instance if no entities are provided. | ||
* | ||
* @param entities A variable number of MegaMekLabMainUI instances that will be added | ||
* as tabs to the UI. If no entities are provided, a default BMMainUI | ||
* instance will be created and added. | ||
*/ | ||
public MegaMekLabTabbedUI(MegaMekLabMainUI... entities) { | ||
super("MegaMekLab"); | ||
|
||
if (entities.length == 0) { | ||
throw new IllegalArgumentException("At least one entity must be provided"); | ||
// Create a blank Mek by default | ||
if (entities.length == 0) { | ||
entities = new MegaMekLabMainUI[] { new BMMainUI(false, false) }; | ||
} | ||
|
||
// If there are more tabs than can fit, show a scroll bar instead of stacking tabs in multiple rows | ||
// This is a matter of preference, I could be convinced to switch this. | ||
tabs.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); | ||
|
||
|
||
// Add the given editors as tabs, then add the New Button. | ||
// The New Button is actually just another blank Mek with the name "+", | ||
// There is nothing special about it | ||
for (MegaMekLabMainUI e : entities) { | ||
addTab(e); | ||
|
||
} | ||
addNewButton(); | ||
|
||
|
||
tabs.addChangeListener(this); | ||
setContentPane(tabs); | ||
|
||
menuBar = new MenuBar(this); | ||
setJMenuBar(menuBar); | ||
|
||
// Enable opening unit and mul files by drag-and-drop | ||
setTransferHandler(new MMLFileDropTransferHandler(this)); | ||
|
||
|
||
// Remember the size and position of the window from last time MML was launched | ||
pack(); | ||
restrictToScrenSize(); | ||
setLocationRelativeTo(null); | ||
|
||
CConfig.getMainUiWindowSize(this).ifPresent(this::setSize); | ||
CConfig.getMainUiWindowPosition(this).ifPresent(this::setLocation); | ||
|
||
// ...and save that size nad position on exit | ||
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); | ||
addWindowListener(new ExitOnWindowClosingListener(this)); | ||
setExtendedState(CConfig.getIntParam(CConfig.GUI_FULLSCREEN)); | ||
} | ||
|
||
/** | ||
* Retrieves the currently selected editor from the tabbed user interface. | ||
* | ||
* @return The currently selected MegaMekLabMainUI instance, which represents the | ||
* active editor in the tabbed UI. | ||
*/ | ||
public MegaMekLabMainUI currentEditor() { | ||
return editors.get(tabs.getSelectedIndex()); | ||
} | ||
|
||
@Override | ||
public JFrame getFrame() { | ||
return this; | ||
} | ||
|
||
@Override | ||
public Entity getEntity() { | ||
return currentEditor().getEntity(); | ||
} | ||
|
||
@Override | ||
public String getFileName() { | ||
return currentEditor().getFileName(); | ||
} | ||
|
||
@Override | ||
public boolean hasEntityNameChanged() { | ||
return currentEditor().hasEntityNameChanged(); | ||
} | ||
|
||
@Override | ||
public void refreshMenuBar() { | ||
menuBar.refreshMenuBar(); | ||
} | ||
|
||
@Override | ||
public MenuBar getMMLMenuBar() { | ||
return menuBar; | ||
} | ||
|
||
@Override | ||
public void stateChanged(ChangeEvent e) { | ||
// This watches for the user selecting the New Tab button, which is actually a normal tab. | ||
// When they select it, we quickly rename the tab to "New Mek" and add a new "New Tab" button onto the end. | ||
if (tabs.getSelectedIndex() == editors.size() - 1) { | ||
tabs.setTabComponentAt( | ||
tabs.getSelectedIndex(), | ||
|
@@ -128,18 +130,41 @@ public void stateChanged(ChangeEvent e) { | |
} | ||
} | ||
|
||
/** | ||
* Updates the name of the currently selected tab in the tabbed user interface. | ||
* Should typically be called when the name of the unit being edited changes. | ||
* | ||
* @param tabName The new name to be set for the currently selected tab. | ||
*/ | ||
public void setTabName(String tabName) { | ||
// ClosableTab is a label with the unit name, and a close button. | ||
// If we didn't need that close button, this could be tabs.setTitleAt | ||
tabs.setTabComponentAt(tabs.getSelectedIndex(), new ClosableTab(tabName, currentEditor()) ); | ||
} | ||
|
||
/** | ||
* Adds a new editor tab to the tabbed UI. This includes adding the editor | ||
* to the internal editor collection, refreshing it, setting the ownership, | ||
* and adding the tab to the tabs UI. | ||
* | ||
* @param editor The MegaMekLabMainUI instance to be added as a new tab. | ||
*/ | ||
private void addTab(MegaMekLabMainUI editor) { | ||
editors.add(editor); | ||
editor.refreshAll(); | ||
editor.setOwner(this); | ||
tabs.addTab(editor.getEntity().getDisplayName(), editor.getContentPane()); | ||
// See ClosableTab later in this file for what's going on here. | ||
tabs.setTabComponentAt(tabs.getTabCount() - 1, new ClosableTab(editor.getEntity().getDisplayName(), editor)); | ||
} | ||
|
||
/** | ||
* Similar to addTab above, this adds a blank Mek editor, but with the name "➕" | ||
* so that it looks like a button for creating a new tab. | ||
* <p> | ||
* The JTabbedPane doesn't come with any functionality for the user adding/removing tabs out of the box, | ||
* so this is how we fake it. | ||
*/ | ||
private void addNewButton() { | ||
var editor = new BMMainUI(false, false); | ||
editors.add(editor); | ||
|
@@ -149,6 +174,15 @@ private void addNewButton() { | |
tabs.setTabComponentAt(tabs.getTabCount() - 1, new NewTabButton()); | ||
} | ||
|
||
/** | ||
* The name is misleading, this is actually the Switch Unit Type operation! | ||
* Replaces the current editor with a new blank one of the given unit type. | ||
* Disposes of the old editor UI after the new one is initialized. | ||
* | ||
* @param type the type of unit to load for the new editor UI | ||
* @param primitive whether the unit is primitive | ||
* @param industrial whether the unit is an IndustrialMek | ||
*/ | ||
private void newUnit(long type, boolean primitive, boolean industrial) { | ||
var oldUi = editors.get(tabs.getSelectedIndex()); | ||
var newUi = UiLoader.getUI(type, primitive, industrial); | ||
|
@@ -159,18 +193,38 @@ private void newUnit(long type, boolean primitive, boolean industrial) { | |
oldUi.dispose(); | ||
} | ||
|
||
/** | ||
* The name is misleading, this is actually the Switch Unit Type operation! | ||
* Replaces the current editor with a new blank one of the given unit type. | ||
* Disposes of the old editor UI after the new one is initialized. | ||
* | ||
* @param type the type of unit to load for the new editor UI | ||
* @param primitive whether the unit is primitive | ||
*/ | ||
@Override | ||
public void newUnit(long type, boolean primitive) { | ||
newUnit(type, primitive, false); | ||
} | ||
|
||
public void addEditor(Entity entity, String filename) { | ||
|
||
/** | ||
* Adds a new tab with the given unit to the tabbed user interface. | ||
* | ||
* @param entity The Entity object representing the unit to be added. | ||
* @param filename The name of the file associated with the unit being added. | ||
*/ | ||
public void addUnit(Entity entity, String filename) { | ||
// Create a new "new tab" button, since we're about to replace the existing one | ||
addNewButton(); | ||
// Select the old "new tab" button... | ||
tabs.setSelectedIndex(tabs.getTabCount() - 2); | ||
// ...and replace it, since newUnit is actually the Switch Unit Type operation. | ||
newUnit(UnitUtil.getEditorTypeForEntity(entity), entity.isPrimitive(), entity.isIndustrialMek()); | ||
|
||
currentEditor().setEntity(entity, filename); | ||
currentEditor().reloadTabs(); | ||
currentEditor().refreshAll(); | ||
// Set the tab name | ||
tabs.setTabComponentAt(tabs.getSelectedIndex(), new ClosableTab(entity.getDisplayName(), currentEditor())); | ||
} | ||
|
||
|
@@ -199,6 +253,47 @@ private void restrictToScrenSize() { | |
setSize(new Dimension(w, h)); | ||
} | ||
|
||
|
||
//<editor-fold desc="MenuBarOwner interface implementation"> | ||
@Override | ||
public JFrame getFrame() { | ||
return this; | ||
} | ||
|
||
@Override | ||
public Entity getEntity() { | ||
return currentEditor().getEntity(); | ||
} | ||
|
||
@Override | ||
public String getFileName() { | ||
return currentEditor().getFileName(); | ||
} | ||
|
||
@Override | ||
public boolean hasEntityNameChanged() { | ||
return currentEditor().hasEntityNameChanged(); | ||
} | ||
|
||
@Override | ||
public void refreshMenuBar() { | ||
menuBar.refreshMenuBar(); | ||
} | ||
|
||
@Override | ||
public MenuBar getMMLMenuBar() { | ||
return menuBar; | ||
} | ||
//</editor-fold> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think IDE-specific formatting comments should be included in the main repo. |
||
|
||
|
||
/** | ||
* Represents a button used for creating new tabs in the MegaMekLabTabbedUI interface. | ||
* This class extends JPanel and is rendered as a non-opaque panel containing a "+" symbol. | ||
* Used to mimic functionality for adding new tabs in a tabbed user interface. | ||
* <p> | ||
* The only reason this is a separate class instead of just the text for a tab is so that it can be green. | ||
*/ | ||
private static class NewTabButton extends JPanel { | ||
public NewTabButton() { | ||
setOpaque(false); | ||
|
@@ -208,6 +303,12 @@ public NewTabButton() { | |
} | ||
} | ||
|
||
/** | ||
* Represents a custom tab component for use in a tabbed user interface, designed to display | ||
* the name of a unit and provide a close button for removing the associated tab. | ||
* The close button can be shift-clicked to skip the editor's safety prompt. | ||
* This class extends JPanel and is initialized with a unit name and its associated editor instance. | ||
*/ | ||
private class ClosableTab extends JPanel { | ||
JLabel unitName; | ||
JButton closeButton; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"and"