mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
Recent change replaced getRowCol in MacText::getMouseLink with getLineCharacter. This commits reverts it because getLineCharacter counts lines by paragraph not by rendered rows, which causes issues with clicking on links in help menu due to long text in said menu.
2169 lines
58 KiB
C++
2169 lines
58 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "common/file.h"
|
|
#include "common/timer.h"
|
|
#include "graphics/macgui/macwindowborder.h"
|
|
|
|
#include "graphics/macgui/mactext.h"
|
|
|
|
namespace Graphics {
|
|
|
|
enum {
|
|
kConScrollStep = 12,
|
|
|
|
kCursorMaxHeight = 100
|
|
};
|
|
|
|
static void cursorTimerHandler(void *refCon);
|
|
|
|
#define DEBUG 0
|
|
|
|
#if DEBUG
|
|
#define D(...) debug(__VA_ARGS__)
|
|
#else
|
|
#define D(...) ;
|
|
#endif
|
|
|
|
const Font *MacFontRun::getFont() {
|
|
if (font)
|
|
return font;
|
|
|
|
MacFont macFont = MacFont(fontId, fontSize, textSlant);
|
|
|
|
font = wm->_fontMan->getFont(macFont);
|
|
|
|
return font;
|
|
}
|
|
|
|
const Common::String MacFontRun::toString() {
|
|
return Common::String::format("\001\016%04x%02x%04x%04x%04x%04x", fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3);
|
|
}
|
|
|
|
bool MacFontRun::equals(MacFontRun &to) {
|
|
return (fontId == to.fontId && textSlant == to.textSlant
|
|
&& fontSize == to.fontSize && palinfo1 == to.palinfo1
|
|
&& palinfo2 == to.palinfo2 && palinfo3 == to.palinfo3);
|
|
}
|
|
|
|
Common::CodePage MacFontRun::getEncoding() {
|
|
if (wm->_mode & kWMModeWin95)
|
|
return Common::kUtf8;
|
|
return wm->_fontMan->getFontEncoding(fontId);
|
|
}
|
|
|
|
bool MacFontRun::plainByteMode() {
|
|
Common::CodePage encoding = getEncoding();
|
|
// This return statement accounts for utf8, invalid.
|
|
// For future Unicode font compatibility, it should account for all codepages instead.
|
|
return encoding != Common::kUtf8 && encoding != Common::kCodePageInvalid;
|
|
}
|
|
|
|
Common::String MacFontRun::getEncodedText() {
|
|
Common::CodePage encoding = getEncoding();
|
|
return Common::convertFromU32String(text, encoding);
|
|
}
|
|
|
|
uint MacTextLine::getChunkNum(int *col) {
|
|
if (!col)
|
|
return 0;
|
|
|
|
int pos = *col;
|
|
uint i;
|
|
|
|
if (chunks.empty()) {
|
|
*col = 0;
|
|
return 0;
|
|
}
|
|
|
|
for (i = 0; i < chunks.size(); i++) {
|
|
if (pos >= (int)chunks[i].text.size()) {
|
|
pos -= chunks[i].text.size();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (i && i == chunks.size()) {
|
|
i--; // touch the last chunk
|
|
pos = chunks[i].text.size();
|
|
}
|
|
|
|
if (pos < 0)
|
|
pos = 0;
|
|
*col = pos;
|
|
|
|
return i;
|
|
}
|
|
|
|
|
|
MacText::MacText(MacWidget *parent, int x, int y, int w, int h, MacWindowManager *wm, const Common::U32String &s, const MacFont *macFont, uint32 fgcolor, uint32 bgcolor, int maxWidth, TextAlign textAlignment, int interlinear, uint16 border, uint16 gutter, uint16 boxShadow, uint16 textShadow, bool fixedDims, bool scrollBar) :
|
|
MacWidget(parent, x, y, w, h, wm, true, border, gutter, boxShadow),
|
|
_macFont(macFont) {
|
|
|
|
D(6, "MacText::MacText(): fgcolor: %d, bgcolor: %d s: \"%s\"", fgcolor, bgcolor, Common::toPrintable(s.encode()).c_str());
|
|
|
|
_str = s;
|
|
_fullRefresh = true;
|
|
|
|
_fixedDims = fixedDims;
|
|
_scrollBorder.setWindowManager(wm);
|
|
setScrollBar(scrollBar);
|
|
_wm = wm;
|
|
_scrollBorder.setWindowManager(wm);
|
|
|
|
if (macFont) {
|
|
_defaultFormatting = MacFontRun(_wm);
|
|
_defaultFormatting.font = wm->_fontMan->getFont(*macFont);
|
|
byte r, g, b;
|
|
if (_wm->_pixelformat.bytesPerPixel == 4) {
|
|
_wm->decomposeColor<uint32>(fgcolor, r, g, b);
|
|
} else {
|
|
_wm->decomposeColor<byte>(fgcolor, r, g, b);
|
|
}
|
|
_defaultFormatting.setValues(_wm, macFont->getId(), macFont->getSlant(), macFont->getSize(), r, g, b);
|
|
} else {
|
|
_defaultFormatting.font = NULL;
|
|
}
|
|
|
|
init(fgcolor, bgcolor, maxWidth, textAlignment, interlinear, textShadow, true);
|
|
}
|
|
|
|
// NOTE: This constructor and the one afterward are for MacText engines that don't use widgets. This is the classic was MacText was constructed.
|
|
MacText::MacText(const Common::U32String &s, MacWindowManager *wm, const MacFont *macFont, uint32 fgcolor, uint32 bgcolor, int maxWidth, TextAlign textAlignment, int interlinear, bool fixedDims, bool scrollBar) :
|
|
MacWidget(nullptr, 0, 0, 0, 0, wm, false, 0, 0, 0),
|
|
_macFont(macFont) {
|
|
|
|
_str = s;
|
|
|
|
_fixedDims = fixedDims;
|
|
_scrollBorder.setWindowManager(wm);
|
|
setScrollBar(scrollBar);
|
|
_wm = wm;
|
|
_scrollBorder.setWindowManager(wm);
|
|
|
|
if (macFont) {
|
|
_defaultFormatting = MacFontRun(_wm, macFont->getId(), macFont->getSlant(), macFont->getSize(), 0, 0, 0);
|
|
_defaultFormatting.font = wm->_fontMan->getFont(*macFont);
|
|
byte r, g, b;
|
|
_wm->_pixelformat.colorToRGB(fgcolor, r, g, b);
|
|
_defaultFormatting.setValues(_wm, macFont->getId(), macFont->getSlant(), macFont->getSize(), r, g, b);
|
|
} else {
|
|
_defaultFormatting.font = NULL;
|
|
}
|
|
|
|
init(fgcolor, bgcolor, maxWidth, textAlignment, interlinear, 0, true);
|
|
}
|
|
|
|
// Working with plain Font
|
|
MacText::MacText(const Common::U32String &s, MacWindowManager *wm, const Font *font, uint32 fgcolor, uint32 bgcolor, int maxWidth, TextAlign textAlignment, int interlinear, bool fixedDims, bool scrollBar) :
|
|
MacWidget(nullptr, 0, 0, 0, 0, wm, false, 0, 0, 0),
|
|
_macFont(nullptr) {
|
|
|
|
_str = s;
|
|
|
|
_fixedDims = fixedDims;
|
|
_scrollBorder.setWindowManager(wm);
|
|
setScrollBar(scrollBar);
|
|
_wm = wm;
|
|
|
|
if (font) {
|
|
_defaultFormatting = MacFontRun(_wm, font, 0, font->getFontHeight(), 0, 0, 0);
|
|
_defaultFormatting.font = font;
|
|
} else {
|
|
_defaultFormatting.font = NULL;
|
|
}
|
|
|
|
init(fgcolor, bgcolor, maxWidth, textAlignment, interlinear, 0, false);
|
|
}
|
|
|
|
void MacText::init(uint32 fgcolor, uint32 bgcolor, int maxWidth, TextAlign textAlignment, int interlinear, uint16 textShadow, bool macFontMode) {
|
|
_fullRefresh = true;
|
|
|
|
_canvas._maxWidth = maxWidth;
|
|
_canvas._textAlignment = textAlignment;
|
|
_canvas._textShadow = textShadow;
|
|
_canvas._interLinear = interlinear;
|
|
_canvas._wm = _wm;
|
|
_canvas._tfgcolor = fgcolor;
|
|
_canvas._tbgcolor = bgcolor;
|
|
_canvas._macFontMode = macFontMode;
|
|
_canvas._macText = this;
|
|
_canvas._textMaxWidth = 0;
|
|
_canvas._textMaxHeight = 0;
|
|
_canvas._surface = nullptr;
|
|
_canvas._shadowSurface = nullptr;
|
|
|
|
if (!_fixedDims) {
|
|
int right = _dims.right;
|
|
_dims.right = MAX<int>(_dims.right, _dims.left + _canvas._maxWidth + (2 * _border) + (2 * _gutter) + _shadow);
|
|
if (right != _dims.right) {
|
|
delete _composeSurface;
|
|
_composeSurface = new ManagedSurface(_dims.width(), _dims.height(), _wm->_pixelformat);
|
|
}
|
|
}
|
|
|
|
_selEnd = -1;
|
|
_selStart = -1;
|
|
|
|
_defaultFormatting.wm = _wm;
|
|
|
|
_canvas.splitString(_str, -1, _defaultFormatting);
|
|
recalcDims();
|
|
|
|
_fullRefresh = true;
|
|
_inTextSelection = false;
|
|
|
|
_scrollPos = 0;
|
|
_editable = false;
|
|
_selectable = false;
|
|
|
|
_editableRow = 0;
|
|
|
|
_menu = nullptr;
|
|
|
|
_cursorX = 0;
|
|
_cursorY = 0;
|
|
_cursorState = false;
|
|
_cursorOff = false;
|
|
|
|
_cursorRow = getLineCount() - 1;
|
|
_cursorCol = _canvas.getLineCharWidth(_cursorRow);
|
|
|
|
_cursorRect = new Common::Rect(0, 0, 1, 1);
|
|
|
|
// currently, we are not using fg color to render text. And we are not passing fg color correctly, thus we read it our self.
|
|
MacFontRun colorFontRun = getFgColor();
|
|
if (!colorFontRun.text.empty()) {
|
|
_canvas._tfgcolor = colorFontRun.fgcolor;
|
|
colorFontRun.text.clear();
|
|
debug(9, "Reading fg color though text, instead of the argument, read %d", _canvas._tfgcolor);
|
|
_defaultFormatting = colorFontRun;
|
|
_defaultFormatting.wm = _wm;
|
|
}
|
|
|
|
_currentFormatting = _defaultFormatting;
|
|
_composeSurface->clear(_canvas._tbgcolor);
|
|
|
|
_cursorSurface = new ManagedSurface(1, kCursorMaxHeight, _wm->_pixelformat);
|
|
_cursorSurface->clear(_canvas._tfgcolor);
|
|
_cursorSurface2 = new ManagedSurface(1, kCursorMaxHeight, _wm->_pixelformat);
|
|
_cursorSurface2->clear(_canvas._tbgcolor);
|
|
|
|
_canvas.reallocSurface();
|
|
setAlignOffset(_canvas._textAlignment);
|
|
updateCursorPos();
|
|
render();
|
|
}
|
|
|
|
MacText::~MacText() {
|
|
if (_wm->getActiveWidget() == this)
|
|
_wm->setActiveWidget(nullptr);
|
|
|
|
_borderSurface.free();
|
|
|
|
delete _cursorRect;
|
|
delete _cursorSurface;
|
|
delete _cursorSurface2;
|
|
}
|
|
|
|
WindowClick MacText::isInScrollBar(int x, int y) const {
|
|
int bTop = kBorderWidth;
|
|
int bRight = kBorderWidth;
|
|
int bBottom = kBorderWidth;
|
|
if (_scrollBorder.hasOffsets()) {
|
|
bTop = _scrollBorder.getOffset().top;
|
|
bRight = _scrollBorder.getOffset().right;
|
|
bBottom = _scrollBorder.getOffset().bottom;
|
|
}
|
|
|
|
if (x >= _dims.right - bRight && x < _dims.right) {
|
|
if (y < _dims.top + bTop)
|
|
return kBorderBorder;
|
|
|
|
if (y >= _dims.bottom - bBottom)
|
|
return kBorderBorder;
|
|
|
|
if (y >= _dims.top + _dims.height() / 2)
|
|
return kBorderScrollDown;
|
|
|
|
return kBorderScrollUp;
|
|
}
|
|
|
|
return kBorderBorder;
|
|
}
|
|
void MacText::setScrollBar(bool enable) {
|
|
if (enable && _fixedDims) {
|
|
_scrollBorder.setBorderType(kWindowBorderMacOSNoBorderScrollbar);
|
|
_scrollBorder.loadInternalBorder(kWindowBorderScrollbar | kWindowBorderActive);
|
|
resizeScrollBar(_dims.width(), _dims.height());
|
|
} else {
|
|
_scrollBorder.disableBorder();
|
|
}
|
|
}
|
|
|
|
void MacText::resizeScrollBar(int w, int h) {
|
|
_borderSurface.free();
|
|
_borderSurface.create(w, h, _wm->_pixelformat);
|
|
if (_wm->_pixelformat.bytesPerPixel == 1) {
|
|
_borderSurface.clear(_wm->_colorGreen);
|
|
}
|
|
_scrollBorder.blitBorderInto(_borderSurface, kWindowBorderScrollbar | kWindowBorderActive);
|
|
}
|
|
|
|
// this func returns the fg color of the first character we met in text
|
|
MacFontRun MacText::getFgColor() {
|
|
if (_canvas._text.empty())
|
|
return MacFontRun();
|
|
for (uint i = 0; i < _canvas._text.size(); i++) {
|
|
for (uint j = 0; j < _canvas._text[i].chunks.size(); j++) {
|
|
if (!_canvas._text[i].chunks[j].text.empty())
|
|
return _canvas._text[i].chunks[j];
|
|
}
|
|
}
|
|
return MacFontRun();
|
|
}
|
|
|
|
// we are doing this because we may need to dealing with the plain byte. See ctor of mactext which contains String str instead of U32String str
|
|
// thus, if we are passing the str, meaning we are using plainByteMode. And when we calculate the string width. we need to convert it to it's original state first;
|
|
int getStringWidth(MacFontRun &format, const Common::U32String &str) {
|
|
if (format.plainByteMode())
|
|
return format.getFont()->getStringWidth(Common::convertFromU32String(str, format.getEncoding()));
|
|
else
|
|
return format.getFont()->getStringWidth(str);
|
|
}
|
|
|
|
void MacText::setMaxWidth(int maxWidth) {
|
|
if (maxWidth == _canvas._maxWidth)
|
|
return;
|
|
|
|
if (maxWidth < 0) {
|
|
warning("trying to set maxWidth to %d", maxWidth);
|
|
return;
|
|
}
|
|
|
|
for (uint i = 0; i < _canvas._text.size(); i++) {
|
|
if (_canvas._text[i].table) {
|
|
// TODO
|
|
debug(0, "MacText::setMaxWidth(): Skipping resize for MacText with tables");
|
|
return;
|
|
}
|
|
}
|
|
|
|
int absoluteCharOffset = 0;
|
|
for (int i = 0; i < _cursorRow; ++i)
|
|
absoluteCharOffset += _canvas.getLineCharWidth(i);
|
|
absoluteCharOffset += _cursorCol;
|
|
_canvas.setMaxWidth(maxWidth, _defaultFormatting);
|
|
|
|
// restore the cursor pos
|
|
_cursorRow = 0;
|
|
while (_cursorRow < (int)_canvas._text.size() - 1) {
|
|
int lineWidth = _canvas.getLineCharWidth(_cursorRow, true);
|
|
if (absoluteCharOffset <= lineWidth)
|
|
break;
|
|
|
|
absoluteCharOffset -= lineWidth;
|
|
++_cursorRow;
|
|
}
|
|
|
|
int ppos = 0;
|
|
if (absoluteCharOffset > _canvas.getLineCharWidth(_cursorRow, true))
|
|
ppos = _canvas.getLineCharWidth(_cursorRow, true);
|
|
else
|
|
ppos = absoluteCharOffset;
|
|
|
|
_cursorCol = ppos;
|
|
|
|
// after we set maxWidth, we reset the selection
|
|
_selectedText.endY = -1;
|
|
|
|
recalcDims();
|
|
updateCursorPos();
|
|
|
|
_fullRefresh = true;
|
|
_contentIsDirty = true;
|
|
}
|
|
|
|
void MacText::setColors(uint32 fg, uint32 bg) {
|
|
_canvas._tbgcolor = bg;
|
|
_canvas._tfgcolor = fg;
|
|
// also set the cursor color
|
|
_cursorSurface->clear(_canvas._tfgcolor);
|
|
for (uint i = 0; i < _canvas._text.size(); i++)
|
|
setTextColor(fg, i);
|
|
|
|
_fullRefresh = true;
|
|
render();
|
|
_contentIsDirty = true;
|
|
}
|
|
|
|
void MacText::enforceTextFont(uint16 fontId) {
|
|
for (uint i = 0; i < _canvas._text.size(); i++) {
|
|
for (uint j = 0; j < _canvas._text[i].chunks.size(); j++) {
|
|
_canvas._text[i].chunks[j].fontId = fontId;
|
|
}
|
|
}
|
|
|
|
_fullRefresh = true;
|
|
render();
|
|
_contentIsDirty = true;
|
|
}
|
|
|
|
void MacText::setTextSize(int textSize) {
|
|
for (uint i = 0; i < _canvas._text.size(); i++) {
|
|
for (uint j = 0; j < _canvas._text[i].chunks.size(); j++) {
|
|
_canvas._text[i].chunks[j].fontSize = textSize;
|
|
}
|
|
}
|
|
|
|
_fullRefresh = true;
|
|
render();
|
|
_contentIsDirty = true;
|
|
}
|
|
|
|
void MacText::setTextColor(uint32 color, uint32 line) {
|
|
if (line >= _canvas._text.size()) {
|
|
warning("MacText::setTextColor(): line %d is out of bounds", line);
|
|
return;
|
|
}
|
|
|
|
uint32 fgcol = _wm->findBestColor(color);
|
|
for (uint j = 0; j < _canvas._text[line].chunks.size(); j++) {
|
|
_canvas._text[line].chunks[j].fgcolor = fgcol;
|
|
}
|
|
|
|
// if we are calling this func separately, then here need a refresh
|
|
}
|
|
|
|
void MacText::getChunkPosFromIndex(int index, uint &lineNum, uint &chunkNum, uint &offset) {
|
|
if (_canvas._text.empty()) {
|
|
lineNum = chunkNum = offset = 0;
|
|
return;
|
|
}
|
|
for (uint i = 0; i < _canvas._text.size(); i++) {
|
|
if (_canvas.getLineCharWidth(i) <= index) {
|
|
index -= _canvas.getLineCharWidth(i);
|
|
} else {
|
|
lineNum = i;
|
|
chunkNum = _canvas._text[i].getChunkNum(&index);
|
|
offset = index;
|
|
return;
|
|
}
|
|
}
|
|
lineNum = _canvas._text.size() - 1;
|
|
chunkNum = _canvas._text[lineNum].chunks.size() - 1;
|
|
offset = 0;
|
|
}
|
|
|
|
void setTextColorCallback(MacFontRun &macFontRun, int color) {
|
|
macFontRun.fgcolor = color;
|
|
}
|
|
|
|
void MacText::setTextColor(uint32 color, uint32 start, uint32 end) {
|
|
uint32 col = _wm->findBestColor(color);
|
|
setTextChunks(start, end, col, setTextColorCallback);
|
|
}
|
|
|
|
void setTextSizeCallback(MacFontRun &macFontRun, int textSize) {
|
|
macFontRun.fontSize = textSize;
|
|
}
|
|
|
|
void MacText::setTextSize(int textSize, int start, int end) {
|
|
setTextChunks(start, end, textSize, setTextSizeCallback);
|
|
}
|
|
|
|
void MacText::setTextChunks(int start, int end, int param, void (*callback)(MacFontRun &, int)) {
|
|
if (_canvas._text.empty())
|
|
return;
|
|
if (start > end)
|
|
SWAP(start, end);
|
|
|
|
uint startRow, startCol;
|
|
uint endRow, endCol;
|
|
uint offset;
|
|
|
|
getChunkPosFromIndex(start, startRow, startCol, offset);
|
|
// if offset != 0, then we need to split the chunk
|
|
if (offset != 0) {
|
|
uint textSize = _canvas._text[startRow].chunks[startCol].text.size();
|
|
MacFontRun newChunk = _canvas._text[startRow].chunks[startCol];
|
|
newChunk.text = newChunk.text.substr(offset, textSize - offset);
|
|
_canvas._text[startRow].chunks[startCol].text = _canvas._text[startRow].chunks[startCol].text.substr(0, offset);
|
|
_canvas._text[startRow].chunks.insert_at(startCol + 1, newChunk);
|
|
startCol++;
|
|
}
|
|
|
|
getChunkPosFromIndex(end, endRow, endCol, offset);
|
|
if (offset != 0) {
|
|
uint textSize = _canvas._text[endRow].chunks[endCol].text.size();
|
|
MacFontRun newChunk = _canvas._text[endRow].chunks[endCol];
|
|
newChunk.text = newChunk.text.substr(offset, textSize - offset);
|
|
_canvas._text[endRow].chunks[endCol].text = _canvas._text[endRow].chunks[endCol].text.substr(0, offset);
|
|
_canvas._text[endRow].chunks.insert_at(endCol + 1, newChunk);
|
|
endCol++;
|
|
}
|
|
|
|
for (uint i = startRow; i <= endRow; i++) {
|
|
uint from, to;
|
|
if (i == startRow && i == endRow) {
|
|
from = startCol;
|
|
to = endCol;
|
|
} else if (i == startRow) {
|
|
from = startCol;
|
|
to = _canvas._text[startRow].chunks.size();
|
|
} else if (i == endRow) {
|
|
from = 0;
|
|
to = endCol;
|
|
} else {
|
|
from = 0;
|
|
to = _canvas._text[i].chunks.size();
|
|
}
|
|
for (uint j = from; j < to; j++) {
|
|
callback(_canvas._text[i].chunks[j], param);
|
|
}
|
|
}
|
|
|
|
_fullRefresh = true;
|
|
render();
|
|
_contentIsDirty = true;
|
|
}
|
|
|
|
void setTextFontCallback(MacFontRun &macFontRun, int fontId) {
|
|
macFontRun.fontId = fontId;
|
|
}
|
|
|
|
void MacText::setTextFont(int fontId, int start, int end) {
|
|
setTextChunks(start, end, fontId, setTextFontCallback);
|
|
}
|
|
|
|
void setTextSlantCallback(MacFontRun &macFontRun, int textSlant) {
|
|
macFontRun.textSlant = textSlant;
|
|
}
|
|
|
|
void MacText::setTextSlant(int textSlant, int start, int end) {
|
|
setTextChunks(start, end, textSlant, setTextSlantCallback);
|
|
}
|
|
|
|
void MacText::enforceTextSlant(int textSlant) {
|
|
for (uint i = 0; i < _canvas._text.size(); i++) {
|
|
for (uint j = 0; j < _canvas._text[i].chunks.size(); j++) {
|
|
_canvas._text[i].chunks[j].textSlant = textSlant;
|
|
}
|
|
}
|
|
|
|
_fullRefresh = true;
|
|
render();
|
|
_contentIsDirty = true;
|
|
}
|
|
|
|
// Return the number of rows of text in the rendered output.
|
|
// This means e.g. a line of text that wraps will count as 2 or more rows.
|
|
int MacText::getRowCount() {
|
|
return (int)_canvas._text.size();
|
|
}
|
|
|
|
// this maybe need to amend
|
|
// currently, we just return the text size of first character.
|
|
int MacText::getTextSize(int start, int end) {
|
|
return getTextChunks(start, end).fontSize;
|
|
}
|
|
|
|
uint32 MacText::getTextColor(int start, int end) {
|
|
return getTextChunks(start, end).fgcolor;
|
|
}
|
|
|
|
int MacText::getTextFont(int start, int end) {
|
|
return getTextChunks(start, end).fontId;
|
|
}
|
|
|
|
int MacText::getTextSlant(int start, int end) {
|
|
return getTextChunks(start, end).textSlant;
|
|
}
|
|
|
|
// only getting the first chunk for the selected area
|
|
MacFontRun MacText::getTextChunks(int start, int end) {
|
|
if (_canvas._text.empty())
|
|
return _defaultFormatting;
|
|
if (start > end)
|
|
SWAP(start, end);
|
|
|
|
uint startRow, startCol;
|
|
uint offset;
|
|
|
|
getChunkPosFromIndex(start, startRow, startCol, offset);
|
|
return _canvas._text[startRow].chunks[startCol];
|
|
}
|
|
|
|
void MacText::setDefaultFormatting(uint16 fontId, byte textSlant, uint16 fontSize,
|
|
uint16 palinfo1, uint16 palinfo2, uint16 palinfo3) {
|
|
_defaultFormatting.setValues(_defaultFormatting.wm, fontId, textSlant, fontSize, palinfo1, palinfo2, palinfo3);
|
|
|
|
MacFont macFont = MacFont(fontId, fontSize, textSlant);
|
|
|
|
_defaultFormatting.font = _wm->_fontMan->getFont(macFont);
|
|
}
|
|
|
|
void MacText::render() {
|
|
if (_fullRefresh) {
|
|
_canvas._surface->clear(_canvas._tbgcolor);
|
|
if (_canvas._textShadow)
|
|
_canvas._shadowSurface->clear(_canvas._tbgcolor);
|
|
|
|
_canvas.render(0, _canvas._text.size());
|
|
|
|
_fullRefresh = false;
|
|
|
|
#if 0
|
|
Common::DumpFile out;
|
|
Common::String filename = Common::String::format("z-%p.png", (void *)this);
|
|
if (out.open(filename)) {
|
|
warning("Wrote: %s", filename.c_str());
|
|
Image::writePNG(out, _canvas._surface->rawSurface());
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
int MacText::getLastLineWidth() {
|
|
if (_canvas._text.size() == 0)
|
|
return 0;
|
|
|
|
return _canvas.getLineWidth(_canvas._text.size() - 1, true);
|
|
}
|
|
|
|
int MacText::getLineHeight(int line) {
|
|
return _canvas.getLineHeight(line);
|
|
}
|
|
|
|
void MacText::setInterLinear(int interLinear) {
|
|
_canvas._interLinear = interLinear;
|
|
|
|
recalcDims();
|
|
_fullRefresh = true;
|
|
render();
|
|
_contentIsDirty = true;
|
|
}
|
|
|
|
void MacText::recalcDims() {
|
|
_canvas.recalcDims();
|
|
|
|
if (!_fixedDims) {
|
|
int newBottom = _dims.top + _canvas._textMaxHeight + (2 * _border) + _gutter + _shadow;
|
|
if (newBottom > _dims.bottom) {
|
|
_dims.bottom = newBottom;
|
|
delete _composeSurface;
|
|
_composeSurface = new ManagedSurface(_dims.width(), _dims.height(), _wm->_pixelformat);
|
|
_canvas.reallocSurface();
|
|
if (!_fullRefresh) {
|
|
_fullRefresh = true;
|
|
render();
|
|
}
|
|
_fullRefresh = true;
|
|
_contentIsDirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MacText::setAlignOffset(TextAlign align) {
|
|
if (_canvas._textAlignment == align)
|
|
return;
|
|
|
|
_contentIsDirty = true;
|
|
_fullRefresh = true;
|
|
_canvas._textAlignment = align;
|
|
}
|
|
|
|
Common::Point MacText::calculateOffset() {
|
|
return Common::Point(_border + _gutter, _border + _gutter / 2);
|
|
}
|
|
|
|
void MacText::setSelRange(int selStart, int selEnd) {
|
|
if (selStart == _selStart && selEnd == _selEnd)
|
|
return;
|
|
_selStart = selStart;
|
|
_selEnd = selEnd;
|
|
}
|
|
|
|
void MacText::setActive(bool active) {
|
|
if (_active == active)
|
|
return;
|
|
|
|
MacWidget::setActive(active);
|
|
|
|
g_system->getTimerManager()->removeTimerProc(&cursorTimerHandler);
|
|
if (_active) {
|
|
g_system->getTimerManager()->installTimerProc(&cursorTimerHandler, 200000, this, "macEditableText");
|
|
// inactive -> active, we reset the selection
|
|
setSelection(_selStart, true);
|
|
setSelection(_selEnd, false);
|
|
} else {
|
|
// clear the selection and cursor
|
|
_selectedText.endY = -1;
|
|
_cursorState = false;
|
|
_inTextSelection = false;
|
|
}
|
|
|
|
// after we change the status of active, we need to do a refresh to clear the stuff we don't need
|
|
_contentIsDirty = true;
|
|
|
|
if (!_cursorOff && _cursorState == true)
|
|
undrawCursor();
|
|
}
|
|
|
|
void MacText::setEditable(bool editable) {
|
|
if (editable == _editable)
|
|
return;
|
|
|
|
// if we are not editable, then we also update the state of active, and tell wm too
|
|
if (!editable) {
|
|
setActive(false);
|
|
if (_wm->getActiveWidget() == this)
|
|
_wm->setActiveWidget(nullptr);
|
|
}
|
|
|
|
_editable = editable;
|
|
_cursorOff = !editable;
|
|
_selectable = editable;
|
|
_focusable = editable;
|
|
|
|
if (!editable) {
|
|
undrawCursor();
|
|
}
|
|
}
|
|
|
|
void MacText::resize(int w, int h) {
|
|
if (_canvas._surface->w == w && _canvas._surface->h == h)
|
|
return;
|
|
|
|
setMaxWidth(w);
|
|
if (_composeSurface->w != w || _composeSurface->h != h) {
|
|
delete _composeSurface;
|
|
_composeSurface = new ManagedSurface(w, h, _wm->_pixelformat);
|
|
_dims.right = _dims.left + w;
|
|
_dims.bottom = _dims.top + h;
|
|
|
|
_contentIsDirty = true;
|
|
_fullRefresh = true;
|
|
}
|
|
}
|
|
|
|
void MacText::appendText(const Common::U32String &str, int fontId, int fontSize, int fontSlant, bool skipAdd) {
|
|
appendText(str, fontId, fontSize, fontSlant, 0, 0, 0, skipAdd);
|
|
}
|
|
|
|
void MacText::appendText(const Common::U32String &str, int fontId, int fontSize, int fontSlant, uint16 r, uint16 g, uint16 b, bool skipAdd) {
|
|
uint oldLen = _canvas._text.size();
|
|
|
|
MacFontRun fontRun = MacFontRun(_wm, fontId, fontSlant, fontSize, r, g, b);
|
|
|
|
_currentFormatting = fontRun;
|
|
|
|
// we check _str here, if _str is empty but _canvas._text is not empty, and they are not the end of paragraph
|
|
// then we remove those empty lines
|
|
// too many special check may cause some strange problem in the future
|
|
if (_str.empty()) {
|
|
while (!_canvas._text.empty() && !_canvas._text.back().paragraphEnd)
|
|
removeLastLine();
|
|
}
|
|
|
|
// we need to split the string with the font, in order to get the correct font
|
|
Common::U32String strWithFont = Common::U32String(fontRun.toString()) + str;
|
|
|
|
if (!skipAdd)
|
|
_str += strWithFont;
|
|
|
|
appendText_(strWithFont, oldLen);
|
|
}
|
|
|
|
void MacText::appendText(const Common::U32String &str, const Font *font, uint16 r, uint16 g, uint16 b, bool skipAdd) {
|
|
uint oldLen = _canvas._text.size();
|
|
|
|
MacFontRun fontRun = MacFontRun(_wm, font, 0, font->getFontHeight(), r, g, b);
|
|
|
|
_currentFormatting = fontRun;
|
|
|
|
if (_str.empty()) {
|
|
while (!_canvas._text.empty() && !_canvas._text.back().paragraphEnd)
|
|
removeLastLine();
|
|
}
|
|
|
|
Common::U32String strWithFont = Common::U32String(fontRun.toString()) + str;
|
|
|
|
if (!skipAdd)
|
|
_str += strWithFont;
|
|
|
|
appendText_(strWithFont, oldLen);
|
|
}
|
|
|
|
void MacText::appendText_(const Common::U32String &strWithFont, uint oldLen) {
|
|
clearChunkInput();
|
|
|
|
_canvas.splitString(strWithFont, -1, _defaultFormatting);
|
|
recalcDims();
|
|
|
|
_canvas.render(oldLen - 1, _canvas._text.size());
|
|
|
|
_contentIsDirty = true;
|
|
|
|
if (_editable) {
|
|
_scrollPos = MAX<int>(0, getTextHeight() - getDimensions().height());
|
|
|
|
_cursorRow = getLineCount();
|
|
_cursorCol = _canvas.getLineCharWidth(_cursorRow);
|
|
|
|
updateCursorPos();
|
|
}
|
|
}
|
|
|
|
void MacText::appendTextDefault(const Common::U32String &str, bool skipAdd) {
|
|
uint oldLen = _canvas._text.size();
|
|
|
|
_currentFormatting = _defaultFormatting;
|
|
Common::U32String strWithFont = Common::U32String(_defaultFormatting.toString()) + str;
|
|
|
|
if (!skipAdd) {
|
|
_str += strWithFont;
|
|
}
|
|
_canvas.splitString(strWithFont, -1, _defaultFormatting);
|
|
recalcDims();
|
|
|
|
_canvas.render(oldLen - 1, _canvas._text.size());
|
|
}
|
|
|
|
void MacText::appendTextDefault(const Common::String &str, bool skipAdd) {
|
|
appendTextDefault(Common::U32String(str), skipAdd);
|
|
}
|
|
|
|
void MacText::clearChunkInput() {
|
|
int canvasTextSize = _canvas._text.size() - 1;
|
|
|
|
if (canvasTextSize >= 0 && _editable) {
|
|
int lastChunkIdx = _canvas._text[canvasTextSize].chunks.size() - 1;
|
|
|
|
if (lastChunkIdx >= 0)
|
|
_canvas._text[canvasTextSize].chunks[lastChunkIdx].text = "";
|
|
}
|
|
}
|
|
|
|
void MacText::clearText() {
|
|
_contentIsDirty = true;
|
|
_canvas._text.clear();
|
|
_str.clear();
|
|
|
|
if (_canvas._surface)
|
|
_canvas._surface->clear(_canvas._tbgcolor);
|
|
|
|
recalcDims();
|
|
|
|
_cursorRow = _cursorCol = 0;
|
|
updateCursorPos();
|
|
}
|
|
|
|
void MacText::removeLastLine() {
|
|
if (!_canvas._text.size())
|
|
return;
|
|
|
|
int h = getLineHeight(_canvas._text.size() - 1) + _canvas._interLinear;
|
|
|
|
_canvas._surface->fillRect(Common::Rect(0, _canvas._textMaxHeight - h, _canvas._surface->w, _canvas._textMaxHeight), _canvas._tbgcolor);
|
|
|
|
_canvas._text.pop_back();
|
|
_canvas._textMaxHeight -= h;
|
|
}
|
|
|
|
void MacText::draw(ManagedSurface *g, int x, int y, int w, int h, int xoff, int yoff) {
|
|
if (_canvas._text.empty())
|
|
return;
|
|
|
|
render();
|
|
|
|
if (x + w < _canvas._surface->w || y + h < _canvas._surface->h)
|
|
g->fillRect(Common::Rect(x + xoff, y + yoff, x + w + xoff, y + h + yoff), _canvas._tbgcolor);
|
|
|
|
// blit shadow surface first
|
|
if (_canvas._textShadow)
|
|
g->blitFrom(*_canvas._shadowSurface, Common::Rect(MIN<int>(_canvas._surface->w, x), MIN<int>(_canvas._surface->h, y), MIN<int>(_canvas._surface->w, x + w), MIN<int>(_canvas._surface->h, y + h)), Common::Point(xoff + _canvas._textShadow, yoff + _canvas._textShadow));
|
|
|
|
g->transBlitFrom(*_canvas._surface, Common::Rect(MIN<int>(_canvas._surface->w, x), MIN<int>(_canvas._surface->h, y), MIN<int>(_canvas._surface->w, x + w), MIN<int>(_canvas._surface->h, y + h)), Common::Point(xoff, yoff), _canvas._tbgcolor);
|
|
|
|
if (_scrollBar && _scrollBorder.hasBorder(kWindowBorderScrollbar)) {
|
|
uint32 transcolor = (_wm->_pixelformat.bytesPerPixel == 1) ? _wm->_colorGreen : 0;
|
|
|
|
g->transBlitFrom(_borderSurface, Common::Rect(0, 0, _borderSurface.w, _borderSurface.h), Common::Point(0, 0), transcolor);
|
|
}
|
|
|
|
_contentIsDirty = false;
|
|
_cursorDirty = false;
|
|
}
|
|
|
|
bool MacText::draw(bool forceRedraw) {
|
|
if (!needsRedraw() && !forceRedraw)
|
|
return false;
|
|
|
|
if (!_canvas._surface) {
|
|
warning("MacText::draw: Null surface");
|
|
return false;
|
|
}
|
|
|
|
// we need to find out a way to judge whether we need to clear the surface
|
|
// currently, we just use the _contentIsDirty
|
|
if (_contentIsDirty)
|
|
_composeSurface->clear(_canvas._tbgcolor);
|
|
|
|
// TODO: Clear surface fully when background colour changes.
|
|
_cursorDirty = false;
|
|
|
|
Common::Point offset(calculateOffset());
|
|
|
|
// if we are drawing the selection text or we are selecting, then we don't draw the cursor
|
|
if (!((_inTextSelection || _selectedText.endY != -1) && _active)) {
|
|
if (!_cursorState)
|
|
_composeSurface->blitFrom(*_cursorSurface2, *_cursorRect, Common::Point(_cursorX + offset.x, _cursorY + offset.y));
|
|
else
|
|
_composeSurface->blitFrom(*_cursorSurface, *_cursorRect, Common::Point(_cursorX + offset.x, _cursorY + offset.y));
|
|
}
|
|
|
|
if (!(_contentIsDirty || forceRedraw))
|
|
return true;
|
|
|
|
draw(_composeSurface, 0, _scrollPos, _canvas._surface->w, _scrollPos + _canvas._surface->h, offset.x, offset.y);
|
|
|
|
for (int bb = 0; bb < _shadow; bb++) {
|
|
_composeSurface->hLine(_shadow, _composeSurface->h - _shadow + bb, _composeSurface->w, 0);
|
|
_composeSurface->vLine(_composeSurface->w - _shadow + bb, _shadow, _composeSurface->h - _shadow, 0);
|
|
}
|
|
|
|
for (int bb = 0; bb < _border; bb++) {
|
|
Common::Rect borderRect(bb, bb, _composeSurface->w - bb, _composeSurface->h - bb);
|
|
_composeSurface->frameRect(borderRect, 0xff);
|
|
}
|
|
|
|
if (_selectedText.endY != -1)
|
|
drawSelection(offset.x, offset.y);
|
|
|
|
_contentIsDirty = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool MacText::draw(ManagedSurface *g, bool forceRedraw) {
|
|
if (!draw(forceRedraw))
|
|
return false;
|
|
|
|
g->transBlitFrom(*_composeSurface, _composeSurface->getBounds(), Common::Point(_dims.left, _dims.top), _wm->_colorGreen2);
|
|
|
|
return true;
|
|
}
|
|
|
|
void MacText::drawToPoint(ManagedSurface *g, Common::Rect srcRect, Common::Point dstPoint) {
|
|
if (_canvas._text.empty())
|
|
return;
|
|
|
|
render();
|
|
|
|
srcRect.clip(_canvas._surface->getBounds());
|
|
|
|
if (srcRect.isEmpty())
|
|
return;
|
|
|
|
g->blitFrom(*_canvas._surface, srcRect, dstPoint);
|
|
}
|
|
|
|
void MacText::drawToPoint(ManagedSurface *g, Common::Point dstPoint) {
|
|
if (_canvas._text.empty())
|
|
return;
|
|
|
|
render();
|
|
|
|
g->blitFrom(*_canvas._surface, dstPoint);
|
|
}
|
|
|
|
// Count newline characters in String
|
|
uint getNewlinesInString(const Common::U32String &str) {
|
|
Common::U32String::const_iterator p = str.begin();
|
|
uint newLines = 0;
|
|
while (*p) {
|
|
if (*p == '\n')
|
|
newLines++;
|
|
p++;
|
|
}
|
|
return newLines;
|
|
}
|
|
|
|
void MacText::drawSelection(int xoff, int yoff) {
|
|
if (_selectedText.endY == -1)
|
|
return;
|
|
|
|
// we check if the selection size is 0, then we don't draw it anymore, and we set the cursor here
|
|
// it's a small optimize, but can bring us correct behavior
|
|
if (!_inTextSelection && _selectedText.startX == _selectedText.endX && _selectedText.startY == _selectedText.endY) {
|
|
_cursorRow = _selectedText.startRow;
|
|
_cursorCol = _selectedText.startCol;
|
|
updateCursorPos();
|
|
_selectedText.startY = _selectedText.endY = -1;
|
|
return;
|
|
}
|
|
|
|
SelectedText s = _selectedText;
|
|
|
|
if (s.startY > s.endY || (s.startY == s.endY && s.startX > s.endX)) {
|
|
SWAP(s.startX, s.endX);
|
|
SWAP(s.startY, s.endY);
|
|
SWAP(s.startRow, s.endRow);
|
|
SWAP(s.startCol, s.endCol);
|
|
}
|
|
|
|
int lastLineStart = s.endY;
|
|
s.endY += getLineHeight(s.endRow);
|
|
|
|
int start = s.startY - _scrollPos;
|
|
start = MAX(0, start);
|
|
|
|
if (start > getDimensions().height())
|
|
return;
|
|
|
|
int end = s.endY - _scrollPos;
|
|
|
|
if (end < 0)
|
|
return;
|
|
|
|
int maxSelectionHeight = getDimensions().height() - _border - _gutter / 2;
|
|
int maxSelectionWidth = getDimensions().width() - _border - _gutter;
|
|
|
|
if (s.endCol == _canvas.getLineCharWidth(s.endRow))
|
|
s.endX = maxSelectionWidth;
|
|
|
|
end = MIN((int)maxSelectionHeight, end);
|
|
|
|
// if we are selecting all text, then we invert the whole area
|
|
if ((uint)s.endRow == _canvas._text.size() - 1)
|
|
end = maxSelectionHeight;
|
|
|
|
int numLines = 0;
|
|
int x1 = 0, x2 = maxSelectionWidth;
|
|
int row = s.startRow;
|
|
int alignOffset = 0;
|
|
|
|
// we may draw part of the selection, so we need to calc the height of first line
|
|
if (s.startY < _scrollPos) {
|
|
int start_row = 0;
|
|
getRowCol(s.startX, _scrollPos, nullptr, &numLines, &start_row, nullptr);
|
|
numLines = getLineHeight(start_row) - (_scrollPos - numLines);
|
|
if (start_row == s.startRow)
|
|
x1 = s.startX;
|
|
if (start_row == s.endRow)
|
|
x2 = s.endX;
|
|
// deal with the first line, which is not a complete line
|
|
if (numLines) {
|
|
alignOffset = _canvas.getAlignOffset(start_row);
|
|
|
|
if (start_row == s.startRow && s.startCol != 0) {
|
|
x1 = MIN<int>(x1 + xoff + alignOffset, maxSelectionWidth);
|
|
x2 = MIN<int>(x2 + xoff + alignOffset, maxSelectionWidth);
|
|
} else {
|
|
x1 = MIN<int>(x1 + xoff, maxSelectionWidth);
|
|
x2 = MIN<int>(x2 + xoff + alignOffset, maxSelectionWidth);
|
|
}
|
|
|
|
row = start_row + 1;
|
|
}
|
|
}
|
|
|
|
end = MIN(end, maxSelectionHeight - yoff);
|
|
for (int y = start; y < end; y++) {
|
|
if (!numLines && (uint)row < _canvas._text.size()) {
|
|
x1 = 0;
|
|
x2 = maxSelectionWidth;
|
|
|
|
alignOffset = _canvas.getAlignOffset(row);
|
|
|
|
numLines = getLineHeight(row);
|
|
if (y + _scrollPos == s.startY && s.startX > 0)
|
|
x1 = s.startX;
|
|
if (y + _scrollPos >= lastLineStart)
|
|
x2 = s.endX;
|
|
|
|
// if we are selecting text reversely, and we are at the first line but not the select from beginning, then we add offset to x1
|
|
// the reason here is if we are not drawing the single line, then we draw selection from x1 to x2 + offset. i.e. we draw from begin
|
|
// otherwise, we draw selection from x1 + offset to x2 + offset
|
|
if (row == s.startRow && s.startCol != 0) {
|
|
x1 = MIN<int>(x1 + xoff + alignOffset, maxSelectionWidth);
|
|
x2 = MIN<int>(x2 + xoff + alignOffset, maxSelectionWidth);
|
|
} else {
|
|
x1 = MIN<int>(x1 + xoff, maxSelectionWidth);
|
|
x2 = MIN<int>(x2 + xoff + alignOffset, maxSelectionWidth);
|
|
}
|
|
row++;
|
|
}
|
|
numLines--;
|
|
|
|
byte *ptr = (byte *)_composeSurface->getBasePtr(x1, MIN<int>(y + yoff, maxSelectionHeight - 1));
|
|
|
|
for (int x = x1; x < x2; x++, ptr++)
|
|
if (*ptr == _canvas._tfgcolor)
|
|
*ptr = _canvas._tbgcolor;
|
|
else
|
|
*ptr = _canvas._tfgcolor;
|
|
}
|
|
}
|
|
|
|
Common::U32String MacText::getSelection(bool formatted, bool newlines) {
|
|
if (_selectedText.endY == -1)
|
|
return Common::U32String();
|
|
|
|
SelectedText s = _selectedText;
|
|
|
|
if (s.startY > s.endY || (s.startY == s.endY && s.startX > s.endX)) {
|
|
SWAP(s.startRow, s.endRow);
|
|
SWAP(s.startCol, s.endCol);
|
|
}
|
|
|
|
return _canvas.getTextChunk(s.startRow, s.startCol, s.endRow, s.endCol, formatted, newlines);
|
|
}
|
|
|
|
void MacText::clearSelection() {
|
|
_selectedText.endY = _selectedText.startY = -1;
|
|
}
|
|
|
|
uint MacText::getSelectionIndex(bool start) {
|
|
int pos = 0;
|
|
|
|
if (!_inTextSelection && (_selectedText.startY < 0 && _selectedText.endY < 0))
|
|
return pos;
|
|
|
|
if (start) {
|
|
for (int row = 0; row < _selectedText.startRow; row++)
|
|
pos += _canvas.getLineCharWidth(row);
|
|
|
|
pos += _selectedText.startCol;
|
|
return pos;
|
|
} else {
|
|
for (int row = 0; row < _selectedText.endRow; row++)
|
|
pos += _canvas.getLineCharWidth(row);
|
|
|
|
pos += _selectedText.endCol;
|
|
return pos;
|
|
}
|
|
}
|
|
|
|
void MacText::setSelection(int pos, bool start) {
|
|
// -1 for start represent the beginning of text, i.e. 0
|
|
if (pos == -1 && start)
|
|
pos = 0;
|
|
int row = 0, col = 0;
|
|
int colX = 0;
|
|
|
|
if (pos > 0) {
|
|
while (pos > 0) {
|
|
if (pos < _canvas.getLineCharWidth(row)) {
|
|
for (uint i = 0; i < _canvas._text[row].chunks.size(); i++) {
|
|
if ((uint)pos < _canvas._text[row].chunks[i].text.size()) {
|
|
colX += getStringWidth(_canvas._text[row].chunks[i], _canvas._text[row].chunks[i].text.substr(0, pos));
|
|
col += pos;
|
|
pos = 0;
|
|
break;
|
|
} else {
|
|
colX += getStringWidth(_canvas._text[row].chunks[i], _canvas._text[row].chunks[i].text);
|
|
pos -= _canvas._text[row].chunks[i].text.size();
|
|
col += _canvas._text[row].chunks[i].text.size();
|
|
}
|
|
}
|
|
break;
|
|
} else {
|
|
pos -= _canvas.getLineCharWidth(row); // (row ? 1 : 0);
|
|
}
|
|
|
|
row++;
|
|
if ((uint)row >= _canvas._text.size()) {
|
|
row = _canvas._text.size() - 1;
|
|
colX = _canvas._surface->w;
|
|
col = _canvas.getLineCharWidth(row);
|
|
|
|
break;
|
|
}
|
|
}
|
|
} else if (pos == 0) {
|
|
colX = col = row = 0;
|
|
} else {
|
|
row = _canvas._text.size() - 1;
|
|
col = _canvas.getLineCharWidth(row);
|
|
// if we don't have any text, then we won't select the whole area.
|
|
if (_canvas._textMaxWidth == 0)
|
|
colX = 0;
|
|
else
|
|
colX = _canvas._textMaxWidth;
|
|
}
|
|
|
|
int rowY = 0;
|
|
if (!_canvas._text.empty())
|
|
rowY = _canvas._text[row].y;
|
|
|
|
if (start) {
|
|
_selectedText.startX = colX;
|
|
_selectedText.startY = rowY;
|
|
_selectedText.startCol = col;
|
|
_selectedText.startRow = row;
|
|
} else {
|
|
_selectedText.endX = colX;
|
|
_selectedText.endY = rowY;
|
|
_selectedText.endCol = col;
|
|
_selectedText.endRow = row;
|
|
}
|
|
|
|
_contentIsDirty = true;
|
|
}
|
|
|
|
bool MacText::isCutAllowed() {
|
|
if (_selectedText.startRow >= _editableRow &&
|
|
_selectedText.endRow >= _editableRow)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
Common::U32String MacText::getEditedString() {
|
|
return _canvas.getTextChunk(_editableRow, 0, -1, -1);
|
|
}
|
|
|
|
Common::U32String MacText::getPlainText() {
|
|
Common::U32String res;
|
|
for (uint i = 0; i < _canvas._text.size(); i++) {
|
|
for (uint j = 0; j < _canvas._text[i].chunks.size(); j++) {
|
|
res += _canvas._text[i].chunks[j].text;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
Common::U32String MacText::cutSelection() {
|
|
if (!isCutAllowed())
|
|
return Common::U32String();
|
|
|
|
SelectedText s = _selectedText;
|
|
|
|
if (s.startY > s.endY || (s.startY == s.endY && s.startX > s.endX)) {
|
|
SWAP(s.startRow, s.endRow);
|
|
SWAP(s.startCol, s.endCol);
|
|
}
|
|
|
|
Common::U32String selection = _canvas.getTextChunk(s.startRow, s.startCol, s.endRow, s.endCol, true, true);
|
|
|
|
deleteSelection();
|
|
clearSelection();
|
|
|
|
return selection;
|
|
}
|
|
|
|
bool MacText::processEvent(Common::Event &event) {
|
|
if (event.type == Common::EVENT_KEYDOWN) {
|
|
if (!_editable)
|
|
return false;
|
|
|
|
setActive(true);
|
|
|
|
if (event.kbd.flags & (Common::KBD_ALT | Common::KBD_CTRL | Common::KBD_META)) {
|
|
switch (event.kbd.keycode) {
|
|
case Common::KEYCODE_x:
|
|
_wm->setTextInClipboard(cutSelection());
|
|
return true;
|
|
case Common::KEYCODE_c:
|
|
_wm->setTextInClipboard(getSelection(true, true));
|
|
return true;
|
|
case Common::KEYCODE_v:
|
|
if (g_system->hasTextInClipboard()) {
|
|
if (_selectedText.endY != -1)
|
|
cutSelection();
|
|
insertTextFromClipboard();
|
|
}
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
switch (event.kbd.keycode) {
|
|
case Common::KEYCODE_BACKSPACE:
|
|
// if we have the selectedText, then we delete it
|
|
if (_selectedText.endY != -1) {
|
|
cutSelection();
|
|
_contentIsDirty = true;
|
|
return true;
|
|
}
|
|
if (_cursorRow > 0 || _cursorCol > 0) {
|
|
deletePreviousChar(&_cursorRow, &_cursorCol);
|
|
updateCursorPos();
|
|
_contentIsDirty = true;
|
|
}
|
|
return true;
|
|
|
|
case Common::KEYCODE_RETURN:
|
|
addNewLine(&_cursorRow, &_cursorCol);
|
|
updateCursorPos();
|
|
_contentIsDirty = true;
|
|
return true;
|
|
|
|
case Common::KEYCODE_LEFT:
|
|
if (_cursorCol == 0) {
|
|
if (_cursorRow == 0) { // Nowhere to go
|
|
return true;
|
|
}
|
|
_cursorRow--;
|
|
_cursorCol = _canvas.getLineCharWidth(_cursorRow);
|
|
} else {
|
|
_cursorCol--;
|
|
}
|
|
updateCursorPos();
|
|
|
|
return true;
|
|
|
|
case Common::KEYCODE_RIGHT:
|
|
if (_cursorCol >= _canvas.getLineCharWidth(_cursorRow)) {
|
|
if (_cursorRow == getLineCount() - 1) { // Nowhere to go
|
|
return true;
|
|
}
|
|
_cursorRow++;
|
|
_cursorCol = 0;
|
|
} else {
|
|
_cursorCol++;
|
|
}
|
|
updateCursorPos();
|
|
|
|
return true;
|
|
|
|
case Common::KEYCODE_UP:
|
|
if (_cursorRow == 0)
|
|
return true;
|
|
|
|
_cursorRow--;
|
|
|
|
getRowCol(_cursorX, _canvas._text[_cursorRow].y, nullptr, nullptr, &_cursorRow, &_cursorCol);
|
|
updateCursorPos();
|
|
|
|
return true;
|
|
|
|
case Common::KEYCODE_DOWN:
|
|
if (_cursorRow == getLineCount() - 1)
|
|
return true;
|
|
|
|
_cursorRow++;
|
|
|
|
getRowCol(_cursorX, _canvas._text[_cursorRow].y, nullptr, nullptr, &_cursorRow, &_cursorCol);
|
|
updateCursorPos();
|
|
|
|
return true;
|
|
|
|
case Common::KEYCODE_DELETE:
|
|
// first try to delete the selected text
|
|
if (_selectedText.endY != -1) {
|
|
cutSelection();
|
|
_contentIsDirty = true;
|
|
return true;
|
|
}
|
|
// move cursor to next one and delete previous char
|
|
if (_cursorCol >= _canvas.getLineCharWidth(_cursorRow)) {
|
|
if (_cursorRow == getLineCount() - 1) {
|
|
return true;
|
|
}
|
|
_cursorRow++;
|
|
_cursorCol = 0;
|
|
} else {
|
|
_cursorCol++;
|
|
}
|
|
deletePreviousChar(&_cursorRow, &_cursorCol);
|
|
updateCursorPos();
|
|
_contentIsDirty = true;
|
|
return true;
|
|
|
|
default:
|
|
if (event.kbd.ascii == '~')
|
|
return false;
|
|
|
|
if (event.kbd.ascii >= 0x20 && event.kbd.ascii <= 0x7f) {
|
|
// if we have selected text, then we delete it, then we try to insert char
|
|
if (_selectedText.endY != -1) {
|
|
cutSelection();
|
|
_contentIsDirty = true;
|
|
}
|
|
insertChar((byte)event.kbd.ascii, &_cursorRow, &_cursorCol);
|
|
updateCursorPos();
|
|
_contentIsDirty = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (event.type == Common::EVENT_WHEELUP) {
|
|
scroll(-2);
|
|
return true;
|
|
}
|
|
|
|
if (event.type == Common::EVENT_WHEELDOWN) {
|
|
scroll(2);
|
|
return true;
|
|
}
|
|
|
|
if (event.type == Common::EVENT_LBUTTONDOWN && _scrollBar) {
|
|
switch (isInScrollBar(event.mouse.x, event.mouse.y)) {
|
|
case kBorderScrollDown:
|
|
scroll(2);
|
|
return true;
|
|
break;
|
|
case kBorderScrollUp:
|
|
scroll(-2);
|
|
return true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!_selectable)
|
|
return false;
|
|
|
|
if (event.type == Common::EVENT_LBUTTONDOWN) {
|
|
bool active = _active;
|
|
_wm->setActiveWidget(this);
|
|
if (active == true) {
|
|
// inactive -> active switching, we don't start marking the selection, because we have initial selection
|
|
startMarking(event.mouse.x, event.mouse.y);
|
|
}
|
|
|
|
return true;
|
|
} else if (event.type == Common::EVENT_LBUTTONUP) {
|
|
if (_inTextSelection) {
|
|
_inTextSelection = false;
|
|
|
|
if (_selectedText.endY == -1 ||
|
|
(_selectedText.endX == _selectedText.startX && _selectedText.endY == _selectedText.startY)) {
|
|
_selectedText.startY = _selectedText.endY = -1;
|
|
_contentIsDirty = true;
|
|
|
|
if (_menu)
|
|
_menu->enableCommand("Edit", "Copy", false);
|
|
|
|
Common::Point offset = calculateOffset();
|
|
int x = event.mouse.x - getDimensions().left - offset.x;
|
|
int y = event.mouse.y - getDimensions().top + _scrollPos - offset.y;
|
|
|
|
getRowCol(x, y, nullptr, nullptr, &_cursorRow, &_cursorCol);
|
|
updateCursorPos();
|
|
} else {
|
|
if (_menu) {
|
|
_menu->enableCommand("Edit", "Copy", true);
|
|
|
|
bool cutAllowed = isCutAllowed();
|
|
|
|
_menu->enableCommand("Edit", "Cut", cutAllowed);
|
|
_menu->enableCommand("Edit", "Clear", cutAllowed);
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
} else if (event.type == Common::EVENT_MOUSEMOVE) {
|
|
if (_inTextSelection) {
|
|
updateTextSelection(event.mouse.x, event.mouse.y);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void MacText::scroll(int delta) {
|
|
// disable the scroll for auto expand text
|
|
// should be amend to check the text type. e.g. auto expand type, fixed type, scroll type.
|
|
if (!_fixedDims)
|
|
return;
|
|
|
|
int oldScrollPos = _scrollPos;
|
|
|
|
_scrollPos += delta * kConScrollStep;
|
|
|
|
if (_editable)
|
|
_scrollPos = CLIP<int>(_scrollPos, 0, MacText::getTextHeight() - kConScrollStep);
|
|
else
|
|
_scrollPos = CLIP<int>(_scrollPos, 0, MAX<int>(0, MacText::getTextHeight() - getDimensions().height()));
|
|
|
|
undrawCursor();
|
|
_cursorY -= (_scrollPos - oldScrollPos);
|
|
_contentIsDirty = true;
|
|
}
|
|
|
|
void MacText::startMarking(int x, int y) {
|
|
if (_canvas._text.size() == 0)
|
|
return;
|
|
|
|
Common::Point offset = calculateOffset();
|
|
x -= getDimensions().left - offset.x;
|
|
y -= getDimensions().top - offset.y;
|
|
|
|
y += _scrollPos;
|
|
|
|
getRowCol(x, y, &_selectedText.startX, &_selectedText.startY, &_selectedText.startRow, &_selectedText.startCol);
|
|
|
|
_selectedText.endY = -1;
|
|
|
|
_inTextSelection = true;
|
|
}
|
|
|
|
void MacText::updateTextSelection(int x, int y) {
|
|
Common::Point offset = calculateOffset();
|
|
x -= getDimensions().left - offset.x;
|
|
y -= getDimensions().top - offset.y;
|
|
|
|
y += _scrollPos;
|
|
|
|
getRowCol(x, y, &_selectedText.endX, &_selectedText.endY, &_selectedText.endRow, &_selectedText.endCol);
|
|
|
|
debug(3, "s: %d,%d (%d, %d) e: %d,%d (%d, %d)", _selectedText.startX, _selectedText.startY,
|
|
_selectedText.startRow, _selectedText.startCol, _selectedText.endX,
|
|
_selectedText.endY, _selectedText.endRow, _selectedText.endCol);
|
|
|
|
_contentIsDirty = true;
|
|
}
|
|
|
|
int MacText::getMouseChar(int x, int y) {
|
|
Common::Point offset = calculateOffset();
|
|
x -= getDimensions().left - offset.x;
|
|
y -= getDimensions().top - offset.y;
|
|
y += _scrollPos;
|
|
|
|
int dx, dy, row, col;
|
|
getLineCharacter(x, y, &dx, &dy, &row, &col);
|
|
debug(5, "mouseChar: x: %d, y: %d, row: %d, col: %d", x, y, row, col);
|
|
|
|
int index = 1;
|
|
for (int r = 0; r < row; r++) {
|
|
debug(5, "mouseChar: char %d: \"%s\"", index, _canvas.getTextChunk(r, 0, r, _canvas.getLineCharWidth(r)).encode().c_str());
|
|
index += _canvas.getLineCharWidth(r);
|
|
index++; // linebreak
|
|
}
|
|
debug(5, "mouseChar: char %d: \"%s\"", index, _canvas.getTextChunk(row, 0, row, col).encode().c_str());
|
|
index += col;
|
|
|
|
return index;
|
|
}
|
|
|
|
int MacText::getMouseWord(int x, int y) {
|
|
Common::Point offset = calculateOffset();
|
|
x -= getDimensions().left - offset.x;
|
|
y -= getDimensions().top - offset.y;
|
|
y += _scrollPos;
|
|
|
|
int dx, dy, row, col;
|
|
getLineCharacter(x, y, &dx, &dy, &row, &col);
|
|
|
|
int index = 1;
|
|
bool inWhitespace = true;
|
|
// getMouseWord:
|
|
// - starts from index 1
|
|
// - a word goes from the first leading whitespace character
|
|
// up until the end of the word
|
|
// - trailing whitespace or empty space at the end of a line
|
|
// counts as part of the word on the next line
|
|
// - empty space at the end of the text counts as a word
|
|
debug(5, "mouseWord: x: %d, y: %d, row: %d, col: %d", x, y, row, col);
|
|
for (int i = 0; i < row; i++) {
|
|
for (uint j = 0; j < _canvas._text[i].chunks.size(); j++) {
|
|
if (_canvas._text[i].chunks[j].text.empty())
|
|
continue;
|
|
Common::String data = _canvas._text[i].chunks[j].getEncodedText();
|
|
uint oldIdx = 0;
|
|
for (uint k = 0; k < data.size(); k++) {
|
|
char it = data[k];
|
|
if (it == ' ' && !inWhitespace) {
|
|
debug(5, "mouseWord: word %d: %s", index, data.substr(oldIdx, k-oldIdx).c_str());
|
|
oldIdx = k;
|
|
index++;
|
|
inWhitespace = true;
|
|
} else if (it != ' ' && inWhitespace) {
|
|
inWhitespace = false;
|
|
}
|
|
}
|
|
}
|
|
// Reached the end of a line, if we're in a word that's the end of it
|
|
if (!inWhitespace) {
|
|
debug(5, "mouseWord: word %d: <redacted>", index);
|
|
index++;
|
|
inWhitespace = true;
|
|
}
|
|
}
|
|
|
|
int cur = 0;
|
|
for (uint j = 0; j < _canvas._text[row].chunks.size(); j++) {
|
|
if (_canvas._text[row].chunks[j].text.empty())
|
|
continue;
|
|
Common::String data = _canvas._text[row].chunks[j].getEncodedText();
|
|
uint oldIdx = 0;
|
|
for (uint k = 0; k < data.size(); k++) {
|
|
char it = data[k];
|
|
cur++;
|
|
if (it == ' ' && !inWhitespace) {
|
|
debug(5, "mouseWord: word %d: %s", index, data.substr(oldIdx, k-oldIdx).c_str());
|
|
oldIdx = k;
|
|
index++;
|
|
inWhitespace = true;
|
|
} else if (it != ' ' && inWhitespace) {
|
|
inWhitespace = false;
|
|
}
|
|
if (cur > col)
|
|
break;
|
|
}
|
|
if (cur > col)
|
|
break;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
int MacText::getMouseItem(int x, int y) {
|
|
Common::Point offset = calculateOffset();
|
|
x -= getDimensions().left - offset.x;
|
|
y -= getDimensions().top - offset.y;
|
|
y += _scrollPos;
|
|
|
|
int dx, dy, row, col;
|
|
getLineCharacter(x, y, &dx, &dy, &row, &col);
|
|
|
|
// getMouseItem
|
|
// - starts from index 1
|
|
// - an item goes from the first non-comma character up until a comma
|
|
// - trailing whitespace or empty space at the end of a line
|
|
// counts as part of the item on the next line
|
|
// - empty space at the end of the text counts as an item
|
|
int index = 1;
|
|
bool onComma = false;
|
|
for (int i = 0; i < row; i++) {
|
|
for (uint j = 0; j < _canvas._text[i].chunks.size(); j++) {
|
|
if (_canvas._text[i].chunks[j].text.empty())
|
|
continue;
|
|
Common::String data = _canvas._text[i].chunks[j].getEncodedText();
|
|
for (auto it : data) {
|
|
if (onComma) {
|
|
index += 1;
|
|
onComma = false;
|
|
}
|
|
if (it == ',') {
|
|
onComma = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int cur = 0;
|
|
for (uint i = 0; i < _canvas._text[row].chunks.size(); i++) {
|
|
if (_canvas._text[row].chunks[i].text.empty())
|
|
continue;
|
|
Common::String data = _canvas._text[row].chunks[i].getEncodedText();
|
|
for (auto it : data) {
|
|
if (onComma) {
|
|
index += 1;
|
|
onComma = false;
|
|
}
|
|
if (it == ',') {
|
|
onComma = true;
|
|
}
|
|
cur++;
|
|
if (cur > col)
|
|
break;
|
|
}
|
|
if (cur > col)
|
|
break;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
int MacText::getMouseLine(int x, int y) {
|
|
Common::Point offset = calculateOffset();
|
|
x -= getDimensions().left - offset.x;
|
|
y -= getDimensions().top - offset.y;
|
|
y += _scrollPos;
|
|
|
|
int dx, dy, row, col;
|
|
getLineCharacter(x, y, &dx, &dy, &row, &col);
|
|
|
|
return row + 1;
|
|
}
|
|
|
|
Common::U32String MacText::getMouseLink(int x, int y) {
|
|
Common::Point offset = calculateOffset();
|
|
x -= getDimensions().left - offset.x;
|
|
y -= getDimensions().top - offset.y;
|
|
y += _scrollPos;
|
|
|
|
int row, chunk;
|
|
getRowCol(x, y, nullptr, nullptr, &row, nullptr, &chunk);
|
|
|
|
if (chunk < 0)
|
|
return Common::U32String();
|
|
|
|
if (!_canvas._text[row].picfname.empty())
|
|
return _canvas._text[row].pictitle;
|
|
|
|
if (!_canvas._text[row].chunks[chunk].link.empty())
|
|
return _canvas._text[row].chunks[chunk].link;
|
|
|
|
return Common::U32String();
|
|
}
|
|
|
|
void MacText::getRowCol(int x, int y, int *sx, int *sy, int *row, int *col, int *chunk_) {
|
|
int nsx = 0, nsy = 0, nrow = 0, ncol = 0;
|
|
|
|
if (y > _canvas._textMaxHeight) {
|
|
x = _canvas._surface->w;
|
|
}
|
|
|
|
y = CLIP(y, 0, _canvas._textMaxHeight);
|
|
|
|
nrow = _canvas._text.size();
|
|
// use [lb, ub) bsearch here, final answer would be lb
|
|
int lb = 0, ub = nrow;
|
|
while (ub - lb > 1) {
|
|
int mid = (ub + lb) / 2;
|
|
if (_canvas._text[mid].y <= y) {
|
|
lb = mid;
|
|
} else {
|
|
ub = mid;
|
|
}
|
|
}
|
|
nrow = lb;
|
|
|
|
nsy = _canvas._text[nrow].y;
|
|
int chunk = -1;
|
|
|
|
if (_canvas._text[nrow].chunks.size() > 0) {
|
|
int alignOffset = _canvas.getAlignOffset(nrow) + _canvas._text[nrow].indent + _canvas._text[nrow].firstLineIndent;
|
|
|
|
int width = 0, pwidth = 0;
|
|
int mcol = 0, pmcol = 0;
|
|
|
|
for (chunk = 0; chunk < (int)_canvas._text[nrow].chunks.size(); chunk++) {
|
|
pwidth = width;
|
|
pmcol = mcol;
|
|
if (!_canvas._text[nrow].chunks[chunk].text.empty()) {
|
|
width += getStringWidth(_canvas._text[nrow].chunks[chunk], _canvas._text[nrow].chunks[chunk].text);
|
|
mcol += _canvas._text[nrow].chunks[chunk].text.size();
|
|
}
|
|
|
|
if (width + alignOffset > x)
|
|
break;
|
|
}
|
|
|
|
if (chunk >= (int)_canvas._text[nrow].chunks.size())
|
|
chunk = _canvas._text[nrow].chunks.size() - 1;
|
|
|
|
if (chunk_)
|
|
*chunk_ = (int)chunk;
|
|
|
|
Common::U32String str = _canvas._text[nrow].chunks[chunk].text;
|
|
|
|
ncol = mcol;
|
|
nsx = pwidth;
|
|
|
|
for (int i = str.size(); i >= 0; i--) {
|
|
int strw = getStringWidth(_canvas._text[nrow].chunks[chunk], str);
|
|
if (strw + pwidth + alignOffset <= x) {
|
|
ncol = pmcol + i;
|
|
nsx = strw + pwidth;
|
|
break;
|
|
}
|
|
|
|
str.deleteLastChar();
|
|
}
|
|
}
|
|
|
|
if (sx)
|
|
*sx = nsx;
|
|
if (sy)
|
|
*sy = nsy;
|
|
if (col)
|
|
*col = ncol;
|
|
if (row)
|
|
*row = nrow;
|
|
if (chunk_)
|
|
*chunk_ = (int)chunk;
|
|
}
|
|
|
|
void MacText::getLineCharacter(int x, int y, int *sx, int *sy, int *line, int *character, int *chunk_) {
|
|
int nsx = 0, nsy = 0, nrow = 0, ncol = 0;
|
|
getRowCol(x, y, &nsx, &nsy, &nrow, &ncol, chunk_);
|
|
|
|
int nline = 0, ncharacter = 0;
|
|
// Determine the position in character space, taking into account newlines.
|
|
for (int i = 0; i < nrow; i++) {
|
|
if (_canvas._text[i].paragraphEnd) {
|
|
nline += 1;
|
|
ncharacter = 0;
|
|
} else {
|
|
for (auto &it : _canvas._text[i].chunks) {
|
|
ncharacter += it.text.size();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
for (uint i = 0; i < _canvas._text[nrow].chunks.size(); i++) {
|
|
Common::U32String &text = _canvas._text[nrow].chunks[i].text;
|
|
if (ncol > (int)text.size()) {
|
|
ncol -= text.size();
|
|
ncharacter += text.size();
|
|
} else {
|
|
ncharacter += ncol;
|
|
}
|
|
}
|
|
|
|
if (line)
|
|
*line = nline;
|
|
if (character)
|
|
*character = ncharacter;
|
|
}
|
|
|
|
|
|
|
|
Common::U32String MacText::getTextChunk(int startRow, int startCol, int endRow, int endCol, bool formatted, bool newlines) {
|
|
return _canvas.getTextChunk(startRow, startCol, endRow, endCol, formatted, newlines);
|
|
}
|
|
|
|
// mostly, we referring reshuffleParagraph to implement this function
|
|
void MacText::insertTextFromClipboard() {
|
|
int ppos = 0;
|
|
Common::U32String str = _wm->getTextFromClipboard(Common::U32String(_defaultFormatting.toString()), &ppos);
|
|
|
|
if (_canvas._text.empty()) {
|
|
_canvas.splitString(str, -1, _defaultFormatting);
|
|
} else {
|
|
int start = _cursorRow, end = _cursorRow;
|
|
|
|
while (start && !_canvas._text[start - 1].paragraphEnd)
|
|
start--;
|
|
|
|
while (end < (int)_canvas._text.size() - 1 && !_canvas._text[end].paragraphEnd)
|
|
end++;
|
|
|
|
for (int i = start; i < _cursorRow; i++)
|
|
ppos += _canvas.getLineCharWidth(i);
|
|
ppos += _cursorCol;
|
|
|
|
Common::U32String pre_str = _canvas.getTextChunk(start, 0, _cursorRow, _cursorCol, true, true);
|
|
Common::U32String sub_str = _canvas.getTextChunk(_cursorRow, _cursorCol, end, _canvas.getLineCharWidth(end, true), true, true);
|
|
|
|
// Remove it from the text
|
|
for (int i = start; i <= end; i++) {
|
|
_canvas._text.remove_at(start);
|
|
}
|
|
_canvas.splitString(pre_str + str + sub_str, start, _defaultFormatting);
|
|
|
|
_cursorRow = start;
|
|
}
|
|
|
|
while (ppos > _canvas.getLineCharWidth(_cursorRow, true)) {
|
|
ppos -= _canvas.getLineCharWidth(_cursorRow, true);
|
|
|
|
if (_cursorRow == (int)_canvas._text.size() - 1)
|
|
break;
|
|
|
|
_cursorRow++;
|
|
}
|
|
_cursorCol = ppos;
|
|
recalcDims();
|
|
updateCursorPos();
|
|
_fullRefresh = true;
|
|
render();
|
|
}
|
|
|
|
void MacText::setText(const Common::U32String &str) {
|
|
_str = str;
|
|
_canvas._text.clear();
|
|
_canvas.splitString(_str, -1, _defaultFormatting);
|
|
|
|
_cursorRow = _cursorCol = 0;
|
|
recalcDims();
|
|
updateCursorPos();
|
|
_fullRefresh = true;
|
|
render();
|
|
|
|
if (_selectable) {
|
|
setSelection(_selStart, true);
|
|
setSelection(_selEnd, false);
|
|
}
|
|
|
|
_contentIsDirty = true;
|
|
}
|
|
|
|
//////////////////
|
|
// Text editing
|
|
void MacText::insertChar(byte c, int *row, int *col) {
|
|
if (_canvas._text.empty() || _canvas._text[*row].chunks.empty()) {
|
|
appendTextDefault(Common::String(c));
|
|
(*col)++;
|
|
|
|
return;
|
|
}
|
|
|
|
MacTextLine *line = &_canvas._text[*row];
|
|
int pos = *col;
|
|
uint ch = line->getChunkNum(&pos);
|
|
|
|
Common::U32String newchunk = line->chunks[ch].text;
|
|
|
|
if (pos >= (int)newchunk.size())
|
|
newchunk += c;
|
|
else
|
|
newchunk.insertChar(c, pos);
|
|
int chunkw = getStringWidth(line->chunks[ch], newchunk);
|
|
int oldw = getStringWidth(line->chunks[ch], line->chunks[ch].text);
|
|
|
|
line->chunks[ch].text = newchunk;
|
|
line->width = -1; // Force recalc
|
|
|
|
(*col)++;
|
|
|
|
if (_canvas.getLineWidth(*row) - oldw + chunkw > _canvas._maxWidth) { // Needs reshuffle
|
|
_canvas.reshuffleParagraph(row, col, _defaultFormatting);
|
|
_fullRefresh = true;
|
|
recalcDims();
|
|
render();
|
|
} else {
|
|
recalcDims();
|
|
_canvas.render(*row, *row);
|
|
}
|
|
for (int i = 0; i < (int)_canvas._text.size(); i++) {
|
|
D(9, "**insertChar line %d isEnd %d", i, _canvas._text[i].paragraphEnd);
|
|
for (int j = 0; j < (int)_canvas._text[i].chunks.size(); j++) {
|
|
D(9, "[%d] \"%s\"",_canvas._text[i].chunks[j].text.size(), Common::toPrintable(_canvas._text[i].chunks[j].text.encode()).c_str());
|
|
}
|
|
}
|
|
D(9, "**insertChar cursor row %d col %d", _cursorRow, _cursorCol);
|
|
}
|
|
|
|
void MacText::deleteSelection() {
|
|
// TODO: maybe we need to implement an individual delete part for mactext
|
|
if (_selectedText.endY == -1 || (_selectedText.startX == _selectedText.endX && _selectedText.startY == _selectedText.endY))
|
|
return;
|
|
if (_selectedText.startRow == -1 || _selectedText.startCol == -1 || _selectedText.endRow == -1 || _selectedText.endCol == -1)
|
|
error("deleting non-existing selected area");
|
|
|
|
SelectedText s = _selectedText;
|
|
if (s.startY > s.endY || (s.startY == s.endY && s.startX > s.endX)) {
|
|
SWAP(s.startX, s.endX);
|
|
SWAP(s.startY, s.endY);
|
|
SWAP(s.startRow, s.endRow);
|
|
SWAP(s.startCol, s.endCol);
|
|
}
|
|
|
|
int row = s.endRow, col = s.endCol;
|
|
|
|
while (row != s.startRow || col != s.startCol) {
|
|
if (row == 0 && col == 0)
|
|
break;
|
|
deletePreviousCharInternal(&row, &col);
|
|
}
|
|
|
|
_canvas.reshuffleParagraph(&row, &col, _defaultFormatting);
|
|
|
|
_fullRefresh = true;
|
|
recalcDims();
|
|
render();
|
|
|
|
// update cursor position
|
|
_cursorRow = row;
|
|
_cursorCol = col;
|
|
updateCursorPos();
|
|
}
|
|
|
|
void MacText::deletePreviousCharInternal(int *row, int *col) {
|
|
if (*col == 0) { // Need to glue the lines
|
|
*col = _canvas.getLineCharWidth(*row - 1);
|
|
(*row)--;
|
|
|
|
#if DEBUG
|
|
D(9, "MacText::deletePreviousCharInternal: Chunks: ");
|
|
for (auto &ch : _canvas._text[*row].chunks)
|
|
ch.debugPrint();
|
|
|
|
D(9, "");
|
|
#endif
|
|
// formatting matches, glue texts as normal
|
|
if (_canvas._text[*row].lastChunk().equals(_canvas._text[*row + 1].firstChunk())) {
|
|
_canvas._text[*row].lastChunk().text += _canvas._text[*row + 1].firstChunk().text;
|
|
_canvas._text[*row + 1].firstChunk().text.clear();
|
|
} else {
|
|
// formatting doesn't match, move whole chunk
|
|
_canvas._text[*row].chunks.push_back(MacFontRun(_canvas._text[*row + 1].firstChunk()));
|
|
_canvas._text[*row].firstChunk().text.clear();
|
|
}
|
|
_canvas._text[*row].paragraphEnd = _canvas._text[*row + 1].paragraphEnd;
|
|
|
|
for (uint i = 1; i < _canvas._text[*row + 1].chunks.size(); i++)
|
|
_canvas._text[*row].chunks.push_back(MacFontRun(_canvas._text[*row + 1].chunks[i]));
|
|
|
|
_canvas._text.remove_at(*row + 1);
|
|
} else {
|
|
int pos = *col - 1;
|
|
uint ch = _canvas._text[*row].getChunkNum(&pos);
|
|
|
|
if (pos == (int)_canvas._text[*row].chunks[ch].text.size())
|
|
pos--;
|
|
|
|
_canvas._text[*row].chunks[ch].text.deleteChar(pos);
|
|
|
|
(*col)--;
|
|
}
|
|
|
|
_canvas._text[*row].width = -1; // flush the cache
|
|
}
|
|
|
|
void MacText::deletePreviousChar(int *row, int *col) {
|
|
if (*col == 0 && *row == 0) // nothing to do
|
|
return;
|
|
deletePreviousCharInternal(row, col);
|
|
|
|
for (int i = 0; i < (int)_canvas._text.size(); i++) {
|
|
D(9, "**deleteChar line %d", i);
|
|
for (int j = 0; j < (int)_canvas._text[i].chunks.size(); j++) {
|
|
D(9, "[%d] \"%s\"",_canvas._text[i].chunks[j].text.size(), Common::toPrintable(_canvas._text[i].chunks[j].text.encode()).c_str());
|
|
}
|
|
}
|
|
D(9, "**deleteChar cursor row %d col %d", _cursorRow, _cursorCol);
|
|
|
|
_canvas.reshuffleParagraph(row, col, _defaultFormatting);
|
|
|
|
_fullRefresh = true;
|
|
recalcDims();
|
|
render();
|
|
}
|
|
|
|
void MacText::addNewLine(int *row, int *col) {
|
|
if (_canvas._text.empty()) {
|
|
appendTextDefault(Common::String("\n"));
|
|
(*row)++;
|
|
|
|
return;
|
|
}
|
|
|
|
MacTextLine *line = &_canvas._text[*row];
|
|
int pos = *col;
|
|
uint ch = line->getChunkNum(&pos);
|
|
|
|
#if DEBUG
|
|
D(9, "MacText::addNewLine: Chunks: ");
|
|
for (auto &c : line->chunks)
|
|
c.debugPrint();
|
|
|
|
D(9, "");
|
|
#endif
|
|
|
|
MacFontRun newchunk = line->chunks[ch];
|
|
MacTextLine newline;
|
|
|
|
// we have to inherit paragraphEnd from the origin line
|
|
newline.paragraphEnd = line->paragraphEnd;
|
|
|
|
newchunk.text = line->chunks[ch].text.substr(pos);
|
|
line->chunks[ch].text = line->chunks[ch].text.substr(0, pos);
|
|
line->paragraphEnd = true;
|
|
newline.chunks.push_back(newchunk);
|
|
|
|
for (uint i = ch + 1; i < line->chunks.size(); i++) {
|
|
newline.chunks.push_back(MacFontRun(line->chunks[i]));
|
|
}
|
|
for (uint i = line->chunks.size() - 1; i >= ch + 1; i--) {
|
|
line->chunks.pop_back();
|
|
}
|
|
line->width = -1; // Drop cache
|
|
|
|
_canvas._text[*row].width = -1; // flush the cache
|
|
|
|
_canvas._text.insert_at(*row + 1, newline);
|
|
|
|
(*row)++;
|
|
*col = 0;
|
|
|
|
_canvas.reshuffleParagraph(row, col, _defaultFormatting);
|
|
|
|
for (int i = 0; i < (int)_canvas._text.size(); i++) {
|
|
D(9, "** addNewLine line %d", i);
|
|
for (int j = 0; j < (int)_canvas._text[i].chunks.size(); j++) {
|
|
D(9, "[%d] \"%s\"",_canvas._text[i].chunks[j].text.size(), Common::toPrintable(_canvas._text[i].chunks[j].text.encode()).c_str());
|
|
}
|
|
}
|
|
D(9, "** addNewLine cursor row %d col %d", _cursorRow, _cursorCol);
|
|
|
|
_fullRefresh = true;
|
|
recalcDims();
|
|
render();
|
|
}
|
|
|
|
//////////////////
|
|
// Cursor stuff
|
|
static void cursorTimerHandler(void *refCon) {
|
|
MacText *w = (MacText *)refCon;
|
|
|
|
if (!w->_cursorOff)
|
|
w->_cursorState = !w->_cursorState;
|
|
|
|
w->_cursorDirty = true;
|
|
}
|
|
|
|
void MacText::updateCursorPos() {
|
|
if (_canvas._text.empty()) {
|
|
_cursorX = _cursorY = 0;
|
|
} else {
|
|
undrawCursor();
|
|
|
|
_cursorRow = MIN<int>(_cursorRow, _canvas._text.size() - 1);
|
|
|
|
int alignOffset = _canvas.getAlignOffset(_cursorRow);
|
|
|
|
_cursorY = _canvas._text[_cursorRow].y - _scrollPos;
|
|
_cursorX = _canvas.getLineWidth(_cursorRow, false, _cursorCol) + alignOffset;
|
|
}
|
|
|
|
int cursorHeight = getLineHeight(_cursorRow);
|
|
|
|
if (cursorHeight == 0)
|
|
cursorHeight = 12;
|
|
|
|
// Do not exceed max height and widget height
|
|
cursorHeight = MIN<int>(MIN<int>(cursorHeight, kCursorMaxHeight), _dims.height());
|
|
|
|
_cursorRect->setHeight(cursorHeight);
|
|
|
|
_cursorDirty = true;
|
|
}
|
|
|
|
void MacText::undrawCursor() {
|
|
_cursorDirty = true;
|
|
|
|
Common::Point offset(calculateOffset());
|
|
_composeSurface->blitFrom(*_cursorSurface2, *_cursorRect, Common::Point(_cursorX + offset.x, _cursorY + offset.y));
|
|
}
|
|
|
|
} // End of namespace Graphics
|