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
;
38 import java
.awt
.event
.FocusEvent
;
39 import java
.awt
.event
.MouseEvent
;
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
;
50 * Trackes focus movements inside myInplaceEditorComponent
52 private final MyFocusWatcher myFocusWatcher
;
54 * Commits or cancels editing
56 private final MyPropertyEditorListener myPropertyEditorListener
;
58 * The component which is currently edited with inplace editor.
59 * This component can be null.
61 private RadComponent myInplaceComponent
;
63 * Currently edited inplace property
65 private Property myInplaceProperty
;
67 * Current inplace editor
69 private PropertyEditor myInplaceEditor
;
71 * JComponent which is used as inplace editor
73 private JComponent myInplaceEditorComponent
;
75 * Preferred bounds of the inplace editor component
77 private Rectangle myPreferredBounds
;
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
) {
85 myEditor
.addComponentSelectionListener(new MyComponentSelectionListener());
86 myFocusWatcher
= new MyFocusWatcher();
87 myPropertyEditorListener
= new MyPropertyEditorListener();
91 * This is optimization. We do not need to invalidate Swing hierarchy
92 * upper than InplaceEditingLayer.
94 public boolean isValidateRoot() {
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
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){
154 if (!myEditor
.ensureEditable()) {
155 myInplaceProperty
= null;
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(
169 context
.isKeepInitialValue() ? myInplaceProperty
.getValue(myInplaceComponent
) : null,
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(
187 myPreferredBounds
.y
+ (myPreferredBounds
.height
- prefSize
.height
)/2,
188 Math
.min(Math
.max(prefSize
.width
, myPreferredBounds
.width
), getWidth() - myPreferredBounds
.x
),
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();
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
);
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
232 myInsideChange
= true;
234 // 1. Apply new value to the component
235 LOG
.assertTrue(myInplaceEditor
!= null);
236 CommandProcessor
.getInstance().executeCommand(
237 myInplaceComponent
.getProject(),
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;
262 disableEvents(MouseEvent
.MOUSE_EVENT_MASK
);
264 myInsideChange
= false;
271 * Cancells current inplace editing
273 private void cancelInplaceEditing(){
274 if(myInplaceComponent
== null || myInsideChange
){ // nothing to finish
277 myInsideChange
= true;
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;
293 disableEvents(MouseEvent
.MOUSE_EVENT_MASK
);
295 myInsideChange
= false;
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());
311 remove(myInplaceEditorComponent
);
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();
335 opposite
!= null && SwingUtilities
.isDescendingFrom(opposite
, getTopComponent())
337 // Do nothing if focus moves inside top component hierarchy
340 // [vova] we need LaterInvocator here to prevent write-access assertions
341 ApplicationManager
.getApplication().invokeLater(new Runnable() {
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();