update copyright
[fedora-idea.git] / plugins / ui-designer / src / com / intellij / uiDesigner / designSurface / InplaceEditingLayer.java
blobe76f4afd80a2c39ed8a36984eaefb28392eaf877
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.uiDesigner.designSurface;
18 import com.intellij.openapi.application.ApplicationManager;
19 import com.intellij.openapi.application.ModalityState;
20 import com.intellij.openapi.command.CommandProcessor;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.wm.FocusWatcher;
23 import com.intellij.openapi.wm.ex.IdeFocusTraversalPolicy;
24 import com.intellij.openapi.wm.ex.LayoutFocusTraversalPolicyExt;
25 import com.intellij.uiDesigner.FormEditingUtil;
26 import com.intellij.uiDesigner.UIDesignerBundle;
27 import com.intellij.uiDesigner.componentTree.ComponentSelectionListener;
28 import com.intellij.uiDesigner.propertyInspector.Property;
29 import com.intellij.uiDesigner.propertyInspector.PropertyEditor;
30 import com.intellij.uiDesigner.propertyInspector.PropertyEditorAdapter;
31 import com.intellij.uiDesigner.propertyInspector.InplaceContext;
32 import com.intellij.uiDesigner.radComponents.RadComponent;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
36 import javax.swing.*;
37 import java.awt.*;
38 import java.awt.event.FocusEvent;
39 import java.awt.event.MouseEvent;
41 /**
42 * @author Anton Katilin
43 * @author Vladimir Kondratyev
45 public final class InplaceEditingLayer extends JComponent{
46 private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.InplaceEditingLayer");
48 private final GuiEditor myEditor;
49 /**
50 * Trackes focus movements inside myInplaceEditorComponent
52 private final MyFocusWatcher myFocusWatcher;
53 /**
54 * Commits or cancels editing
56 private final MyPropertyEditorListener myPropertyEditorListener;
57 /**
58 * The component which is currently edited with inplace editor.
59 * This component can be null.
61 private RadComponent myInplaceComponent;
62 /**
63 * Currently edited inplace property
65 private Property myInplaceProperty;
66 /**
67 * Current inplace editor
69 private PropertyEditor myInplaceEditor;
70 /**
71 * JComponent which is used as inplace editor
73 private JComponent myInplaceEditorComponent;
74 /**
75 * Preferred bounds of the inplace editor component
77 private Rectangle myPreferredBounds;
78 /**
79 * If <code>true</code> then we do not have to react on own events
81 private boolean myInsideChange;
83 public InplaceEditingLayer(@NotNull final GuiEditor editor) {
84 myEditor = editor;
85 myEditor.addComponentSelectionListener(new MyComponentSelectionListener());
86 myFocusWatcher = new MyFocusWatcher();
87 myPropertyEditorListener = new MyPropertyEditorListener();
90 /**
91 * This is optimization. We do not need to invalidate Swing hierarchy
92 * upper than InplaceEditingLayer.
94 public boolean isValidateRoot() {
95 return true;
98 /**
99 * When there is an inplace editor we "listen" all mouse event
100 * and finish editing by any MOUSE_PRESSED or MOUSE_RELEASED event.
101 * We are acting like yet another glass pane over the standard glass layer.
103 protected void processMouseEvent(final MouseEvent e) {
105 myInplaceComponent != null &&
106 (MouseEvent.MOUSE_PRESSED == e.getID() || MouseEvent.MOUSE_RELEASED == e.getID())
108 finishInplaceEditing();
110 // [vova] this is very important! Without this code Swing doen't close popup menu on our
111 // layered pane. Swing adds MouseListeners to all component to close popup. If we do not
112 // invoke super then we lock all mouse listeners.
113 super.processMouseEvent(e);
117 * @return whether the layer is in "editing" state or not
119 public boolean isEditing(){
120 return myInplaceComponent != null;
124 * Starts editing of "inplace" property for the component at the
125 * specified point <code>(x, y)</code>.
127 * @param x x coordinate in the editor coordinate system
128 * @param y y coordinate in the editor coordinate system
130 public void startInplaceEditing(final int x, final int y){
131 final RadComponent inplaceComponent = FormEditingUtil.getRadComponentAt(myEditor.getRootContainer(), x, y);
132 if(inplaceComponent == null){ // nothing to edit
133 return;
136 // Try to find property with inplace editor
137 final Point p = SwingUtilities.convertPoint(this, x, y, inplaceComponent.getDelegee());
138 final Property inplaceProperty = inplaceComponent.getInplaceProperty(p.x, p.y);
139 if (inplaceProperty != null) {
140 final Rectangle bounds = inplaceComponent.getInplaceEditorBounds(inplaceProperty, p.x, p.y);
141 startInplaceEditing(inplaceComponent, inplaceProperty, bounds, new InplaceContext(true));
145 public void startInplaceEditing(@NotNull final RadComponent inplaceComponent,
146 @Nullable final Property property,
147 @Nullable final Rectangle bounds,
148 final InplaceContext context) {
149 myInplaceProperty = property;
150 if(myInplaceProperty == null){
151 return;
154 if (!myEditor.ensureEditable()) {
155 myInplaceProperty = null;
156 return;
159 // Now we have to cancel previous inplace editing (if any)
161 // Start new inplace editing
162 myInplaceComponent = inplaceComponent;
163 myInplaceEditor = myInplaceProperty.getEditor();
164 LOG.assertTrue(myInplaceEditor != null);
166 // 1. Get editor component
167 myInplaceEditorComponent = myInplaceEditor.getComponent(
168 myInplaceComponent,
169 context.isKeepInitialValue() ? myInplaceProperty.getValue(myInplaceComponent) : null,
170 context
172 LOG.assertTrue(myInplaceEditorComponent != null);
173 myInplaceEditor.addPropertyEditorListener(myPropertyEditorListener);
175 // 2. Set editor component bounds
176 final Dimension prefSize = myInplaceEditorComponent.getPreferredSize();
177 if(bounds != null){ // use bounds provided by the component itself
178 final Point _p = SwingUtilities.convertPoint(myInplaceComponent.getDelegee(), bounds.x, bounds.y, this);
179 myPreferredBounds = new Rectangle(_p.x, _p.y, bounds.width, bounds.height);
181 else{ // set some default bounds
182 final Point _p = SwingUtilities.convertPoint(myInplaceComponent.getDelegee(), 0, 0, this);
183 myPreferredBounds = new Rectangle(_p.x, _p.y, myInplaceComponent.getWidth(), myInplaceComponent.getHeight());
185 myInplaceEditorComponent.setBounds(
186 myPreferredBounds.x,
187 myPreferredBounds.y + (myPreferredBounds.height - prefSize.height)/2,
188 Math.min(Math.max(prefSize.width, myPreferredBounds.width), getWidth() - myPreferredBounds.x),
189 prefSize.height
192 // 3. Add it into layer
193 add(myInplaceEditorComponent);
194 myInplaceEditorComponent.revalidate();
195 myInplaceEditorComponent.requestFocusInWindow();
197 // 4. Request focus into proper component
198 JComponent componentToFocus = myInplaceEditor.getPreferredFocusedComponent(myInplaceEditorComponent);
199 if(componentToFocus == null){
200 componentToFocus = IdeFocusTraversalPolicy.getPreferredFocusedComponent(myInplaceEditorComponent);
202 if(componentToFocus != null){
203 componentToFocus.requestFocusInWindow();
205 else{
206 myInplaceEditorComponent.requestFocusInWindow();
208 myFocusWatcher.install(myInplaceEditorComponent);
210 // 5. Block any mouse event to finish editing by any of them
211 enableEvents(MouseEvent.MOUSE_EVENT_MASK);
213 repaint();
216 private void adjustEditorComponentSize(){
217 final Dimension preferredSize = myInplaceEditorComponent.getPreferredSize();
218 int width = Math.max(preferredSize.width, myPreferredBounds.width);
219 // Editor component should not be extended to invisible area
220 width = Math.min(width, getWidth() - myInplaceEditorComponent.getX());
221 myInplaceEditorComponent.setSize(width, myInplaceEditorComponent.getHeight());
222 myInplaceEditorComponent.revalidate();
226 * Finishes current inplace editing
228 private void finishInplaceEditing(){
229 if(myInplaceComponent == null || myInsideChange){ // nothing to finish
230 return;
232 myInsideChange = true;
233 try{
234 // 1. Apply new value to the component
235 LOG.assertTrue(myInplaceEditor != null);
236 CommandProcessor.getInstance().executeCommand(
237 myInplaceComponent.getProject(),
238 new Runnable() {
239 public void run() {
240 try {
241 final Object value = myInplaceEditor.getValue();
242 myInplaceProperty.setValue(myInplaceComponent, value);
243 } catch (Exception ignored){}
244 myEditor.refreshAndSave(true);
246 }, UIDesignerBundle.message("command.set.property.value"), null);
248 // 2. Remove editor from the layer
250 if (myInplaceEditorComponent != null) { // reenterability guard
251 removeInplaceEditorComponent();
252 myFocusWatcher.deinstall(myInplaceEditorComponent);
255 myInplaceEditor.removePropertyEditorListener(myPropertyEditorListener);
257 myInplaceComponent = null;
258 myInplaceEditorComponent = null;
259 myInplaceComponent = null;
261 // 3. Let AWT work
262 disableEvents(MouseEvent.MOUSE_EVENT_MASK);
263 }finally{
264 myInsideChange = false;
267 repaint();
271 * Cancells current inplace editing
273 private void cancelInplaceEditing(){
274 if(myInplaceComponent == null || myInsideChange){ // nothing to finish
275 return;
277 myInsideChange = true;
278 try{
279 // 1. Remove editor from the layer
280 LOG.assertTrue(myInplaceProperty != null);
281 LOG.assertTrue(myInplaceEditor != null);
283 removeInplaceEditorComponent();
285 myInplaceEditor.removePropertyEditorListener(myPropertyEditorListener);
286 myFocusWatcher.deinstall(myInplaceEditorComponent);
288 myInplaceComponent = null;
289 myInplaceEditorComponent = null;
290 myInplaceComponent = null;
292 // 2. Let AWT work
293 disableEvents(MouseEvent.MOUSE_EVENT_MASK);
294 }finally{
295 myInsideChange = false;
298 repaint();
301 private void removeInplaceEditorComponent() {
302 // [vova] before removing component from Swing tree we have to
303 // request component into glass layer. Otherwise focus from component being removed
304 // can go to some RadComponent.
306 // This cast is safe because InplaceEditingLayer is always in TabbedPaneWrapper
307 final Container ancestor = getFocusCycleRootAncestor();
308 LOG.assertTrue(ancestor != null);
309 LayoutFocusTraversalPolicyExt.setOverridenDefaultComponent(myEditor.getGlassLayer());
310 try {
311 remove(myInplaceEditorComponent);
313 finally {
314 LayoutFocusTraversalPolicyExt.setOverridenDefaultComponent(null);
319 * Finish inplace editing when selection changes
321 private final class MyComponentSelectionListener implements ComponentSelectionListener{
322 public void selectedComponentChanged(final GuiEditor source) {
323 finishInplaceEditing();
328 * Finish inplace editing when inplace editor component loses focus
330 private final class MyFocusWatcher extends FocusWatcher{
331 protected void focusLostImpl(final FocusEvent e) {
332 final Component opposite = e.getOppositeComponent();
334 e.isTemporary() ||
335 opposite != null && SwingUtilities.isDescendingFrom(opposite, getTopComponent())
337 // Do nothing if focus moves inside top component hierarchy
338 return;
340 // [vova] we need LaterInvocator here to prevent write-access assertions
341 ApplicationManager.getApplication().invokeLater(new Runnable() {
342 public void run() {
343 finishInplaceEditing();
345 }, ModalityState.NON_MODAL);
350 * Finishes editing by "Enter" and cancels editing by "Esc"
352 private final class MyPropertyEditorListener extends PropertyEditorAdapter{
353 public void valueCommitted(final PropertyEditor source, final boolean continueEditing, final boolean closeEditorOnError) {
354 finishInplaceEditing();
357 public void editingCanceled(final PropertyEditor source) {
358 cancelInplaceEditing();
361 public void preferredSizeChanged(final PropertyEditor source) {
362 adjustEditorComponentSize();