Skip to content
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
merged 15 commits into from
Jan 2, 2025
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add comments to TabbedUI
pavelbraginskiy committed Jan 1, 2025
commit b62d04c4691fa94f4a4c4f5680f91a5ef1334875
169 changes: 135 additions & 34 deletions megameklab/src/megameklab/ui/MegaMekLabTabbedUI.java
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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"and"

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>
Copy link
Collaborator

Choose a reason for hiding this comment

The 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;
2 changes: 1 addition & 1 deletion megameklab/src/megameklab/ui/MenuBar.java
Original file line number Diff line number Diff line change
@@ -1232,7 +1232,7 @@ public void loadFile(File unitFile) {
reload();
refresh();
} else if (owner instanceof MegaMekLabTabbedUI tabbedUi) {
tabbedUi.addEditor(loadedUnit, unitFile.toString());
tabbedUi.addUnit(loadedUnit, unitFile.toString());
refresh();
}
} catch (Exception ex) {
2 changes: 1 addition & 1 deletion megameklab/src/megameklab/ui/StartupGUI.java
Original file line number Diff line number Diff line change
@@ -278,7 +278,7 @@ public static void selectAndLoadUnitFromCache(MenuBarOwner previousFrame) {

CConfig.setMostRecentFile(fileName);
if (previousFrame instanceof MegaMekLabTabbedUI tabbedUi) {
tabbedUi.addEditor(newUnit, fileName);
tabbedUi.addUnit(newUnit, fileName);
} else if (!(previousFrame instanceof MegaMekLabMainUI)
|| (newUnit.getEntityType() != previousFrame.getEntity().getEntityType())) {
previousFrame.getFrame().setVisible(false);