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