blob: b9ea339d9cd9b729fd59c1dd89b49b3b1c65f3f6 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tools.idea.structure.services;
import com.android.tools.idea.ui.ProportionalLayout;
import com.android.tools.idea.ui.properties.BindingsManager;
import com.android.tools.idea.ui.properties.InvalidationListener;
import com.android.tools.idea.ui.properties.Observable;
import com.android.tools.idea.ui.properties.collections.ObservableList;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.ui.AbstractCollectionComboBoxModel;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.util.containers.Stack;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import java.awt.*;
import java.io.IOException;
import java.net.URI;
import java.util.List;
/**
* Builder that builds up the panel which is defined in service.xml. Use {@link #getPanel()} to get
* the underlying Swing panel.
*/
public final class ServicePanelBuilder {
@NotNull private final JPanel myRootPanel;
@NotNull private final BindingsManager myBindings = new BindingsManager();
@NotNull private final Stack<UiGrid> myGrids = new Stack<UiGrid>();
public ServicePanelBuilder() {
myRootPanel = new JPanel(new VerticalFlowLayout(VerticalFlowLayout.TOP, 0, 0, true, false));
// Pass BindingsManager to the panel so it stays alive after this builder goes away.
myRootPanel.putClientProperty("bindings", myBindings);
}
public JPanel getPanel() {
assert myGrids.isEmpty() : "startGrid called without matching endGrid";
return myRootPanel;
}
/**
* Return the active bindings manager for this panel, useful so we can register new bindings
* externally.
*/
@NotNull
public BindingsManager getBindings() {
return myBindings;
}
/**
* Begin a grid, which groups added components into a table-like layout until {@link #endGrid()}
* is called. You can nest grids.
*/
public JPanel startGrid(@NotNull String colDefinitions) {
UiGrid uiGrid = new UiGrid(colDefinitions);
myGrids.push(uiGrid);
return uiGrid.getPanel();
}
public void endGrid() {
assert !myGrids.isEmpty() : "endGrid called without matching startGrid";
addComponent(myGrids.pop().getPanel());
}
/**
* Set the row index of the next added component. It is an error to call this while we are not
* constructing a grid.
*/
public void setRow(int row) {
assert !myGrids.isEmpty() : "setRow called without calling startGrid";
myGrids.peek().setCurrRow(row);
}
/**
* Set the row index of the next added component. It is an error to call this while we are not
* constructing a grid.
*/
public void setCol(int col) {
assert !myGrids.isEmpty() : "setCol called without calling startGrid";
myGrids.peek().setCurrCol(col);
}
public JButton addButton() {
JButton button = new JButton();
addComponent(button);
return button;
}
public JCheckBox addCheckbox() {
JCheckBox checkbox = new JCheckBox();
addComponent(checkbox);
return checkbox;
}
public JLabel addLabel() {
JLabel label = new JLabel();
addComponent(label);
return label;
}
public HyperlinkLabel addLink(@NotNull String text, @NotNull final URI uri) {
HyperlinkLabel linkLabel = new HyperlinkLabel(text);
linkLabel.addHyperlinkListener(new HyperlinkListener() {
@Override
public void hyperlinkUpdate(HyperlinkEvent e) {
try {
Desktop.getDesktop().browse(uri);
}
catch (IOException e1) {
// Don't care
}
}
});
addComponent(linkLabel);
return linkLabel;
}
public JTextField addField() {
JTextField field = new JTextField();
addComponent(field);
return field;
}
public JComboBox addComboBox(@NotNull final ObservableList<String> backingList) {
final AbstractCollectionComboBoxModel<String> model = new AbstractCollectionComboBoxModel<String>(null) {
@NotNull
@Override
protected List<String> getItems() {
return backingList;
}
};
final ComboBox comboBox = new ComboBox(model);
InvalidationListener onListModified = new InvalidationListener() {
@Override
protected void onInvalidated(@NotNull Observable sender) {
model.update();
if (backingList.size() > 0 && comboBox.getSelectedIndex() < 0) {
comboBox.setSelectedIndex(0);
}
}
};
addComponent(comboBox);
backingList.addWeakListener(onListModified);
// Keep weak listener alive as long as the combobox is alive
comboBox.putClientProperty("onListModified", onListModified);
return comboBox;
}
private void addComponent(@NotNull JComponent component) {
if (!myGrids.isEmpty()) {
myGrids.peek().addComponent(component);
}
else {
myRootPanel.add(component);
}
}
/**
* Tracks the state of the row of parameters currently being built.
*/
private static class UiGrid {
@NotNull private final JPanel myPanel;
private final int myNumCols;
private int myCurrCol;
private int myCurrRow;
public UiGrid(@NotNull String colDefinitions) {
ProportionalLayout layout = ProportionalLayout.fromString(colDefinitions);
myNumCols = layout.getNumColumns();
myPanel = new JPanel(layout);
}
@NotNull
public JPanel getPanel() {
return myPanel;
}
public void setCurrCol(int col) {
if (col >= myNumCols) {
throw new IllegalArgumentException(String.format("Can't set col = %1$d on a grid with only %2$d columns", col, myNumCols));
}
myCurrCol = col;
}
public void setCurrRow(int row) {
myCurrRow = row;
}
public void addComponent(@NotNull JComponent component) {
myPanel.add(component, new ProportionalLayout.Constraint(myCurrRow, myCurrCol));
myCurrRow = 0;
myCurrCol = 0;
}
}
}