replaced <code></code> with more concise {@code}
[idea/community.git] / platform / platform-impl / src / com / intellij / openapi / editor / impl / softwrap / CompositeSoftWrapPainter.java
1 /*
2  * Copyright 2000-2015 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.openapi.editor.impl.softwrap;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.editor.colors.EditorColors;
20 import com.intellij.openapi.editor.ex.EditorEx;
21 import com.intellij.openapi.editor.impl.ColorProvider;
22 import com.intellij.openapi.editor.impl.TextDrawingCallback;
23 import org.jetbrains.annotations.NotNull;
24 import org.jetbrains.annotations.Nullable;
25
26 import java.awt.*;
27 import java.util.*;
28 import java.util.List;
29
30 import static com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType.AFTER_SOFT_WRAP;
31 import static com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED;
32 import static java.util.Arrays.asList;
33
34 /**
35  * Encapsulates logic of wrapping multiple {@link SoftWrapPainter} implementations; chooses the one to use and delegates all
36  * processing to it.
37  * <p/>
38  * Not thread-safe.
39  *
40  * @author Denis Zhdanov
41  * @since Jul 2, 2010 10:20:14 AM
42  */
43 public class CompositeSoftWrapPainter implements SoftWrapPainter {
44
45   /**
46    * Defines a key to use for checking for code of the custom unicode symbol to use for {@code 'before soft wrap'} representation.
47    * <p/>
48    * Target value (if any) is assumed to be in hex format.
49    */
50   public static final String CUSTOM_BEFORE_SOFT_WRAP_SIGN_KEY = "idea.editor.wrap.soft.before.code";
51
52   /**
53    * Defines a key to use for checking for code of the custom unicode symbol to use for {@code 'after soft wrap'} representation.
54    * <p/>
55    * Target value (if any) is assumed to be in hex format.
56    */
57   public static final String CUSTOM_AFTER_SOFT_WRAP_SIGN_KEY = "idea.editor.wrap.soft.after.code";
58
59   private static final Logger LOG = Logger.getInstance(CompositeSoftWrapPainter.class);
60
61   private static final List<Map<SoftWrapDrawingType, Character>> SYMBOLS = new ArrayList<>();
62
63   static {
64     // Pickup custom soft wraps drawing symbols if both of the are defined.
65     Character customBeforeSymbol = parse(CUSTOM_BEFORE_SOFT_WRAP_SIGN_KEY);
66     if (customBeforeSymbol != null) {
67       Character customAfterSymbol = parse(CUSTOM_AFTER_SOFT_WRAP_SIGN_KEY);
68       if (customAfterSymbol != null) {
69         LOG.info(String.format("Picked up custom soft wrap drawing symbols: '%c' and '%c'", customBeforeSymbol, customAfterSymbol));
70         SYMBOLS.add(asMap(
71           asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP),
72           asList(customBeforeSymbol,         customAfterSymbol))
73         );
74       }
75     }
76
77     SYMBOLS.add(asMap(
78       asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP),
79       asList('\u2926', '\u2925'))
80     );
81     SYMBOLS.add(asMap(
82       asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP),
83       asList('\u21B2',                   '\u21B3'))
84     );
85     SYMBOLS.add(asMap(
86       asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP),
87       asList('\u2936',                   '\u2937'))
88     );
89     SYMBOLS.add(asMap(
90       asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP),
91       asList('\u21A9',                   '\u21AA'))
92     );
93     SYMBOLS.add(asMap(
94       asList(BEFORE_SOFT_WRAP_LINE_FEED, AFTER_SOFT_WRAP),
95       asList('\uE48B',                   '\uE48C'))
96     );
97   }
98
99   private final EditorEx        myEditor;
100   private       SoftWrapPainter myDelegate;
101
102   /**
103    * There is a possible case that particular symbols configured to be used as a soft wrap drawings are not supported by
104    * available fonts. We would like to try another symbols then.
105    * <p/>
106    * Current field points to the index of symbols collection from {@link #SYMBOLS} tried last time.
107    */
108   private int mySymbolsDrawingIndex = -1;
109
110   public CompositeSoftWrapPainter(EditorEx editor) {
111     myEditor = editor;
112   }
113
114   @Nullable
115   private static Character parse(String key) {
116     String value = System.getProperty(key);
117     if (value == null) {
118       return null;
119     }
120
121     value = value.trim();
122     if (value.isEmpty()) {
123       return null;
124     }
125
126     int code;
127     try {
128       code = Integer.parseInt(value, 16);
129     }
130     catch (NumberFormatException e) {
131       LOG.info(String.format("Detected invalid code for system property '%s' - '%s'. Expected to find hex number there. " +
132                                "Custom soft wraps signs will not be applied", key, value));
133       return null;
134     }
135
136     return (char)code;
137   }
138
139   @Override
140   public int paint(@NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight) {
141     initDelegateIfNecessary();
142     return myDelegate.paint(g, drawingType, x, y, lineHeight);
143   }
144
145   @Override
146   public int getDrawingHorizontalOffset(@NotNull Graphics g, @NotNull SoftWrapDrawingType drawingType, int x, int y, int lineHeight) {
147     initDelegateIfNecessary();
148     return myDelegate.getDrawingHorizontalOffset(g, drawingType, x, y, lineHeight);
149   }
150
151   @Override
152   public int getMinDrawingWidth(@NotNull SoftWrapDrawingType drawingType) {
153     initDelegateIfNecessary();
154     return myDelegate.getMinDrawingWidth(drawingType);
155   }
156
157   @Override
158   public boolean canUse() {
159     return true;
160   }
161
162   private void initDelegateIfNecessary() {
163     if (myDelegate != null && myDelegate.canUse()) {
164       return;
165     }
166     if (++mySymbolsDrawingIndex < SYMBOLS.size()) {
167       TextDrawingCallback callback = myEditor.getTextDrawingCallback();
168       ColorProvider colorHolder = ColorProvider.byColorScheme(myEditor, EditorColors.SOFT_WRAP_SIGN_COLOR);
169       myDelegate = new TextBasedSoftWrapPainter(SYMBOLS.get(mySymbolsDrawingIndex), myEditor, callback, colorHolder);
170       initDelegateIfNecessary();
171       return;
172     }
173     myDelegate = new ArrowSoftWrapPainter(myEditor);
174   }
175   
176   public void reinit() {
177     myDelegate = null;
178     mySymbolsDrawingIndex = -1;
179   }
180
181   private static <K, V> Map<K, V> asMap(Iterable<K> keys, Iterable<V> values) throws IllegalArgumentException {
182     Map<K, V> result = new HashMap<>();
183     Iterator<K> keyIterator = keys.iterator();
184     Iterator<V> valueIterator = values.iterator();
185     while (keyIterator.hasNext()) {
186       if (!valueIterator.hasNext()) {
187         throw new IllegalArgumentException(
188           String.format("Can't build for the given data. Reason: number of keys differs from number of values. "
189                         + "Keys: %s, values: %s", keys, values)
190         );
191       }
192       result.put(keyIterator.next(), valueIterator.next());
193     }
194
195     if (valueIterator.hasNext()) {
196       throw new IllegalArgumentException(
197         String.format("Can't build for the given data. Reason: number of keys differs from number of values. "
198                       + "Keys: %s, values: %s", keys, values)
199       );
200     }
201     return result;
202   }
203 }