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