All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.intellij.uiDesigner.designSurface.InplaceEditingLayer Maven / Gradle / Ivy

Go to download

A packaging of the IntelliJ Community Edition ui-designer library. This is release number 1 of trunk branch 142.

The newest version!
/*
 * Copyright 2000-2009 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.uiDesigner.designSurface;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.command.CommandProcessor;
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.FormEditingUtil;
import com.intellij.uiDesigner.UIDesignerBundle;
import com.intellij.uiDesigner.componentTree.ComponentSelectionListener;
import com.intellij.uiDesigner.propertyInspector.Property;
import com.intellij.uiDesigner.propertyInspector.PropertyEditor;
import com.intellij.uiDesigner.propertyInspector.PropertyEditorAdapter;
import com.intellij.uiDesigner.propertyInspector.InplaceContext;
import com.intellij.uiDesigner.radComponents.RadComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.MouseEvent;

/**
 * @author Anton Katilin
 * @author Vladimir Kondratyev
 */
public final class InplaceEditingLayer extends JComponent{
  private static final Logger LOG = Logger.getInstance("#com.intellij.uiDesigner.InplaceEditingLayer");

  private final GuiEditor myEditor;
  /**
   * Trackes focus movements inside myInplaceEditorComponent
   */
  private final MyFocusWatcher myFocusWatcher;
  /**
   * Commits or cancels editing
   */
  private final MyPropertyEditorListener myPropertyEditorListener;
  /**
   * The component which is currently edited with inplace editor.
   * This component can be null.
   */
  private RadComponent myInplaceComponent;
  /**
   * Currently edited inplace property
   */
  private Property myInplaceProperty;
  /**
   * Current inplace editor
   */
  private PropertyEditor myInplaceEditor;
  /**
   * JComponent which is used as inplace editor
   */
  private JComponent myInplaceEditorComponent;
  /**
   * Preferred bounds of the inplace editor component
   */
  private Rectangle myPreferredBounds;
  /**
   * If true then we do not have to react on own events
   */
  private boolean myInsideChange;

  public InplaceEditingLayer(@NotNull final GuiEditor editor) {
    myEditor = editor;
    myEditor.addComponentSelectionListener(new MyComponentSelectionListener());
    myFocusWatcher = new MyFocusWatcher();
    myPropertyEditorListener = new MyPropertyEditorListener();
  }

  /**
   * This is optimization. We do not need to invalidate Swing hierarchy
   * upper than InplaceEditingLayer.
   */
  public boolean isValidateRoot() {
    return true;
  }

  /**
   * 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(final MouseEvent e) {
    if(
      myInplaceComponent != null &&
      (MouseEvent.MOUSE_PRESSED == e.getID() || MouseEvent.MOUSE_RELEASED == e.getID())
    ){
      finishInplaceEditing();
    }
    // [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);
  }

  /**
   * @return whether the layer is in "editing" state or not
   */
  public boolean isEditing(){
    return myInplaceComponent != null;
  }

  /**
   * Starts editing of "inplace" property for the component at the
   * specified point (x, y).
   *
   * @param x x coordinate in the editor coordinate system
   * @param y y coordinate in the editor coordinate system
   */
  public void startInplaceEditing(final int x, final int y){
    final RadComponent inplaceComponent = FormEditingUtil.getRadComponentAt(myEditor.getRootContainer(), x, y);
    if(inplaceComponent == null){ // nothing to edit
      return;
    }

    // Try to find property with inplace editor
    final Point p = SwingUtilities.convertPoint(this, x, y, inplaceComponent.getDelegee());
    final Property inplaceProperty = inplaceComponent.getInplaceProperty(p.x, p.y);
    if (inplaceProperty != null) {
      final Rectangle bounds = inplaceComponent.getInplaceEditorBounds(inplaceProperty, p.x, p.y);
      startInplaceEditing(inplaceComponent, inplaceProperty, bounds, new InplaceContext(true));
    }
  }

  public void startInplaceEditing(@NotNull final RadComponent inplaceComponent,
                                  @Nullable final Property property,
                                  @Nullable final Rectangle bounds,
                                  final InplaceContext context) {
    myInplaceProperty = property;
    if(myInplaceProperty == null){
      return;
    }

    if (!myEditor.ensureEditable()) {
      myInplaceProperty = null;
      return;
    }

    // Now we have to cancel previous inplace editing (if any)

    // Start new inplace editing
    myInplaceComponent = inplaceComponent;
    myInplaceEditor  = myInplaceProperty.getEditor();
    LOG.assertTrue(myInplaceEditor != null);

    // 1. Get editor component
    myInplaceEditorComponent = myInplaceEditor.getComponent(
      myInplaceComponent,
      context.isKeepInitialValue() ? myInplaceProperty.getValue(myInplaceComponent) : null,
      context
    );

    if (context.isModalDialogDisplayed()) {  // ListModel, for example
      finishInplaceEditing();
      return;
    }

    LOG.assertTrue(myInplaceEditorComponent != null);
    myInplaceEditor.addPropertyEditorListener(myPropertyEditorListener);

    // 2. Set editor component bounds
    final Dimension prefSize = myInplaceEditorComponent.getPreferredSize();
    if(bounds != null){ // use bounds provided by the component itself
      final Point _p = SwingUtilities.convertPoint(myInplaceComponent.getDelegee(), bounds.x, bounds.y, this);
      myPreferredBounds = new Rectangle(_p.x, _p.y, bounds.width, bounds.height);
    }
    else{ // set some default bounds
      final Point _p = SwingUtilities.convertPoint(myInplaceComponent.getDelegee(), 0, 0, this);
      myPreferredBounds = new Rectangle(_p.x,  _p.y, myInplaceComponent.getWidth(), myInplaceComponent.getHeight());
    }
    myInplaceEditorComponent.setBounds(
      myPreferredBounds.x,
      myPreferredBounds.y + (myPreferredBounds.height - prefSize.height)/2,
      Math.min(Math.max(prefSize.width, myPreferredBounds.width), getWidth() - myPreferredBounds.x),
      prefSize.height
    );

    // 3. Add it into layer
    add(myInplaceEditorComponent);
    myInplaceEditorComponent.revalidate();

    // 4. Request focus into proper component
    JComponent componentToFocus = myInplaceEditor.getPreferredFocusedComponent(myInplaceEditorComponent);
    if (componentToFocus == null) {
      componentToFocus = IdeFocusTraversalPolicy.getPreferredFocusedComponent(myInplaceEditorComponent);
    }
    if (componentToFocus == null) {
      componentToFocus = myInplaceEditorComponent;
    }
    if (componentToFocus.requestFocusInWindow()) {
      myFocusWatcher.install(myInplaceEditorComponent);
    }
    else {
      grabFocus();
      final JComponent finalComponentToFocus = componentToFocus;
      ApplicationManager.getApplication().invokeLater(new Runnable() {
        public void run() {
          finalComponentToFocus.requestFocusInWindow();
          myFocusWatcher.install(myInplaceEditorComponent);
        }
      });
    }

    // 5. Block any mouse event to finish editing by any of them
    enableEvents(MouseEvent.MOUSE_EVENT_MASK);

    repaint();
  }

  private void adjustEditorComponentSize(){
    if (myInplaceEditorComponent == null) return;
    final Dimension preferredSize = myInplaceEditorComponent.getPreferredSize();
    int width = Math.max(preferredSize.width, myPreferredBounds.width);
    // Editor component should not be extended to invisible area
    width = Math.min(width, getWidth() - myInplaceEditorComponent.getX());
    myInplaceEditorComponent.setSize(width, myInplaceEditorComponent.getHeight());
    myInplaceEditorComponent.revalidate();
  }

  /**
   * Finishes current inplace editing
   */
  private void finishInplaceEditing(){
    if (myInplaceComponent == null || myInsideChange) { // nothing to finish
      return;
    }
    myInsideChange = true;
    try{
      // 1. Apply new value to the component
      LOG.assertTrue(myInplaceEditor != null);
      if (!myEditor.isUndoRedoInProgress()) {
        CommandProcessor.getInstance().executeCommand(
          myInplaceComponent.getProject(),
          new Runnable() {
            public void run() {
              try {
                final Object value = myInplaceEditor.getValue();
                myInplaceProperty.setValue(myInplaceComponent, value);
              }
              catch (Exception ignored) {
              }
              myEditor.refreshAndSave(true);
            }
          }, UIDesignerBundle.message("command.set.property.value"), null);
      }
      // 2. Remove editor from the layer

      if (myInplaceEditorComponent != null) {  // reenterability guard
        removeInplaceEditorComponent();
        myFocusWatcher.deinstall(myInplaceEditorComponent);
      }

      myInplaceEditor.removePropertyEditorListener(myPropertyEditorListener);

      myInplaceComponent = null;
      myInplaceEditorComponent = null;
      myInplaceComponent = null;

      // 3. Let AWT work
      disableEvents(MouseEvent.MOUSE_EVENT_MASK);
    }finally{
      myInsideChange = false;
    }

    repaint();
  }

  /**
   * Cancells current inplace editing
   */
  private void cancelInplaceEditing(){
    if(myInplaceComponent == null || myInsideChange){ // nothing to finish
      return;
    }
    myInsideChange = true;
    try{
      // 1. Remove editor from the layer
      LOG.assertTrue(myInplaceProperty != null);
      LOG.assertTrue(myInplaceEditor != null);

      removeInplaceEditorComponent();

      myInplaceEditor.removePropertyEditorListener(myPropertyEditorListener);
      myFocusWatcher.deinstall(myInplaceEditorComponent);

      myInplaceComponent = null;
      myInplaceEditorComponent = null;
      myInplaceComponent = null;

      // 2. Let AWT work
      disableEvents(MouseEvent.MOUSE_EVENT_MASK);
    }finally{
      myInsideChange = false;
    }

    repaint();
  }

  private void removeInplaceEditorComponent() {
    // [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(myEditor.getGlassLayer());
    try {
      remove(myInplaceEditorComponent);
    }
    finally {
      LayoutFocusTraversalPolicyExt.setOverridenDefaultComponent(null);
    }
  }

  /**
   * Finish inplace editing when selection changes
   */
  private final class MyComponentSelectionListener implements ComponentSelectionListener{
    public void selectedComponentChanged(final GuiEditor source) {
      finishInplaceEditing();
    }
  }

  /**
   * Finish inplace editing when inplace editor component loses focus
   */
  private final class MyFocusWatcher extends FocusWatcher{
    protected void focusLostImpl(final FocusEvent e) {
      final 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() {
          finishInplaceEditing();
        }
      }, ModalityState.NON_MODAL);
    }
  }

  /**
   * Finishes editing by "Enter" and cancels editing by "Esc"
   */
  private final class MyPropertyEditorListener extends PropertyEditorAdapter{
    public void valueCommitted(final PropertyEditor source, final boolean continueEditing, final boolean closeEditorOnError) {
      finishInplaceEditing();
    }

    public void editingCanceled(final PropertyEditor source) {
      cancelInplaceEditing();
    }

    public void preferredSizeChanged(final PropertyEditor source) {
      adjustEditorComponentSize();
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy