less garbage
[fedora-idea.git] / ui-designer / impl / com / intellij / uiDesigner / designSurface / GridCaptionPanel.java
blob21b9f9621be76ea284728ab30689a368cd7ba85c
1 /*
2 * Copyright (c) 2000-2006 JetBrains s.r.o. All Rights Reserved.
3 */
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;
25 import javax.swing.*;
26 import javax.swing.event.ChangeEvent;
27 import javax.swing.event.ChangeListener;
28 import javax.swing.event.ListSelectionEvent;
29 import javax.swing.event.ListSelectionListener;
30 import java.awt.*;
31 import java.awt.event.*;
32 import java.util.ArrayList;
34 /**
35 * @author yole
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) {
51 myEditor = editor;
52 myIsRow = isRow;
53 mySelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
54 mySelectionModel.addListSelectionListener(new ListSelectionListener() {
55 public void valueChanged(ListSelectionEvent e) {
56 repaint();
57 myEditor.fireSelectedComponentChanged();
59 });
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());
67 setFocusable(true);
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) {
74 repaint();
75 // ensure we don't have two repaints of properties panel - one from focus gain and another from click
76 myAlarm.addRequest(new Runnable() {
77 public void run() {
78 editor.fireSelectedComponentChanged();
80 }, 1000);
83 @Override public void focusLost(FocusEvent e) {
84 repaint();
86 });
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() {
98 return myIsRow;
101 @Override public Dimension getPreferredSize() {
102 return new Dimension(16, 16);
105 @Override
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) {
120 return;
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();
145 int deltaX = 0;
146 int deltaY = 0;
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;
153 else {
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);
161 if (myIsRow) {
162 g.drawLine(paintOffset+paintedSize, 0, paintOffset+paintedSize, bounds.height);
164 else {
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];
171 if (myIsRow) {
172 coord = SwingUtilities.convertPoint(container.getDelegee(), 0, coord, this).y;
174 else {
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);
180 if (myIsRow) {
181 g.drawLine(bounds.x+1, coord, bounds.x+bounds.width-1, coord);
183 else {
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) {
200 return Color.PINK;
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)) {
212 return container;
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()) {
223 return null;
225 return container;
228 public void selectedComponentChanged(GuiEditor source) {
229 checkSelectionChanged();
230 repaint();
233 private void checkSelectionChanged() {
234 RadContainer container = getSelectedGridContainer();
235 if (container != mySelectedContainer) {
236 mySelectedContainer = container;
237 mySelectionModel.clearSelection();
238 repaint();
242 @Nullable public Object getData(String dataId) {
243 if (dataId.equals(GuiEditor.class.getName())) {
244 return myEditor;
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) {
258 repaint();
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)) {
278 selection.add(i);
281 if (selection.size() == 0 && dragOrigin != null) {
282 int cell = getCellAt(dragOrigin);
283 if (cell >= 0) {
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();
291 return result;
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;
309 requestFocus();
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());
335 doResize(pnt);
336 myResizeLine = -1;
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();
356 if (group != null) {
357 final ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UNKNOWN, group);
358 popupMenu.getComponent().show(GridCaptionPanel.this, e.getX(), e.getY());
359 return true;
362 return false;
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) {
370 return;
372 int newSize = newCoord - prevCoord;
374 if (!myEditor.ensureEditable()) {
375 return;
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
390 if (gridLine <= 0) {
391 setCursor(Cursor.getDefaultCursor());
393 else if (myIsRow) {
394 setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
396 else {
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) {
409 return;
411 int newSize = newCoord - prevCoord;
413 String toolTip = mySelectedContainer.getGridLayoutManager().getCellResizeTooltip(mySelectedContainer, myIsRow, myResizeLine-1, newSize);
414 final ActiveDecorationLayer layer = myEditor.getActiveDecorationLayer();
415 Rectangle rc;
416 if (myIsRow) {
417 rc = new Rectangle(0, e.getPoint().y, layer.getSize().width, 1);
419 else {
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);
430 if (rc.width == 1) {
431 g.drawLine(rc.x, rc.y, rc.x, rc.y+rc.height);
433 else {
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()) {
449 return false;
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");
461 return false;
463 RadContainer container = getSelectedGridContainer();
464 if (container != null &&
465 container.getGridLayoutManager().getGridLineNear(mySelectedContainer, myIsRow, dragOrigin, 4) != -1) {
466 LOG.debug("canStartDragging(): have gridline near");
467 return false;
469 int[] selectedCells = getSelectedCells(dragOrigin);
470 for(int cell: selectedCells) {
471 if (!canDragCell(cell)) {
472 LOG.debug("canStartDragging(): cannot drag cell");
473 return false;
476 LOG.debug("canStartDragging(): starting drag");
477 return true;
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) {
484 return false;
487 return true;
490 public DnDDragStartBean startDragging(DnDAction action, Point dragOrigin) {
491 return new DnDDragStartBean(new MyDragBean(myIsRow, getSelectedCells(dragOrigin)));
494 @Nullable
495 public Pair<Image, Point> createDraggedImage(DnDAction action, Point dragOrigin) {
496 return null;
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) {
510 return false;
512 if (!(aEvent.getAttachedObject() instanceof MyDragBean)) {
513 return false;
515 MyDragBean bean = (MyDragBean) aEvent.getAttachedObject();
516 if (bean.isRow != myIsRow || bean.cells.length == 0) {
517 return false;
519 int gridLine = getDropGridLine(aEvent);
520 setDropInsertLine(gridLine);
521 aEvent.setDropPossible(gridLine >= 0, null);
522 if (gridLine >= 0) {
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);
530 else {
531 myEditor.getActiveDecorationLayer().removeFeedback();
533 return false;
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)) {
543 return;
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);
552 cleanUpOnLeave();
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;
566 repaint();
571 private static class MyDragBean {
572 public boolean isRow;
573 public int[] cells;
575 public MyDragBean(final boolean row, final int[] cells) {
576 isRow = row;
577 this.cells = 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)) {
594 moveSelection(e, 1);
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);
605 else {
606 mySelectionModel.setSelectionInterval(newLeadIndex, newLeadIndex);