You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
314 lines
10 KiB
314 lines
10 KiB
// Scintilla source code edit control |
|
/** @file CallTip.cxx |
|
** Code for displaying call tips. |
|
**/ |
|
// Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org> |
|
// The License.txt file describes the conditions under which this software may be distributed. |
|
|
|
#include <stdlib.h> |
|
#include <string.h> |
|
|
|
#include "Platform.h" |
|
|
|
#include "Scintilla.h" |
|
#include "CallTip.h" |
|
|
|
static const int insetX = 5; // text inset in x from calltip border |
|
static const int widthArrow = 14; |
|
|
|
CallTip::CallTip() { |
|
wCallTip = 0; |
|
inCallTipMode = false; |
|
posStartCallTip = 0; |
|
val = 0; |
|
rectUp = PRectangle(0,0,0,0); |
|
rectDown = PRectangle(0,0,0,0); |
|
lineHeight = 1; |
|
startHighlight = 0; |
|
endHighlight = 0; |
|
tabSize = 0; |
|
useStyleCallTip = false; // for backwards compatibility |
|
|
|
colourBG.desired = ColourDesired(0xff, 0xff, 0xff); |
|
colourUnSel.desired = ColourDesired(0x80, 0x80, 0x80); |
|
colourSel.desired = ColourDesired(0, 0, 0x80); |
|
colourShade.desired = ColourDesired(0, 0, 0); |
|
colourLight.desired = ColourDesired(0xc0, 0xc0, 0xc0); |
|
} |
|
|
|
CallTip::~CallTip() { |
|
font.Release(); |
|
wCallTip.Destroy(); |
|
delete []val; |
|
val = 0; |
|
} |
|
|
|
void CallTip::RefreshColourPalette(Palette &pal, bool want) { |
|
pal.WantFind(colourBG, want); |
|
pal.WantFind(colourUnSel, want); |
|
pal.WantFind(colourSel, want); |
|
pal.WantFind(colourShade, want); |
|
pal.WantFind(colourLight, want); |
|
} |
|
|
|
// Although this test includes 0, we should never see a \0 character. |
|
static bool IsArrowCharacter(char ch) { |
|
return (ch == 0) || (ch == '\001') || (ch == '\002'); |
|
} |
|
|
|
// We ignore tabs unless a tab width has been set. |
|
bool CallTip::IsTabCharacter(char ch) { |
|
return (tabSize > 0) && (ch == '\t'); |
|
} |
|
|
|
int CallTip::NextTabPos(int x) { |
|
if (tabSize > 0) { // paranoia... not called unless this is true |
|
x -= insetX; // position relative to text |
|
x = (x + tabSize) / tabSize; // tab "number" |
|
return tabSize*x + insetX; // position of next tab |
|
} else { |
|
return x + 1; // arbitrary |
|
} |
|
} |
|
|
|
// Draw a section of the call tip that does not include \n in one colour. |
|
// The text may include up to numEnds tabs or arrow characters. |
|
void CallTip::DrawChunk(Surface *surface, int &x, const char *s, |
|
int posStart, int posEnd, int ytext, PRectangle rcClient, |
|
bool highlight, bool draw) { |
|
s += posStart; |
|
int len = posEnd - posStart; |
|
|
|
// Divide the text into sections that are all text, or that are |
|
// single arrows or single tab characters (if tabSize > 0). |
|
int maxEnd = 0; |
|
const int numEnds = 10; |
|
int ends[numEnds + 2]; |
|
for (int i=0;i<len;i++) { |
|
if ((maxEnd < numEnds) && |
|
(IsArrowCharacter(s[i]) || IsTabCharacter(s[i])) ) { |
|
if (i > 0) |
|
ends[maxEnd++] = i; |
|
ends[maxEnd++] = i+1; |
|
} |
|
} |
|
ends[maxEnd++] = len; |
|
int startSeg = 0; |
|
int xEnd; |
|
for (int seg = 0; seg<maxEnd; seg++) { |
|
int endSeg = ends[seg]; |
|
if (endSeg > startSeg) { |
|
if (IsArrowCharacter(s[startSeg])) { |
|
bool upArrow = s[startSeg] == '\001'; |
|
rcClient.left = x; |
|
rcClient.right = rcClient.left + widthArrow; |
|
if (draw) { |
|
const int halfWidth = widthArrow / 2 - 3; |
|
const int centreX = rcClient.left + widthArrow / 2 - 1; |
|
const int centreY = (rcClient.top + rcClient.bottom) / 2; |
|
surface->FillRectangle(rcClient, colourBG.allocated); |
|
PRectangle rcClientInner(rcClient.left + 1, rcClient.top + 1, |
|
rcClient.right - 2, rcClient.bottom - 1); |
|
surface->FillRectangle(rcClientInner, colourUnSel.allocated); |
|
|
|
if (upArrow) { // Up arrow |
|
Point pts[] = { |
|
Point(centreX - halfWidth, centreY + halfWidth / 2), |
|
Point(centreX + halfWidth, centreY + halfWidth / 2), |
|
Point(centreX, centreY - halfWidth + halfWidth / 2), |
|
}; |
|
surface->Polygon(pts, sizeof(pts) / sizeof(pts[0]), |
|
colourBG.allocated, colourBG.allocated); |
|
} else { // Down arrow |
|
Point pts[] = { |
|
Point(centreX - halfWidth, centreY - halfWidth / 2), |
|
Point(centreX + halfWidth, centreY - halfWidth / 2), |
|
Point(centreX, centreY + halfWidth - halfWidth / 2), |
|
}; |
|
surface->Polygon(pts, sizeof(pts) / sizeof(pts[0]), |
|
colourBG.allocated, colourBG.allocated); |
|
} |
|
} |
|
xEnd = rcClient.right; |
|
offsetMain = xEnd; |
|
if (upArrow) { |
|
rectUp = rcClient; |
|
} else { |
|
rectDown = rcClient; |
|
} |
|
} else if (IsTabCharacter(s[startSeg])) { |
|
xEnd = NextTabPos(x); |
|
} else { |
|
xEnd = x + surface->WidthText(font, s + startSeg, endSeg - startSeg); |
|
if (draw) { |
|
rcClient.left = x; |
|
rcClient.right = xEnd; |
|
surface->DrawTextTransparent(rcClient, font, ytext, |
|
s+startSeg, endSeg - startSeg, |
|
highlight ? colourSel.allocated : colourUnSel.allocated); |
|
} |
|
} |
|
x = xEnd; |
|
startSeg = endSeg; |
|
} |
|
} |
|
} |
|
|
|
int CallTip::PaintContents(Surface *surfaceWindow, bool draw) { |
|
PRectangle rcClientPos = wCallTip.GetClientPosition(); |
|
PRectangle rcClientSize(0, 0, rcClientPos.right - rcClientPos.left, |
|
rcClientPos.bottom - rcClientPos.top); |
|
PRectangle rcClient(1, 1, rcClientSize.right - 1, rcClientSize.bottom - 1); |
|
|
|
// To make a nice small call tip window, it is only sized to fit most normal characters without accents |
|
int ascent = surfaceWindow->Ascent(font) - surfaceWindow->InternalLeading(font); |
|
|
|
// For each line... |
|
// Draw the definition in three parts: before highlight, highlighted, after highlight |
|
int ytext = rcClient.top + ascent + 1; |
|
rcClient.bottom = ytext + surfaceWindow->Descent(font) + 1; |
|
char *chunkVal = val; |
|
bool moreChunks = true; |
|
int maxWidth = 0; |
|
while (moreChunks) { |
|
char *chunkEnd = strchr(chunkVal, '\n'); |
|
if (chunkEnd == NULL) { |
|
chunkEnd = chunkVal + strlen(chunkVal); |
|
moreChunks = false; |
|
} |
|
int chunkOffset = chunkVal - val; |
|
int chunkLength = chunkEnd - chunkVal; |
|
int chunkEndOffset = chunkOffset + chunkLength; |
|
int thisStartHighlight = Platform::Maximum(startHighlight, chunkOffset); |
|
thisStartHighlight = Platform::Minimum(thisStartHighlight, chunkEndOffset); |
|
thisStartHighlight -= chunkOffset; |
|
int thisEndHighlight = Platform::Maximum(endHighlight, chunkOffset); |
|
thisEndHighlight = Platform::Minimum(thisEndHighlight, chunkEndOffset); |
|
thisEndHighlight -= chunkOffset; |
|
rcClient.top = ytext - ascent - 1; |
|
|
|
int x = insetX; // start each line at this inset |
|
|
|
DrawChunk(surfaceWindow, x, chunkVal, 0, thisStartHighlight, |
|
ytext, rcClient, false, draw); |
|
DrawChunk(surfaceWindow, x, chunkVal, thisStartHighlight, thisEndHighlight, |
|
ytext, rcClient, true, draw); |
|
DrawChunk(surfaceWindow, x, chunkVal, thisEndHighlight, chunkLength, |
|
ytext, rcClient, false, draw); |
|
|
|
chunkVal = chunkEnd + 1; |
|
ytext += lineHeight; |
|
rcClient.bottom += lineHeight; |
|
maxWidth = Platform::Maximum(maxWidth, x); |
|
} |
|
return maxWidth; |
|
} |
|
|
|
void CallTip::PaintCT(Surface *surfaceWindow) { |
|
if (!val) |
|
return; |
|
PRectangle rcClientPos = wCallTip.GetClientPosition(); |
|
PRectangle rcClientSize(0, 0, rcClientPos.right - rcClientPos.left, |
|
rcClientPos.bottom - rcClientPos.top); |
|
PRectangle rcClient(1, 1, rcClientSize.right - 1, rcClientSize.bottom - 1); |
|
|
|
surfaceWindow->FillRectangle(rcClient, colourBG.allocated); |
|
|
|
offsetMain = insetX; // initial alignment assuming no arrows |
|
PaintContents(surfaceWindow, true); |
|
|
|
// Draw a raised border around the edges of the window |
|
surfaceWindow->MoveTo(0, rcClientSize.bottom - 1); |
|
surfaceWindow->PenColour(colourShade.allocated); |
|
surfaceWindow->LineTo(rcClientSize.right - 1, rcClientSize.bottom - 1); |
|
surfaceWindow->LineTo(rcClientSize.right - 1, 0); |
|
surfaceWindow->PenColour(colourLight.allocated); |
|
surfaceWindow->LineTo(0, 0); |
|
surfaceWindow->LineTo(0, rcClientSize.bottom - 1); |
|
} |
|
|
|
void CallTip::MouseClick(Point pt) { |
|
clickPlace = 0; |
|
if (rectUp.Contains(pt)) |
|
clickPlace = 1; |
|
if (rectDown.Contains(pt)) |
|
clickPlace = 2; |
|
} |
|
|
|
PRectangle CallTip::CallTipStart(int pos, Point pt, const char *defn, |
|
const char *faceName, int size, |
|
int codePage_, int characterSet, Window &wParent) { |
|
clickPlace = 0; |
|
if (val) |
|
delete []val; |
|
val = new char[strlen(defn) + 1]; |
|
if (!val) |
|
return PRectangle(); |
|
strcpy(val, defn); |
|
codePage = codePage_; |
|
Surface *surfaceMeasure = Surface::Allocate(); |
|
if (!surfaceMeasure) |
|
return PRectangle(); |
|
surfaceMeasure->Init(wParent.GetID()); |
|
surfaceMeasure->SetUnicodeMode(SC_CP_UTF8 == codePage); |
|
surfaceMeasure->SetDBCSMode(codePage); |
|
startHighlight = 0; |
|
endHighlight = 0; |
|
inCallTipMode = true; |
|
posStartCallTip = pos; |
|
int deviceHeight = surfaceMeasure->DeviceHeightFont(size); |
|
font.Create(faceName, characterSet, deviceHeight, false, false); |
|
// Look for multiple lines in the text |
|
// Only support \n here - simply means container must avoid \r! |
|
int numLines = 1; |
|
const char *newline; |
|
const char *look = val; |
|
rectUp = PRectangle(0,0,0,0); |
|
rectDown = PRectangle(0,0,0,0); |
|
offsetMain = insetX; // changed to right edge of any arrows |
|
int width = PaintContents(surfaceMeasure, false) + insetX; |
|
while ((newline = strchr(look, '\n')) != NULL) { |
|
look = newline + 1; |
|
numLines++; |
|
} |
|
lineHeight = surfaceMeasure->Height(font); |
|
|
|
// Extra line for border and an empty line at top and bottom. The returned |
|
// rectangle is aligned to the right edge of the last arrow encountered in |
|
// the tip text, else to the tip text left edge. |
|
int height = lineHeight * numLines - surfaceMeasure->InternalLeading(font) + 2 + 2; |
|
delete surfaceMeasure; |
|
return PRectangle(pt.x - offsetMain, pt.y + 1, pt.x + width - offsetMain, pt.y + 1 + height); |
|
} |
|
|
|
void CallTip::CallTipCancel() { |
|
inCallTipMode = false; |
|
if (wCallTip.Created()) { |
|
wCallTip.Destroy(); |
|
} |
|
} |
|
|
|
void CallTip::SetHighlight(int start, int end) { |
|
// Avoid flashing by checking something has really changed |
|
if ((start != startHighlight) || (end != endHighlight)) { |
|
startHighlight = start; |
|
endHighlight = end; |
|
if (wCallTip.Created()) { |
|
wCallTip.InvalidateAll(); |
|
} |
|
} |
|
} |
|
|
|
// Set the tab size (sizes > 0 enable the use of tabs). This also enables the |
|
// use of the STYLE_CALLTIP. |
|
void CallTip::SetTabSize(int tabSz) { |
|
tabSize = tabSz; |
|
useStyleCallTip = true; |
|
} |
|
|
|
// It might be better to have two access functions for this and to use |
|
// them for all settings of colours. |
|
void CallTip::SetForeBack(const ColourPair &fore, const ColourPair &back) { |
|
colourBG = back; |
|
colourUnSel = fore; |
|
}
|
|
|