package com.jediterm.terminal.ui;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.jediterm.terminal.RequestOrigin;
import com.jediterm.terminal.TerminalDisplay;
import com.jediterm.terminal.TtyConnector;
import com.jediterm.terminal.TtyConnectorWaitFor;
import com.jediterm.terminal.ui.settings.SettingsProvider;
import com.jediterm.terminal.ui.settings.TabbedSettingsProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
/**
* @author traff
*/
public class TabbedTerminalWidget extends JPanel implements TerminalWidget, TerminalActionProvider {
private final Object myLock = new Object();
private TerminalPanelListener myTerminalPanelListener = null;
private JediTermWidget myTermWidget = null;
private JTabbedPane myTabbedPane;
private TabbedSettingsProvider mySettingsProvider;
private List<TabListener> myTabListeners = Lists.newArrayList();
private TerminalActionProvider myNextActionProvider;
private final Predicate<TerminalWidget> myCreateNewSessionAction;
public TabbedTerminalWidget(@NotNull TabbedSettingsProvider settingsProvider, @NotNull Predicate<TerminalWidget> createNewSessionAction) {
super(new BorderLayout());
mySettingsProvider = settingsProvider;
myCreateNewSessionAction = createNewSessionAction;
setFocusTraversalPolicy(new DefaultFocusTraversalPolicy());
}
@Override
public TerminalSession createTerminalSession(final TtyConnector ttyConnector) {
final JediTermWidget terminal = createInnerTerminalWidget(mySettingsProvider);
terminal.setTtyConnector(ttyConnector);
terminal.setNextProvider(this);
new TtyConnectorWaitFor(ttyConnector, Executors.newSingleThreadExecutor()).setTerminationCallback(new Predicate<Integer>() {
@Override
public boolean apply(Integer integer) {
if (mySettingsProvider.shouldCloseTabOnLogout(ttyConnector)) {
if (myTabbedPane != null) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
removeTab(terminal);
}
});
} else {
if (myTermWidget == terminal) {
myTermWidget = null;
}
}
fireTabClosed(terminal);
}
return true;
}
});
if (myTerminalPanelListener != null) {
terminal.setTerminalPanelListener(myTerminalPanelListener);
}
if (myTermWidget == null && myTabbedPane == null) {
myTermWidget = terminal;
Dimension size = terminal.getComponent().getSize();
add(myTermWidget.getComponent(), BorderLayout.CENTER);
setSize(size);
if (myTerminalPanelListener != null) {
myTerminalPanelListener.onPanelResize(size, RequestOrigin.User);
}
onSessionChanged();
} else {
if (myTabbedPane == null) {
myTabbedPane = setupTabbedPane();
}
addTab(terminal, myTabbedPane);
}
return terminal;
}
protected JediTermWidget createInnerTerminalWidget(SettingsProvider settingsProvider) {
return new JediTermWidget(settingsProvider);
}
private void addTab(JediTermWidget terminal, JTabbedPane tabbedPane) {
tabbedPane.addTab(generateUniqueName(mySettingsProvider.tabName(terminal.getTtyConnector(), terminal.getSessionName()), tabbedPane), null, terminal);
tabbedPane.setTabComponentAt(tabbedPane.getTabCount() - 1, new TabComponent(tabbedPane, terminal));
tabbedPane.setSelectedComponent(terminal);
}
private static String generateUniqueName(String suggestedName, JTabbedPane tabbedPane) {
final Set<String> names = Sets.newHashSet();
for (int i = 0; i < tabbedPane.getTabCount(); i++) {
names.add(tabbedPane.getTitleAt(i));
}
String newSdkName = suggestedName;
int i = 0;
while (names.contains(newSdkName)) {
newSdkName = suggestedName + " (" + (++i) + ")";
}
return newSdkName;
}
private JTabbedPane setupTabbedPane() {
final JTabbedPane tabbedPane = createTabbedPane();
tabbedPane.addChangeListener(new ChangeListener() {
@Override
public void stateChanged(ChangeEvent event) {
onSessionChanged();
}
});
remove(myTermWidget);
addTab(myTermWidget, tabbedPane);
myTermWidget = null;
add(tabbedPane, BorderLayout.CENTER);
return tabbedPane;
}
public boolean isNoActiveSessions() {
return myTabbedPane == null && myTermWidget == null;
}
private void onSessionChanged() {
JediTermWidget session = getCurrentSession();
if (session != null) {
if (myTerminalPanelListener != null) {
myTerminalPanelListener.onSessionChanged(session);
}
doRequestFocus(session.getTerminalPanel());
}
}
protected void doRequestFocus(JComponent component) {
component.requestFocusInWindow();
}
protected JTabbedPane createTabbedPane() {
return new JTabbedPane();
}
private JPopupMenu createPopup() {
JPopupMenu popupMenu = new JPopupMenu();
TerminalAction.addToMenu(popupMenu, this);
JMenuItem rename = new JMenuItem("Rename Tab");
rename.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent actionEvent) {
renameTab();
}
});
popupMenu.add(rename);
return popupMenu;
}
private void renameTab() {
if (myTabbedPane != null) {
final int selectedIndex = myTabbedPane.getSelectedIndex();
final Component component = myTabbedPane.getTabComponentAt(selectedIndex);
final JTextField jTextField = new JTextField(myTabbedPane.getTitleAt(selectedIndex));
final FocusAdapter focusAdapter = new FocusAdapter() {
@Override
public void focusLost(FocusEvent focusEvent) {
finishRename(selectedIndex, component, jTextField.getText());
}
};
jTextField.addFocusListener(focusAdapter);
jTextField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent keyEvent) {
if (keyEvent.getKeyCode() == KeyEvent.VK_ESCAPE) {
jTextField.removeFocusListener(focusAdapter);
finishRename(selectedIndex, component, null);
} else if (keyEvent.getKeyCode() == KeyEvent.VK_ENTER) {
jTextField.removeFocusListener(focusAdapter);
finishRename(selectedIndex, component, jTextField.getText());
} else {
super.keyPressed(keyEvent);
}
}
});
myTabbedPane.setTabComponentAt(myTabbedPane.getSelectedIndex(), jTextField);
jTextField.requestFocus();
jTextField.selectAll();
}
}
private void finishRename(int selectedIndex, Component component, String newName) {
myTabbedPane.setTabComponentAt(selectedIndex, component);
if (newName != null) {
myTabbedPane.setTitleAt(selectedIndex, newName);
}
}
private void close(JediTermWidget terminal) {
if (terminal != null) {
terminal.close();
if (myTabbedPane != null) {
removeTab(terminal);
} else {
myTermWidget = null;
}
fireTabClosed(terminal);
}
}
public void closeCurrentSession() {
close(getCurrentSession());
}
public void dispose() {
for (TerminalSession s : getAllTerminalSessions()) {
if (s != null) s.close();
}
}
private List<JediTermWidget> getAllTerminalSessions() {
List<JediTermWidget> session = Lists.newArrayList();
if (myTabbedPane != null) {
for (int i = 0; i < myTabbedPane.getTabCount(); i++) {
session.add(getTerminalPanel(i));
}
} else {
if (myTermWidget != null) {
session.add(myTermWidget);
}
}
return session;
}
private void removeTab(JediTermWidget terminal) {
synchronized (myLock) {
if (myTabbedPane != null) {
myTabbedPane.remove(terminal);
if (myTabbedPane.getTabCount() == 1) {
myTermWidget = getTerminalPanel(0);
myTabbedPane.removeAll();
remove(myTabbedPane);
myTabbedPane = null;
add(myTermWidget.getComponent(), BorderLayout.CENTER);
}
}
onSessionChanged();
}
}
@Override
public List<TerminalAction> getActions() {
return Lists.newArrayList(
new TerminalAction("New Session", mySettingsProvider.getNewSessionKeyStrokes(), new Predicate<KeyEvent>() {
@Override
public boolean apply(KeyEvent input) {
handleNewSession();
return true;
}
}).withMnemonicKey(KeyEvent.VK_N),
new TerminalAction("Close Session", mySettingsProvider.getCloseSessionKeyStrokes(), new Predicate<KeyEvent>() {
@Override
public boolean apply(KeyEvent input) {
handleCloseSession();
return true;
}
}).withMnemonicKey(KeyEvent.VK_S)
);
}
@Override
public TerminalActionProvider getNextProvider() {
return myNextActionProvider;
}
@Override
public void setNextProvider(TerminalActionProvider provider) {
myNextActionProvider = provider;
}
private void handleCloseSession() {
closeCurrentSession();
}
private void handleNewSession() {
myCreateNewSessionAction.apply(this);
}
public Component getFocusableComponent() {
return myTabbedPane != null ? myTabbedPane : myTermWidget != null ? myTermWidget : this;
}
private class TabComponent extends JPanel implements FocusListener {
private JediTermWidget myTerminal;
private TabComponent(final @NotNull JTabbedPane pane, final JediTermWidget terminal) {
super(new FlowLayout(FlowLayout.LEFT, 0, 0));
myTerminal = terminal;
setOpaque(false);
setFocusable(false);
addFocusListener(this);
//make JLabel read titles from JTabbedPane
JLabel label = new JLabel() {
public String getText() {
int i = pane.indexOfTabComponent(TabComponent.this);
if (i != -1) {
return pane.getTitleAt(i);
}
return null;
}
};
label.addFocusListener(this);
//add more space between the label and the button
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
label.addMouseListener(new MouseAdapter() {
@Override
public void mouseReleased(MouseEvent event) {
handleMouse(event);
}
@Override
public void mousePressed(MouseEvent event) {
handleMouse(event);
}
private void handleMouse(MouseEvent event) {
if (event.isPopupTrigger()) {
JPopupMenu menu = createPopup();
menu.show(event.getComponent(), event.getX(), event.getY());
} else {
pane.setSelectedComponent(terminal);
if (event.getClickCount() == 2 && !event.isConsumed()) {
event.consume();
renameTab();
}
}
}
});
add(label);
}
@Override
public void focusGained(FocusEvent e) {
doRequestFocus(myTerminal.getComponent());
}
@Override
public void focusLost(FocusEvent e) {
}
}
@Override
public JComponent getComponent() {
return this;
}
@Override
public boolean canOpenSession() {
return true;
}
@Override
public void setTerminalPanelListener(TerminalPanelListener terminalPanelListener) {
if (myTabbedPane != null) {
for (int i = 0; i < myTabbedPane.getTabCount(); i++) {
getTerminalPanel(i).setTerminalPanelListener(terminalPanelListener);
}
}
myTerminalPanelListener = terminalPanelListener;
}
@Override
@Nullable
public JediTermWidget getCurrentSession() {
if (myTabbedPane != null) {
return getTerminalPanel(myTabbedPane.getSelectedIndex());
} else {
return myTermWidget;
}
}
@Override
public TerminalDisplay getTerminalDisplay() {
return getCurrentSession().getTerminalDisplay();
}
@Nullable
private JediTermWidget getTerminalPanel(int index) {
if (index < myTabbedPane.getTabCount() && index >= 0) {
return (JediTermWidget) myTabbedPane.getComponentAt(index);
} else {
return null;
}
}
public void addTabListener(TabListener listener) {
myTabListeners.add(listener);
}
public void removeTabListener(TabListener listener) {
myTabListeners.remove(listener);
}
private void fireTabClosed(JediTermWidget terminal) {
for (TabListener l : myTabListeners) {
l.tabClosed(terminal);
}
}
public interface TabListener {
void tabClosed(JediTermWidget terminal);
}
}