1 // Scintilla source code edit control
2 /** @file CallTip.cxx
3 ** Code for displaying call tips.
4 **/
5 // Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7
8 #include <cstddef>
9 #include <cstdlib>
10 #include <cstring>
11 #include <cstdio>
12 #include <cmath>
13
14 #include <stdexcept>
15 #include <string>
16 #include <vector>
17 #include <algorithm>
18 #include <memory>
19
20 #include "Platform.h"
21
22 #include "Scintilla.h"
23
24 #include "StringCopy.h"
25 #include "Position.h"
26 #include "IntegerRectangle.h"
27 #include "CallTip.h"
28
29 using namespace Scintilla;
30
CallTip()31 CallTip::CallTip() {
32 wCallTip = 0;
33 inCallTipMode = false;
34 posStartCallTip = 0;
35 rectUp = PRectangle(0,0,0,0);
36 rectDown = PRectangle(0,0,0,0);
37 lineHeight = 1;
38 offsetMain = 0;
39 startHighlight = 0;
40 endHighlight = 0;
41 tabSize = 0;
42 above = false;
43 useStyleCallTip = false; // for backwards compatibility
44
45 insetX = 5;
46 widthArrow = 14;
47 borderHeight = 2; // Extra line for border and an empty line at top and bottom.
48 verticalOffset = 1;
49
50 #ifdef __APPLE__
51 // proper apple colours for the default
52 colourBG = ColourDesired(0xff, 0xff, 0xc6);
53 colourUnSel = ColourDesired(0, 0, 0);
54 #else
55 colourBG = ColourDesired(0xff, 0xff, 0xff);
56 colourUnSel = ColourDesired(0x80, 0x80, 0x80);
57 #endif
58 colourSel = ColourDesired(0, 0, 0x80);
59 colourShade = ColourDesired(0, 0, 0);
60 colourLight = ColourDesired(0xc0, 0xc0, 0xc0);
61 codePage = 0;
62 clickPlace = 0;
63 }
64
~CallTip()65 CallTip::~CallTip() {
66 font.Release();
67 wCallTip.Destroy();
68 }
69
70 // Although this test includes 0, we should never see a \0 character.
IsArrowCharacter(char ch)71 static bool IsArrowCharacter(char ch) {
72 return (ch == 0) || (ch == '\001') || (ch == '\002');
73 }
74
75 // We ignore tabs unless a tab width has been set.
IsTabCharacter(char ch) const76 bool CallTip::IsTabCharacter(char ch) const {
77 return (tabSize > 0) && (ch == '\t');
78 }
79
NextTabPos(int x) const80 int CallTip::NextTabPos(int x) const {
81 if (tabSize > 0) { // paranoia... not called unless this is true
82 x -= insetX; // position relative to text
83 x = (x + tabSize) / tabSize; // tab "number"
84 return tabSize*x + insetX; // position of next tab
85 } else {
86 return x + 1; // arbitrary
87 }
88 }
89
90 // Draw a section of the call tip that does not include \n in one colour.
91 // The text may include up to numEnds tabs or arrow characters.
DrawChunk(Surface * surface,int & x,const char * s,int posStart,int posEnd,int ytext,PRectangle rcClient,bool highlight,bool draw)92 void CallTip::DrawChunk(Surface *surface, int &x, const char *s,
93 int posStart, int posEnd, int ytext, PRectangle rcClient,
94 bool highlight, bool draw) {
95 s += posStart;
96 const int len = posEnd - posStart;
97
98 // Divide the text into sections that are all text, or that are
99 // single arrows or single tab characters (if tabSize > 0).
100 int maxEnd = 0;
101 const int numEnds = 10;
102 int ends[numEnds + 2];
103 for (int i=0; i<len; i++) {
104 if ((maxEnd < numEnds) &&
105 (IsArrowCharacter(s[i]) || IsTabCharacter(s[i]))) {
106 if (i > 0)
107 ends[maxEnd++] = i;
108 ends[maxEnd++] = i+1;
109 }
110 }
111 ends[maxEnd++] = len;
112 int startSeg = 0;
113 int xEnd;
114 for (int seg = 0; seg<maxEnd; seg++) {
115 const int endSeg = ends[seg];
116 if (endSeg > startSeg) {
117 if (IsArrowCharacter(s[startSeg])) {
118 xEnd = x + widthArrow;
119 const bool upArrow = s[startSeg] == '\001';
120 rcClient.left = static_cast<XYPOSITION>(x);
121 rcClient.right = static_cast<XYPOSITION>(xEnd);
122 if (draw) {
123 const int halfWidth = widthArrow / 2 - 3;
124 const int quarterWidth = halfWidth / 2;
125 const int centreX = x + widthArrow / 2 - 1;
126 const int centreY = static_cast<int>(rcClient.top + rcClient.bottom) / 2;
127 surface->FillRectangle(rcClient, colourBG);
128 const PRectangle rcClientInner(rcClient.left + 1, rcClient.top + 1,
129 rcClient.right - 2, rcClient.bottom - 1);
130 surface->FillRectangle(rcClientInner, colourUnSel);
131
132 if (upArrow) { // Up arrow
133 Point pts[] = {
134 Point::FromInts(centreX - halfWidth, centreY + quarterWidth),
135 Point::FromInts(centreX + halfWidth, centreY + quarterWidth),
136 Point::FromInts(centreX, centreY - halfWidth + quarterWidth),
137 };
138 surface->Polygon(pts, ELEMENTS(pts), colourBG, colourBG);
139 } else { // Down arrow
140 Point pts[] = {
141 Point::FromInts(centreX - halfWidth, centreY - quarterWidth),
142 Point::FromInts(centreX + halfWidth, centreY - quarterWidth),
143 Point::FromInts(centreX, centreY + halfWidth - quarterWidth),
144 };
145 surface->Polygon(pts, ELEMENTS(pts), colourBG, colourBG);
146 }
147 }
148 offsetMain = xEnd;
149 if (upArrow) {
150 rectUp = rcClient;
151 } else {
152 rectDown = rcClient;
153 }
154 } else if (IsTabCharacter(s[startSeg])) {
155 xEnd = NextTabPos(x);
156 } else {
157 xEnd = x + static_cast<int>(lround(surface->WidthText(font, s + startSeg, endSeg - startSeg)));
158 if (draw) {
159 rcClient.left = static_cast<XYPOSITION>(x);
160 rcClient.right = static_cast<XYPOSITION>(xEnd);
161 surface->DrawTextTransparent(rcClient, font, static_cast<XYPOSITION>(ytext),
162 s+startSeg, endSeg - startSeg,
163 highlight ? colourSel : colourUnSel);
164 }
165 }
166 x = xEnd;
167 startSeg = endSeg;
168 }
169 }
170 }
171
PaintContents(Surface * surfaceWindow,bool draw)172 int CallTip::PaintContents(Surface *surfaceWindow, bool draw) {
173 const PRectangle rcClientPos = wCallTip.GetClientPosition();
174 const PRectangle rcClientSize(0.0f, 0.0f, rcClientPos.right - rcClientPos.left,
175 rcClientPos.bottom - rcClientPos.top);
176 PRectangle rcClient(1.0f, 1.0f, rcClientSize.right - 1, rcClientSize.bottom - 1);
177
178 // To make a nice small call tip window, it is only sized to fit most normal characters without accents
179 const int ascent = static_cast<int>(lround(surfaceWindow->Ascent(font) - surfaceWindow->InternalLeading(font)));
180
181 // For each line...
182 // Draw the definition in three parts: before highlight, highlighted, after highlight
183 int ytext = static_cast<int>(rcClient.top) + ascent + 1;
184 rcClient.bottom = ytext + surfaceWindow->Descent(font) + 1;
185 const char *chunkVal = val.c_str();
186 bool moreChunks = true;
187 int maxWidth = 0;
188
189 while (moreChunks) {
190 const char *chunkEnd = strchr(chunkVal, '\n');
191 if (!chunkEnd) {
192 chunkEnd = chunkVal + strlen(chunkVal);
193 moreChunks = false;
194 }
195 const int chunkOffset = static_cast<int>(chunkVal - val.c_str());
196 const int chunkLength = static_cast<int>(chunkEnd - chunkVal);
197 const int chunkEndOffset = chunkOffset + chunkLength;
198 int thisStartHighlight = std::max(startHighlight, chunkOffset);
199 thisStartHighlight = std::min(thisStartHighlight, chunkEndOffset);
200 thisStartHighlight -= chunkOffset;
201 int thisEndHighlight = std::max(endHighlight, chunkOffset);
202 thisEndHighlight = std::min(thisEndHighlight, chunkEndOffset);
203 thisEndHighlight -= chunkOffset;
204 rcClient.top = static_cast<XYPOSITION>(ytext - ascent - 1);
205
206 int x = insetX; // start each line at this inset
207
208 DrawChunk(surfaceWindow, x, chunkVal, 0, thisStartHighlight,
209 ytext, rcClient, false, draw);
210 DrawChunk(surfaceWindow, x, chunkVal, thisStartHighlight, thisEndHighlight,
211 ytext, rcClient, true, draw);
212 DrawChunk(surfaceWindow, x, chunkVal, thisEndHighlight, chunkLength,
213 ytext, rcClient, false, draw);
214
215 chunkVal = chunkEnd + 1;
216 ytext += lineHeight;
217 rcClient.bottom += lineHeight;
218 maxWidth = std::max(maxWidth, x);
219 }
220 return maxWidth;
221 }
222
PaintCT(Surface * surfaceWindow)223 void CallTip::PaintCT(Surface *surfaceWindow) {
224 if (val.empty())
225 return;
226 const PRectangle rcClientPos = wCallTip.GetClientPosition();
227 const PRectangle rcClientSize(0.0f, 0.0f, rcClientPos.right - rcClientPos.left,
228 rcClientPos.bottom - rcClientPos.top);
229 const PRectangle rcClient(1.0f, 1.0f, rcClientSize.right - 1, rcClientSize.bottom - 1);
230
231 surfaceWindow->FillRectangle(rcClient, colourBG);
232
233 offsetMain = insetX; // initial alignment assuming no arrows
234 PaintContents(surfaceWindow, true);
235
236 #ifndef __APPLE__
237 // OSX doesn't put borders on "help tags"
238 // Draw a raised border around the edges of the window
239 const IntegerRectangle ircClientSize(rcClientSize);
240 surfaceWindow->MoveTo(0, ircClientSize.bottom - 1);
241 surfaceWindow->PenColour(colourShade);
242 surfaceWindow->LineTo(ircClientSize.right - 1, ircClientSize.bottom - 1);
243 surfaceWindow->LineTo(ircClientSize.right - 1, 0);
244 surfaceWindow->PenColour(colourLight);
245 surfaceWindow->LineTo(0, 0);
246 surfaceWindow->LineTo(0, ircClientSize.bottom - 1);
247 #endif
248 }
249
MouseClick(Point pt)250 void CallTip::MouseClick(Point pt) {
251 clickPlace = 0;
252 if (rectUp.Contains(pt))
253 clickPlace = 1;
254 if (rectDown.Contains(pt))
255 clickPlace = 2;
256 }
257
CallTipStart(Sci::Position pos,Point pt,int textHeight,const char * defn,const char * faceName,int size,int codePage_,int characterSet,int technology,const Window & wParent)258 PRectangle CallTip::CallTipStart(Sci::Position pos, Point pt, int textHeight, const char *defn,
259 const char *faceName, int size,
260 int codePage_, int characterSet,
261 int technology, const Window &wParent) {
262 clickPlace = 0;
263 val = defn;
264 codePage = codePage_;
265 std::unique_ptr<Surface> surfaceMeasure(Surface::Allocate(technology));
266 surfaceMeasure->Init(wParent.GetID());
267 surfaceMeasure->SetUnicodeMode(SC_CP_UTF8 == codePage);
268 surfaceMeasure->SetDBCSMode(codePage);
269 startHighlight = 0;
270 endHighlight = 0;
271 inCallTipMode = true;
272 posStartCallTip = pos;
273 const XYPOSITION deviceHeight = static_cast<XYPOSITION>(surfaceMeasure->DeviceHeightFont(size));
274 const FontParameters fp(faceName, deviceHeight / SC_FONT_SIZE_MULTIPLIER, SC_WEIGHT_NORMAL, false, 0, technology, characterSet);
275 font.Create(fp);
276 // Look for multiple lines in the text
277 // Only support \n here - simply means container must avoid \r!
278 const int numLines = 1 + static_cast<int>(std::count(val.begin(), val.end(), '\n'));
279 rectUp = PRectangle(0,0,0,0);
280 rectDown = PRectangle(0,0,0,0);
281 offsetMain = insetX; // changed to right edge of any arrows
282 const int width = PaintContents(surfaceMeasure.get(), false) + insetX;
283 lineHeight = static_cast<int>(lround(surfaceMeasure->Height(font)));
284
285 // The returned
286 // rectangle is aligned to the right edge of the last arrow encountered in
287 // the tip text, else to the tip text left edge.
288 const int height = lineHeight * numLines - static_cast<int>(surfaceMeasure->InternalLeading(font)) + borderHeight * 2;
289 if (above) {
290 return PRectangle(pt.x - offsetMain, pt.y - verticalOffset - height, pt.x + width - offsetMain, pt.y - verticalOffset);
291 } else {
292 return PRectangle(pt.x - offsetMain, pt.y + verticalOffset + textHeight, pt.x + width - offsetMain, pt.y + verticalOffset + textHeight + height);
293 }
294 }
295
CallTipCancel()296 void CallTip::CallTipCancel() {
297 inCallTipMode = false;
298 if (wCallTip.Created()) {
299 wCallTip.Destroy();
300 }
301 }
302
SetHighlight(int start,int end)303 void CallTip::SetHighlight(int start, int end) {
304 // Avoid flashing by checking something has really changed
305 if ((start != startHighlight) || (end != endHighlight)) {
306 startHighlight = start;
307 endHighlight = (end > start) ? end : start;
308 if (wCallTip.Created()) {
309 wCallTip.InvalidateAll();
310 }
311 }
312 }
313
314 // Set the tab size (sizes > 0 enable the use of tabs). This also enables the
315 // use of the STYLE_CALLTIP.
SetTabSize(int tabSz)316 void CallTip::SetTabSize(int tabSz) {
317 tabSize = tabSz;
318 useStyleCallTip = true;
319 }
320
321 // Set the calltip position, below the text by default or if above is false
322 // else above the text.
SetPosition(bool aboveText)323 void CallTip::SetPosition(bool aboveText) {
324 above = aboveText;
325 }
326
327 // It might be better to have two access functions for this and to use
328 // them for all settings of colours.
SetForeBack(const ColourDesired & fore,const ColourDesired & back)329 void CallTip::SetForeBack(const ColourDesired &fore, const ColourDesired &back) {
330 colourBG = back;
331 colourUnSel = fore;
332 }
333