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