Commits

Jonathan Giles  committed 9a69273 Merge

Merged in henrib/controlsfxs (pull request #343)

SpreadsheetCellBase now implements EventTarget and is firing two EventType for the editable state and the corners states.

  • Participants
  • Parent commits 6c008ae, 64e7a57

Comments (0)

Files changed (5)

File controlsfx/src/main/java/impl/org/controlsfx/spreadsheet/CellViewSkin.java

 /**
- * Copyright (c) 2013, 2014  ControlsFX
+ * Copyright (c) 2013, 2014 ControlsFX
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  */
 package impl.org.controlsfx.spreadsheet;
 
+import com.sun.javafx.scene.control.skin.TableCellSkin;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
+import javafx.beans.value.WeakChangeListener;
 import javafx.collections.ObservableList;
+import javafx.event.Event;
+import javafx.event.EventHandler;
 import javafx.scene.control.TableCell;
 import javafx.scene.layout.Region;
-
 import org.controlsfx.control.spreadsheet.SpreadsheetCell;
-
-import com.sun.javafx.scene.control.skin.TableCellSkin;
-import javafx.beans.value.WeakChangeListener;
+import org.controlsfx.control.spreadsheet.SpreadsheetCell.CornerPosition;
 
 /**
- * 
+ *
  * This is the skin for the {@link CellView}.
- * 
+ *
  * Its main goal is to draw an object (a triangle) on cells which have their
  * {@link SpreadsheetCell#commentedProperty()} set to true.
- * 
+ *
  */
 public class CellViewSkin extends TableCellSkin<ObservableList<SpreadsheetCell>, SpreadsheetCell> {
 
+    private final static String TOP_LEFT_CLASS = "top-left"; //$NON-NLS-1$
+    private final static String TOP_RIGHT_CLASS = "top-right"; //$NON-NLS-1$
+    private final static String BOTTOM_RIGHT_CLASS = "bottom-right"; //$NON-NLS-1$
+    private final static String BOTTOM_LEFT_CLASS = "bottom-left"; //$NON-NLS-1$
     /**
      * The size of the edge of the triangle FIXME Handling of static variable
      * will be changed.
     /**
      * The region we will add on the cell when necessary.
      */
-    private Region commentTriangle = null;
+    private Region topLeftRegion = null;
+    private Region topRightRegion = null;
+    private Region bottomRightRegion = null;
+    private Region bottomLeftRegion = null;
 
     public CellViewSkin(TableCell<ObservableList<SpreadsheetCell>, SpreadsheetCell> tableCell) {
         super(tableCell);
         tableCell.itemProperty().addListener(new WeakChangeListener<>(itemChangeListener));
-
         if (tableCell.getItem() != null) {
-            tableCell.getItem().commentedProperty().addListener(new WeakChangeListener<>(triangleListener));
+            tableCell.getItem().addEventHandler(SpreadsheetCell.CORNER_EVENT_TYPE, triangleEventHandler);
         }
     }
 
     protected void layoutChildren(double x, final double y, final double w, final double h) {
         super.layoutChildren(x, y, w, h);
         if (getSkinnable().getItem() != null) {
-            layoutTriangle(getSkinnable().getItem().isCommented());
+            layoutTriangle();
         }
     }
 
-    private void layoutTriangle(boolean isCommented) {
-        if (isCommented) {
-            if (commentTriangle == null) {
-                commentTriangle = new Region();
-            }
-            if (!getChildren().contains(commentTriangle)) {
-                getChildren().add(commentTriangle);
-            }
-            commentTriangle.resize(TRIANGLE_SIZE, TRIANGLE_SIZE);
-            // Setting the style class is important for the shape.
-            commentTriangle.getStyleClass().add("comment"); //$NON-NLS-1$
-            commentTriangle.relocate(getSkinnable().getWidth() - TRIANGLE_SIZE, snappedTopInset() - 1);
-        } else if (commentTriangle != null) {
-            getChildren().remove(commentTriangle);
-            commentTriangle = null;
-        }
+    private void layoutTriangle() {
+        SpreadsheetCell cell = getSkinnable().getItem();
+        
+        handleTopLeft(cell);
+        handleTopRight(cell);
+        handleBottomLeft(cell);
+        handleBottomRight(cell);
+
         getSkinnable().requestLayout();
     }
 
-    private final ChangeListener<Boolean> triangleListener = new ChangeListener<Boolean>() {
+    private void handleTopLeft(SpreadsheetCell cell) {
+        if (cell.isCornerActivated(CornerPosition.TOP_LEFT)) {
+            if (topLeftRegion == null) {
+                topLeftRegion = getRegion(CornerPosition.TOP_LEFT);
+            }
+            if (!getChildren().contains(topLeftRegion)) {
+                getChildren().add(topLeftRegion);
+            }
+            topLeftRegion.relocate(0, snappedTopInset() - 1);
+        } else if (topLeftRegion != null) {
+            getChildren().remove(topLeftRegion);
+            topLeftRegion = null;
+        }
+    }
+
+    private void handleTopRight(SpreadsheetCell cell) {
+        if (cell.isCornerActivated(CornerPosition.TOP_RIGHT)) {
+            if (topRightRegion == null) {
+                topRightRegion = getRegion(CornerPosition.TOP_RIGHT);
+            }
+            if (!getChildren().contains(topRightRegion)) {
+                getChildren().add(topRightRegion);
+            }
+            topRightRegion.relocate(getSkinnable().getWidth() - TRIANGLE_SIZE, snappedTopInset() - 1);
+        } else if (topRightRegion != null) {
+            getChildren().remove(topRightRegion);
+            topRightRegion = null;
+        }
+    }
+
+    private void handleBottomRight(SpreadsheetCell cell) {
+        if (cell.isCornerActivated(CornerPosition.BOTTOM_RIGHT)) {
+            if (bottomRightRegion == null) {
+                bottomRightRegion = getRegion(CornerPosition.BOTTOM_RIGHT);
+            }
+            if (!getChildren().contains(bottomRightRegion)) {
+                getChildren().add(bottomRightRegion);
+            }
+            bottomRightRegion.relocate(getSkinnable().getWidth() - TRIANGLE_SIZE, getSkinnable().getHeight() - TRIANGLE_SIZE);
+        } else if (bottomRightRegion != null) {
+            getChildren().remove(bottomRightRegion);
+            bottomRightRegion = null;
+        }
+    }
+    private void handleBottomLeft(SpreadsheetCell cell) {
+        if (cell.isCornerActivated(CornerPosition.BOTTOM_LEFT)) {
+            if (bottomLeftRegion == null) {
+                bottomLeftRegion = getRegion(CornerPosition.BOTTOM_LEFT);
+            }
+            if (!getChildren().contains(bottomLeftRegion)) {
+                getChildren().add(bottomLeftRegion);
+            }
+            bottomLeftRegion.relocate(0, getSkinnable().getHeight() - TRIANGLE_SIZE);
+        } else if (bottomLeftRegion != null) {
+            getChildren().remove(bottomLeftRegion);
+            bottomLeftRegion = null;
+        }
+    }
+
+    private static Region getRegion(CornerPosition position) {
+        Region region = new Region();
+        region.resize(TRIANGLE_SIZE, TRIANGLE_SIZE);
+        region.getStyleClass().add("cell-corner"); //$NON-NLS-1$
+        switch (position) {
+            case TOP_LEFT:
+                region.getStyleClass().add(TOP_LEFT_CLASS);
+                break;
+            case TOP_RIGHT:
+                region.getStyleClass().add(TOP_RIGHT_CLASS);
+                break;
+            case BOTTOM_RIGHT:
+                region.getStyleClass().add(BOTTOM_RIGHT_CLASS);
+                break;
+            case BOTTOM_LEFT:
+                region.getStyleClass().add(BOTTOM_LEFT_CLASS);
+                break;
+
+        }
+
+        return region;
+    }
+    
+    private final EventHandler<Event> triangleEventHandler = new EventHandler<Event>() {
+
         @Override
-        public void changed(ObservableValue<? extends Boolean> arg0, Boolean oldValue, Boolean newValue) {
+        public void handle(Event event) {
             getSkinnable().requestLayout();
         }
     };
-    
+
     private final ChangeListener<SpreadsheetCell> itemChangeListener = new ChangeListener<SpreadsheetCell>() {
         @Override
         public void changed(ObservableValue<? extends SpreadsheetCell> arg0, SpreadsheetCell oldCell,
                 SpreadsheetCell newCell) {
             if (oldCell != null) {
-                oldCell.commentedProperty().removeListener(triangleListener);
+                oldCell.removeEventHandler(SpreadsheetCell.CORNER_EVENT_TYPE, triangleEventHandler);
             }
             if (newCell != null) {
-                newCell.commentedProperty().addListener(new WeakChangeListener<>(triangleListener));
+                newCell.addEventHandler(SpreadsheetCell.CORNER_EVENT_TYPE, triangleEventHandler);
+            }
+            if (getSkinnable().getItem() != null) {
+                layoutTriangle();
             }
         }
     };

File controlsfx/src/main/java/org/controlsfx/control/spreadsheet/SpreadsheetCell.java

 package org.controlsfx.control.spreadsheet;
 
 import java.util.Optional;
-import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ReadOnlyStringProperty;
 import javafx.beans.property.StringProperty;
 import javafx.collections.ObservableSet;
+import javafx.event.Event;
+import javafx.event.EventHandler;
+import javafx.event.EventType;
 import javafx.scene.Node;
 
 /**
  * @see SpreadsheetCellBase
  */
 public interface SpreadsheetCell {
+    /**
+     * This EventType can be used with an {@link EventHandler} in order to catch
+     * when the editable state of a SpreadsheetCell is changed.
+     */
+    public static final EventType EDITABLE_EVENT_TYPE = new EventType("EditableEventType");
+    
+    /**
+     * This EventType can be used with an {@link EventHandler} in order to catch
+     * when a corner state of a SpreadsheetCell is changed.
+     */
+    public static final EventType CORNER_EVENT_TYPE = new EventType("CornerEventType");
 
     /**
+     * This enum states the four different corner available for positioning 
+     * some elements in a cell.
+     */
+    public static enum CornerPosition {
+
+        TOP_LEFT ,
+        TOP_RIGHT ,
+        BOTTOM_RIGHT ,
+        BOTTOM_LEFT 
+    }
+    
+    /**
      * Verify that the upcoming cell value can be set to the current cell. This
      * is currently used by the Copy/Paste.
      *
     /**
      * Change the editable state of this cell
      *
-     * @param readOnly
+     * @param editable
      */
-    public void setEditable(boolean readOnly);
+    public void setEditable(boolean editable);
 
     /**
-     * The {@link BooleanProperty} linked with the editable state.
-     *
-     * @return The {@link BooleanProperty} linked with the editable state.
+     * This activate the given cornerPosition.
+     * @param position
      */
-    public BooleanProperty editableProperty();
+    public void activateCorner(CornerPosition position);
+    
+    /**
+     * This deactivate the given cornerPosition.
+     * @param position
+     */
+    public void deactivateCorner(CornerPosition position);
 
     /**
-     * Return if this cell has a comment or not.
-     *
-     * @return true if this cell has a comment.
+     * 
+     * @param position
+     * @return the current state of a specific corner.
      */
-    public boolean isCommented();
-
-    /**
-     * Change the commented state of this cell.
-     *
-     * @param flag
-     */
-    public void setCommented(boolean flag);
-
-    /**
-     * The {@link BooleanProperty} linked with the commented state.
-     *
-     * @return The {@link BooleanProperty} linked with the commented state.
-     */
-    public BooleanProperty commentedProperty();
-
+    public boolean isCornerActivated(CornerPosition position);
+    
     /**
      * The {@link StringProperty} linked with the format.
      *
      * @return the tooltip associated with this SpreadsheetCell.
      */
     public Optional<String> getTooltip();
+    
+    /**
+     * Registers an event handler to this SpreadsheetCell. 
+     * @param eventType the type of the events to receive by the handler
+     * @param eventHandler the handler to register
+     * @throws NullPointerException if the event type or handler is null
+     */
+    public void addEventHandler(EventType<Event> eventType, EventHandler<Event> eventHandler);
+    
+    /**
+     * Unregisters a previously registered event handler from this SpreadsheetCell. 
+     * @param eventType the event type from which to unregister
+     * @param eventHandler the handler to unregister
+     * @throws NullPointerException if the event type or handler is null
+     */
+    public void removeEventHandler(EventType<Event> eventType, EventHandler<Event> eventHandler);
 }

File controlsfx/src/main/java/org/controlsfx/control/spreadsheet/SpreadsheetCellBase.java

  */
 package org.controlsfx.control.spreadsheet;
 
+import com.sun.javafx.event.EventHandlerManager;
 import java.util.Objects;
 import java.util.Optional;
-
-import javafx.beans.property.BooleanProperty;
 import javafx.beans.property.ObjectProperty;
 import javafx.beans.property.ReadOnlyStringProperty;
-import javafx.beans.property.SimpleBooleanProperty;
 import javafx.beans.property.SimpleObjectProperty;
 import javafx.beans.property.SimpleStringProperty;
 import javafx.beans.property.StringProperty;
 import javafx.beans.value.ChangeListener;
 import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
-import javafx.collections.ObservableMap;
 import javafx.collections.ObservableSet;
+import javafx.event.Event;
+import javafx.event.EventDispatchChain;
+import javafx.event.EventHandler;
+import javafx.event.EventTarget;
+import javafx.event.EventType;
 import javafx.scene.Node;
 import javafx.scene.image.ImageView;
 
  * @see SpreadsheetCellEditor
  * @see SpreadsheetCellType
  */
-public class SpreadsheetCellBase implements SpreadsheetCell{
+public class SpreadsheetCellBase implements SpreadsheetCell, EventTarget{
 
     /***************************************************************************
      * 
      * 
      **************************************************************************/
 
-    @SuppressWarnings("rawtypes")
+    //The Bit position for the editable Property.
+    private static final int EDITABLE_BIT_POSITION = 4;
     private final SpreadsheetCellType type;
     private final int row;
     private final int column;
     private final StringProperty text;
     private final ObjectProperty<Node> graphic;
     private String tooltip;
+    /**
+     * This variable handles all boolean values of this SpreadsheetCell inside
+     * its bits. Instead of using regular boolean, we use that int so that we 
+     * can reduce memory usage to the bare minimum.
+     */
+    private int propertyContainer = 0;
+    private final EventHandlerManager eventHandlerManager = new EventHandlerManager(this);
 
     private ObservableSet<String> styleClass;
 
                 updateText();
             }
         });
+        //Editable is true at the initialisation
+        setEditable(true);
         getStyleClass().add("spreadsheet-cell"); //$NON-NLS-1$
     }
 
     /***************************************************************************
      * 
-     * Abstract Methods
+     * Public Methods
      * 
      **************************************************************************/
-
+    
     /** {@inheritDoc} */
     @Override
     public boolean match(SpreadsheetCell cell) {
         return type.match(cell);
     }
 
-    /***************************************************************************
-     * 
-     * Properties
-     * 
-     ***************************************************************************/
-
     // --- item
     private final ObjectProperty<Object> item = new SimpleObjectProperty<Object>(this, "item") { //$NON-NLS-1$
         @Override
         return item;
     }
 
-    // --- editable
-    private BooleanProperty editable;
-
-   /** {@inheritDoc} */
+    /** {@inheritDoc} */
     @Override
     public final boolean isEditable() {
-        return editable == null ? true : editable.get();
-    }
-
-   /** {@inheritDoc} */
-    @Override
-    public final void setEditable(boolean readOnly) {
-        editableProperty().set(readOnly);
-    }
-
-   /** {@inheritDoc} */
-    @Override
-    public final BooleanProperty editableProperty() {
-        if (editable == null) {
-            editable = new SimpleBooleanProperty(this, "editable", true); //$NON-NLS-1$
-        }
-        return editable;
-    }
-
-    // --- comment
-    private final BooleanProperty commented = new SimpleBooleanProperty(this, "commented", false); //$NON-NLS-1$
-
-    /** {@inheritDoc} */
-    @Override
-    public final boolean isCommented() {
-        return commented == null ? true : commented.get();
-    }
-
-   /** {@inheritDoc} */
-    @Override
-    public final void setCommented(boolean flag) {
-        commentedProperty().set(flag);
+        return isSet(EDITABLE_BIT_POSITION);
     }
 
     /** {@inheritDoc} */
     @Override
-    public final BooleanProperty commentedProperty() {
-        return commented;
+    public final void setEditable(boolean editable) {
+        if(setMask(editable, EDITABLE_BIT_POSITION)){
+            Event.fireEvent(this, new Event(EDITABLE_EVENT_TYPE));
+        }
     }
 
    /** {@inheritDoc} */
         updateText();
     }
 
-    /***************************************************************************
-     * 
-     * Public Methods
-     * 
-     **************************************************************************/
-
     /** {@inheritDoc} */
     @Override
     public final ReadOnlyStringProperty textProperty() {
         return styleClass;
     }
 
-    // A map containing a set of properties for this cell
-    private ObservableMap<Object, Object> properties;
-
-    /**
-     * Returns an observable map of properties on this node for use primarily by
-     * application developers.
-     * 
-     * @return an observable map of properties on this node for use primarily by
-     *         application developers
-     */
-    public final ObservableMap<Object, Object> getProperties() {
-        if (properties == null) {
-            properties = FXCollections.observableHashMap();
-        }
-        return properties;
-    }
-
-    /**
-     * Tests if Node has properties.
-     * 
-     * @return true if node has properties.
-     */
-    public final boolean hasProperties() {
-        return properties != null && !properties.isEmpty();
-    }
-
     /** {@inheritDoc} */
     @Override
     public ObjectProperty<Node> graphicProperty() {
         return graphic.get();
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public Optional<String> getTooltip() {
+        return Optional.ofNullable(tooltip);
+    }
+    
+    /**
+     * Set a new tooltip for this cell.
+     * @param tooltip 
+     */
+    public void setTooltip(String tooltip){
+        this.tooltip = tooltip;
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    public void activateCorner(CornerPosition position) {
+        if(setMask(true, getCornerBitNumber(position))){
+            Event.fireEvent(this, new Event(CORNER_EVENT_TYPE));
+        }
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    public void deactivateCorner(CornerPosition position) {
+        if(setMask(false, getCornerBitNumber(position))){
+             Event.fireEvent(this, new Event(EDITABLE_EVENT_TYPE));
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean isCornerActivated(CornerPosition position) {
+        return isSet(getCornerBitNumber(position));
+    }
+    
+    /** {@inheritDoc} */
+    @Override
+    public EventDispatchChain buildEventDispatchChain(EventDispatchChain tail) {
+        return tail.append(eventHandlerManager);
+    }
+    
     /***************************************************************************
      * 
      * Overridden Methods
         return result;
     }
     
-    /** {@inheritDoc} */
+    /**
+     * Registers an event handler to this SpreadsheetCell. The SpreadsheetCell class allows 
+     * registration of listeners which will be notified when a corner state of
+     * the editable state of this SpreadsheetCell have changed.
+     *
+     * @param eventType the type of the events to receive by the handler
+     * @param eventHandler the handler to register
+     * @throws NullPointerException if the event type or handler is null
+     */
     @Override
-    public Optional<String> getTooltip() {
-        return Optional.ofNullable(tooltip);
+    public void addEventHandler(EventType<Event> eventType, EventHandler<Event> eventHandler) {
+         eventHandlerManager.addEventHandler(eventType, eventHandler);
+    }
+
+    /**
+     * Unregisters a previously registered event handler from this SpreadsheetCell. One
+     * handler might have been registered for different event types, so the
+     * caller needs to specify the particular event type from which to
+     * unregister the handler.
+     *
+     * @param eventType the event type from which to unregister
+     * @param eventHandler the handler to unregister
+     * @throws NullPointerException if the event type or handler is null
+     */
+    @Override
+    public void removeEventHandler(EventType<Event> eventType, EventHandler<Event> eventHandler) {
+         eventHandlerManager.removeEventHandler(eventType, eventHandler);
     }
     
-    /**
-     * Set a new tooltip for this cell.
-     * @param tooltip 
-     */
-    public void setTooltip(String tooltip){
-        this.tooltip = tooltip;
-    }
     /***************************************************************************
      * 
      * Private Implementation
             text.setValue(type.toString(getItem()));
         }
     }
+
+    /**
+     * Return the Bit position for each corner.
+     * @param position
+     * @return 
+     */
+    private int getCornerBitNumber(CornerPosition position) {
+        switch (position) {
+            case TOP_LEFT:
+                return 0;
+
+            case TOP_RIGHT:
+                return 1;
+
+            case BOTTOM_RIGHT:
+                return 2;
+
+            case BOTTOM_LEFT:
+            default:
+                return 3;
+        }
+    }
+
+    /**
+     * Set the specified bit position at the value specified by flag.
+     * @param flag
+     * @param position
+     * @return whether a change has really occured.
+     */
+    private boolean setMask(boolean flag, int position) {
+        int oldCorner = propertyContainer;
+        if (flag) {
+            propertyContainer |= (1 << position);
+        } else {
+            propertyContainer &= ~(1 << position);
+        }
+        return propertyContainer != oldCorner;
+    }
+
+    /**
+     * @param mask
+     * @param position
+     * @return whether the specified bit position is true.
+     */
+    private boolean isSet(int position) {
+        return (propertyContainer & (1 << position)) != 0;
+    }
 }

File controlsfx/src/main/java/org/controlsfx/control/spreadsheet/SpreadsheetView.java

 
 import static impl.org.controlsfx.i18n.Localization.asKey;
 import static impl.org.controlsfx.i18n.Localization.localize;
-
 import impl.org.controlsfx.spreadsheet.CellView;
 import impl.org.controlsfx.spreadsheet.FocusModelListener;
 import impl.org.controlsfx.spreadsheet.GridViewSkin;
 import javafx.scene.Node;
 import javafx.scene.control.ContextMenu;
 import javafx.scene.control.Control;
+import javafx.scene.control.Menu;
 import javafx.scene.control.MenuItem;
 import javafx.scene.control.SelectionMode;
 import javafx.scene.control.Skin;
             }
         });
         
-        final MenuItem commentedItem = new MenuItem(localize(asKey("spreadsheet.view.menu.comment")));
-        commentedItem.setGraphic(new ImageView(new Image(SpreadsheetView.class
+        final Menu cornerMenu = new Menu(localize(asKey("spreadsheet.view.menu.comment")));
+        cornerMenu.setGraphic(new ImageView(new Image(SpreadsheetView.class
                 .getResourceAsStream("comment.png"))));
-        commentedItem.setOnAction(new EventHandler<ActionEvent>() {
+
+        final MenuItem topLeftItem = new MenuItem("top-left");
+        topLeftItem.setOnAction(new EventHandler<ActionEvent>() {
+
             @Override
-            public void handle(ActionEvent e) {
+            public void handle(ActionEvent t) {
                 TablePosition<ObservableList<SpreadsheetCell>, ?> pos = cellsView.getFocusModel().getFocusedCell();
-                SpreadsheetCell cell =  getGrid().getRows().get(pos.getRow()).get(pos.getColumn());
-               cell.setCommented(!cell.isCommented());
+                SpreadsheetCell cell = getGrid().getRows().get(pos.getRow()).get(pos.getColumn());
+                cell.activateCorner(SpreadsheetCell.CornerPosition.TOP_LEFT);
             }
         });
+        final MenuItem topRightItem = new MenuItem("top-right");
+        topRightItem.setOnAction(new EventHandler<ActionEvent>() {
+
+            @Override
+            public void handle(ActionEvent t) {
+                TablePosition<ObservableList<SpreadsheetCell>, ?> pos = cellsView.getFocusModel().getFocusedCell();
+                SpreadsheetCell cell = getGrid().getRows().get(pos.getRow()).get(pos.getColumn());
+                cell.activateCorner(SpreadsheetCell.CornerPosition.TOP_RIGHT);
+            }
+        });
+        final MenuItem bottomRightItem = new MenuItem("bottom-right");
+        bottomRightItem.setOnAction(new EventHandler<ActionEvent>() {
+
+            @Override
+            public void handle(ActionEvent t) {
+                TablePosition<ObservableList<SpreadsheetCell>, ?> pos = cellsView.getFocusModel().getFocusedCell();
+                SpreadsheetCell cell = getGrid().getRows().get(pos.getRow()).get(pos.getColumn());
+                cell.activateCorner(SpreadsheetCell.CornerPosition.BOTTOM_RIGHT);
+            }
+        });
+        final MenuItem bottomLeftItem = new MenuItem("bottom-left");
+        bottomLeftItem.setOnAction(new EventHandler<ActionEvent>() {
+
+            @Override
+            public void handle(ActionEvent t) {
+                TablePosition<ObservableList<SpreadsheetCell>, ?> pos = cellsView.getFocusModel().getFocusedCell();
+                SpreadsheetCell cell = getGrid().getRows().get(pos.getRow()).get(pos.getColumn());
+                cell.activateCorner(SpreadsheetCell.CornerPosition.BOTTOM_LEFT);
+            }
+        });
+
+        cornerMenu.getItems().addAll(topLeftItem, topRightItem, bottomRightItem, bottomLeftItem);
         
-        contextMenu.getItems().addAll(copyItem, pasteItem, commentedItem);
+        contextMenu.getItems().addAll(copyItem, pasteItem, cornerMenu);
         return contextMenu;
     }
 

File controlsfx/src/main/resources/org/controlsfx/control/spreadsheet/spreadsheet.css

 /* HORIZONTAL HEADER VISIBILITY */
 .column-header-background.invisible { visibility: hidden; -fx-padding: -1em; }
 
-.comment{
+.cell-corner{
     -fx-background-color: red;
+}
+
+.cell-corner.top-left{
+    -fx-shape : "M 0 0 L 1 0 L 0 1 z";
+}
+
+.cell-corner.top-right{
     -fx-shape : "M 0 0 L -1 0 L 0 1 z";
 }
 
+.cell-corner.bottom-right{
+    -fx-shape : "M 0 0 L -1 0 L 0 -1 z";
+}
+
+.cell-corner.bottom-left{
+    -fx-shape : "M 0 0 L 1 0 L 0 -1 z";
+}
+
 /* TextField must squeeze tight, so no padding
 on top and bottom */
 .spreadsheet-cell TextField{