explicit types to diamonds
[idea/community.git] / plugins / ui-designer / src / com / intellij / uiDesigner / designSurface / Painter.java
1 /*
2  * Copyright 2000-2012 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package com.intellij.uiDesigner.designSurface;
17
18 import com.intellij.ide.ui.UISettings;
19 import com.intellij.ui.JBColor;
20 import com.intellij.ui.LightColors;
21 import com.intellij.uiDesigner.FormEditingUtil;
22 import com.intellij.uiDesigner.SwingProperties;
23 import com.intellij.uiDesigner.core.GridLayoutManager;
24 import com.intellij.uiDesigner.lw.IProperty;
25 import com.intellij.uiDesigner.lw.StringDescriptor;
26 import com.intellij.uiDesigner.radComponents.*;
27 import com.intellij.uiDesigner.shared.BorderType;
28 import com.intellij.util.ui.PlatformColors;
29 import org.intellij.lang.annotations.JdkConstants;
30 import org.jetbrains.annotations.NotNull;
31
32 import javax.swing.*;
33 import java.awt.*;
34 import java.awt.geom.Rectangle2D;
35 import java.util.ArrayList;
36 import java.util.List;
37
38 /**
39  * @author Anton Katilin
40  * @author Vladimir Kondratyev
41  */
42 public final class Painter {
43   /**
44    * This color is used to paint decoration of non selected components
45    */
46   private static final Color NON_SELECTED_BOUNDARY_COLOR = new Color(114, 126, 143);
47   /**
48    * This color is used to paint decoration of selected components
49    */
50   private static final Color SELECTED_BOUNDARY_COLOR = new Color(8, 8, 108);
51
52   private static final Color HIGHLIGHTED_BOUNDARY_COLOR = Color.RED;
53
54   /**
55    * This color is used to paint grid cell for selected container
56    */
57   static final Color SELECTED_GRID_COLOR = new Color(47, 67, 96);
58   /**
59    * This color is used to paint grid cell for non selected container
60    */
61   static final Color NON_SELECTED_GRID_COLOR = new Color(130, 140, 155);
62
63   public final static int WEST_MASK = 1;
64   public final static int EAST_MASK = 2;
65   public final static int NORTH_MASK = 4;
66   public final static int SOUTH_MASK = 8;
67   private final static int R = 2;
68   private final static int GAP = R;
69   private static final int NW = 0;
70   private static final int N = 1;
71   private static final int NE = 2;
72   private static final int E = 3;
73   private static final int SE = 4;
74   private static final int S = 5;
75   private static final int SW = 6;
76   private static final int W = 7;
77
78   private Painter() {
79   }
80
81   public static void paintComponentDecoration(final GuiEditor editor, final RadComponent component, final Graphics g) {
82     // Collect selected components and paint decoration for non selected components
83     final ArrayList<RadComponent> selection = new ArrayList<>();
84     final Rectangle layeredPaneRect = editor.getLayeredPane().getVisibleRect();
85     FormEditingUtil.iterate(
86       component,
87       new FormEditingUtil.ComponentVisitor<RadComponent>() {
88         public boolean visit(final RadComponent component) {
89           if (!component.getDelegee().isShowing()) { // Skip invisible components
90             return true;
91           }
92           final Shape oldClip = g.getClip();
93           final RadContainer parent = component.getParent();
94           if (parent != null) {
95             final Point p = SwingUtilities.convertPoint(component.getDelegee(), 0, 0, editor.getLayeredPane());
96             final Rectangle visibleRect = layeredPaneRect.intersection(new Rectangle(p.x, p.y, parent.getWidth(), parent.getHeight()));
97             g.setClip(visibleRect);
98           }
99           if (component.isSelected()) { // we will paint selection later
100             selection.add(component);
101           }
102           else {
103             paintComponentBoundsImpl(editor, component, g);
104           }
105           paintGridOutline(editor, component, g);
106           if (parent != null) {
107             g.setClip(oldClip);
108           }
109           return true;
110         }
111       }
112     );
113
114     // Let's paint decoration for selected components
115     for (int i = selection.size() - 1; i >= 0; i--) {
116       final Shape oldClip = g.getClip();
117       final RadComponent c = selection.get(i);
118       final RadContainer parent = c.getParent();
119       if (parent != null) {
120         final Point p = SwingUtilities.convertPoint(c.getDelegee(), 0, 0, editor.getLayeredPane());
121         final Rectangle visibleRect = layeredPaneRect.intersection(new Rectangle(p.x, p.y, parent.getWidth(), parent.getHeight()));
122         g.setClip(visibleRect);
123       }
124       paintComponentBoundsImpl(editor, c, g);
125       if (parent != null) {
126         g.setClip(oldClip);
127       }
128     }
129   }
130
131   /**
132    * Paints container border. For grids the method also paints vertical and
133    * horizontal lines that indicate bounds of the rows and columns.
134    * Method does nothing if the <code>component</code> is not an instance
135    * of <code>RadContainer</code>.
136    */
137   private static void paintComponentBoundsImpl(final GuiEditor editor, @NotNull final RadComponent component, final Graphics g) {
138     if (!(component instanceof RadContainer) && !(component instanceof RadNestedForm) && !component.isDragBorder()) {
139       return;
140     }
141
142     boolean highlightBoundaries = (getDesignTimeInsets(component) > 2);
143
144     if (component instanceof RadContainer && !component.isDragBorder()) {
145       RadContainer container = (RadContainer)component;
146       if (!highlightBoundaries && (container.getBorderTitle() != null || container.getBorderType() != BorderType.NONE)) {
147         return;
148       }
149     }
150     final Point point = SwingUtilities.convertPoint(
151       component.getDelegee(),
152       0,
153       0,
154       editor.getRootContainer().getDelegee()
155     );
156     g.translate(point.x, point.y);
157     try {
158       if (component.isDragBorder()) {
159         Graphics2D g2d = (Graphics2D)g;
160         g2d.setColor(LightColors.YELLOW);
161         g2d.setStroke(new BasicStroke(2.0f));
162         g2d.translate(1, 1);
163       }
164       else if (highlightBoundaries) {
165         g.setColor(HIGHLIGHTED_BOUNDARY_COLOR);
166       }
167       else if (component.isSelected()) {
168         g.setColor(SELECTED_BOUNDARY_COLOR);
169       }
170       else {
171         g.setColor(NON_SELECTED_BOUNDARY_COLOR);
172       }
173       g.drawRect(0, 0, component.getWidth() - 1, component.getHeight() - 1);
174       if (component.isDragBorder()) {
175         g.translate(-1, -1);
176       }
177     }
178     finally {
179       g.translate(-point.x, -point.y);
180     }
181   }
182
183   private static int getDesignTimeInsets(RadComponent component) {
184     while (component != null) {
185       Integer designTimeInsets = (Integer)component.getDelegee().getClientProperty(GridLayoutManager.DESIGN_TIME_INSETS);
186       if (designTimeInsets != null) {
187         return designTimeInsets.intValue();
188       }
189       component = component.getParent();
190     }
191     return 0;
192   }
193
194   /**
195    * This method paints grid bounds for "grid" containers
196    */
197   public static void paintGridOutline(final GuiEditor editor, @NotNull final RadComponent component, final Graphics g) {
198     if (!editor.isShowGrid()) {
199       return;
200     }
201     if (!(component instanceof RadContainer)) {
202       return;
203     }
204     final RadContainer container = (RadContainer)component;
205     if (!container.getLayoutManager().isGrid()) {
206       return;
207     }
208
209     // performance: don't paint grid outline in drag layer
210     Container parent = component.getDelegee().getParent();
211     while (parent != null) {
212       if (parent == editor.getDragLayer()) {
213         return;
214       }
215       parent = parent.getParent();
216     }
217
218     final Point point = SwingUtilities.convertPoint(
219       component.getDelegee(),
220       0,
221       0,
222       editor.getRootContainer().getDelegee()
223     );
224     g.translate(point.x, point.y);
225     try {
226       // Paint grid
227       if (container.getWidth() > 0 && container.getHeight() > 0) {
228         Image gridImage = CachedGridImage.getGridImage(container);
229         g.drawImage(gridImage, 0, 0, null);
230       }
231     }
232     finally {
233       g.translate(-point.x, -point.y);
234     }
235   }
236
237   /**
238    * Paints selection for the specified <code>component</code>.
239    */
240   public static void paintSelectionDecoration(@NotNull RadComponent component, Graphics g,
241                                               boolean focused) {
242     if (component.isSelected()) {
243       if (focused) {
244         g.setColor(PlatformColors.BLUE);
245       }
246       else {
247         g.setColor(Color.GRAY);
248       }
249       final Point[] points = getPoints(component.getWidth(), component.getHeight());
250       for (final Point point : points) {
251         g.fillRect(point.x - R, point.y - R, 2 * R + 1, 2 * R + 1);
252       }
253     }
254     else if (component.getWidth() < FormEditingUtil.EMPTY_COMPONENT_SIZE || component.getHeight() < FormEditingUtil.EMPTY_COMPONENT_SIZE) {
255       Graphics2D g2d = (Graphics2D)g;
256       Composite oldComposite = g2d.getComposite();
257       Stroke oldStroke = g2d.getStroke();
258       Color oldColor = g2d.getColor();
259
260       g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.5f));
261       g2d.setStroke(new BasicStroke(0.7f));
262       g2d.setColor(Color.black);
263       g2d.drawRect(0, 0, Math.max(component.getWidth(), FormEditingUtil.EMPTY_COMPONENT_SIZE),
264                    Math.max(component.getHeight(), FormEditingUtil.EMPTY_COMPONENT_SIZE));
265
266       g2d.setComposite(oldComposite);
267       g2d.setStroke(oldStroke);
268       g2d.setColor(oldColor);
269     }
270   }
271
272   /**
273    * @param x in component's coord system
274    * @param y in component's coord system
275    */
276   public static int getResizeMask(@NotNull final RadComponent component, final int x, final int y) {
277     if (component.getParent() == null || !component.isSelected()) {
278       return 0;
279     }
280
281     // only components in XY can be resized...
282     /*
283     if (!component.getParent().isXY()) {
284       return 0;
285     }
286     */
287
288     final int width = component.getWidth();
289     final int height = component.getHeight();
290
291     final Point[] points = getPoints(width, height);
292
293     if (isInside(x, y, points[SE])) {
294       return EAST_MASK | SOUTH_MASK;
295     }
296     else if (isInside(x, y, points[NW])) {
297       return WEST_MASK | NORTH_MASK;
298     }
299     else if (isInside(x, y, points[N])) {
300       return NORTH_MASK;
301     }
302     else if (isInside(x, y, points[NE])) {
303       return EAST_MASK | NORTH_MASK;
304     }
305     else if (isInside(x, y, points[W])) {
306       return WEST_MASK;
307     }
308     else if (isInside(x, y, points[E])) {
309       return EAST_MASK;
310     }
311     else if (isInside(x, y, points[SW])) {
312       return WEST_MASK | SOUTH_MASK;
313     }
314     else if (isInside(x, y, points[S])) {
315       return SOUTH_MASK;
316     }
317     else {
318       return 0;
319     }
320   }
321
322   private static boolean isInside(final int x, final int y, final Point r) {
323     return x >= r.x - R && x <= r.x + R && y >= r.y - R && y <= r.y + R;
324   }
325
326   @JdkConstants.CursorType
327   public static int getResizeCursor(final int resizeMask) {
328     if (resizeMask == (WEST_MASK | NORTH_MASK)) {
329       return Cursor.NW_RESIZE_CURSOR;
330     }
331     else if (resizeMask == NORTH_MASK) {
332       return Cursor.N_RESIZE_CURSOR;
333     }
334     else if (resizeMask == (EAST_MASK | NORTH_MASK)) {
335       return Cursor.NE_RESIZE_CURSOR;
336     }
337     else if (resizeMask == WEST_MASK) {
338       return Cursor.W_RESIZE_CURSOR;
339     }
340     else if (resizeMask == EAST_MASK) {
341       return Cursor.E_RESIZE_CURSOR;
342     }
343     else if (resizeMask == (WEST_MASK | SOUTH_MASK)) {
344       return Cursor.SW_RESIZE_CURSOR;
345     }
346     else if (resizeMask == SOUTH_MASK) {
347       return Cursor.S_RESIZE_CURSOR;
348     }
349     else if (resizeMask == (EAST_MASK | SOUTH_MASK)) {
350       return Cursor.SE_RESIZE_CURSOR;
351     }
352     else {
353       throw new IllegalArgumentException("unknown resizeMask: " + resizeMask);
354     }
355   }
356
357   public static Point[] getPoints(final int width, final int height) {
358     final Point[] points = new Point[8];
359
360     points[NW] = new Point(GAP, GAP); // NW
361     points[N] = new Point(width / 2, GAP); // N
362     points[NE] = new Point(width - GAP - 1, GAP); // NE
363     points[E] = new Point(width - GAP - 1, height / 2); // E
364     points[SE] = new Point(width - GAP - 1, height - GAP - 1); // SE
365     points[S] = new Point(width / 2, height - GAP - 1); // S
366     points[SW] = new Point(GAP, height - GAP - 1); // SW
367     points[W] = new Point(GAP, height / 2); // W
368
369     return points;
370   }
371
372   public static void paintButtonGroupLines(RadRootContainer rootContainer, RadButtonGroup group, Graphics g) {
373     List<RadComponent> components = rootContainer.getGroupContents(group);
374     if (components.size() < 2) return;
375     Rectangle[] allBounds = new Rectangle[components.size()];
376     int lastTop = -1;
377     int minLeft = Integer.MAX_VALUE;
378     for (int i = 0; i < components.size(); i++) {
379       final Rectangle rc = SwingUtilities.convertRectangle(
380         components.get(i).getParent().getDelegee(),
381         components.get(i).getBounds(),
382         rootContainer.getDelegee()
383       );
384       allBounds[i] = rc;
385
386       minLeft = Math.min(minLeft, rc.x);
387       if (i == 0) {
388         lastTop = rc.y;
389       }
390       else if (lastTop != rc.y) {
391         lastTop = Integer.MIN_VALUE;
392       }
393     }
394
395     Graphics2D g2d = (Graphics2D)g;
396     Stroke oldStroke = g2d.getStroke();
397     g2d.setStroke(new BasicStroke(2.0f));
398     g2d.setColor(new Color(104, 107, 130));
399     if (lastTop != Integer.MIN_VALUE) {
400       // all items in group have same Y
401       int left = Integer.MAX_VALUE;
402       int right = Integer.MIN_VALUE;
403       for (Rectangle rc : allBounds) {
404         final int midX = (int)rc.getCenterX();
405         left = Math.min(left, midX);
406         right = Math.max(right, midX);
407         g2d.drawLine(midX, lastTop - 8, midX, lastTop);
408       }
409       g2d.drawLine(left, lastTop - 8, right, lastTop - 8);
410     }
411     else {
412       int top = Integer.MAX_VALUE;
413       int bottom = Integer.MIN_VALUE;
414       for (Rectangle rc : allBounds) {
415         final int midY = (int)rc.getCenterY();
416         top = Math.min(top, midY);
417         bottom = Math.max(bottom, midY);
418         g2d.drawLine(minLeft - 8, midY, rc.x, midY);
419       }
420       g2d.drawLine(minLeft - 8, top, minLeft - 8, bottom);
421     }
422     g2d.setStroke(oldStroke);
423   }
424
425   public static void paintComponentTag(final RadComponent component, final Graphics g) {
426     if (component instanceof RadContainer) return;
427     for (IProperty prop : component.getModifiedProperties()) {
428       if (prop.getName().equals(SwingProperties.TEXT)) {
429         final Object desc = prop.getPropertyValue(component);
430         if (!(desc instanceof StringDescriptor) || ((StringDescriptor)desc).getValue() == null ||
431             ((StringDescriptor)desc).getValue().length() > 0) {
432           return;
433         }
434       }
435       else if (prop.getName().equals(SwingProperties.MODEL)) {
436         // don't paint tags on non-empty lists
437         final Object value = prop.getPropertyValue(component);
438         if (value instanceof String[] && ((String[])value).length > 0) {
439           return;
440         }
441       }
442     }
443
444     Rectangle bounds = component.getDelegee().getBounds();
445     if (bounds.width > 100 && bounds.height > 40) {
446       StringBuilder tagBuilder = new StringBuilder();
447       if (component.getBinding() != null) {
448         tagBuilder.append(component.getBinding()).append(':');
449       }
450       String className = component.getComponentClassName();
451       int pos = className.lastIndexOf('.');
452       if (pos >= 0) {
453         tagBuilder.append(className.substring(pos + 1));
454       }
455       else {
456         tagBuilder.append(className);
457       }
458       final Rectangle2D stringBounds = g.getFontMetrics().getStringBounds(tagBuilder.toString(), g);
459       Graphics2D g2d = (Graphics2D)g;
460       g2d.setColor(PlatformColors.BLUE);
461       g2d.fillRect(0, 0, (int)stringBounds.getWidth(), (int)stringBounds.getHeight());
462       g2d.setColor(JBColor.WHITE);
463       UISettings.setupAntialiasing(g);
464       g.drawString(tagBuilder.toString(), 0, g.getFontMetrics().getAscent());
465     }
466   }
467 }