blob: d3774706c97181981ea0a85f8e59ed9f2379f60f [file] [log] [blame]
/*
* Copyright 2000-2013 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.debugger.ui;
import com.intellij.debugger.engine.evaluation.*;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.event.DocumentAdapter;
import com.intellij.openapi.editor.event.DocumentEvent;
import com.intellij.openapi.editor.event.DocumentListener;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.ui.EditorComboBoxEditor;
import com.intellij.ui.EditorComboBoxRenderer;
import com.intellij.ui.EditorTextField;
import com.intellij.xdebugger.XExpression;
import com.intellij.xdebugger.impl.XDebuggerHistoryManager;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.plaf.basic.ComboPopup;
import java.awt.*;
import java.util.ArrayList;
import java.util.List;
/**
* @author ven
*/
public class DebuggerExpressionComboBox extends DebuggerEditorImpl {
public static final Key<String> KEY = Key.create("DebuggerComboBoxEditor.KEY");
public static final int MAX_ROWS = 20;
private MyEditorComboBoxEditor myEditor;
private ComboBox myComboBox;
private class MyEditorComboBoxEditor extends EditorComboBoxEditor {
public MyEditorComboBoxEditor(Project project, FileType fileType) {
super(project, fileType);
}
public Object getItem() {
Document document = (Document)super.getItem();
return createItem(document, getProject());
}
public void setItem(Object item) {
TextWithImports twi = (TextWithImports)item;
if (twi != null) {
restoreFactory(twi);
}
final Document document = createDocument(twi);
getEditorComponent().setNewDocumentAndFileType(getCurrentFactory().getFileType(), document);
super.setItem(document);
// need to replace newlines with spaces, see IDEA-81789
if (document != null) {
document.addDocumentListener(REPLACE_NEWLINES_LISTENER);
}
/* Causes PSI being modified from PSI events. See IDEADEV-22102
final Editor editor = getEditor();
if (editor != null) {
DaemonCodeAnalyzer.getInstance(getProject()).updateVisibleHighlighters(editor);
}
*/
}
}
private static DocumentListener REPLACE_NEWLINES_LISTENER = new DocumentAdapter() {
@Override
public void documentChanged(DocumentEvent e) {
final String text = e.getNewFragment().toString();
final String replaced = text.replace('\n', ' ');
if (replaced != text) {
e.getDocument().replaceString(e.getOffset(), e.getOffset() + e.getNewLength(), replaced);
}
}
};
public DebuggerExpressionComboBox(Project project, @NonNls String recentsId) {
this(project, null, recentsId, DefaultCodeFragmentFactory.getInstance());
}
public DebuggerExpressionComboBox(Project project, PsiElement context, @NonNls String recentsId, final CodeFragmentFactory factory) {
super(project, context, recentsId, factory);
setLayout(new BorderLayout(0, 0));
myComboBox = new ComboBox(new MyComboboxModel(getRecents()), 100);
myComboBox.setSwingPopup(false);
// Have to turn this off because when used in DebuggerTreeInplaceEditor, the combobox popup is hidden on every change of selection
// See comment to SynthComboBoxUI.FocusHandler.focusLost()
myComboBox.setLightWeightPopupEnabled(false);
myEditor = new MyEditorComboBoxEditor(getProject(), getCurrentFactory().getFileType());
//noinspection GtkPreferredJComboBoxRenderer
myComboBox.setRenderer(new EditorComboBoxRenderer(myEditor));
myComboBox.setEditable(true);
myComboBox.setEditor(myEditor);
add(addChooseFactoryLabel(myComboBox, false));
}
public void selectPopupValue() {
//selectAll();
final Object currentPopupValue = getCurrentPopupValue();
if (currentPopupValue != null) {
myComboBox.getModel().setSelectedItem(currentPopupValue);
myComboBox.getEditor().setItem(currentPopupValue);
}
myComboBox.setPopupVisible(false);
}
public boolean isPopupVisible() {
return myComboBox.isVisible() && myComboBox.isPopupVisible();
}
public void setPopupVisible(final boolean b) {
myComboBox.setPopupVisible(b);
}
@Nullable
public Object getCurrentPopupValue() {
if (!isPopupVisible()) return null;
final ComboPopup popup = myComboBox.getPopup();
if (popup != null) {
return popup.getList().getSelectedValue();
}
return null;
}
@Override
protected void doSetText(TextWithImports item) {
final String itemText = item.getText().replace('\n', ' ');
restoreFactory(item);
item.setText(itemText);
if (!StringUtil.isEmpty(itemText)) {
if (myComboBox.getItemCount() == 0 || !item.equals(myComboBox.getItemAt(0))) {
myComboBox.insertItemAt(item, 0);
}
}
if (myComboBox.getItemCount() > 0) {
myComboBox.setSelectedIndex(0);
}
myComboBox.getEditor().setItem(item);
}
@Override
protected void updateEditorUi() {
}
public TextWithImports getText() {
return (TextWithImports)myComboBox.getEditor().getItem();
}
@Nullable
private List<TextWithImports> getRecents() {
final String recentsId = getRecentsId();
if (recentsId != null) {
final List<TextWithImports> result = new ArrayList<TextWithImports>();
List<XExpression> recents = XDebuggerHistoryManager.getInstance(getProject()).getRecentExpressions(getRecentsId());
for (XExpression expression : recents) {
if (expression.getExpression().indexOf('\n') == -1) {
result.add(TextWithImportsImpl.fromXExpression(expression));
}
}
return result;
}
return null;
}
public Dimension getMinimumSize() {
Dimension size = super.getMinimumSize();
size.width = 100;
return size;
}
public TextWithImports createText(String text, String importsString) {
return new TextWithImportsImpl(CodeFragmentKind.EXPRESSION, text, importsString, getCurrentFactory().getFileType());
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
myComboBox.setEnabled(enabled);
//if (enabled) {
// final ComboBoxEditor editor = myComboBox.getEditor();
// editor.setItem(editor.getItem());
//}
}
public JComponent getPreferredFocusedComponent() {
return (JComponent)myComboBox.getEditor().getEditorComponent();
}
public void selectAll() {
myComboBox.getEditor().selectAll();
}
public Editor getEditor() {
return myEditor.getEditor();
}
public JComponent getEditorComponent() {
return myEditor.getEditorComponent();
}
public void addRecent(TextWithImports text) {
if (text.getText().length() != 0) {
Component editorComponent = myComboBox.getEditor().getEditorComponent();
final boolean focusOwner = editorComponent.isFocusOwner();
int offset = -1;
if (editorComponent instanceof EditorTextField) {
final EditorTextField textField = (EditorTextField)editorComponent;
if (textField.getEditor() != null) {
offset = textField.getCaretModel().getOffset();
}
}
super.addRecent(text);
myComboBox.insertItemAt(text, 0);
myComboBox.setSelectedIndex(0);
editorComponent = myComboBox.getEditor().getEditorComponent();
if (offset != -1 && editorComponent instanceof EditorTextField) {
final EditorTextField textField = (EditorTextField)editorComponent;
final Editor editor = textField.getEditor();
if (editor != null) {
int textLength = editor.getDocument().getTextLength();
offset = Math.min(offset, textLength);
textField.getCaretModel().moveToOffset(offset);
editor.getSelectionModel().setSelection(offset, offset);
}
}
if (focusOwner) {
editorComponent.requestFocus();
}
}
}
private static class MyComboboxModel extends AbstractListModel implements MutableComboBoxModel {
private List<TextWithImports> myItems = new ArrayList<TextWithImports>();
private int mySelectedIndex = -1;
private MyComboboxModel(@Nullable final List<TextWithImports> recents) {
if (recents != null) {
myItems = recents;
}
}
@Override
public void setSelectedItem(final Object anItem) {
final int oldSelectedIndex = mySelectedIndex;
mySelectedIndex = anItem instanceof TextWithImports ? myItems.indexOf(anItem) : -1;
if (oldSelectedIndex != mySelectedIndex) fireContentsChanged(this, -1, -1);
}
@Override
public Object getSelectedItem() {
return mySelectedIndex == -1 || mySelectedIndex > myItems.size() - 1 ? null : myItems.get(mySelectedIndex);
}
@Override
public int getSize() {
return myItems.size();
}
@Override
public Object getElementAt(int index) {
return myItems.get(index);
}
@Override
public void addElement(final Object obj) {
insertElementAt(obj, myItems.size() - 1);
if (mySelectedIndex == -1 && myItems.size() == 1 && obj != null) {
setSelectedItem(obj);
}
}
@Override
public void removeElement(Object obj) {
removeElement(obj, true);
}
public void removeElement(final Object obj, final boolean checkSelection) {
if (!(obj instanceof TextWithImports)) throw new IllegalArgumentException();
final int index = myItems.indexOf((TextWithImports)obj);
if (index != -1) {
myItems.remove(index);
if (checkSelection) {
if (mySelectedIndex == index) {
if (myItems.size() == 0) {
setSelectedItem(null);
}
else if (index > myItems.size() - 1) {
setSelectedItem(myItems.get(myItems.size() - 1));
}
}
fireIntervalRemoved(this, index, index);
}
}
}
@Override
public void insertElementAt(final Object obj, final int index) {
if (!(obj instanceof TextWithImports)) throw new IllegalArgumentException();
removeElement(obj, false); // remove duplicate entry if any
myItems.add(index, (TextWithImports)obj);
fireIntervalAdded(this, index, index);
if (myItems.size() > MAX_ROWS) {
for (int i = myItems.size() - 1; i > MAX_ROWS - 1; i--) {
myItems.remove(i);
}
// will not fire events here to not recreate the editor
//fireIntervalRemoved(this, myItems.size() - 1, MAX_ROWS - 1);
}
}
@Override
public void removeElementAt(final int index) {
if (index < 0 || index > myItems.size() - 1) throw new IndexOutOfBoundsException();
removeElement(myItems.get(index));
}
}
}