/* * Copyright 2000-2012 JetBrains s.r.o. * * 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.intellij.ui.popup.util; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.*; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.ui.*; import com.intellij.ui.components.JBList; import com.intellij.ui.speedSearch.FilteringListModel; import com.intellij.util.Function; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import java.awt.*; import java.awt.event.*; public class MasterDetailPopupBuilder implements MasterController { private static final Color BORDER_COLOR = Gray._135; private final Project myProject; private ActionGroup myActions; private Delegate myDelegate; private boolean myCloseOnEnter; private DetailView myDetailView; private JLabel myPathLabel; private JBPopup myPopup; private JComponent myChooserComponent; private ActionToolbar myActionToolbar; private boolean myAddDetailViewToEast = true; private Dimension myMinSize; private boolean myCancelOnWindowDeactivation = true; private Runnable myDoneRunnable; private boolean myCancelOnClickOutside; private final DetailController myDetailController = new DetailController(this); private boolean myUseDimensionServiceForXYLocation; public void setUseDimensionServiceForXYLocation(boolean useDimensionServiceForXYLocation) { myUseDimensionServiceForXYLocation = useDimensionServiceForXYLocation; } public String getDimensionServiceKey() { return myDimensionServiceKey; } public void setDimensionServiceKey(String dimensionServiceKey) { myDimensionServiceKey = dimensionServiceKey; } private String myDimensionServiceKey; public MasterDetailPopupBuilder setDetailView(DetailView detailView) { myDetailView = detailView; myDetailController.setDetailView(myDetailView); return this; } public ActionToolbar getActionToolbar() { return myActionToolbar; } public MasterDetailPopupBuilder(Project project) { myProject = project; } public JBPopup createMasterDetailPopup() { setupRenderer(); myPathLabel = new JLabel(" "); myPathLabel.setHorizontalAlignment(SwingConstants.RIGHT); final Font font = myPathLabel.getFont(); myPathLabel.setFont(font.deriveFont((float)10)); if (myDetailView == null) { myDetailView = new DetailViewImpl(myProject); } JPanel footerPanel = new JPanel(new BorderLayout()) { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(BORDER_COLOR); g.drawLine(0, 0, getWidth(), 0); } }; Runnable runnable = new Runnable() { @Override public void run() { IdeFocusManager.getInstance(myProject).doWhenFocusSettlesDown(new Runnable() { @Override public void run() { Object[] values = getSelectedItems(); if (values.length == 1) { myDelegate.itemChosen((ItemWrapper)values[0], myProject, myPopup, false); } else { for (Object value : values) { if (value instanceof ItemWrapper) { myDelegate.itemChosen((ItemWrapper)value, myProject, myPopup, false); } } } } }); } }; footerPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); footerPanel.add(myPathLabel); JComponent toolBar = null; if (myActions != null) { myActionToolbar = ActionManager.getInstance().createActionToolbar("", myActions, true); myActionToolbar.setReservePlaceAutoPopupIcon(false); myActionToolbar.setMinimumButtonSize(new Dimension(20, 20)); toolBar = myActionToolbar.getComponent(); toolBar.setOpaque(false); } final PopupChooserBuilder builder = createInnerBuilder(). setMovable(true). setResizable(true). setAutoselectOnMouseMove(false). setSettingButton(toolBar). setSouthComponent(footerPanel). setCancelOnWindowDeactivation(myCancelOnWindowDeactivation). setCancelOnClickOutside(myCancelOnClickOutside). setUseDimensionServiceForXYLocation(myUseDimensionServiceForXYLocation); if (myDoneRunnable != null) { ActionListener actionListener = new ActionListener() { @Override public void actionPerformed(ActionEvent event) { myDoneRunnable.run(); } }; //native button is pretty enough if ((SystemInfo.isMacOSLion || SystemInfo.isMacOSMountainLion) && !UIUtil.isUnderDarcula()) { final JButton done = new JButton("Done"); done.setMnemonic('o'); done.addActionListener(actionListener); builder.setCommandButton(new ActiveComponent() { @Override public void setActive(boolean active) { } @Override public JComponent getComponent() { return done; } }); } else { builder.setCommandButton(new InplaceButton(new IconButton("Close", AllIcons.Actions.CloseNew, AllIcons.Actions.CloseNewHovered), actionListener)); } } String title = myDelegate.getTitle(); if (title != null) { builder.setTitle(title); } builder. setItemChoosenCallback(runnable). setCloseOnEnter(myCloseOnEnter). setMayBeParent(true). setDimensionServiceKey(myDimensionServiceKey). setFilteringEnabled(new Function<Object, String>() { @Override public String fun(Object o) { return ((ItemWrapper)o).speedSearchText(); } }); if (myMinSize != null) { builder.setMinSize(myMinSize); } myPopup = builder.createPopup(); builder.getScrollPane().setBorder(IdeBorderFactory.createBorder(SideBorder.RIGHT)); myPopup.addListener(new JBPopupListener() { @Override public void beforeShown(LightweightWindowEvent event) { } @Override public void onClosed(LightweightWindowEvent event) { myDetailView.clearEditor(); } }); if (myDoneRunnable != null) { new AnAction("Done") { @Override public void actionPerformed(AnActionEvent e) { myDoneRunnable.run(); } }.registerCustomShortcutSet(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK, myPopup.getContent()); } return myPopup; } private void setupRenderer() { if (myChooserComponent instanceof JList) { final JList list = (JList)myChooserComponent; list.setCellRenderer(new ListItemRenderer(myDelegate, myProject)); } } private PopupChooserBuilder createInnerBuilder() { if (myChooserComponent instanceof JList) { return new MyPopupChooserBuilder((JList)myChooserComponent); } else if (myChooserComponent instanceof JTree) { return new MyPopupChooserBuilder((JTree)myChooserComponent); } return null; } @Override public ItemWrapper[] getSelectedItems() { Object[] values = new Object[0]; if (myChooserComponent instanceof JList) { values = ((JList)myChooserComponent).getSelectedValues(); } else if (myChooserComponent instanceof JTree) { values = myDelegate.getSelectedItemsInTree(); } ItemWrapper[] items = new ItemWrapper[values.length]; for (int i = 0; i < values.length; i++) { items[i] = (ItemWrapper)values[i]; } return items; } public void setAddDetailViewToEast(boolean addDetailViewToEast) { myAddDetailViewToEast = addDetailViewToEast; } public MasterDetailPopupBuilder setMinSize(Dimension minSize) { myMinSize = minSize; return this; } public MasterDetailPopupBuilder setCancelOnWindowDeactivation(boolean cancelOnWindowDeactivation) { myCancelOnWindowDeactivation = cancelOnWindowDeactivation; return this; } public MasterDetailPopupBuilder setDoneRunnable(Runnable doneRunnable) { myDoneRunnable = doneRunnable; return this; } public void setCancelOnClickOutside(boolean cancelOnClickOutside) { myCancelOnClickOutside = cancelOnClickOutside; } @Override public JLabel getPathLabel() { return myPathLabel; } public static boolean allowedToRemoveItems(Object[] values) { for (Object value : values) { ItemWrapper item = (ItemWrapper)value; if (!item.allowedToRemove()) { return false; } } return values.length > 0; } public void removeSelectedItems(Project project) { if (myChooserComponent instanceof JList) { final JList list = (JList)myChooserComponent; int index = list.getSelectedIndex(); if (index == -1 || index >= list.getModel().getSize()) { return; } Object[] values = list.getSelectedValues(); for (Object value : values) { ItemWrapper item = (ItemWrapper)value; DefaultListModel model = list.getModel() instanceof DefaultListModel ? (DefaultListModel)list.getModel() : (DefaultListModel)((FilteringListModel)list.getModel()).getOriginalModel(); if (item.allowedToRemove()) { model.removeElement(item); if (model.getSize() > 0) { if (model.getSize() == index) { list.setSelectedIndex(model.getSize() - 1); } else if (model.getSize() > index) { list.setSelectedIndex(index); } } else { list.clearSelection(); } item.removed(project); } } } else { myDelegate.removeSelectedItemsInTree(); } } public MasterDetailPopupBuilder setActionsGroup(@Nullable ActionGroup actions) { myActions = actions; return this; } public MasterDetailPopupBuilder setList(final JBList list) { setChooser(list); myDetailController.setList(list); list.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent event) { myDetailController.updateDetailView(); } }); return this; } private void setChooser(JComponent list) { myChooserComponent = list; list.addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_DELETE) { removeSelectedItems(myProject); } else if (e.getModifiersEx() == 0) { myDelegate.handleMnemonic(e, myProject, myPopup); } } }); new AnAction() { @Override public void actionPerformed(AnActionEvent e) { chooseItemWithEnterOrDoubleClick(); } }.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0)), list); new AnAction() { @Override public void actionPerformed(AnActionEvent e) { chooseItemWithEnterOrDoubleClick(); } }.registerCustomShortcutSet(CommonShortcuts.DOUBLE_CLICK_1, list); } private void chooseItemWithEnterOrDoubleClick() { ItemWrapper[] items = getSelectedItems(); if (items.length > 0) { myDelegate.itemChosen(items[0], myProject, myPopup, true); } } public MasterDetailPopupBuilder setDelegate(Delegate delegate) { myDelegate = delegate; return this; } public MasterDetailPopupBuilder setCloseOnEnter(boolean closeOnEnter) { myCloseOnEnter = closeOnEnter; return this; } public interface Delegate { @Nullable String getTitle(); void handleMnemonic(KeyEvent e, Project project, JBPopup popup); @Nullable JComponent createAccessoryView(Project project); Object[] getSelectedItemsInTree(); void itemChosen(ItemWrapper item, Project project, JBPopup popup, boolean withEnterOrDoubleClick); void removeSelectedItemsInTree(); } public static class ListItemRenderer extends JPanel implements ListCellRenderer { private final Project myProject; private final ColoredListCellRenderer myRenderer; private final Delegate myDelegate; private ListItemRenderer(Delegate delegate, Project project) { super(new BorderLayout()); myProject = project; setBackground(UIUtil.getListBackground()); this.myDelegate = delegate; final JComponent accessory = myDelegate.createAccessoryView(project); if (accessory != null) { add(accessory, BorderLayout.WEST); } myRenderer = new ItemWrapperListRenderer(myProject, accessory); add(myRenderer, BorderLayout.CENTER); } @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { if (value instanceof SplitterItem) { String label = ((SplitterItem)value).getText(); final TitledSeparator separator = new TitledSeparator(label); separator.setBackground(UIUtil.getListBackground()); separator.setForeground(UIUtil.getListForeground()); return separator; } myRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); myRenderer.revalidate(); return this; } } private class MyPopupChooserBuilder extends PopupChooserBuilder { public MyPopupChooserBuilder(@NotNull JList list) { super(list); } private MyPopupChooserBuilder(@NotNull JTree tree) { super(tree); } @Override protected void addCenterComponentToContentPane(JPanel contentPane, JComponent component) { if (myAddDetailViewToEast) { JBSplitter splitPane = new JBSplitter(0.3f); splitPane.setSplitterProportionKey(getSplitterProportionKey()); splitPane.setFirstComponent(component); splitPane.setSecondComponent((JComponent)myDetailView); contentPane.add(splitPane, BorderLayout.CENTER); } else { super.addCenterComponentToContentPane(contentPane, component); } } } private String getSplitterProportionKey() { return myDimensionServiceKey + ".splitter"; } }