2 * Copyright (c) 2000-2006 JetBrains s.r.o. All Rights Reserved.
5 package com
.intellij
.uiDesigner
.designSurface
;
7 import com
.intellij
.ide
.DeleteProvider
;
8 import com
.intellij
.ide
.dnd
.*;
9 import com
.intellij
.openapi
.actionSystem
.*;
10 import com
.intellij
.openapi
.diagnostic
.Logger
;
11 import com
.intellij
.openapi
.util
.Pair
;
12 import com
.intellij
.ui
.LightColors
;
13 import com
.intellij
.uiDesigner
.CaptionSelection
;
14 import com
.intellij
.uiDesigner
.FormEditingUtil
;
15 import com
.intellij
.uiDesigner
.GridChangeUtil
;
16 import com
.intellij
.uiDesigner
.componentTree
.ComponentSelectionListener
;
17 import com
.intellij
.uiDesigner
.radComponents
.RadAbstractGridLayoutManager
;
18 import com
.intellij
.uiDesigner
.radComponents
.RadComponent
;
19 import com
.intellij
.uiDesigner
.radComponents
.RadContainer
;
20 import com
.intellij
.uiDesigner
.radComponents
.RadRootContainer
;
21 import com
.intellij
.util
.Alarm
;
22 import com
.intellij
.util
.ArrayUtil
;
23 import org
.jetbrains
.annotations
.Nullable
;
26 import javax
.swing
.event
.ChangeEvent
;
27 import javax
.swing
.event
.ChangeListener
;
28 import javax
.swing
.event
.ListSelectionEvent
;
29 import javax
.swing
.event
.ListSelectionListener
;
31 import java
.awt
.event
.*;
32 import java
.util
.ArrayList
;
37 public class GridCaptionPanel
extends JPanel
implements ComponentSelectionListener
, DataProvider
{
38 private static final Logger LOG
= Logger
.getInstance("#com.intellij.uiDesigner.designSurface.GridCaptionPanel");
40 private GuiEditor myEditor
;
41 private boolean myIsRow
;
42 private RadContainer mySelectedContainer
;
43 private DefaultListSelectionModel mySelectionModel
= new DefaultListSelectionModel();
44 private int myResizeLine
= -1;
45 private int myDropInsertLine
= -1;
46 private LineFeedbackPainter myFeedbackPainter
= new LineFeedbackPainter();
47 private DeleteProvider myDeleteProvider
= new MyDeleteProvider();
48 private Alarm myAlarm
= new Alarm();
50 public GridCaptionPanel(final GuiEditor editor
, final boolean isRow
) {
53 mySelectionModel
.setSelectionMode(ListSelectionModel
.MULTIPLE_INTERVAL_SELECTION
);
54 mySelectionModel
.addListSelectionListener(new ListSelectionListener() {
55 public void valueChanged(ListSelectionEvent e
) {
57 myEditor
.fireSelectedComponentChanged();
60 setBackground(Color
.LIGHT_GRAY
);
61 editor
.addComponentSelectionListener(this);
63 final MyMouseListener listener
= new MyMouseListener();
64 addMouseListener(listener
);
65 addMouseMotionListener(listener
);
66 addKeyListener(new MyKeyListener());
69 DnDManager
.getInstance().registerSource(new MyDnDSource(), this);
70 DnDManager
.getInstance().registerTarget(new MyDnDTarget(), this);
72 addFocusListener(new FocusAdapter() {
73 @Override public void focusGained(FocusEvent e
) {
75 // ensure we don't have two repaints of properties panel - one from focus gain and another from click
76 myAlarm
.addRequest(new Runnable() {
78 editor
.fireSelectedComponentChanged();
83 @Override public void focusLost(FocusEvent e
) {
89 public RadContainer
getSelectedContainer() {
90 // when the selected component changes, and we have focus, PropertyInspector asks us about our container and selection.
91 // PropertyInspector's selection changed listener can be called before our own listener, so we need to update ourselves
92 // so that we don't return stale and invalid data.
93 checkSelectionChanged();
94 return mySelectedContainer
;
97 public boolean isRow() {
101 @Override public Dimension
getPreferredSize() {
102 return new Dimension(16, 16);
106 public Dimension
getMinimumSize() {
107 return getPreferredSize();
110 @Override public void paintComponent(Graphics g
) {
111 super.paintComponent(g
);
112 Graphics2D g2d
= (Graphics2D
) g
;
114 final Rectangle bounds
= getBounds();
115 final int paintedSize
= 8;
116 final int paintOffset
= 7;
118 RadContainer container
= getSelectedGridContainer();
119 if (container
== null) {
122 RadAbstractGridLayoutManager layout
= container
.getGridLayoutManager();
123 int[] coords
= layout
.getGridCellCoords(container
, myIsRow
);
124 int[] sizes
= layout
.getGridCellSizes(container
, myIsRow
);
125 int count
= myIsRow ? layout
.getGridRowCount(container
) : layout
.getGridColumnCount(container
);
127 for(int i
=0; i
<count
; i
++) {
128 int x
= myIsRow ?
0 : coords
[i
];
129 int y
= myIsRow ? coords
[i
] : 0;
130 Point pnt
= SwingUtilities
.convertPoint(container
.getDelegee(), x
, y
, this);
132 Rectangle rc
= myIsRow
133 ?
new Rectangle(bounds
.x
+paintOffset
, pnt
.y
, paintedSize
, sizes
[i
])
134 : new Rectangle(pnt
.x
, bounds
.y
+paintOffset
, sizes
[i
], paintedSize
);
136 g
.setColor(getCaptionColor(i
));
137 g
.fillRect(rc
.x
, rc
.y
, rc
.width
, rc
.height
);
139 Rectangle rcDecoration
= myIsRow
140 ?
new Rectangle(bounds
.x
, pnt
.y
, bounds
.width
, sizes
[i
])
141 : new Rectangle(pnt
.x
, bounds
.y
, sizes
[i
], bounds
.height
);
142 layout
.paintCaptionDecoration(container
, myIsRow
, i
, g2d
, rcDecoration
);
144 Stroke oldStroke
= g2d
.getStroke();
147 if (isFocusOwner() && i
== mySelectionModel
.getLeadSelectionIndex()) {
148 g
.setColor(Color
.BLACK
);
149 g2d
.setStroke(new BasicStroke(2.0f
));
150 deltaX
= myIsRow ?
1 : 0;
151 deltaY
= myIsRow ?
0 : 1;
154 g
.setColor(Color
.DARK_GRAY
);
156 g
.drawRect(rc
.x
+ deltaX
, rc
.y
+ deltaY
, rc
.width
- deltaX
, rc
.height
- deltaY
);
157 g2d
.setStroke(oldStroke
);
160 g
.setColor(Color
.DARK_GRAY
);
162 g
.drawLine(paintOffset
+paintedSize
, 0, paintOffset
+paintedSize
, bounds
.height
);
165 g
.drawLine(0, paintOffset
+paintedSize
, bounds
.width
, paintOffset
+paintedSize
);
168 if (myDropInsertLine
>= 0) {
169 int[] lines
= myIsRow ? layout
.getHorizontalGridLines(container
) : layout
.getVerticalGridLines(container
);
170 int coord
= lines
[myDropInsertLine
];
172 coord
= SwingUtilities
.convertPoint(container
.getDelegee(), 0, coord
, this).y
;
175 coord
= SwingUtilities
.convertPoint(container
.getDelegee(), coord
, 0, this).x
;
177 Stroke oldStroke
= g2d
.getStroke();
178 g2d
.setStroke(new BasicStroke(2.0f
));
179 g
.setColor(Color
.BLUE
);
181 g
.drawLine(bounds
.x
+1, coord
, bounds
.x
+bounds
.width
-1, coord
);
184 g
.drawLine(coord
, bounds
.y
+1, coord
, bounds
.y
+bounds
.height
-1);
187 g2d
.setStroke(oldStroke
);
192 private Color
getCaptionColor(final int i
) {
193 if (mySelectionModel
.isSelectedIndex(i
)) {
194 return LightColors
.BLUE
;
196 if (mySelectedContainer
!= null) {
197 if (i
>= 0 && i
< mySelectedContainer
.getGridCellCount(myIsRow
)) {
198 final GridChangeUtil
.CellStatus status
= GridChangeUtil
.canDeleteCell(mySelectedContainer
, i
, myIsRow
);
199 if (status
== GridChangeUtil
.CellStatus
.Empty
|| status
== GridChangeUtil
.CellStatus
.Redundant
) {
204 return LightColors
.GREEN
;
207 @Nullable private RadContainer
getSelectedGridContainer() {
208 final ArrayList
<RadComponent
> selection
= FormEditingUtil
.getSelectedComponents(myEditor
);
209 if (selection
.size() == 1 && selection
.get(0) instanceof RadContainer
) {
210 RadContainer container
= (RadContainer
) selection
.get(0);
211 if (container
.getLayoutManager().isGrid() && (container
.getParent() instanceof RadRootContainer
|| container
.getComponentCount() > 0)) {
215 RadContainer container
= FormEditingUtil
.getSelectionParent(selection
);
216 if (container
== null && myEditor
.getRootContainer().getComponentCount() > 0) {
217 final RadComponent topComponent
= myEditor
.getRootContainer().getComponent(0);
218 if (topComponent
instanceof RadContainer
) {
219 container
= (RadContainer
) topComponent
;
222 if (container
!= null && !container
.getLayoutManager().isGrid()) {
228 public void selectedComponentChanged(GuiEditor source
) {
229 checkSelectionChanged();
233 private void checkSelectionChanged() {
234 RadContainer container
= getSelectedGridContainer();
235 if (container
!= mySelectedContainer
) {
236 mySelectedContainer
= container
;
237 mySelectionModel
.clearSelection();
242 @Nullable public Object
getData(String dataId
) {
243 if (dataId
.equals(GuiEditor
.class.getName())) {
246 if (dataId
.equals(CaptionSelection
.class.getName())) {
247 return new CaptionSelection(mySelectedContainer
, myIsRow
, getSelectedCells(null), mySelectionModel
.getLeadSelectionIndex());
249 if (dataId
.equals(DataConstants
.DELETE_ELEMENT_PROVIDER
)) {
250 return myDeleteProvider
;
252 return myEditor
.getData(dataId
);
255 public void attachToScrollPane(final JScrollPane scrollPane
) {
256 scrollPane
.getViewport().addChangeListener(new ChangeListener() {
257 public void stateChanged(ChangeEvent e
) {
263 private int getCellAt(Point pnt
) {
264 if (mySelectedContainer
== null) return -1;
265 pnt
= SwingUtilities
.convertPoint(this, pnt
, mySelectedContainer
.getDelegee());
266 return myIsRow ? mySelectedContainer
.getGridRowAt(pnt
.y
) : mySelectedContainer
.getGridColumnAt(pnt
.x
);
269 public int[] getSelectedCells(@Nullable final Point dragOrigin
) {
270 ArrayList
<Integer
> selection
= new ArrayList
<Integer
>();
271 RadContainer container
= getSelectedGridContainer();
272 if (container
== null) {
273 return ArrayUtil
.EMPTY_INT_ARRAY
;
275 int size
= getCellCount();
276 for(int i
=0; i
<size
; i
++) {
277 if (mySelectionModel
.isSelectedIndex(i
)) {
281 if (selection
.size() == 0 && dragOrigin
!= null) {
282 int cell
= getCellAt(dragOrigin
);
284 return new int[] { cell
};
287 int[] result
= new int[selection
.size()];
288 for(int i
=0; i
<selection
.size(); i
++) {
289 result
[i
] = selection
.get(i
).intValue();
294 private int getCellCount() {
295 final RadContainer gridContainer
= getSelectedGridContainer();
296 assert gridContainer
!= null;
297 return myIsRow ? gridContainer
.getGridRowCount() : gridContainer
.getGridColumnCount();
300 private class MyMouseListener
extends MouseAdapter
implements MouseMotionListener
{
301 private static final int MINIMUM_RESIZED_SIZE
= 8;
303 @Override public void mouseExited(MouseEvent e
) {
304 setCursor(Cursor
.getDefaultCursor());
307 @Override public void mousePressed(MouseEvent e
) {
308 if (mySelectedContainer
== null) return;
310 Point pnt
= SwingUtilities
.convertPoint(GridCaptionPanel
.this, e
.getPoint(),
311 mySelectedContainer
.getDelegee());
312 RadAbstractGridLayoutManager layout
= mySelectedContainer
.getGridLayoutManager();
313 if (layout
.canResizeCells()) {
314 myResizeLine
= layout
.getGridLineNear(mySelectedContainer
, myIsRow
, pnt
, 4);
316 if (!checkShowPopupMenu(e
)) {
317 int cell
= getCellAt(e
.getPoint());
318 if (cell
== -1) return;
319 if ((e
.getModifiers() & MouseEvent
.CTRL_MASK
) != 0) {
320 mySelectionModel
.addSelectionInterval(cell
, cell
);
322 else if ((e
.getModifiers() & MouseEvent
.SHIFT_MASK
) != 0) {
323 mySelectionModel
.addSelectionInterval(mySelectionModel
.getAnchorSelectionIndex(), cell
);
328 @Override public void mouseReleased(MouseEvent e
) {
329 setCursor(Cursor
.getDefaultCursor());
330 myEditor
.getActiveDecorationLayer().removeFeedback();
332 if (myResizeLine
> 0) {
333 Point pnt
= SwingUtilities
.convertPoint(GridCaptionPanel
.this, e
.getPoint(),
334 mySelectedContainer
.getDelegee());
339 if (!checkShowPopupMenu(e
)) {
340 int cell
= getCellAt(e
.getPoint());
341 if (cell
== -1) return;
342 if ((e
.getModifiers() & (MouseEvent
.CTRL_MASK
| MouseEvent
.SHIFT_MASK
)) == 0) {
343 mySelectionModel
.setSelectionInterval(cell
, cell
);
348 private boolean checkShowPopupMenu(final MouseEvent e
) {
349 int cell
= getCellAt(e
.getPoint());
351 if (cell
>= 0 && e
.isPopupTrigger()) {
352 if (!mySelectionModel
.isSelectedIndex(cell
)) {
353 mySelectionModel
.setSelectionInterval(cell
, cell
);
355 ActionGroup group
= mySelectedContainer
.getGridLayoutManager().getCaptionActions();
357 final ActionPopupMenu popupMenu
= ActionManager
.getInstance().createActionPopupMenu(ActionPlaces
.UNKNOWN
, group
);
358 popupMenu
.getComponent().show(GridCaptionPanel
.this, e
.getX(), e
.getY());
365 private void doResize(final Point pnt
) {
366 int[] coords
= mySelectedContainer
.getGridLayoutManager().getGridCellCoords(mySelectedContainer
, myIsRow
);
367 int prevCoord
= coords
[myResizeLine
-1];
368 int newCoord
= myIsRow ? pnt
.y
: pnt
.x
;
369 if (newCoord
< prevCoord
+ MINIMUM_RESIZED_SIZE
) {
372 int newSize
= newCoord
- prevCoord
;
374 if (!myEditor
.ensureEditable()) {
378 mySelectedContainer
.getGridLayoutManager().processCellResized(mySelectedContainer
, myIsRow
, myResizeLine
-1, newSize
);
379 mySelectedContainer
.revalidate();
380 myEditor
.refreshAndSave(true);
383 public void mouseMoved(MouseEvent e
) {
384 if (mySelectedContainer
== null || !mySelectedContainer
.getGridLayoutManager().canResizeCells()) return;
385 Point pnt
= SwingUtilities
.convertPoint(GridCaptionPanel
.this, e
.getPoint(),
386 mySelectedContainer
.getDelegee());
387 int gridLine
= mySelectedContainer
.getGridLayoutManager().getGridLineNear(mySelectedContainer
, myIsRow
, pnt
, 4);
389 // first grid line may not be dragged
391 setCursor(Cursor
.getDefaultCursor());
394 setCursor(Cursor
.getPredefinedCursor(Cursor
.N_RESIZE_CURSOR
));
397 setCursor(Cursor
.getPredefinedCursor(Cursor
.W_RESIZE_CURSOR
));
401 public void mouseDragged(MouseEvent e
) {
402 if (myResizeLine
> 0) {
403 Point pnt
= SwingUtilities
.convertPoint(GridCaptionPanel
.this, e
.getPoint(),
404 mySelectedContainer
.getDelegee());
405 int[] coords
= mySelectedContainer
.getGridLayoutManager().getGridCellCoords(mySelectedContainer
, myIsRow
);
406 int prevCoord
= coords
[myResizeLine
-1];
407 int newCoord
= myIsRow ? pnt
.y
: pnt
.x
;
408 if (newCoord
< prevCoord
+ MINIMUM_RESIZED_SIZE
) {
411 int newSize
= newCoord
- prevCoord
;
413 String toolTip
= mySelectedContainer
.getGridLayoutManager().getCellResizeTooltip(mySelectedContainer
, myIsRow
, myResizeLine
-1, newSize
);
414 final ActiveDecorationLayer layer
= myEditor
.getActiveDecorationLayer();
417 rc
= new Rectangle(0, e
.getPoint().y
, layer
.getSize().width
, 1);
420 rc
= new Rectangle(e
.getPoint().x
, 0, 1, layer
.getSize().height
);
422 layer
.putFeedback(GridCaptionPanel
.this, rc
, myFeedbackPainter
, toolTip
);
427 private static class LineFeedbackPainter
implements FeedbackPainter
{
428 public void paintFeedback(Graphics2D g
, Rectangle rc
) {
429 g
.setColor(LightColors
.YELLOW
);
431 g
.drawLine(rc
.x
, rc
.y
, rc
.x
, rc
.y
+rc
.height
);
434 g
.drawLine(rc
.x
, rc
.y
, rc
.x
+rc
.width
, rc
.y
);
439 private class MyDeleteProvider
implements DeleteProvider
{
440 public void deleteElement(DataContext dataContext
) {
441 int[] selection
= getSelectedCells(null);
442 if (selection
.length
> 0) {
443 FormEditingUtil
.deleteRowOrColumn(myEditor
, mySelectedContainer
, selection
, myIsRow
);
447 public boolean canDeleteElement(DataContext dataContext
) {
448 if (mySelectedContainer
== null || mySelectionModel
.isSelectionEmpty()) {
451 int[] selection
= getSelectedCells(null);
452 return mySelectedContainer
.getGridCellCount(myIsRow
) - selection
.length
>= mySelectedContainer
.getGridLayoutManager().getMinCellCount();
456 private class MyDnDSource
implements DnDSource
{
457 public boolean canStartDragging(DnDAction action
, Point dragOrigin
) {
458 LOG
.debug("canStartDragging(): dragOrigin=" + dragOrigin
);
459 if (myResizeLine
!= -1) {
460 LOG
.debug("canStartDragging(): have resize line");
463 RadContainer container
= getSelectedGridContainer();
464 if (container
!= null &&
465 container
.getGridLayoutManager().getGridLineNear(mySelectedContainer
, myIsRow
, dragOrigin
, 4) != -1) {
466 LOG
.debug("canStartDragging(): have gridline near");
469 int[] selectedCells
= getSelectedCells(dragOrigin
);
470 for(int cell
: selectedCells
) {
471 if (!canDragCell(cell
)) {
472 LOG
.debug("canStartDragging(): cannot drag cell");
476 LOG
.debug("canStartDragging(): starting drag");
480 private boolean canDragCell(final int cell
) {
481 if (mySelectedContainer
== null) return false;
482 for(RadComponent c
: mySelectedContainer
.getComponents()) {
483 if (c
.getConstraints().contains(myIsRow
, cell
) && c
.getConstraints().getSpan(myIsRow
) > 1) {
490 public DnDDragStartBean
startDragging(DnDAction action
, Point dragOrigin
) {
491 return new DnDDragStartBean(new MyDragBean(myIsRow
, getSelectedCells(dragOrigin
)));
495 public Pair
<Image
, Point
> createDraggedImage(DnDAction action
, Point dragOrigin
) {
499 public void dragDropEnd() {
502 public void dropActionChanged(final int gestureModifiers
) {
506 private class MyDnDTarget
implements DnDTarget
{
507 public boolean update(DnDEvent aEvent
) {
508 aEvent
.setDropPossible(false, null);
509 if (mySelectedContainer
== null) {
512 if (!(aEvent
.getAttachedObject() instanceof MyDragBean
)) {
515 MyDragBean bean
= (MyDragBean
) aEvent
.getAttachedObject();
516 if (bean
.isRow
!= myIsRow
|| bean
.cells
.length
== 0) {
519 int gridLine
= getDropGridLine(aEvent
);
520 setDropInsertLine(gridLine
);
521 aEvent
.setDropPossible(gridLine
>= 0, null);
523 FeedbackPainter painter
= myIsRow ? HorzInsertFeedbackPainter
.INSTANCE
: VertInsertFeedbackPainter
.INSTANCE
;
524 Rectangle rcFeedback
= new Rectangle(mySelectedContainer
.getDelegee().getSize());
525 Rectangle cellRect
= new Rectangle(gridLine
, gridLine
, 1, 1);
526 rcFeedback
= GridInsertLocation
.getInsertFeedbackPosition(myIsRow ? GridInsertMode
.RowBefore
: GridInsertMode
.ColumnBefore
,
527 mySelectedContainer
, cellRect
, rcFeedback
);
528 myEditor
.getActiveDecorationLayer().putFeedback(mySelectedContainer
.getDelegee(), rcFeedback
, painter
, null);
531 myEditor
.getActiveDecorationLayer().removeFeedback();
536 private int getDropGridLine(final DnDEvent aEvent
) {
537 final Point point
= aEvent
.getPointOn(mySelectedContainer
.getDelegee());
538 return mySelectedContainer
.getGridLayoutManager().getGridLineNear(mySelectedContainer
, myIsRow
, point
, 20);
541 public void drop(DnDEvent aEvent
) {
542 if (!(aEvent
.getAttachedObject() instanceof MyDragBean
)) {
545 MyDragBean dragBean
= (MyDragBean
) aEvent
.getAttachedObject();
546 int targetCell
= getDropGridLine(aEvent
);
547 if (targetCell
< 0) return;
548 mySelectedContainer
.getGridLayoutManager().processCellsMoved(mySelectedContainer
, myIsRow
, dragBean
.cells
, targetCell
);
549 mySelectionModel
.clearSelection();
550 mySelectedContainer
.revalidate();
551 myEditor
.refreshAndSave(true);
555 public void cleanUpOnLeave() {
556 setDropInsertLine(-1);
557 myEditor
.getActiveDecorationLayer().removeFeedback();
560 public void updateDraggedImage(Image image
, Point dropPoint
, Point imageOffset
) {
563 private void setDropInsertLine(final int i
) {
564 if (myDropInsertLine
!= i
) {
565 myDropInsertLine
= i
;
571 private static class MyDragBean
{
572 public boolean isRow
;
575 public MyDragBean(final boolean row
, final int[] cells
) {
581 private class MyKeyListener
extends KeyAdapter
{
582 @Override public void keyPressed(KeyEvent e
) {
583 if (e
.getKeyCode() == KeyEvent
.VK_HOME
) {
584 mySelectionModel
.setSelectionInterval(0, 0);
586 else if (e
.getKeyCode() == KeyEvent
.VK_END
) {
587 int cellCount
= getCellCount();
588 mySelectionModel
.setSelectionInterval(cellCount
-1, cellCount
-1);
590 else if (e
.getKeyCode() == (myIsRow ? KeyEvent
.VK_UP
: KeyEvent
.VK_LEFT
)) {
591 moveSelection(e
, -1);
593 else if (e
.getKeyCode() == (myIsRow ? KeyEvent
.VK_DOWN
: KeyEvent
.VK_RIGHT
)) {
598 private void moveSelection(final KeyEvent e
, final int delta
) {
599 int leadIndex
= mySelectionModel
.getLeadSelectionIndex();
600 int newLeadIndex
= leadIndex
+ delta
;
601 if (newLeadIndex
>= 0 && newLeadIndex
< getCellCount()) {
602 if ((e
.getModifiers() & KeyEvent
.SHIFT_MASK
) != 0) {
603 mySelectionModel
.setSelectionInterval(mySelectionModel
.getAnchorSelectionIndex(), newLeadIndex
);
606 mySelectionModel
.setSelectionInterval(newLeadIndex
, newLeadIndex
);