Inplace editing

This commit is contained in:
Alexander Lobas
2012-05-24 21:44:17 +04:00
parent 63887b3c97
commit 8426046528
24 changed files with 668 additions and 87 deletions
@@ -193,9 +193,13 @@ public class PropertyParser {
Class<?> superComponentClass = componentClass.getSuperclass();
if (superComponentClass != null) {
superComponentClass = configureClass(superComponentClass);
MetaModel superModel = myMetaManager.getModelByTarget(superComponentClass.getName());
List<Property> superProperties = loadWidgetProperties(superComponentClass,
myMetaManager.getModelByTarget(superComponentClass.getName()));
if (model != null && superModel != null && model.getInplaceProperties().isEmpty()) {
model.setInplaceProperties(superModel.getInplaceProperties());
}
List<Property> superProperties = loadWidgetProperties(superComponentClass, superModel);
for (Property superProperty : superProperties) {
if (PropertyTable.findProperty(properties, superProperty) == -1) {
if (model == null) {
@@ -22,6 +22,7 @@ import com.intellij.designer.model.MetaManager;
import com.intellij.designer.model.MetaModel;
import com.intellij.designer.model.RadComponent;
import com.intellij.designer.propertyTable.Property;
import com.intellij.designer.propertyTable.PropertyTable;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.text.StringUtil;
@@ -50,32 +51,6 @@ public class RadCustomViewComponent extends RadViewComponent implements IConfigu
"\"/>";
}
@Override
public MetaModel getMetaModelForProperties() throws Exception {
MetaModel metaModel = getClientProperty(MODEL_KEY);
if (metaModel == null) {
Module module = getRoot().getClientProperty(ModelParser.MODULE_KEY);
MetaManager metaManager = ViewsMetaManager.getInstance(module.getProject());
PsiClass viewClass = ChooseClassDialog.findClass(module, getViewClass());
while (viewClass != null) {
metaModel = metaManager.getModelByTarget(viewClass.getQualifiedName());
if (metaModel != null) {
break;
}
viewClass = viewClass.getSuperClass();
}
if (metaModel == null) {
metaModel = myMetaModel;
}
setClientProperty(MODEL_KEY, metaModel);
}
return metaModel;
}
@Override
public void configure(RadComponent rootComponent) throws Exception {
String view = chooseView(rootComponent);
@@ -126,6 +101,52 @@ public class RadCustomViewComponent extends RadViewComponent implements IConfigu
}
}
@Override
public MetaModel getMetaModelForProperties() throws Exception {
MetaModel metaModel = getClientProperty(MODEL_KEY);
if (metaModel == null) {
Module module = getRoot().getClientProperty(ModelParser.MODULE_KEY);
MetaManager metaManager = ViewsMetaManager.getInstance(module.getProject());
PsiClass viewClass = ChooseClassDialog.findClass(module, getViewClass());
while (viewClass != null) {
metaModel = metaManager.getModelByTarget(viewClass.getQualifiedName());
if (metaModel != null) {
break;
}
viewClass = viewClass.getSuperClass();
}
if (metaModel == null) {
metaModel = myMetaModel;
}
setClientProperty(MODEL_KEY, metaModel);
}
return metaModel;
}
@Override
public List<Property> getInplaceProperties() throws Exception {
List<Property> properties = new ArrayList<Property>();
MetaModel metaModel = getMetaModelForProperties();
List<Property> allProperties = getProperties();
properties.add(CLASS_PROPERTY);
for (String name : metaModel.getInplaceProperties()) {
Property property = PropertyTable.findProperty(allProperties, name);
if (property != null) {
properties.add(property);
}
}
properties.add(PropertyTable.findProperty(allProperties, "id"));
return properties;
}
@Override
public void setProperties(List<Property> properties) {
List<Property> oldProperties = getProperties();
@@ -19,6 +19,7 @@ import com.android.ide.common.rendering.api.ViewInfo;
import com.intellij.designer.model.RadComponent;
import com.intellij.designer.model.RadVisualComponent;
import com.intellij.designer.propertyTable.Property;
import com.intellij.designer.propertyTable.PropertyTable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.xml.XmlAttribute;
@@ -196,6 +197,16 @@ public class RadViewComponent extends RadVisualComponent {
myProperties = properties;
}
@Override
public List<Property> getInplaceProperties() throws Exception {
List<Property> properties = super.getInplaceProperties();
Property idProperty = PropertyTable.findProperty(myProperties, "id");
if (idProperty != null) {
properties.add(idProperty);
}
return properties;
}
@Override
public void copyTo(Element parent) throws Exception {
// skip root
@@ -27,6 +27,8 @@
<palette title="include" icon="/com/intellij/android/designer/icons/include.png"
tooltip="Lets you statically include XML layout inside other XML layouts."/>
<properties inplace="layout:xml"/>
</meta>
<meta model="com.intellij.android.designer.model.RadFragment"
@@ -37,6 +39,8 @@
<palette title="fragment" icon="/com/intellij/android/designer/icons/fragment.png"
tooltip="A Fragment is a piece of an application's user interface or behavior that can be placed in an Activity."/>
<properties inplace="name"/>
</meta>
@@ -70,6 +74,7 @@
tooltip="Displays text to the user and optionally allows them to edit it."/>
<properties
inplace="text"
important="text hint textColor typeface textStyle gravity editable enabled"
expert="bufferType textScaleX cursorVisible scrollHorizontally includeFontPadding shadowDx shadowDy shadowRadius freezesText drawableTop drawableLeft drawableStart drawablePadding lineSpacingExtra lineSpacingMultiplier imeOptions privateImeOptions imeActionLabel imeActionId editorExtras textSelectHandleLeft textSelectHandleRight textSelectHandle textEditPasteWindowLayout textEditNoPasteWindowLayout textEditSideNoPasteWindowLayout textEditSuggestionItemLayout textCursorDrawable textAllCaps maxEms ems minEms"
/>
@@ -95,6 +100,7 @@
tooltip="Represents a push-button widget."/>
<properties
inplace="text"
important="onClick"
expert="editable password autoLink linksClickable numeric digits phoneNumber inputMethod autoText inputType textIsSelectable"
/>
@@ -111,7 +117,8 @@
</meta>
<meta class="android.widget.CompoundButton">
<properties important="checked button"/>
<properties inplace="text checked"
important="checked button"/>
</meta>
<meta model="com.intellij.android.designer.model.RadViewComponent"
@@ -205,7 +212,7 @@
<palette title="CheckedTextView" icon="/com/intellij/android/designer/icons/CheckedTextView.png"
tooltip="An extension to TextView that supports the Checkable interface."/>
<properties important="checked checkMark"/>
<properties inplace="text checked" important="checked checkMark"/>
<creation>
<![CDATA[
@@ -247,6 +254,8 @@
<palette title="AutoCompleteTextView" icon="/com/intellij/android/designer/icons/AutoCompleteTextView.png"
tooltip="An editable text view that shows completion suggestions automatically while the user is typing."/>
<properties inplace="text inputType"/>
<creation>
<![CDATA[
<AutoCompleteTextView
@@ -305,8 +314,6 @@
<palette title="AnalogClock" icon="/com/intellij/android/designer/icons/AnalogClock.png"
tooltip="This widget display an analogic clock with two hands for hours and minutes."/>
<properties important=""/>
<creation>
<![CDATA[
<AnalogClock
@@ -341,7 +348,8 @@
<palette title="Chronometer" icon="/com/intellij/android/designer/icons/Chronometer.png"
tooltip="Class that implements a simple timer."/>
<properties important="format"/>
<properties inplace="text format"
important="format"/>
<creation>
<![CDATA[
@@ -411,7 +419,8 @@
<palette title="ProgressBar" icon="/com/intellij/android/designer/icons/ProgressBar.png"
tooltip="Visual indicator of progress in some operation."/>
<properties important="max progress indeterminate"/>
<properties inplace="max progress indeterminate"
important="max progress indeterminate"/>
<creation>
<![CDATA[
@@ -447,7 +456,9 @@
<palette title="RatingBar" icon="/com/intellij/android/designer/icons/RatingBar.png"
tooltip="A RatingBar is an extension of SeekBar and ProgressBar that shows a rating in stars."/>
<properties important="numStars rating stepSize" expert="max progress indeterminate isIndicator"/>
<properties inplace="numStars rating stepSize"
important="numStars rating stepSize"
expert="max progress indeterminate isIndicator"/>
<creation>
<![CDATA[
@@ -466,7 +477,8 @@
<palette title="ImageView" icon="/com/intellij/android/designer/icons/ImageView.png"
tooltip="Displays an arbitrary image, such as an icon."/>
<properties important="src"/>
<properties inplace="src"
important="src"/>
<creation>
<![CDATA[
@@ -572,7 +584,8 @@
<palette title="GestureOverlayView" icon="/com/intellij/android/designer/icons/GestureOverlayView.png"
tooltip="A transparent overlay for gesture input that can be placed on top of other widgets or contain other widgets."/>
<properties important="gestureColor orientation"/>
<properties inplace="gestureColor orientation"
important="gestureColor orientation"/>
<creation>
<![CDATA[
@@ -592,7 +605,8 @@
<palette title="ViewStub" icon="/com/intellij/android/designer/icons/ViewStub.png"
tooltip="A ViewStub is an invisible, zero-sized View that can be used to lazily inflate layout resources at runtime."/>
<properties important="layout inflatedId"/>
<properties inplace="layout inflatedId"
important="layout inflatedId"/>
<creation>
<![CDATA[
@@ -652,8 +666,8 @@
<palette title="TimePicker" icon="/com/intellij/android/designer/icons/TimePicker.png"
tooltip="A view for selecting the time of day, in either 24 hour or AM/PM mode."/>
<properties
important="layout"/>
<properties inplace="layout"
important="layout"/>
<creation>
<![CDATA[
@@ -706,7 +720,8 @@
<palette title="SearchView" icon="/com/intellij/android/designer/icons/SearchView.png"
tooltip="A widget that provides a user interface for the user to enter a search query and submit a request to a search provider. Shows a list of query suggestions or results, if available, and allows the user to pick a suggestion or result to launch into."/>
<properties important="iconifiedByDefault queryHint imeOptions inputType"/>
<properties inplace="queryHint inputType"
important="iconifiedByDefault queryHint imeOptions inputType"/>
<creation>
<![CDATA[
@@ -719,7 +734,8 @@
</meta>
<meta class="android.widget.AbsListView">
<properties important="choiceMode"/>
<properties inplace="choiceMode"
important="choiceMode"/>
</meta>
<meta model="com.intellij.android.designer.model.RadViewComponent"
@@ -767,7 +783,8 @@
<palette title="GridView" icon="/com/intellij/android/designer/icons/GridView.png"
tooltip="A view that shows items in two-dimensional scrolling grid. The items in the grid come from the ListAdapter associated with this view."/>
<properties important="horizontalSpacing verticalSpacing stretchMode columnWidth numColumns gravity"/>
<properties inplace="numColumns"
important="horizontalSpacing verticalSpacing stretchMode columnWidth numColumns gravity"/>
<creation>
<![CDATA[
@@ -804,7 +821,8 @@
<palette title="ScrollView" icon="/com/intellij/android/designer/icons/ScrollView.png"
tooltip="Layout container for a view hierarchy that can be scrolled by the user, allowing it to be larger than the physical display. ScrollView only supports vertical scrolling."/>
<properties important="fillViewport"/>
<properties inplace="fillViewport"
important="fillViewport"/>
<creation>
<![CDATA[
@@ -824,7 +842,8 @@
<palette title="HorizontalScrollView" icon="/com/intellij/android/designer/icons/HorizontalScrollView.png"
tooltip="Layout container for a view hierarchy that can be scrolled by the user, allowing it to be larger than the physical display."/>
<properties important="fillViewport"/>
<properties inplace="fillViewport"
important="fillViewport"/>
<creation>
<![CDATA[
@@ -843,7 +862,8 @@
<palette title="Gallery" icon="/com/intellij/android/designer/icons/Gallery.png"
tooltip="A view that shows items in a center-locked, horizontally scrolling list."/>
<properties important="gravity"/>
<properties inplace="gravity"
important="gravity"/>
<creation>
<![CDATA[
@@ -862,7 +882,8 @@
<palette title="Spinner" icon="/com/intellij/android/designer/icons/Spinner.png"
tooltip="A view that displays one child at a time and lets the user pick among them."/>
<properties important="entries prompt spinnerMode gravity"/>
<properties inplace="spinnerMode"
important="entries prompt spinnerMode gravity"/>
<creation>
<![CDATA[
@@ -1033,7 +1054,9 @@
<palette title="RadioGroup" icon="/com/intellij/android/designer/icons/RadioGroup.png"
tooltip="This class is used to create a multiple-exclusion scope for a set of radio buttons. Checking one radio button that belongs to a radio group unchecks any previously checked radio button within the same group."/>
<properties important="orientation checkedButton" top="gravity orientation"/>
<properties inplace="orientation checkedButton"
important="orientation checkedButton"
top="gravity orientation"/>
<creation>
<![CDATA[
@@ -1050,7 +1073,8 @@
class="android.widget.TwoLineListItem"
tag="TwoLineListItem">
<properties important="mode"/>
<properties inplace="mode"
important="mode"/>
<palette title="TwoLineListItem" icon="/com/intellij/android/designer/icons/TwoLineListItem.png"
tooltip="A view group with two children, intended for use in ListViews."/>
@@ -1156,7 +1180,9 @@
class="android.widget.SlidingDrawer"
tag="SlidingDrawer">
<properties important="orientation" expert="handle content"/>
<properties inplace="orientation"
important="orientation"
expert="handle content"/>
<palette title="SlidingDrawer" icon="/com/intellij/android/designer/icons/SlidingDrawer.png"
tooltip="SlidingDrawer hides content out of the screen and allows the user to drag a handle to bring the content on screen. SlidingDrawer can be used vertically or horizontally. A special widget composed of two children views: the handle, that the users drags, and the content, attached to the handle and dragged with it."/>
@@ -1195,7 +1221,8 @@
<palette title="LinearLayout" icon="/com/intellij/android/designer/icons/LinearLayout.png"
tooltip="A Layout that arranges its children in a single column or a single row."/>
<properties important="orientation gravity baselineAligned measureWithLargestChild showDividers"
<properties inplace="orientation"
important="orientation gravity baselineAligned measureWithLargestChild showDividers"
expert="layout:gravity.fill layout:gravity.clip layout:gravity.start layout:gravity.end"
top="gravity orientation"
/>
@@ -1294,7 +1321,8 @@
class="android.widget.GridLayout"
tag="GridLayout">
<properties important="orientation rowCount columnCount"
<properties inplace="orientation rowCount columnCount"
important="orientation rowCount columnCount"
top="rowCount columnCount"
expert="layout:gravity.clip layout:gravity.start layout:gravity.end"
/>
@@ -1317,7 +1345,8 @@
class="android.widget.RelativeLayout"
tag="RelativeLayout">
<properties important="gravity"/>
<properties inplace="gravity"
important="gravity"/>
<palette title="RelativeLayout" icon="/com/intellij/android/designer/icons/RelativeLayout.png"
tooltip="A Layout where the positions of the children can be described in relation to each other or to the parent."/>
@@ -18,6 +18,7 @@ package com.intellij.android.designer.propertyTable.editors;
import com.android.resources.ResourceType;
import com.intellij.android.designer.model.ModelParser;
import com.intellij.designer.model.RadComponent;
import com.intellij.designer.propertyTable.InplaceContext;
import com.intellij.designer.propertyTable.PropertyEditor;
import com.intellij.designer.propertyTable.editors.ComboEditor;
import com.intellij.openapi.module.Module;
@@ -26,11 +27,14 @@ import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.ComboboxWithBrowseButton;
import com.intellij.ui.DocumentAdapter;
import org.jetbrains.android.dom.attrs.AttributeFormat;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.awt.*;
import java.awt.event.*;
import java.util.EnumSet;
import java.util.Set;
@@ -51,7 +55,12 @@ public class ResourceEditor extends PropertyEditor {
myTypes = types;
if (formats.contains(AttributeFormat.Enum) || formats.contains(AttributeFormat.Boolean)) {
ComboboxWithBrowseButton editor = new ComboboxWithBrowseButton();
ComboboxWithBrowseButton editor = new ComboboxWithBrowseButton() {
@Override
public Dimension getPreferredSize() {
return getComponentPreferredSize();
}
};
final JComboBox comboBox = editor.getComboBox();
DefaultComboBoxModel model;
@@ -78,18 +87,32 @@ public class ResourceEditor extends PropertyEditor {
comboBox.setSelectedIndex(0);
}
else {
myEditor = new TextFieldWithBrowseButton();
myEditor = new TextFieldWithBrowseButton() {
@Override
public Dimension getPreferredSize() {
return getComponentPreferredSize();
}
};
myEditor.registerKeyboardAction(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
}
}, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
getComboText().addActionListener(new ActionListener() {
JTextField textField = getComboText();
textField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
fireValueCommitted(true, true);
}
});
textField.getDocument().addDocumentListener(
new DocumentAdapter() {
protected void textChanged(final DocumentEvent e) {
preferredSizeChanged();
}
}
);
}
myEditor.addActionListener(new ActionListener() {
@@ -106,6 +129,12 @@ public class ResourceEditor extends PropertyEditor {
});
}
private Dimension getComponentPreferredSize() {
Dimension size1 = myEditor.getChildComponent().getPreferredSize();
Dimension size2 = myEditor.getButton().getPreferredSize();
return new Dimension(size1.width + size2.width + 5, size1.height);
}
private static ResourceType[] convertTypes(Set<AttributeFormat> formats) {
Set<ResourceType> types = EnumSet.noneOf(ResourceType.class);
for (AttributeFormat format : formats) {
@@ -141,13 +170,24 @@ public class ResourceEditor extends PropertyEditor {
@NotNull
@Override
public JComponent getComponent(@NotNull RadComponent rootComponent, @Nullable RadComponent component, Object value) {
public JComponent getComponent(@NotNull RadComponent rootComponent,
@Nullable RadComponent component,
Object value,
@Nullable InplaceContext inplaceContext) {
myRootComponent = rootComponent;
JTextField text = getComboText();
text.setText((String)value);
if (inplaceContext != null && inplaceContext.isStartChar()) {
text.setText(inplaceContext.getText(text.getText()));
}
return myEditor;
}
@Override
public JComponent getPreferredFocusedComponent() {
return getComboText();
}
@Override
public Object getValue() throws Exception {
String value = getComboText().getText();
@@ -16,7 +16,7 @@
package com.intellij.android.designer.propertyTable.editors;
import com.intellij.designer.model.RadComponent;
import com.intellij.designer.propertyTable.PropertyEditor;
import com.intellij.designer.propertyTable.InplaceContext;
import com.intellij.designer.propertyTable.editors.ComboEditor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -54,7 +54,10 @@ public class StringsComboEditor extends ComboEditor {
@NotNull
@Override
public JComponent getComponent(@NotNull RadComponent rootComponent, @Nullable RadComponent component, Object value) {
public JComponent getComponent(@NotNull RadComponent rootComponent,
@Nullable RadComponent component,
Object value,
@Nullable InplaceContext inplaceContext) {
myCombo.setSelectedItem(value);
return myCombo;
}
@@ -19,7 +19,6 @@ import com.intellij.designer.componentTree.ComponentTree;
import com.intellij.designer.componentTree.ComponentTreeBuilder;
import com.intellij.designer.componentTree.TreeEditableArea;
import com.intellij.designer.designSurface.DesignerEditorPanel;
import com.intellij.designer.propertyTable.PropertyTable;
import com.intellij.designer.propertyTable.PropertyTablePanel;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
@@ -156,10 +155,6 @@ public final class DesignerToolWindowManager implements ProjectComponent {
return editors.length > 0 ? getDesigner(editors[0]) : null;
}
public PropertyTable getPropertyTable() {
return myPropertyTablePanel.getPropertyTable();
}
private void bindToDesigner(final DesignerEditorPanel designer) {
myWindowQueue.cancelAllUpdates();
myWindowQueue.queue(new Update("update") {
@@ -177,14 +172,12 @@ public final class DesignerToolWindowManager implements ProjectComponent {
clearTreeBuilder();
myComponentTree.newModel();
if (designer == null) {
myComponentTree.setDecorator(null);
myComponentTree.setActionPanel(null);
myComponentTree.setDesignerPanel(null);
myPropertyTablePanel.getPropertyTable().setArea(null, null);
myToolWindow.setAvailable(false, null);
}
else {
myComponentTree.setDecorator(designer.getTreeDecorator());
myComponentTree.setActionPanel(designer.getActionPanel());
myComponentTree.setDesignerPanel(designer);
myTreeBuilder = new ComponentTreeBuilder(myComponentTree, designer);
myPropertyTablePanel.getPropertyTable().setArea(designer, myTreeBuilder.getTreeArea());
myToolWindow.setAvailable(true, null);
@@ -65,7 +65,9 @@ public class CommonEditActionsProvider implements DeleteProvider, CopyProvider,
@Override
public boolean canDeleteElement(@NotNull DataContext dataContext) {
// TODO: InplaceEditing
if (myDesigner.getInplaceEditingLayer().isEditing()) {
return false;
}
List<RadComponent> selection = getArea().getSelection();
if (selection.isEmpty()) {
return false;
@@ -143,8 +145,7 @@ public class CommonEditActionsProvider implements DeleteProvider, CopyProvider,
@Override
public boolean isCopyEnabled(@NotNull DataContext dataContext) {
// TODO: InplaceEditing
return !getArea().getSelection().isEmpty();
return !myDesigner.getInplaceEditingLayer().isEditing() && !getArea().getSelection().isEmpty();
}
@Override
@@ -185,8 +186,7 @@ public class CommonEditActionsProvider implements DeleteProvider, CopyProvider,
@Override
public boolean isPasteEnabled(@NotNull DataContext dataContext) {
// TODO: InplaceEditing
return getSerializedComponentData() != null;
return !myDesigner.getInplaceEditingLayer().isEditing() && getSerializedComponentData() != null;
}
@Nullable
@@ -48,6 +48,8 @@ public class DesignerActionPanel implements DataProvider {
myCommonEditActionsProvider = new CommonEditActionsProvider(designer);
myShortcuts = shortcuts;
createInplaceEditingAction(myShortcuts).setDesignerPanel(designer);
myActionGroup.add(myStaticGroup);
myActionGroup.add(myDynamicGroup);
@@ -91,6 +93,12 @@ public class DesignerActionPanel implements DataProvider {
});
}
public static StartInplaceEditing createInplaceEditingAction(JComponent shortcuts) {
StartInplaceEditing action = new StartInplaceEditing();
action.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0)), shortcuts);
return action;
}
private void registerAction(AnAction action, @NonNls String actionId) {
action.registerCustomShortcutSet(
ActionManager.getInstance().getAction(actionId).getShortcutSet(),
@@ -0,0 +1,58 @@
/*
* 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.designer.actions;
import com.intellij.designer.designSurface.DesignerEditorPanel;
import com.intellij.designer.model.RadComponent;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import org.jetbrains.annotations.Nullable;
import java.util.List;
/**
* @author Alexander Lobas
*/
public class StartInplaceEditing extends AnAction {
private DesignerEditorPanel myDesigner;
public void setDesignerPanel(@Nullable DesignerEditorPanel designer) {
myDesigner = designer;
}
@Override
public void update(AnActionEvent event) {
boolean enabled = false;
if (myDesigner != null) {
List<RadComponent> selection = myDesigner.getSurfaceArea().getSelection();
if (selection.size() == 1) {
try {
enabled = !selection.get(0).getInplaceProperties().isEmpty();
}
catch (Exception e) {
}
}
}
event.getPresentation().setEnabled(enabled);
}
@Override
public void actionPerformed(AnActionEvent e) {
myDesigner.getInplaceEditingLayer().startEditing(null);
}
}
@@ -16,6 +16,8 @@
package com.intellij.designer.componentTree;
import com.intellij.designer.actions.DesignerActionPanel;
import com.intellij.designer.actions.StartInplaceEditing;
import com.intellij.designer.designSurface.DesignerEditorPanel;
import com.intellij.designer.designSurface.FeedbackTreeLayer;
import com.intellij.designer.model.RadComponent;
import com.intellij.openapi.actionSystem.DataProvider;
@@ -35,6 +37,7 @@ import java.awt.*;
* @author Alexander Lobas
*/
public final class ComponentTree extends Tree implements DataProvider {
private final StartInplaceEditing myInplaceEditingAction;
private TreeComponentDecorator myDecorator;
private DesignerActionPanel myActionPanel;
private RadComponent myMarkComponent;
@@ -55,20 +58,24 @@ public final class ComponentTree extends Tree implements DataProvider {
// Install convenient keyboard navigation
TreeUtil.installActions(this);
// TODO: F2 should start inplace editing
myInplaceEditingAction = DesignerActionPanel.createInplaceEditingAction(this);
}
public void newModel() {
setModel(new DefaultTreeModel(new DefaultMutableTreeNode()));
}
public void setDecorator(@Nullable TreeComponentDecorator decorator) {
myDecorator = decorator;
public void setDesignerPanel(@Nullable DesignerEditorPanel designer) {
if (designer == null) {
myDecorator = null;
myActionPanel = null;
}
else {
myDecorator = designer.getTreeDecorator();
myActionPanel = designer.getActionPanel();
}
myMarkComponent = null;
}
public void setActionPanel(@Nullable DesignerActionPanel actionPanel) {
myActionPanel = actionPanel;
myInplaceEditingAction.setDesignerPanel(designer);
}
public void mark(RadComponent component, int feedback) {
@@ -24,6 +24,7 @@ import com.intellij.designer.designSurface.tools.*;
import com.intellij.designer.model.FindComponentVisitor;
import com.intellij.designer.model.RadComponent;
import com.intellij.designer.palette.Item;
import com.intellij.designer.propertyTable.InplaceContext;
import com.intellij.designer.propertyTable.Property;
import com.intellij.diagnostic.LogMessageEx;
import com.intellij.diagnostic.errordialog.Attachment;
@@ -97,6 +98,7 @@ public abstract class DesignerEditorPanel extends JPanel implements DataProvider
protected GlassLayer myGlassLayer;
private DecorationLayer myDecorationLayer;
private FeedbackLayer myFeedbackLayer;
private InplaceEditingLayer myInplaceEditingLayer;
private ListSelectionListener myPaletteListener;
protected ToolProvider myToolProvider;
@@ -244,6 +246,11 @@ public abstract class DesignerEditorPanel extends JPanel implements DataProvider
}, command, null);
}
@Override
public void startInplaceEditing(@Nullable InplaceContext inplaceContext) {
myInplaceEditingLayer.startEditing(inplaceContext);
}
@Override
public void showError(@NonNls String message, Throwable e) {
DesignerEditorPanel.this.showError(message, e);
@@ -259,6 +266,9 @@ public abstract class DesignerEditorPanel extends JPanel implements DataProvider
myFeedbackLayer = new FeedbackLayer();
myLayeredPane.add(myFeedbackLayer, LAYER_FEEDBACK);
myInplaceEditingLayer = new InplaceEditingLayer(this);
myLayeredPane.add(myInplaceEditingLayer, LAYER_INPLACE_EDITING);
JPanel content = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
@@ -513,6 +523,10 @@ public abstract class DesignerEditorPanel extends JPanel implements DataProvider
return myActionPanel;
}
public InplaceEditingLayer getInplaceEditingLayer() {
return myInplaceEditingLayer;
}
public JComponent getPreferredFocusedComponent() {
return myDesignerCard.isVisible() ? myGlassLayer : myErrorPanel;
}
@@ -0,0 +1,256 @@
/*
* 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.designer.designSurface;
import com.intellij.designer.designSurface.feedbacks.LineMarginBorder;
import com.intellij.designer.model.RadComponent;
import com.intellij.designer.propertyTable.InplaceContext;
import com.intellij.designer.propertyTable.Property;
import com.intellij.designer.propertyTable.PropertyEditor;
import com.intellij.designer.propertyTable.PropertyEditorListener;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CustomShortcutSet;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.wm.FocusWatcher;
import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
import com.intellij.openapi.wm.ex.LayoutFocusTraversalPolicyExt;
import com.intellij.uiDesigner.core.GridConstraints;
import com.intellij.uiDesigner.core.GridLayoutManager;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
/**
* @author Alexander Lobas
* @author Anton Katilin
* @author Vladimir Kondratyev
*/
public class InplaceEditingLayer extends JComponent {
private static final Logger LOG = Logger.getInstance("#com.intellij.designer.designSurface.InplaceEditingLayer");
private final FocusWatcher myFocusWatcher = new FocusWatcher() {
protected void focusLostImpl(FocusEvent e) {
Component opposite = e.getOppositeComponent();
if (e.isTemporary() || opposite != null && SwingUtilities.isDescendingFrom(opposite, getTopComponent())) {
// Do nothing if focus moves inside top component hierarchy
return;
}
// [vova] we need LaterInvocator here to prevent write-access assertions
ApplicationManager.getApplication().invokeLater(new Runnable() {
public void run() {
finishEditing(true);
}
}, ModalityState.NON_MODAL);
}
};
private final ComponentSelectionListener mySelectionListener = new ComponentSelectionListener() {
@Override
public void selectionChanged(EditableArea area) {
finishEditing(true);
}
};
private PropertyEditorListener myEditorListener = new PropertyEditorListener() {
@Override
public void valueCommitted(PropertyEditor source, boolean continueEditing, boolean closeEditorOnError) {
finishEditing(true);
}
@Override
public void editingCanceled(PropertyEditor source) {
finishEditing(false);
}
@Override
public void preferredSizeChanged(PropertyEditor source) {
adjustInplaceComponentSize();
}
};
private List<PropertyEditor> myEditors;
private final DesignerEditorPanel myDesigner;
private JComponent myInplaceComponent;
private int myPreferredWidth;
public InplaceEditingLayer(DesignerEditorPanel designer) {
myDesigner = designer;
}
public void startEditing(@Nullable InplaceContext inplaceContext) {
try {
List<RadComponent> selection = myDesigner.getSurfaceArea().getSelection();
if (selection.size() != 1) {
return;
}
RadComponent radComponent = selection.get(0);
List<Property> inplaceProperties = radComponent.getInplaceProperties();
if (inplaceProperties.isEmpty()) {
return;
}
myInplaceComponent = new JPanel(new GridLayoutManager(inplaceProperties.size(), 2));
myInplaceComponent.setBorder(new LineMarginBorder(5, 5, 5, 5));
new AnAction() {
@Override
public void actionPerformed(AnActionEvent e) {
finishEditing(false);
}
}.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)), myInplaceComponent);
int row = 0;
myEditors = new ArrayList<PropertyEditor>();
JComponent componentToFocus = null;
Font font = null;
if (inplaceContext == null) {
inplaceContext = new InplaceContext();
}
for (Property property : inplaceProperties) {
JLabel label = new JLabel(property.getName() + ":");
if (font == null) {
font = label.getFont().deriveFont(Font.BOLD);
}
label.setFont(font);
myInplaceComponent.add(label,
new GridConstraints(row, 0, 1, 1, GridConstraints.ANCHOR_WEST, GridConstraints.FILL_NONE, 0, 0, null, null,
null));
PropertyEditor editor = property.getEditor();
myEditors.add(editor);
JComponent component = editor.getComponent(radComponent.getRoot(), radComponent, property.getValue(radComponent), inplaceContext);
myInplaceComponent.add(component,
new GridConstraints(row++, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_HORIZONTAL, 0, 0,
null, null, null));
if (componentToFocus == null) {
componentToFocus = editor.getPreferredFocusedComponent();
}
}
for (PropertyEditor editor : myEditors) {
editor.addPropertyEditorListener(myEditorListener);
}
Rectangle bounds = radComponent.getBounds(this);
Dimension size = myInplaceComponent.getPreferredSize();
myPreferredWidth = size.width;
myInplaceComponent.setBounds(bounds.x, bounds.y, size.width, size.height);
add(myInplaceComponent);
if (componentToFocus == null) {
componentToFocus = IdeFocusTraversalPolicy.getPreferredFocusedComponent(myInplaceComponent);
}
if (componentToFocus != null) {
componentToFocus.requestFocusInWindow();
}
else {
myInplaceComponent.requestFocusInWindow();
}
myDesigner.getSurfaceArea().addSelectionListener(mySelectionListener);
myFocusWatcher.install(myInplaceComponent);
enableEvents(AWTEvent.MOUSE_EVENT_MASK);
repaint();
}
catch (Throwable e) {
LOG.error(e);
}
}
private void finishEditing(boolean commit) {
myDesigner.getSurfaceArea().removeSelectionListener(mySelectionListener);
if (myInplaceComponent != null) {
if (commit) {
// TODO: Auto-generated method stub
}
for (PropertyEditor editor : myEditors) {
editor.removePropertyEditorListener(myEditorListener);
}
removeInplaceComponent();
myFocusWatcher.deinstall(myInplaceComponent);
myInplaceComponent = null;
}
myEditors = null;
myDesigner.getPreferredFocusedComponent().requestFocusInWindow();
disableEvents(AWTEvent.MOUSE_EVENT_MASK);
repaint();
}
private void adjustInplaceComponentSize() {
myInplaceComponent.revalidate();
Dimension size = myInplaceComponent.getPreferredSize();
int width = Math.max(size.width, myPreferredWidth);
width = Math.min(width, getWidth() - myInplaceComponent.getX());
myInplaceComponent.setSize(width, myInplaceComponent.getHeight());
myInplaceComponent.revalidate();
System.out.println("size: " + width);
}
private void removeInplaceComponent() {
// [vova] before removing component from Swing tree we have to
// request component into glass layer. Otherwise focus from component being removed
// can go to some RadComponent.
LayoutFocusTraversalPolicyExt.setOverridenDefaultComponent(myDesigner.getPreferredFocusedComponent());
try {
remove(myInplaceComponent);
}
finally {
LayoutFocusTraversalPolicyExt.setOverridenDefaultComponent(null);
}
}
/**
* When there is an inplace editor we "listen" all mouse event
* and finish editing by any MOUSE_PRESSED or MOUSE_RELEASED event.
* We are acting like yet another glass pane over the standard glass layer.
*/
protected void processMouseEvent(MouseEvent e) {
if (myInplaceComponent != null && (MouseEvent.MOUSE_PRESSED == e.getID() || MouseEvent.MOUSE_RELEASED == e.getID())) {
finishEditing(true);
}
// [vova] this is very important! Without this code Swing doen't close popup menu on our
// layered pane. Swing adds MouseListeners to all component to close popup. If we do not
// invoke super then we lock all mouse listeners.
super.processMouseEvent(e);
}
public boolean isEditing() {
return myInplaceComponent != null;
}
}
@@ -17,10 +17,12 @@ package com.intellij.designer.designSurface.tools;
import com.intellij.designer.designSurface.EditableArea;
import com.intellij.designer.model.RadComponent;
import com.intellij.designer.propertyTable.InplaceContext;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPopupMenu;
import org.jetbrains.annotations.Nullable;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.List;
@@ -85,6 +87,13 @@ public class SelectionTool extends InputTool {
handleMove(); // hack: update cursor
}
@Override
protected void handleDoubleClick(int button) {
if (button == MouseEvent.BUTTON1 && myToolProvider != null) {
myToolProvider.startInplaceEditing(null);
}
}
@Override
protected void handleMove() {
if (myState == STATE_INIT) {
@@ -190,6 +199,11 @@ public class SelectionTool extends InputTool {
if (myTracker != null) {
myTracker.keyTyped(event, area);
}
else if (myToolProvider != null &&
Character.isLetterOrDigit(event.getKeyChar()) &&
(event.getModifiers() & (InputEvent.ALT_MASK | InputEvent.CTRL_MASK)) == 0) {
myToolProvider.startInplaceEditing(new InplaceContext(event.getKeyChar()));
}
}
@Override
@@ -17,6 +17,7 @@ package com.intellij.designer.designSurface.tools;
import com.intellij.designer.designSurface.EditOperation;
import com.intellij.designer.designSurface.EditableArea;
import com.intellij.designer.propertyTable.InplaceContext;
import com.intellij.util.ThrowableRunnable;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
@@ -133,4 +134,6 @@ public abstract class ToolProvider {
public abstract boolean execute(ThrowableRunnable<Exception> operation, String command, boolean updateProperties);
public abstract void execute(List<EditOperation> operations, String command);
public abstract void startInplaceEditing(@Nullable InplaceContext inplaceContext);
}
@@ -114,6 +114,11 @@ public abstract class MetaManager {
Element properties = element.getChild("properties");
if (properties != null) {
Attribute inplace = properties.getAttribute("inplace");
if (inplace != null) {
meta.setInplaceProperties(StringUtil.split(inplace.getValue(), " "));
}
Attribute top = properties.getAttribute("top");
if (top != null) {
meta.setTopProperties(StringUtil.split(top.getValue(), " "));
@@ -39,6 +39,7 @@ public class MetaModel {
private Icon myIcon;
private String myCreation;
private boolean myDelete = true;
private List<String> myInplaceProperties = Collections.emptyList();
private List<String> myTopProperties = Collections.emptyList();
private List<String> myNormalProperties = Collections.emptyList();
private List<String> myImportantProperties = Collections.emptyList();
@@ -116,6 +117,14 @@ public class MetaModel {
myPaletteItem.setMetaModel(this);
}
public List<String> getInplaceProperties() {
return myInplaceProperties;
}
public void setInplaceProperties(List<String> inplaceProperties) {
myInplaceProperties = inplaceProperties;
}
public List<String> getTopProperties() {
return myTopProperties;
}
@@ -22,6 +22,7 @@ import com.intellij.designer.designSurface.StaticDecorator;
import com.intellij.designer.designSurface.tools.DragTracker;
import com.intellij.designer.designSurface.tools.InputTool;
import com.intellij.designer.propertyTable.Property;
import com.intellij.designer.propertyTable.PropertyTable;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
@@ -186,6 +187,22 @@ public abstract class RadComponent {
return Collections.emptyList();
}
public List<Property> getInplaceProperties() throws Exception {
List<Property> properties = new ArrayList<Property>();
if (myMetaModel != null) {
List<Property> allProperties = getProperties();
for (String name : myMetaModel.getInplaceProperties()) {
Property property = PropertyTable.findProperty(allProperties, name);
if (property != null) {
properties.add(property);
}
}
}
return properties;
}
@SuppressWarnings("unchecked")
public final <T> T getClientProperty(@NotNull String key) {
return (T)myClientProperties.get(key);
@@ -0,0 +1,45 @@
/*
* 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.designer.propertyTable;
/**
* @author Alexander Lobas
*/
public class InplaceContext {
private char myStartChar;
public InplaceContext() {
this((char)0);
}
public InplaceContext(char startChar) {
myStartChar = startChar;
}
public boolean isStartChar() {
return myStartChar != 0;
}
public String getText(String text) {
if (text == null) {
text = "";
}
text += myStartChar;
myStartChar = 0;
return text;
}
}
@@ -29,10 +29,13 @@ public abstract class PropertyEditor {
private final EventListenerList myListenerList = new EventListenerList();
@NotNull
public abstract JComponent getComponent(@NotNull RadComponent rootComponent, @Nullable RadComponent component, Object value);
public abstract JComponent getComponent(@NotNull RadComponent rootComponent,
@Nullable RadComponent component,
Object value,
@Nullable InplaceContext inplaceContext);
@Nullable
public JComponent getPreferredFocusedComponent(@NotNull JComponent component) {
public JComponent getPreferredFocusedComponent() {
return null;
}
@@ -533,7 +533,7 @@ public final class PropertyTable extends JBTable implements ComponentSelectionLi
editCellAt(index, convertColumnIndexToView(1));
LOG.assertTrue(editorComp != null);
JComponent preferredComponent = editor.getPreferredFocusedComponent((JComponent)editorComp);
JComponent preferredComponent = editor.getPreferredFocusedComponent();
if (preferredComponent == null) {
preferredComponent = IdeFocusTraversalPolicy.getPreferredFocusedComponent((JComponent)editorComp);
}
@@ -855,7 +855,7 @@ public final class PropertyTable extends JBTable implements ComponentSelectionLi
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
try {
JComponent component = myEditor.getComponent(myDesigner.getRootComponent(), getCurrentComponent(), getValue((Property)value));
JComponent component = myEditor.getComponent(myDesigner.getRootComponent(), getCurrentComponent(), getValue((Property)value), null);
if (component instanceof JComboBox) {
component.putClientProperty("JComboBox.isTableCellEditor", Boolean.TRUE);
@@ -16,6 +16,7 @@
package com.intellij.designer.propertyTable.editors;
import com.intellij.designer.model.RadComponent;
import com.intellij.designer.propertyTable.InplaceContext;
import com.intellij.designer.propertyTable.PropertyEditor;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
@@ -52,8 +53,16 @@ public class BooleanEditor extends PropertyEditor {
return Boolean.valueOf(myCheckBox.isSelected());
}
@Override
public JComponent getPreferredFocusedComponent() {
return myCheckBox;
}
@NotNull
public JComponent getComponent(@NotNull RadComponent rootComponent, @Nullable RadComponent component, Object value) {
public JComponent getComponent(@NotNull RadComponent rootComponent,
@Nullable RadComponent component,
Object value,
@Nullable InplaceContext inplaceContext) {
try {
myInsideChange = true;
myCheckBox.setBackground(UIUtil.getTableBackground());
@@ -52,6 +52,11 @@ public abstract class ComboEditor extends PropertyEditor {
}
}
@Override
public JComponent getPreferredFocusedComponent() {
return myCombo;
}
private static class ComboListeners implements PopupMenuListener, ActionListener {
private final PropertyEditor myEditor;
private boolean myCancelled;
@@ -16,11 +16,15 @@
package com.intellij.designer.propertyTable.editors;
import com.intellij.designer.model.RadComponent;
import com.intellij.designer.propertyTable.InplaceContext;
import com.intellij.designer.propertyTable.PropertyEditor;
import com.intellij.ui.DocumentAdapter;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
@@ -37,13 +41,36 @@ public class TextEditor extends PropertyEditor {
fireValueCommitted(true, false);
}
});
myTextField.getDocument().addDocumentListener(
new DocumentAdapter() {
protected void textChanged(final DocumentEvent e) {
preferredSizeChanged();
}
}
);
}
@NotNull
@Override
public JComponent getComponent(@NotNull RadComponent rootComponent, @Nullable RadComponent component, Object value) {
public JComponent getComponent(@NotNull RadComponent rootComponent,
@Nullable RadComponent component,
Object value,
@Nullable InplaceContext inplaceContext) {
setEditorValue(component, value);
myTextField.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 0));
if (inplaceContext == null) {
myTextField.setBorder(BorderFactory.createEmptyBorder(0, 4, 0, 0));
}
else {
myTextField.setBorder(UIUtil.getTextFieldBorder());
if (inplaceContext.isStartChar()) {
myTextField.setText(inplaceContext.getText(myTextField.getText()));
}
}
return myTextField;
}
@Override
public JComponent getPreferredFocusedComponent() {
return myTextField;
}