MuseScore/libmscore/text.cpp
2017-05-22 15:27:16 +02:00

3274 lines
109 KiB
C++

//=============================================================================
// MuseScore
// Music Composition & Notation
//
// Copyright (C) 2011-2014 Werner Schweer
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2
// as published by the Free Software Foundation and appearing in
// the file LICENCE.GPL
//=============================================================================
#include "text.h"
#include "jump.h"
#include "marker.h"
#include "score.h"
#include "segment.h"
#include "measure.h"
#include "system.h"
#include "box.h"
#include "page.h"
#include "textframe.h"
#include "sym.h"
#include "xml.h"
#include "undo.h"
#include "mscore.h"
namespace Ms {
static const qreal subScriptSize = 0.6;
static const qreal subScriptOffset = 0.5; // of x-height
static const qreal superScriptOffset = -.9; // of x-height
//static const qreal tempotextOffset = 0.4; // of x-height // 80% of 50% = 2 spatiums
//---------------------------------------------------------
// TextEditData
//---------------------------------------------------------
struct TextEditData : public ElementEditData {
TextCursor* cursor;
TextEditData() {
cursor = 0;
}
~TextEditData() {
}
};
//---------------------------------------------------------
// operator==
//---------------------------------------------------------
bool CharFormat::operator==(const CharFormat& cf) const
{
if (cf.bold() != bold()
|| cf.italic() != italic()
|| cf.underline() != underline()
|| cf.preedit() != preedit()
|| cf.valign() != valign()
|| cf.fontSize() != fontSize()
)
return false;
return cf.fontFamily() == fontFamily();
}
//---------------------------------------------------------
// clearSelection
//---------------------------------------------------------
void TextCursor::clearSelection()
{
_selectLine = _line;
_selectColumn = _column;
}
//---------------------------------------------------------
// init
//---------------------------------------------------------
void TextCursor::init()
{
_format.setFontFamily(_text->family());
_format.setFontSize(_text->size());
_format.setBold(_text->bold());
_format.setItalic(_text->italic());
_format.setUnderline(_text->underline());
_format.setPreedit(false);
_format.setValign(VerticalAlignment::AlignNormal);
}
//---------------------------------------------------------
// columns
//---------------------------------------------------------
int TextCursor::columns() const
{
return _text->textBlock(_line).columns();
}
//---------------------------------------------------------
// currentCharacter
//---------------------------------------------------------
QChar TextCursor::currentCharacter() const
{
const TextBlock& t = _text->_layout[line()];
QString s = t.text(column(), column());
if (s.isEmpty())
return QChar();
return s[0];
}
//---------------------------------------------------------
// updateCursorFormat
//---------------------------------------------------------
void TextCursor::updateCursorFormat()
{
TextBlock* block = &_text->_layout[line()];
int col = hasSelection() ? selectColumn() : column();
const CharFormat* format = block->formatAt(col);
if (format)
setFormat(*format);
else
init();
}
//---------------------------------------------------------
// TextFragment
//---------------------------------------------------------
TextFragment::TextFragment()
{
}
TextFragment::TextFragment(const QString& s)
{
text = s;
}
#if 0
TextFragment::TextFragment(TextCursor* cursor, SymId id)
{
format = *cursor->format();
ids.append(id);
}
#endif
TextFragment::TextFragment(TextCursor* cursor, const QString& s)
{
format = *cursor->format();
text = s;
}
//---------------------------------------------------------
// split
//---------------------------------------------------------
TextFragment TextFragment::split(int column)
{
int idx = 0;
int col = 0;
TextFragment f;
f.format = format;
for (const QChar& c : text) {
if (col == column) {
if (idx) {
if (idx < text.size()) {
f.text = text.mid(idx);
text = text.left(idx);
}
}
return f;
}
++idx;
if (c.isHighSurrogate())
continue;
++col;
}
return f;
}
//---------------------------------------------------------
// columns
//---------------------------------------------------------
int TextFragment::columns() const
{
int col = 0;
for (const QChar& c : text) {
if (c.isHighSurrogate())
continue;
++col;
}
return col;
}
//---------------------------------------------------------
// operator ==
//---------------------------------------------------------
bool TextFragment::operator ==(const TextFragment& f) const
{
return format == f.format && text == f.text;
}
//---------------------------------------------------------
// draw
//---------------------------------------------------------
void TextFragment::draw(QPainter* p, const Text* t) const
{
QFont f(font(t));
f.setPointSizeF(f.pointSizeF() * MScore::pixelRatio);
p->setFont(f);
p->drawText(pos, text);
}
//---------------------------------------------------------
// font
//---------------------------------------------------------
QFont TextFragment::font(const Text* t) const
{
QFont font;
qreal m = format.fontSize();
if (t->sizeIsSpatiumDependent())
m *= t->spatium() / SPATIUM20;
if (format.valign() != VerticalAlignment::AlignNormal)
m *= subScriptSize;
font.setUnderline(format.underline() || format.preedit());
font.setFamily(format.fontFamily());
font.setBold(format.bold());
font.setItalic(format.italic());
Q_ASSERT(m > 0.0);
font.setPointSizeF(m);
return font;
}
//---------------------------------------------------------
// draw
//---------------------------------------------------------
void TextBlock::draw(QPainter* p, const Text* t) const
{
p->translate(0.0, _y);
for (const TextFragment& f : _fragments)
f.draw(p, t);
p->translate(0.0, -_y);
}
//---------------------------------------------------------
// layout
//---------------------------------------------------------
void TextBlock::layout(Text* t)
{
_bbox = QRectF();
qreal x = 0.0;
_lineSpacing = 0.0;
qreal lm = 0.0;
qreal layoutWidth = 0;
Element* e = t->parent();
if (e && t->layoutToParentWidth()) {
layoutWidth = e->width();
switch(e->type()) {
case ElementType::HBOX:
case ElementType::VBOX:
case ElementType::TBOX: {
Box* b = static_cast<Box*>(e);
layoutWidth -= ((b->leftMargin() + b->rightMargin()) * DPMM);
lm = b->leftMargin() * DPMM;
}
break;
case ElementType::PAGE: {
Page* p = static_cast<Page*>(e);
layoutWidth -= (p->lm() + p->rm());
lm = p->lm();
}
break;
case ElementType::MEASURE: {
Measure* m = static_cast<Measure*>(e);
layoutWidth = m->bbox().width();
}
break;
default:
break;
}
}
if (_fragments.empty()) {
QFontMetricsF fm = t->fontMetrics();
_bbox.setRect(0.0, -fm.ascent(), 1.0, fm.descent());
_lineSpacing = fm.lineSpacing();
}
else {
for (TextFragment& f : _fragments) {
f.pos.setX(x);
QFontMetricsF fm(f.font(t), MScore::paintDevice());
if (f.format.valign() != VerticalAlignment::AlignNormal) {
qreal voffset = fm.xHeight() / subScriptSize; // use original height
if (f.format.valign() == VerticalAlignment::AlignSubScript)
voffset *= subScriptOffset;
else
voffset *= superScriptOffset;
f.pos.setY(voffset);
}
else
f.pos.setY(0.0);
qreal w = fm.width(f.text);
_bbox |= fm.tightBoundingRect(f.text).translated(f.pos);
x += w;
// _lineSpacing = (_lineSpacing == 0 || fm.lineSpacing() == 0) ? qMax(_lineSpacing, fm.lineSpacing()) : qMin(_lineSpacing, fm.lineSpacing());
_lineSpacing = qMax(_lineSpacing, fm.lineSpacing());
}
}
qreal rx;
if (t->align() & Align::RIGHT)
rx = layoutWidth-_bbox.right();
else if (t->align() & Align::HCENTER)
rx = (layoutWidth - (_bbox.left() + _bbox.right())) * .5;
else // Align::LEFT
rx = -_bbox.left();
rx += lm;
for (TextFragment& f : _fragments)
f.pos.rx() += rx;
_bbox.translate(rx, 0.0);
}
//---------------------------------------------------------
// xpos
//---------------------------------------------------------
qreal TextBlock::xpos(int column, const Text* t) const
{
int col = 0;
for (const TextFragment& f : _fragments) {
if (column == col)
return f.pos.x();
QFontMetricsF fm(f.font(t), MScore::paintDevice());
int idx = 0;
for (const QChar& c : f.text) {
++idx;
if (c.isHighSurrogate())
continue;
++col;
if (column == col)
return f.pos.x() + fm.width(f.text.left(idx));
}
}
return _bbox.x();
}
//---------------------------------------------------------
// fragment
//---------------------------------------------------------
const TextFragment* TextBlock::fragment(int column) const
{
if (_fragments.empty())
return 0;
int col = 0;
auto f = _fragments.begin();
for (; f != _fragments.end(); ++f) {
for (const QChar& c : f->text) {
if (c.isHighSurrogate())
continue;
if (column == col)
return &*f;
++col;
}
}
if (column == col)
return &*(f-1);
return 0;
}
//---------------------------------------------------------
// formatAt
//---------------------------------------------------------
const CharFormat* TextBlock::formatAt(int column) const
{
const TextFragment* f = fragment(column);
if (f)
return &(f->format);
return 0;
}
//---------------------------------------------------------
// boundingRect
//---------------------------------------------------------
QRectF TextBlock::boundingRect(int col1, int col2, const Text* t) const
{
qreal x1 = xpos(col1, t);
qreal x2 = xpos(col2, t);
return QRectF(x1, _bbox.y(), x2-x1, _bbox.height());
}
//---------------------------------------------------------
// columns
//---------------------------------------------------------
int TextBlock::columns() const
{
int col = 0;
for (const TextFragment& f : _fragments) {
for (const QChar& c : f.text) {
if (!c.isHighSurrogate())
++col;
}
}
return col;
}
//---------------------------------------------------------
// column
// Return nearest column for position x. X is in
// Text coordinate system
//---------------------------------------------------------
int TextBlock::column(qreal x, Text* t) const
{
int col = 0;
for (const TextFragment& f : _fragments) {
int idx = 0;
if (x <= f.pos.x())
return col;
qreal px = 0.0;
for (const QChar& c : f.text) {
++idx;
if (c.isHighSurrogate())
continue;
QFontMetricsF fm(f.font(t), MScore::paintDevice());
qreal xo = fm.width(f.text.left(idx));
if (x <= f.pos.x() + px + (xo-px)*.5)
return col;
++col;
px = xo;
}
}
return col;
}
//---------------------------------------------------------
// insert
//---------------------------------------------------------
void TextBlock::insert(TextCursor* cursor, const QString& s)
{
int rcol, ridx;
auto i = fragment(cursor->column(), &rcol, &ridx);
if (i != _fragments.end()) {
if (!(i->format == *cursor->format())) {
if (rcol == 0)
_fragments.insert(i, TextFragment(cursor, s));
else {
TextFragment f2 = i->split(rcol);
i = _fragments.insert(i+1, TextFragment(cursor, s));
_fragments.insert(i+1, f2);
}
}
else
i->text.insert(ridx, s);
}
else {
if (!_fragments.empty() && _fragments.back().format == *cursor->format())
_fragments.back().text.append(s);
else
_fragments.append(TextFragment(cursor, s));
}
}
#if 0
void TextBlock::insert(TextCursor* cursor, SymId id)
{
int rcol, ridx;
auto i = fragment(cursor->column(), &rcol, &ridx);
if (i != _fragments.end()) {
if (rcol == 0) {
if (i != _fragments.begin() && (i-1)->format == *cursor->format())
(i-1)->ids.append(id);
else
_fragments.insert(i, TextFragment(cursor, id));
}
else {
TextFragment f2 = i->split(rcol);
i = _fragments.insert(i+1, TextFragment(cursor, id));
_fragments.insert(i+1, f2);
}
}
else
_fragments.append(TextFragment(cursor, id));
}
#endif
//---------------------------------------------------------
// fragment
// inputs:
// column is the column relative to the start of the TextBlock.
// outputs:
// rcol will be the column relative to the start of the TextFragment that the input column is in.
// ridx will be the QChar index into TextFragment's text QString relative to the start of that TextFragment.
//
//---------------------------------------------------------
QList<TextFragment>::iterator TextBlock::fragment(int column, int* rcol, int* ridx)
{
int col = 0;
for (auto i = _fragments.begin(); i != _fragments.end(); ++i) {
*rcol = 0;
*ridx = 0;
for (const QChar& c : i->text) {
if (col == column)
return i;
++*ridx;
if (c.isHighSurrogate())
continue;
++col;
++*rcol;
}
}
return _fragments.end();
}
//---------------------------------------------------------
// remove
//---------------------------------------------------------
QString TextBlock::remove(int column)
{
int col = 0;
QString s;
for (auto i = _fragments.begin(); i != _fragments.end(); ++i) {
int idx = 0;
int rcol = 0;
for (const QChar& c : i->text) {
if (col == column) {
if (c.isSurrogate()) {
s = i->text.mid(idx, 2);
i->text.remove(idx, 2);
}
else {
s = i->text.mid(idx, 1);
i->text.remove(idx, 1);
}
if (i->text.isEmpty())
_fragments.erase(i);
simplify();
return s;
}
++idx;
if (c.isHighSurrogate())
continue;
++col;
++rcol;
}
}
return s;
// qDebug("TextBlock::remove: column %d not found", column);
}
//---------------------------------------------------------
// simplify
//---------------------------------------------------------
void TextBlock::simplify()
{
if (_fragments.size() < 2)
return;
auto i = _fragments.begin();
TextFragment* f = &*i;
++i;
for (; i != _fragments.end(); ++i) {
while (i != _fragments.end() && (i->format == f->format)) {
f->text.append(i->text);
i = _fragments.erase(i);
}
if (i == _fragments.end())
break;
f = &*i;
}
}
//---------------------------------------------------------
// remove
//---------------------------------------------------------
QString TextBlock::remove(int start, int n)
{
if (n == 0)
return QString();
int col = 0;
QString s;
for (auto i = _fragments.begin(); i != _fragments.end();) {
int rcol = 0;
bool inc = true;
for( int idx = 0; idx < i->text.length(); ) {
QChar c = i->text[idx];
if (col == start) {
if (c.isHighSurrogate()) {
s += c;
i->text.remove(idx, 1);
c = i->text[idx];
}
s += c;
i->text.remove(idx, 1);
if (i->text.isEmpty() && (_fragments.size() > 1)) {
i = _fragments.erase(i);
inc = false;
}
--n;
if (n == 0)
return s;
continue;
}
++idx;
if (c.isHighSurrogate())
continue;
++col;
++rcol;
}
if (inc)
++i;
}
return s;
}
//---------------------------------------------------------
// changeFormat
//---------------------------------------------------------
void TextBlock::changeFormat(FormatId id, QVariant data, int start, int n)
{
int col = 0;
for (auto i = _fragments.begin(); i != _fragments.end(); ++i) {
int columns = i->columns();
if (start + n <= col)
break;
if (start >= col + columns) {
col += i->columns();
continue;
}
int endCol = col + columns;
if ((start <= col) && (start < endCol) && ((start+n) < endCol)) {
// left
TextFragment f = i->split(start + n - col);
i->changeFormat(id, data);
i = _fragments.insert(i+1, f);
}
else if (start > col && ((start+n) < endCol)) {
// middle
TextFragment lf = i->split(start+n - col);
TextFragment mf = i->split(start - col);
mf.changeFormat(id, data);
i = _fragments.insert(i+1, mf);
i = _fragments.insert(i+1, lf);
}
else if (start > col) {
// right
TextFragment f = i->split(start - col);
f.changeFormat(id, data);
i = _fragments.insert(i+1, f);
}
else {
// complete fragment
i->changeFormat(id, data);
}
col = endCol;
}
}
//---------------------------------------------------------
// setFormat
//---------------------------------------------------------
void CharFormat::setFormat(FormatId id, QVariant data)
{
switch (id) {
case FormatId::Bold:
_bold = data.toBool();
break;
case FormatId::Italic:
_italic = data.toBool();
break;
case FormatId::Underline:
_underline = data.toBool();
break;
case FormatId::Valign:
_valign = static_cast<VerticalAlignment>(data.toInt());
break;
case FormatId::FontSize:
_fontSize = data.toDouble();
break;
case FormatId::FontFamily:
_fontFamily = data.toString();
break;
}
}
//---------------------------------------------------------
// changeFormat
//---------------------------------------------------------
void TextFragment::changeFormat(FormatId id, QVariant data)
{
format.setFormat(id, data);
}
//---------------------------------------------------------
// split
//---------------------------------------------------------
TextBlock TextBlock::split(int column)
{
TextBlock tl;
int col = 0;
for (auto i = _fragments.begin(); i != _fragments.end(); ++i) {
int idx = 0;
for (const QChar& c : i->text) {
if (col == column) {
if (idx) {
if (idx < i->text.size()) {
TextFragment tf(i->text.mid(idx));
tf.format = i->format;
tl._fragments.append(tf);
i->text = i->text.left(idx);
++i;
}
}
for (; i != _fragments.end(); i = _fragments.erase(i))
tl._fragments.append(*i);
return tl;
}
++idx;
if (c.isHighSurrogate())
continue;
++col;
}
}
TextFragment tf("");
if (_fragments.size() > 0)
tf.format = _fragments.last().format;
tl._fragments.append(tf);
return tl;
}
//---------------------------------------------------------
// text
// extract text, symbols are marked with <sym>xxx</sym>
//---------------------------------------------------------
QString TextBlock::text(int col1, int len) const
{
QString s;
int col = 0;
for (auto f : _fragments) {
if (f.text.isEmpty())
continue;
for (const QChar& c : f.text) {
if (col >= col1 && (len < 0 || ((col-col1) < len)))
s += XmlWriter::xmlString(c.unicode());
if (!c.isHighSurrogate())
++col;
}
}
return s;
}
//---------------------------------------------------------
// Text
//---------------------------------------------------------
Text::Text(Score* s)
: Element(s)
{
_size = 10.0;
initSubStyle(SubStyle::DEFAULT); // we assume all properties are set
setFlag(ElementFlag::MOVABLE, true);
}
Text::Text(SubStyle st, Score* s)
: Element(s)
{
_family = "FreeSerif";
_size = 10.0;
_bold = false;
_italic = false;
_underline = false;
_bgColor = QColor(255, 255, 255, 0);
_frameColor = QColor(0, 0, 0, 255);
_align = Align::LEFT;
_hasFrame = false;
_circle = false;
_square = false;
_sizeIsSpatiumDependent = true;
_frameWidth = Spatium(0.1);
_paddingWidth = Spatium(0.2);
_frameRound = 0;
_offset = QPointF();
_offsetType = OffsetType::SPATIUM;
initSubStyle(st);
setFlag(ElementFlag::MOVABLE, true);
}
Text::Text(const Text& st)
: Element(st)
{
_text = st._text;
_layout = st._layout;
textInvalid = st.textInvalid;
layoutInvalid = st.layoutInvalid;
frame = st.frame;
_subStyle = st._subStyle;
_layoutToParentWidth = st._layoutToParentWidth;
// _editMode = false;
hexState = -1;
_family = st._family;
_size = st._size;
_bold = st._bold;
_italic = st._italic;
_underline = st._underline;
_bgColor = st._bgColor;
_frameColor = st._frameColor;
_align = st._align;
_hasFrame = st._hasFrame;
_circle = st._circle;
_square = st._square;
_sizeIsSpatiumDependent = st._sizeIsSpatiumDependent;
_frameWidth = st._frameWidth;
_paddingWidth = st._paddingWidth;
_frameRound = st._frameRound;
_offset = st._offset;
_offsetType = st._offsetType;
_familyStyle = st._familyStyle;
_sizeStyle = st._sizeStyle;
_boldStyle = st._boldStyle;
_italicStyle = st._italicStyle;
_underlineStyle = st._underlineStyle;
_bgColorStyle = st._bgColorStyle;
_frameColorStyle = st._frameColorStyle;
_alignStyle = st._alignStyle;
_hasFrameStyle = st._hasFrameStyle;
_circleStyle = st._circleStyle;
_squareStyle = st._squareStyle;
_sizeIsSpatiumDependentStyle = st._sizeIsSpatiumDependentStyle;
_frameWidthStyle = st._frameWidthStyle;
_paddingWidthStyle = st._paddingWidthStyle;
_frameRoundStyle = st._frameRoundStyle;
_offsetStyle = st._offsetStyle;
_offsetTypeStyle = st._offsetTypeStyle;
}
Text::~Text()
{
}
//---------------------------------------------------------
// drawSelection
//---------------------------------------------------------
void Text::drawSelection(QPainter* p, const QRectF& r) const
{
QBrush bg(QColor("steelblue"));
p->setCompositionMode(QPainter::CompositionMode_HardLight);
p->setBrush(bg);
p->setPen(Qt::NoPen);
p->drawRect(r);
p->setCompositionMode(QPainter::CompositionMode_SourceOver);
p->setPen(textColor());
}
//---------------------------------------------------------
// cursorRect
//---------------------------------------------------------
QRectF TextCursor::cursorRect() const
{
const TextBlock& tline = curLine();
const TextFragment* fragment = tline.fragment(column());
QFont _font = fragment ? fragment->font(_text) : _text->font();
qreal ascent = QFontMetricsF(_font, MScore::paintDevice()).ascent();
qreal h = ascent;
qreal x = tline.xpos(column(), _text);
qreal y = tline.y() - ascent * .9;
return QRectF(x, y, 4.0, h);
}
//---------------------------------------------------------
// textColor
//---------------------------------------------------------
QColor Text::textColor() const
{
if (score() && !score()->printing()) {
if (selected())
return (track() > -1) ? MScore::selectColor[voice()] : MScore::selectColor[0];
if (!visible())
return Qt::gray;
}
return color();
}
//---------------------------------------------------------
// insert
// version for Supplementary Unicode, which must be inputted together as a pair
//---------------------------------------------------------
void Text::insert(TextCursor* cursor, QChar highSurrogate, QChar lowSurrogate)
{
if (cursor->line() >= _layout.size())
_layout.append(TextBlock());
QString surrogatePair = QString(highSurrogate).append(lowSurrogate);
_layout[cursor->line()].insert(cursor, surrogatePair);
cursor->setColumn(cursor->column() + 1);
cursor->clearSelection();
}
//---------------------------------------------------------
// insert
// version for Basic Unicode
//---------------------------------------------------------
void Text::insert(TextCursor* cursor, QChar c)
{
if (cursor->line() >= _layout.size())
_layout.append(TextBlock());
if (c == QChar::Tabulation)
c = QChar::Space;
else if (c == QChar::LineFeed) {
_layout[cursor->line()].setEol(true);
cursor->setLine(cursor->line() + 1);
cursor->setColumn(0);
if (_layout.size() <= cursor->line())
_layout.append(TextBlock());
}
else {
_layout[cursor->line()].insert(cursor, QString(c));
cursor->setColumn(cursor->column() + 1);
}
cursor->clearSelection();
}
//---------------------------------------------------------
// insert
// version for SMUFL symbols
//---------------------------------------------------------
#if 0
void Text::insert(TextCursor* cursor, SymId id)
{
if (cursor->line() >= _layout.size())
_layout.append(TextBlock());
_layout[cursor->line()].insert(cursor, id);
cursor->setColumn(cursor->column() + 1);
cursor->clearSelection();
}
#endif
//---------------------------------------------------------
// parseStringProperty
//---------------------------------------------------------
static QString parseStringProperty(const QString& s)
{
QString rs;
for (const QChar& c : s) {
if (c == '"')
break;
rs += c;
}
return rs;
}
//---------------------------------------------------------
// parseNumProperty
//---------------------------------------------------------
static qreal parseNumProperty(const QString& s)
{
return parseStringProperty(s).toDouble();
}
//---------------------------------------------------------
// createLayout
// create layout from text
//---------------------------------------------------------
void Text::createLayout()
{
_layout.clear();
TextCursor cursor((Text*)this);
cursor.init();
int state = 0;
QString token;
QString sym;
bool symState = false;
for (int i = 0; i < _text.length(); i++) {
const QChar& c = _text[i];
if (state == 0) {
if (c == '<') {
state = 1;
token.clear();
}
else if (c == '&') {
state = 2;
token.clear();
}
else {
if (symState)
sym += c;
else {
if (c.isHighSurrogate()) {
const QChar& highSurrogate = c;
i++;
Q_ASSERT(i < _text.length());
const QChar& lowSurrogate = _text[i];
insert(&cursor, highSurrogate, lowSurrogate);
}
else
insert(&cursor, c);
}
}
}
else if (state == 1) {
if (c == '>') {
state = 0;
if (token == "b")
cursor.format()->setBold(true);
else if (token == "/b")
cursor.format()->setBold(false);
else if (token == "i")
cursor.format()->setItalic(true);
else if (token == "/i")
cursor.format()->setItalic(false);
else if (token == "u")
cursor.format()->setUnderline(true);
else if (token == "/u")
cursor.format()->setUnderline(false);
else if (token == "sub")
cursor.format()->setValign(VerticalAlignment::AlignSubScript);
else if (token == "/sub")
cursor.format()->setValign(VerticalAlignment::AlignNormal);
else if (token == "sup")
cursor.format()->setValign(VerticalAlignment::AlignSuperScript);
else if (token == "/sup")
cursor.format()->setValign(VerticalAlignment::AlignNormal);
else if (token == "sym") {
symState = true;
sym.clear();
}
else if (token == "/sym") {
symState = false;
QString sfn = score()->styleSt(StyleIdx::MusicalTextFont);
cursor.format()->setFontFamily(sfn);
SymId id = Sym::name2id(sym);
const Sym& sym = score()->scoreFont()->sym(id);
int code = sym.code();
if (code & 0xffff0000)
insert(&cursor, QChar(QChar::highSurrogate(code)), QChar(QChar::lowSurrogate(code)));
else
insert(&cursor, QChar(code));
}
else if (token.startsWith("font ")) {
token = token.mid(5);
if (token.startsWith("size=\""))
cursor.format()->setFontSize(parseNumProperty(token.mid(6)));
else if (token.startsWith("face=\"")) {
QString face = parseStringProperty(token.mid(6));
face = unEscape(face);
cursor.format()->setFontFamily(face);
}
else
qDebug("cannot parse html property <%s>", qPrintable(token));
}
}
else
token += c;
}
else if (state == 2) {
if (c == ';') {
state = 0;
if (token == "lt")
insert(&cursor, '<');
else if (token == "gt")
insert(&cursor, '>');
else if (token == "amp")
insert(&cursor, '&');
else if (token == "quot")
insert(&cursor, '"');
else {
// TODO insert(&cursor, Sym::name2id(token));
}
}
else
token += c;
}
}
layoutInvalid = false;
}
//---------------------------------------------------------
// layout
//---------------------------------------------------------
void Text::layout()
{
QPointF o(_offset * (_offsetType == OffsetType::SPATIUM ? spatium() : DPI));
setPos(o);
layout1();
adjustReadPos();
}
//---------------------------------------------------------
// layout1
//---------------------------------------------------------
void Text::layout1()
{
if (layoutInvalid)
createLayout();
if (_layout.empty())
_layout.append(TextBlock());
QRectF bb;
qreal y = 0;
for (int i = 0; i < _layout.size(); ++i) {
TextBlock* t = &_layout[i];
t->layout(this);
const QRectF* r = &t->boundingRect();
if (r->height() == 0)
r = &_layout[i-i].boundingRect();
y += t->lineSpacing();
t->setY(y);
bb |= r->translated(0.0, y);
}
qreal yoff = 0;
qreal h = 0;
if (parent()) {
if (layoutToParentWidth()) {
if (parent()->isTBox()) {
// hack: vertical alignment is always TOP
_align = Align(((char)_align) & ((char)Align::HMASK)) | Align::TOP;
}
else if (parent()->isBox()) {
// consider inner margins of frame
Box* b = toBox(parent());
yoff = b->topMargin() * DPMM;
h = b->height() - yoff - b->bottomMargin() * DPMM;
}
else if (parent()->isPage()) {
Page* p = toPage(parent());
h = p->height() - p->tm() - p->bm();
yoff = p->tm();
}
else if (parent()->isMeasure())
;
else
h = parent()->height();
}
}
else
setPos(QPointF());
if (align() & Align::BOTTOM)
yoff += h - bb.bottom();
else if (align() & Align::VCENTER)
yoff += (h - (bb.top() + bb.bottom())) * .5;
else if (align() & Align::BASELINE)
yoff += h * .5 - _layout.front().lineSpacing();
else
yoff += -bb.top();
for (TextBlock& t : _layout)
t.setY(t.y() + yoff);
bb.translate(0.0, yoff);
#if 0
if (_editMode)
bb |= cursorRect();
#endif
setbbox(bb);
if (hasFrame())
layoutFrame();
}
//---------------------------------------------------------
// layoutFrame
//---------------------------------------------------------
void Text::layoutFrame()
{
frame = bbox();
if (square()) {
#if 0
// "real" square
if (frame.width() > frame.height()) {
qreal w = frame.width() - frame.height();
frame.adjust(0.0, -w * .5, 0.0, w * .5);
}
else {
qreal w = frame.height() - frame.width();
frame.adjust(-w * .5, 0.0, w * .5, 0.0);
}
#endif
// make sure width >= height
if (frame.height() > frame.width()) {
qreal w = frame.height() - frame.width();
frame.adjust(-w * .5, 0.0, w * .5, 0.0);
}
}
if (circle()) {
if (frame.width() > frame.height()) {
frame.setY(frame.y() + (frame.width() - frame.height()) * -.5);
frame.setHeight(frame.width());
}
else {
frame.setX(frame.x() + (frame.height() - frame.width()) * -.5);
frame.setWidth(frame.height());
}
}
qreal _spatium = spatium();
qreal w = (paddingWidth() + frameWidth() * .5f).val() * _spatium;
frame.adjust(-w, -w, w, w);
w = frameWidth().val() * _spatium;
setbbox(frame.adjusted(-w, -w, w, w));
}
//---------------------------------------------------------
// lineSpacing
//---------------------------------------------------------
qreal Text::lineSpacing() const
{
return fontMetrics().lineSpacing() * MScore::pixelRatio;
}
//---------------------------------------------------------
// lineHeight
//---------------------------------------------------------
qreal Text::lineHeight() const
{
return fontMetrics().height();
}
//---------------------------------------------------------
// baseLine
//---------------------------------------------------------
qreal Text::baseLine() const
{
return fontMetrics().ascent();
}
//---------------------------------------------------------
// XmlNesting
//---------------------------------------------------------
class XmlNesting : public QStack<QString> {
QString* _s;
public:
XmlNesting(QString* s) { _s = s; }
void pushToken(const QString& t) {
*_s += "<";
*_s += t;
*_s += ">";
push(t);
}
void pushB() { pushToken("b"); }
void pushI() { pushToken("i"); }
void pushU() { pushToken("u"); }
QString popToken() {
QString s = pop();
*_s += "</";
*_s += s;
*_s += ">";
return s;
}
void popToken(const char* t) {
QStringList ps;
for (;;) {
QString s = popToken();
if (s == t)
break;
ps += s;
}
for (const QString& s : ps)
pushToken(s);
}
void popB() { popToken("b"); }
void popI() { popToken("i"); }
void popU() { popToken("u"); }
};
//---------------------------------------------------------
// genText
//---------------------------------------------------------
void Text::genText()
{
_text.clear();
bool _bold = false;
bool _italic = false;
bool _underline = false;
for (const TextBlock& block : _layout) {
for (const TextFragment& f : block.fragments()) {
if (!f.format.bold() && bold())
_bold = true;
if (!f.format.italic() && italic())
_italic = true;
if (!f.format.underline() && underline())
_underline = true;
}
}
TextCursor cursor(this);
cursor.init();
XmlNesting xmlNesting(&_text);
if (_bold)
xmlNesting.pushB();
if (_italic)
xmlNesting.pushI();
if (_underline)
xmlNesting.pushU();
for (const TextBlock& block : _layout) {
for (const TextFragment& f : block.fragments()) {
if (f.text.isEmpty()) // skip empty fragments, not to
continue; // insert extra HTML formatting
const CharFormat& format = f.format;
if (cursor.format()->bold() != format.bold()) {
if (format.bold())
xmlNesting.pushB();
else
xmlNesting.popB();
}
if (cursor.format()->italic() != format.italic()) {
if (format.italic())
xmlNesting.pushI();
else
xmlNesting.popI();
}
if (cursor.format()->underline() != format.underline()) {
if (format.underline())
xmlNesting.pushU();
else
xmlNesting.popU();
}
if (format.fontSize() != cursor.format()->fontSize())
_text += QString("<font size=\"%1\"/>").arg(format.fontSize());
if (format.fontFamily() != cursor.format()->fontFamily())
_text += QString("<font face=\"%1\"/>").arg(Text::escape(format.fontFamily()));
VerticalAlignment va = format.valign();
VerticalAlignment cva = cursor.format()->valign();
if (cva != va) {
switch (va) {
case VerticalAlignment::AlignNormal:
xmlNesting.popToken(cva == VerticalAlignment::AlignSuperScript ? "sup" : "sub");
break;
case VerticalAlignment::AlignSuperScript:
xmlNesting.pushToken("sup");
break;
case VerticalAlignment::AlignSubScript:
xmlNesting.pushToken("sub");
break;
}
}
_text += XmlWriter::xmlString(f.text);
cursor.setFormat(format);
}
if (block.eol())
_text += QChar::LineFeed;
}
while (!xmlNesting.empty())
xmlNesting.popToken();
textInvalid = false;
}
//---------------------------------------------------------
// startEdit
//---------------------------------------------------------
void Text::startEdit(EditData& ed)
{
TextEditData* ted = new TextEditData();
ted->e = this;
ted->cursor = new TextCursor(this);
ted->cursor->setText(this);
ted->cursor->setLine(0);
ted->cursor->setColumn(0);
ted->cursor->clearSelection();
if (!ted->cursor->set(ed.startMove))
ted->cursor->init();
ed.addData(ted);
if (layoutInvalid)
layout();
}
//---------------------------------------------------------
// endEdit
//---------------------------------------------------------
void Text::endEdit(EditData&)
{
// printf("%p Text::endEdit\n", this);
static const qreal w = 2.0;
score()->addRefresh(canvasBoundingRect().adjusted(-w, -w, w, w));
}
//---------------------------------------------------------
// curLine
// return the current text line in edit mode
//---------------------------------------------------------
TextBlock& TextCursor::curLine() const
{
return _text->_layout[_line];
}
//---------------------------------------------------------
// editInsertText
//---------------------------------------------------------
void Text::editInsertText(TextCursor* cursor, const QString& s)
{
textInvalid = true;
if (s.size() == 1 && (s[0] == QChar::CarriageReturn)) {
int line = cursor->line();
_layout.insert(line + 1, cursor->curLine().split(cursor->column()));
_layout[line].setEol(true);
if (_layout.last() != _layout[line+1])
_layout[line+1].setEol(true);
}
else if (s.size() == 1 && (s[0].unicode() == 0x7f))
cursor->deleteChar();
else {
cursor->curLine().insert(cursor, s);
cursor->setColumn(cursor->column() + s.size());
cursor->clearSelection();
}
triggerLayout();
}
//---------------------------------------------------------
// endHexState
//---------------------------------------------------------
void Text::endHexState()
{
#if 0
if (hexState >= 0) {
if (hexState > 0) {
int c2 = _cursor->column();
int c1 = c2 - (hexState + 1);
TextBlock& t = _layout[_cursor->line()];
QString ss = t.remove(c1, hexState + 1);
bool ok;
int code = ss.mid(1).toInt(&ok, 16);
_cursor->setColumn(c1);
_cursor->clearSelection();
if (ok)
editInsertText(_cursor, QString(code));
else
qDebug("cannot convert hex string <%s>, state %d (%d-%d)",
qPrintable(ss.mid(1)), hexState, c1, c2);
}
hexState = -1;
}
#endif
}
//---------------------------------------------------------
// selectAll
//---------------------------------------------------------
void Text::selectAll(TextCursor* _cursor)
{
_cursor->setSelectLine(0);
_cursor->setSelectColumn(0);
_cursor->setLine(_layout.size() - 1);
_cursor->setColumn(_cursor->curLine().columns());
}
//---------------------------------------------------------
// movePosition
//---------------------------------------------------------
bool TextCursor::movePosition(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode, int count)
{
for (int i = 0; i < count; i++) {
switch (op) {
case QTextCursor::Left:
if (hasSelection() && mode == QTextCursor::MoveAnchor) {
int r1 = selectLine();
int r2 = line();
int c1 = selectColumn();
int c2 = column();
if (r1 > r2) {
qSwap(r1, r2);
qSwap(c1, c2);
}
else if (r1 == r2) {
if (c1 > c2)
qSwap(c1, c2);
}
clearSelection();
setLine(r1);
setColumn(c1);
}
else if (column() == 0) {
if (line() == 0)
return false;
setLine(line()-1);
setColumn(curLine().columns());
}
else
setColumn(column()-1);
break;
case QTextCursor::Right:
if (hasSelection() && mode == QTextCursor::MoveAnchor) {
int r1 = selectLine();
int r2 = line();
int c1 = selectColumn();
int c2 = column();
if (r1 > r2) {
qSwap(r1, r2);
qSwap(c1, c2);
}
else if (r1 == r2) {
if (c1 > c2)
qSwap(c1, c2);
}
clearSelection();
setLine(r2);
setColumn(c2);
}
else if (column() >= curLine().columns()) {
if (line() >= _text->_layout.size()-1)
return false;
setLine(line()+1);
setColumn(0);
}
else
setColumn(column()+1);
break;
case QTextCursor::Up:
if (line() == 0)
return false;
setLine(line()-1);
if (column() > curLine().columns())
setColumn(curLine().columns());
break;
case QTextCursor::Down:
if (line() >= _text->_layout.size()-1)
return false;
setLine(line()+1);
if (column() > curLine().columns())
setColumn(curLine().columns());
break;
case QTextCursor::Start:
setLine(0);
setColumn(0);
break;
case QTextCursor::End:
setLine(_text->_layout.size() - 1);
setColumn(curLine().columns());
break;
case QTextCursor::StartOfLine:
setColumn(0);
break;
case QTextCursor::EndOfLine:
setColumn(curLine().columns());
break;
case QTextCursor::WordLeft:
if (column() > 0) {
setColumn(column()-1);
while (column() > 0 && currentCharacter().isSpace())
setColumn(column()-1);
while (column() > 0 && !currentCharacter().isSpace())
setColumn(column()-1);
if (currentCharacter().isSpace())
setColumn(column()+1);
}
break;
case QTextCursor::NextWord: {
int cols = columns();
if (column() < cols) {
setColumn(column() + 1);
while (column() < cols && !currentCharacter().isSpace())
setColumn(column()+1);
while (column() < cols && currentCharacter().isSpace())
setColumn(column()+1);
}
}
break;
default:
qDebug("Text::movePosition: not implemented");
return false;
}
if (mode == QTextCursor::MoveAnchor)
clearSelection();
}
updateCursorFormat();
_text->score()->addRefresh(_text->canvasBoundingRect());
return true;
}
//---------------------------------------------------------
// set
//---------------------------------------------------------
bool TextCursor::set(const QPointF& p, QTextCursor::MoveMode mode)
{
QPointF pt = p - _text->canvasPos();
if (!_text->bbox().contains(pt))
return false;
setLine(0);
for (int row = 0; row < _text->_layout.size(); ++row) {
const TextBlock& l = _text->_layout.at(row);
if (l.y() > pt.y()) {
setLine(row);
break;
}
}
setColumn(curLine().column(pt.x(), _text));
// printf("cursor set col %d\n", _column);
_text->score()->setUpdateAll();
if (mode == QTextCursor::MoveAnchor)
clearSelection();
if (hasSelection())
QApplication::clipboard()->setText(selectedText(), QClipboard::Selection);
updateCursorFormat();
return true;
}
//---------------------------------------------------------
// selectedText
// return current selection
//---------------------------------------------------------
QString TextCursor::selectedText() const
{
QString s;
int r1 = selectLine();
int r2 = line();
int c1 = selectColumn();
int c2 = column();
if (r1 > r2) {
qSwap(r1, r2);
qSwap(c1, c2);
}
else if (r1 == r2) {
if (c1 > c2)
qSwap(c1, c2);
}
int rows = _text->_layout.size();
for (int row = 0; row < rows; ++row) {
const TextBlock& t = _text->_layout.at(row);
if (row >= r1 && row <= r2) {
if (row == r1 && r1 == r2)
s += t.text(c1, c2 - c1);
else if (row == r1) {
s += t.text(c1, -1);
s += "\n";
}
else if (row == r2)
s += t.text(0, c2);
else {
s += t.text(0, -1);
s += "\n";
}
}
}
return s;
}
//---------------------------------------------------------
// deleteSelectedText
//---------------------------------------------------------
bool Text::deleteSelectedText(EditData& ed)
{
TextCursor* _cursor = cursor(ed);
if (!_cursor->hasSelection())
return false;
int r1 = _cursor->selectLine();
int c1 = _cursor->selectColumn();
#if 0
int r2 = _cursor->line();
int c2 = _cursor->column();
if (r1 > r2) {
qSwap(r1, r2);
qSwap(c1, c2);
}
else if (r1 == r2) {
if (c1 > c2)
qSwap(c1, c2);
}
int rows = _layout.size();
for (int row = 0; row < rows; ++row) {
TextBlock& t = _layout[row];
if (row >= r1 && row <= r2) {
if (row == r1 && r1 == r2)
t.remove(c1, c2 - c1);
else if (row == r1)
t.remove(c1, t.columns() - c1);
else if (row == r2)
t.remove(0, c2);
}
}
if (r1 != r2) {
TextBlock& l1 = _layout[r1];
const TextBlock& l2 = _layout[r2];
for (const TextFragment& f : l2.fragments())
l1.fragments().append(f);
_layout.erase(_layout.begin() + r1 + 1, _layout.begin() + r2 + 1);
if (_layout.last() == l1)
l1.setEol(false);
}
#endif
_cursor->setLine(r1);
_cursor->setColumn(c1);
_cursor->clearSelection();
return true;
}
//---------------------------------------------------------
// write
//---------------------------------------------------------
void Text::write(XmlWriter& xml) const
{
xml.stag(name());
writeProperties(xml, true, true);
xml.etag();
}
//---------------------------------------------------------
// read
//---------------------------------------------------------
void Text::read(XmlReader& e)
{
while (e.readNextStartElement()) {
if (!readProperties(e))
e.unknown();
}
}
static const std::array<P_ID, 18> pids { {
P_ID::SUB_STYLE,
P_ID::FONT_FACE,
P_ID::FONT_SIZE,
P_ID::FONT_BOLD,
P_ID::FONT_ITALIC,
P_ID::FONT_UNDERLINE,
P_ID::FRAME,
P_ID::FRAME_SQUARE,
P_ID::FRAME_CIRCLE,
P_ID::FRAME_WIDTH,
P_ID::FRAME_PADDING,
P_ID::FRAME_ROUND,
P_ID::FRAME_FG_COLOR,
P_ID::FRAME_BG_COLOR,
P_ID::FONT_SPATIUM_DEPENDENT,
P_ID::ALIGN,
P_ID::OFFSET,
P_ID::OFFSET_TYPE
} };
//---------------------------------------------------------
// writeProperties
//---------------------------------------------------------
void Text::writeProperties(XmlWriter& xml, bool writeText, bool /*writeStyle*/) const
{
Element::writeProperties(xml);
for (P_ID i :pids)
writeProperty(xml, i);
if (writeText)
xml.writeXml("text", xmlText());
}
//---------------------------------------------------------
// readProperties
//---------------------------------------------------------
bool Text::readProperties(XmlReader& e)
{
const QStringRef& tag(e.name());
if (tag == "style") {
SubStyle s = subStyleFromName(e.readElementText());
initSubStyle(s);
return true;
}
for (P_ID i :pids) {
if (readProperty(tag, e, i)) {
setPropertyFlags(i, PropertyFlags::UNSTYLED);
return true;
}
}
if (tag == "text")
//_text = e.readXml();
setXmlText(e.readXml());
else if (!Element::readProperties(e))
return false;
return true;
}
//---------------------------------------------------------
// insertText
// insert text at cursor position and move cursor
//---------------------------------------------------------
void Text::insertText(EditData& ed, const QString& s)
{
TextCursor* _cursor = cursor(ed);
deleteSelectedText(ed);
_cursor->curLine().insert(_cursor, s);
_cursor->setColumn(_cursor->column() + s.size());
_cursor->clearSelection();
}
//---------------------------------------------------------
// insertSym
//---------------------------------------------------------
#if 0
void TextCursor::insertSym(SymId id)
{
curLine().insert(this, id);
setColumn(column() + 1);
clearSelection();
_text->layout();
}
#endif
//---------------------------------------------------------
// insertSym
//---------------------------------------------------------
void Text::insertSym(EditData& ed, SymId id)
{
deleteSelectedText(ed);
QString s = score()->scoreFont()->toString(id);
score()->undo(new InsertText(cursor(ed), s), &ed);
}
//---------------------------------------------------------
// pageRectangle
//---------------------------------------------------------
QRectF Text::pageRectangle() const
{
if (parent() && (parent()->type() == ElementType::HBOX || parent()->type() == ElementType::VBOX || parent()->type() == ElementType::TBOX)) {
Box* box = static_cast<Box*>(parent());
QRectF r = box->abbox();
qreal x = r.x() + box->leftMargin() * DPMM;
qreal y = r.y() + box->topMargin() * DPMM;
qreal h = r.height() - (box->topMargin() + box->bottomMargin()) * DPMM;
qreal w = r.width() - (box->leftMargin() + box->rightMargin()) * DPMM;
// QSizeF ps = _doc->pageSize();
// return QRectF(x, y, ps.width(), ps.height());
return QRectF(x, y, w, h);
}
if (parent() && parent()->type() == ElementType::PAGE) {
Page* box = static_cast<Page*>(parent());
QRectF r = box->abbox();
qreal x = r.x() + box->lm();
qreal y = r.y() + box->tm();
qreal h = r.height() - box->tm() - box->bm();
qreal w = r.width() - box->lm() - box->rm();
return QRectF(x, y, w, h);
}
return abbox();
}
//---------------------------------------------------------
// dragTo
//---------------------------------------------------------
void Text::dragTo(EditData& ed)
{
TextEditData* ted = static_cast<TextEditData*>(ed.getData(this));
TextCursor* _cursor = ted->cursor;
_cursor->set(ed.pos, QTextCursor::KeepAnchor);
score()->setUpdateAll();
score()->update();
}
//---------------------------------------------------------
// dragAnchor
//---------------------------------------------------------
QLineF Text::dragAnchor() const
{
qreal xp = 0.0;
for (Element* e = parent(); e; e = e->parent())
xp += e->x();
qreal yp;
if (parent()->type() == ElementType::SEGMENT) {
System* system = static_cast<Segment*>(parent())->measure()->system();
yp = system->staffCanvasYpage(staffIdx());
}
else
yp = parent()->canvasPos().y();
QPointF p1(xp, yp);
QPointF p2 = canvasPos();
if (layoutToParentWidth())
p2 += bbox().topLeft();
return QLineF(p1, p2);
}
//---------------------------------------------------------
// paste
//---------------------------------------------------------
void Text::paste(EditData& ed)
{
TextEditData* ted = static_cast<TextEditData*>(ed.getData(this));
TextCursor* _cursor = ted->cursor;
QString txt = QApplication::clipboard()->text(QClipboard::Clipboard);
if (MScore::debugMode)
qDebug("Text::paste() <%s>", qPrintable(txt));
int state = 0;
QString token;
QString sym;
bool symState = false;
for (int i = 0; i < txt.length(); i++ ) {
QChar c = txt[i];
if (state == 0) {
if (c == '<') {
state = 1;
token.clear();
}
else if (c == '&') {
state = 2;
token.clear();
}
else {
if (symState)
sym += c;
else {
deleteSelectedText(ed);
if (c.isHighSurrogate()) {
QChar highSurrogate = c;
Q_ASSERT(i + 1 < txt.length());
i++;
QChar lowSurrogate = txt[i];
insert(_cursor, highSurrogate, lowSurrogate);
}
else {
insert(_cursor, c);
}
}
}
}
else if (state == 1) {
if (c == '>') {
state = 0;
if (token == "sym") {
symState = true;
sym.clear();
}
else if (token == "/sym") {
symState = false;
insertSym(ed, Sym::name2id(sym));
}
}
else
token += c;
}
else if (state == 2) {
if (c == ';') {
state = 0;
if (token == "lt")
insertText(ed, "<");
else if (token == "gt")
insertText(ed, ">");
else if (token == "amp")
insertText(ed, "&");
else if (token == "quot")
insertText(ed, "\"");
else
insertSym(ed, Sym::name2id(token));
}
else if (!c.isLetter()) {
state = 0;
insertText(ed, "&");
insertText(ed, token);
insertText(ed, c);
}
else
token += c;
}
}
if (state == 2) {
insertText(ed, "&");
insertText(ed, token);
}
layoutEdit();
score()->setUpdateAll();
if (type() == ElementType::INSTRUMENT_NAME)
score()->setLayoutAll();
triggerLayout();
}
//---------------------------------------------------------
// mousePress
// set text cursor
//---------------------------------------------------------
bool Text::mousePress(EditData& ed, QMouseEvent* ev)
{
// printf("========================mousePress %p\n", this);
QPointF p = ed.startMove;
bool shift = ev->modifiers() & Qt::ShiftModifier;
TextEditData* ted = static_cast<TextEditData*>(ed.getData(this));
TextCursor* _cursor = ted->cursor;
if (!_cursor->set(p, shift ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor))
return false;
if (ev->button() == Qt::MidButton)
paste(ed);
return true;
}
//---------------------------------------------------------
// layoutEdit
//---------------------------------------------------------
void Text::layoutEdit()
{
layout();
if (parent() && parent()->type() == ElementType::TBOX) {
TBox* tbox = static_cast<TBox*>(parent());
tbox->layout();
System* system = tbox->system();
system->setHeight(tbox->height());
triggerLayout();
}
else {
static const qreal w = 2.0; // 8.0 / view->matrix().m11();
score()->addRefresh(canvasBoundingRect().adjusted(-w, -w, w, w));
}
}
//---------------------------------------------------------
// acceptDrop
//---------------------------------------------------------
bool Text::acceptDrop(EditData& data) const
{
ElementType type = data.element->type();
return type == ElementType::SYMBOL || type == ElementType::FSYMBOL;
}
//---------------------------------------------------------
// drop
//---------------------------------------------------------
Element* Text::drop(EditData& ed)
{
TextCursor* _cursor = cursor(ed);
Element* e = ed.element;
switch (e->type()) {
case ElementType::SYMBOL:
{
SymId id = toSymbol(e)->sym();
delete e;
deleteSelectedText(ed);
insertSym(ed, id);
}
break;
case ElementType::FSYMBOL:
{
int code = toFSymbol(e)->code();
delete e;
deleteSelectedText(ed);
if (QChar::requiresSurrogates(code))
insert(_cursor, QChar::highSurrogate(code), QChar::lowSurrogate(code));
else
insert(_cursor, QChar(code));
}
break;
default:
break;
}
return 0;
}
//---------------------------------------------------------
// setPlainText
//---------------------------------------------------------
void Text::setPlainText(const QString& s)
{
setXmlText(s.toHtmlEscaped());
}
//---------------------------------------------------------
// setXmlText
//---------------------------------------------------------
void Text::setXmlText(const QString& s)
{
_text = s;
layoutInvalid = true;
textInvalid = false;
textChanged();
}
//---------------------------------------------------------
// plainText
// return plain text with symbols
//---------------------------------------------------------
QString Text::plainText(bool noSym) const
{
QString s;
if (layoutInvalid)
((Text*)(this))->createLayout(); // ugh!
for (const TextBlock& block : _layout) {
for (const TextFragment& f : block.fragments()) {
const CharFormat& format = f.format;
s += f.text;
}
if (block.eol())
s += QChar::LineFeed;
}
return s;
}
//---------------------------------------------------------
// xmlText
//---------------------------------------------------------
QString Text::xmlText() const
{
if (textInvalid)
((Text*)(this))->genText(); // ugh!
return _text;
}
//---------------------------------------------------------
// changeSelectionFormat
//---------------------------------------------------------
void TextCursor::changeSelectionFormat(FormatId id, QVariant val)
{
if (!hasSelection())
return;
int r1 = selectLine();
int r2 = line();
int c1 = selectColumn();
int c2 = column();
if (r1 > r2) {
qSwap(r1, r2);
qSwap(c1, c2);
}
else if (r1 == r2) {
if (c1 > c2)
qSwap(c1, c2);
}
int rows = _text->_layout.size();
QList<TextBlock> toDelete;
for (int row = 0; row < rows; ++row) {
TextBlock& t = _text->_layout[row];
if (row < r1)
continue;
if (row > r2)
break;
if (row == r1 && r1 == r2)
t.changeFormat(id, val, c1, c2 - c1);
else if (row == r1)
t.changeFormat(id, val, c1, t.columns() - c1);
else if (row == r2)
t.changeFormat(id, val, 0, c2);
else
t.changeFormat(id, val, 0, t.columns());
}
_text->layout1();
_text->score()->addRefresh(_text->canvasBoundingRect());
}
//---------------------------------------------------------
// setFormat
//---------------------------------------------------------
void TextCursor::setFormat(FormatId id, QVariant val)
{
changeSelectionFormat(id, val);
format()->setFormat(id, val);
}
//---------------------------------------------------------
// convertFromHtml
//---------------------------------------------------------
QString Text::convertFromHtml(const QString& ss) const
{
QTextDocument doc;
doc.setHtml(ss);
QString s;
qreal _size = size();
QString _family = family();
for (auto b = doc.firstBlock(); b.isValid() ; b = b.next()) {
if (!s.isEmpty())
s += "\n";
for (auto it = b.begin(); !it.atEnd(); ++it) {
QTextFragment f = it.fragment();
if (f.isValid()) {
QTextCharFormat tf = f.charFormat();
QFont font = tf.font();
qreal htmlSize = font.pointSizeF();
// html font sizes may have spatium adjustments; need to undo this
if (sizeIsSpatiumDependent())
htmlSize *= SPATIUM20 / spatium();
if (fabs(_size - htmlSize) > 0.1) {
_size = htmlSize;
s += QString("<font size=\"%1\"/>").arg(_size);
}
if (_family != font.family()) {
_family = font.family();
s += QString("<font face=\"%1\"/>").arg(_family);
}
if (font.bold())
s += "<b>";
if (font.italic())
s += "<i>";
if (font.underline())
s += "<u>";
s += f.text().toHtmlEscaped();
if (font.underline())
s += "</u>";
if (font.italic())
s += "</i>";
if (font.bold())
s += "</b>";
}
}
}
if (score() && score()->mscVersion() <= 114) {
s.replace(QChar(0xe10e), QString("<sym>accidentalNatural</sym>")); //natural
s.replace(QChar(0xe10c), QString("<sym>accidentalSharp</sym>")); // sharp
s.replace(QChar(0xe10d), QString("<sym>accidentalFlat</sym>")); // flat
s.replace(QChar(0xe104), QString("<sym>metNoteHalfUp</sym>")), // note2_Sym
s.replace(QChar(0xe105), QString("<sym>metNoteQuarterUp</sym>")); // note4_Sym
s.replace(QChar(0xe106), QString("<sym>metNote8thUp</sym>")); // note8_Sym
s.replace(QChar(0xe107), QString("<sym>metNote16thUp</sym>")); // note16_Sym
s.replace(QChar(0xe108), QString("<sym>metNote32ndUp</sym>")); // note32_Sym
s.replace(QChar(0xe109), QString("<sym>metNote64thUp</sym>")); // note64_Sym
s.replace(QChar(0xe10a), QString("<sym>metAugmentationDot</sym>")); // dot
s.replace(QChar(0xe10b), QString("<sym>metAugmentationDot</sym><sym>space</sym><sym>metAugmentationDot</sym>")); // dotdot
s.replace(QChar(0xe167), QString("<sym>segno</sym>")); // segno
s.replace(QChar(0xe168), QString("<sym>coda</sym>")); // coda
s.replace(QChar(0xe169), QString("<sym>codaSquare</sym>")); // varcoda
}
return s;
}
//---------------------------------------------------------
// convertToHtml
// convert from internal html format to Qt
//---------------------------------------------------------
QString Text::convertToHtml(const QString& s, const TextStyle& /*st*/)
{
//TODO qreal size = st.size();
// QString family = st.family();
qreal size = 10;
QString family = "arial";
return QString("<html><body style=\"font-family:'%1'; font-size:%2pt;\">%3</body></html>").arg(family).arg(size).arg(s);
}
//---------------------------------------------------------
// tagEscape
//---------------------------------------------------------
QString Text::tagEscape(QString s)
{
QStringList tags = { "sym", "b", "i", "u", "sub", "sup" };
for (QString tag : tags) {
QString openTag = "<" + tag + ">";
QString openProxy = "!!" + tag + "!!";
QString closeTag = "</" + tag + ">";
QString closeProxy = "!!/" + tag + "!!";
s.replace(openTag, openProxy);
s.replace(closeTag, closeProxy);
}
s = XmlWriter::xmlString(s);
for (QString tag : tags) {
QString openTag = "<" + tag + ">";
QString openProxy = "!!" + tag + "!!";
QString closeTag = "</" + tag + ">";
QString closeProxy = "!!/" + tag + "!!";
s.replace(openProxy, openTag);
s.replace(closeProxy, closeTag);
}
return s;
}
//---------------------------------------------------------
// unEscape
//---------------------------------------------------------
QString Text::unEscape(QString s)
{
s.replace("&lt;", "<");
s.replace("&gt;", ">");
s.replace("&amp;", "&");
s.replace("&quot;", "\"");
return s;
}
//---------------------------------------------------------
// escape
//---------------------------------------------------------
QString Text::escape(QString s)
{
s.replace("<", "&lt;");
s.replace(">", "&gt;");
s.replace("&", "&amp;");
s.replace("\"", "&quot;");
return s;
}
//---------------------------------------------------------
// accessibleInfo
//---------------------------------------------------------
QString Text::accessibleInfo() const
{
QString rez;
switch (subStyle()) {
case SubStyle::TITLE:
case SubStyle::SUBTITLE:
case SubStyle::COMPOSER:
case SubStyle::POET:
case SubStyle::TRANSLATOR:
case SubStyle::MEASURE_NUMBER:
rez = subStyleUserName(subStyle());
break;
default:
rez = Element::accessibleInfo();
break;
}
QString s = plainText(true).simplified();
if (s.length() > 20) {
s.truncate(20);
s += "...";
}
return QString("%1: %2").arg(rez).arg(s);
}
//---------------------------------------------------------
// subtype
//---------------------------------------------------------
int Text::subtype() const
{
switch (subStyle()) {
case SubStyle::TITLE:
case SubStyle::SUBTITLE:
case SubStyle::COMPOSER:
case SubStyle::POET:
case SubStyle::FRAME:
case SubStyle::INSTRUMENT_EXCERPT:
return int(subStyle());
default: return -1;
}
}
//---------------------------------------------------------
// subtypeName
//---------------------------------------------------------
QString Text::subtypeName() const
{
QString rez;
switch (subStyle()) {
case SubStyle::TITLE:
case SubStyle::SUBTITLE:
case SubStyle::COMPOSER:
case SubStyle::POET:
case SubStyle::FRAME:
case SubStyle::INSTRUMENT_EXCERPT:
rez = subStyleUserName(subStyle());
break;
default: rez = "";
}
return rez;
}
//---------------------------------------------------------
// fragmentList
//---------------------------------------------------------
/**
Return the text as a single list of TextFragment
Used by the MusicXML formatted export to avoid parsing the xml text format
*/
QList<TextFragment> Text::fragmentList() const
{
QList<TextFragment> res;
for (const TextBlock& block : _layout) {
for (const TextFragment& f : block.fragments()) {
/* TODO TBD
if (f.text.empty()) // skip empty fragments, not to
continue; // insert extra HTML formatting
*/
res.append(f);
if (block.eol()) {
// simply append a newline
res.last().text += "\n";
}
}
}
return res;
}
//---------------------------------------------------------
// validateText
// check if s is a valid musescore xml text string
// - simple bugs are automatically adjusted
// return true if text is valid or could be fixed
// (this is incomplete/experimental)
//---------------------------------------------------------
bool Text::validateText(QString& s)
{
QString d;
for (int i = 0; i < s.size(); ++i) {
QChar c = s[i];
if (c == '&') {
const char* ok[] { "amp;", "lt;", "gt;", "quot" };
QString t = s.mid(i+1);
bool found = false;
for (auto k : ok) {
if (t.startsWith(k)) {
d.append(c);
d.append(k);
i += strlen(k);
found = true;
break;
}
}
if (!found)
d.append("&amp;");
}
else if (c == '<') {
const char* ok[] { "b>", "/b>", "i>", "/i>", "u>", "/u", "font ", "/font>", "sym>", "/sym>" };
QString t = s.mid(i+1);
bool found = false;
for (auto k : ok) {
if (t.startsWith(k)) {
d.append(c);
d.append(k);
i += strlen(k);
found = true;
break;
}
}
if (!found)
d.append("&lt;");
}
else
d.append(c);
}
QString ss = "<data>" + d + "</data>\n";
XmlReader xml(0, ss);
while (xml.readNextStartElement())
; // qDebug(" token %d <%s>", int(xml.tokenType()), qPrintable(xml.name().toString()));
if (xml.error() == QXmlStreamReader::NoError) {
s = d;
return true;
}
qDebug("xml error at line %lld column %lld: %s",
xml.lineNumber(),
xml.columnNumber(),
qPrintable(xml.errorString()));
qDebug ("text: |%s|", qPrintable(ss));
return false;
}
//---------------------------------------------------------
// inputTransition
//---------------------------------------------------------
void Text::inputTransition(QInputMethodEvent* ie)
{
#if 0
// remove preedit string
int n = preEdit.size();
while (n--) {
if (movePosition(QTextCursor::Left))
_cursor->deleteChar();
}
qDebug("Text::inputTransition <%s><%s> len %d start %d, preEdit size %d",
qPrintable(ie->commitString()),
qPrintable(ie->preeditString()),
ie->replacementLength(), ie->replacementStart(), preEdit.size());
if (!ie->commitString().isEmpty()) {
_cursor->format()->setPreedit(false);
editInsertText(_cursor, ie->commitString());
preEdit.clear();
}
else {
preEdit = ie->preeditString();
if (!preEdit.isEmpty()) {
#if 0
for (auto a : ie->attributes()) {
switch(a.type) {
case QInputMethodEvent::TextFormat:
{
printf("attribute TextFormat: %d-%d\n", a.start, a.length);
QTextFormat tf = a.value.value<QTextFormat>();
}
break;
case QInputMethodEvent::Cursor:
printf("attribute Cursor at %d\n", a.start);
break;
default:
printf("attribute %d\n", a.type);
}
}
#endif
_cursor->format()->setPreedit(true);
editInsertText(_cursor, preEdit);
ie->accept();
score()->update();
}
}
#endif
}
//---------------------------------------------------------
// font
//---------------------------------------------------------
QFont Text::font() const
{
qreal m = _size;
if (_sizeIsSpatiumDependent)
m *= spatium() / SPATIUM20;
QFont f(_family, m, _bold ? QFont::Bold : QFont::Normal, _italic);
if (_underline)
f.setUnderline(_underline);
return f;
}
//---------------------------------------------------------
// fontMetrics
//---------------------------------------------------------
QFontMetricsF Text::fontMetrics() const
{
return QFontMetricsF(font());
}
//---------------------------------------------------------
// initSubStyle
//---------------------------------------------------------
void Text::initSubStyle(SubStyle s)
{
_subStyle = s;
Element::initSubStyle(s);
}
//---------------------------------------------------------
// getProperty
//---------------------------------------------------------
QVariant Text::getProperty(P_ID propertyId) const
{
switch (propertyId) {
case P_ID::FONT_FACE:
return family();
case P_ID::FONT_SIZE:
return size();
case P_ID::FONT_BOLD:
return bold();
case P_ID::FONT_ITALIC:
return italic();
case P_ID::FONT_UNDERLINE:
return underline();
case P_ID::FRAME:
return hasFrame();
case P_ID::FRAME_SQUARE:
return square();
case P_ID::FRAME_CIRCLE:
return circle();
case P_ID::FRAME_WIDTH:
return frameWidth();
case P_ID::FRAME_PADDING:
return paddingWidth();
case P_ID::FRAME_ROUND:
return frameRound();
case P_ID::FRAME_FG_COLOR:
return frameColor();
case P_ID::FRAME_BG_COLOR:
return bgColor();
case P_ID::FONT_SPATIUM_DEPENDENT:
return sizeIsSpatiumDependent();
case P_ID::ALIGN:
return QVariant::fromValue(align());
case P_ID::TEXT:
return xmlText();
case P_ID::SUB_STYLE:
return int(subStyle());
case P_ID::OFFSET:
return offset();
case P_ID::OFFSET_TYPE:
return int(offsetType());
default:
return Element::getProperty(propertyId);
}
}
//---------------------------------------------------------
// setProperty
//---------------------------------------------------------
bool Text::setProperty(P_ID propertyId, const QVariant& v)
{
score()->addRefresh(canvasBoundingRect());
bool rv = true;
switch (propertyId) {
case P_ID::FONT_FACE:
setFamily(v.toString());
break;
case P_ID::FONT_SIZE:
setSize(v.toReal());
break;
case P_ID::FONT_BOLD:
setBold(v.toBool());
break;
case P_ID::FONT_ITALIC:
setItalic(v.toBool());
break;
case P_ID::FONT_UNDERLINE:
setUnderline(v.toBool());
break;
case P_ID::FRAME:
setHasFrame(v.toBool());
break;
case P_ID::FRAME_SQUARE:
setSquare(v.toBool());
break;
case P_ID::FRAME_CIRCLE:
setCircle(v.toBool());
break;
case P_ID::FRAME_WIDTH:
setFrameWidth(v.value<Spatium>());
break;
case P_ID::FRAME_PADDING:
setPaddingWidth(v.value<Spatium>());
break;
case P_ID::FRAME_ROUND:
setFrameRound(v.toInt());
break;
case P_ID::FRAME_FG_COLOR:
setFrameColor(v.value<QColor>());
break;
case P_ID::FRAME_BG_COLOR:
setBgColor(v.value<QColor>());
break;
case P_ID::FONT_SPATIUM_DEPENDENT:
setSizeIsSpatiumDependent(v.toBool());
break;
case P_ID::TEXT:
setXmlText(v.toString());
break;
case P_ID::ALIGN:
setAlign(v.value<Align>());
break;
case P_ID::SUB_STYLE:
setSubStyle(SubStyle(v.toInt()));
break;
case P_ID::OFFSET:
setOffset(v.toPointF());
break;
case P_ID::OFFSET_TYPE:
setOffsetType(OffsetType(v.toInt()));
break;
default:
rv = Element::setProperty(propertyId, v);
break;
}
triggerLayout();
return rv;
}
//---------------------------------------------------------
// propertyDefault
//---------------------------------------------------------
QVariant Text::propertyDefault(P_ID id) const
{
for (const StyledProperty& p : Ms::subStyle(_subStyle)) {
if (p.propertyIdx == id)
return score()->styleV(p.styleIdx);
}
switch (id) {
case P_ID::SUB_STYLE:
return int(SubStyle::DEFAULT);
case P_ID::TEXT:
return QString();
case P_ID::OFFSET:
return QPointF();
case P_ID::OFFSET_TYPE:
return int (OffsetType::SPATIUM);
default:
for (const StyledProperty& p : Ms::subStyle(SubStyle::DEFAULT)) {
if (p.propertyIdx == id)
return score()->styleV(p.styleIdx);
}
return Element::propertyDefault(id);
}
}
//---------------------------------------------------------
// resetProperty
//---------------------------------------------------------
void Text::resetProperty(P_ID id)
{
PropertyFlags* p = propertyFlagsP(id);
if (p) {
setProperty(id, propertyDefault(id));
*p = PropertyFlags::STYLED;
return;
}
switch (id) {
default:
return Element::resetProperty(id);
}
}
//---------------------------------------------------------
// reset
//---------------------------------------------------------
void Text::reset()
{
for (const StyledProperty& p : Ms::subStyle(_subStyle))
undoResetProperty(p.propertyIdx);
Element::reset();
}
//---------------------------------------------------------
// getPropertyStyle
//---------------------------------------------------------
StyleIdx Text::getPropertyStyle(P_ID id) const
{
for (auto sp : Ms::subStyle(_subStyle)) {
if (sp.propertyIdx == id)
return sp.styleIdx;
}
return Element::getPropertyStyle(id);
}
//---------------------------------------------------------
// styleChanged
//---------------------------------------------------------
void Text::styleChanged()
{
for (const StyledProperty& p : Ms::subStyle(_subStyle)) {
if (propertyFlags(p.propertyIdx) == PropertyFlags::STYLED)
setProperty(p.propertyIdx, propertyDefault(p.propertyIdx));
}
Element::styleChanged();
}
//---------------------------------------------------------
// propertyFlags
//---------------------------------------------------------
PropertyFlags Text::propertyFlags(P_ID id) const
{
const PropertyFlags* p = ((Text*)this)->propertyFlagsP(id); // ugh!
if (p)
return *p;
return Element::propertyFlags(id);
}
//---------------------------------------------------------
// setPropertyFlags
//---------------------------------------------------------
void Text::setPropertyFlags(P_ID id, PropertyFlags f)
{
PropertyFlags* p = propertyFlagsP(id);
if (p)
*p = f;
else
Element::setPropertyFlags(id, f);
}
//---------------------------------------------------------
// propertyFlagsP
//---------------------------------------------------------
PropertyFlags* Text::propertyFlagsP(P_ID id)
{
switch (id) {
case P_ID::FONT_FACE:
return &_familyStyle;
case P_ID::FONT_SIZE:
return &_sizeStyle;
case P_ID::FONT_BOLD:
return &_boldStyle;
case P_ID::FONT_ITALIC:
return &_italicStyle;
case P_ID::FONT_UNDERLINE:
return &_underlineStyle;
case P_ID::FRAME:
return &_hasFrameStyle;
case P_ID::FRAME_SQUARE:
return &_squareStyle;
case P_ID::FRAME_CIRCLE:
return &_circleStyle;
case P_ID::FRAME_WIDTH:
return &_frameWidthStyle;
case P_ID::FRAME_PADDING:
return &_paddingWidthStyle;
case P_ID::FRAME_ROUND:
return &_frameRoundStyle;
case P_ID::FRAME_FG_COLOR:
return &_frameColorStyle;
case P_ID::FRAME_BG_COLOR:
return &_bgColorStyle;
case P_ID::FONT_SPATIUM_DEPENDENT:
return &_sizeIsSpatiumDependentStyle;
case P_ID::ALIGN:
return &_alignStyle;
default:
// qDebug("unknown id: %d %s", int(id), propertyName(id));
break;
}
return 0;
}
//---------------------------------------------------------
// editCut
//---------------------------------------------------------
void Text::editCut(EditData& ed)
{
TextEditData* ted = static_cast<TextEditData*>(ed.getData(this));
TextCursor* _cursor = ted->cursor;
QString s = _cursor->selectedText();
if (!s.isEmpty()) {
QApplication::clipboard()->setText(s, QClipboard::Clipboard);
ed.curGrip = Grip::START;
ed.key = Qt::Key_Delete;
ed.s = QString();
edit(ed);
}
}
//---------------------------------------------------------
// editCopy
//---------------------------------------------------------
void Text::editCopy(EditData& ed)
{
//
// store selection as plain text
//
TextEditData* ted = static_cast<TextEditData*>(ed.getData(this));
TextCursor* _cursor = ted->cursor;
QString s = _cursor->selectedText();
if (!s.isEmpty())
QApplication::clipboard()->setText(s, QClipboard::Clipboard);
}
//---------------------------------------------------------
// cursor
//---------------------------------------------------------
TextCursor* Text::cursor(EditData& ed)
{
TextEditData* ted = static_cast<TextEditData*>(ed.getData(this));
return ted->cursor;
}
//---------------------------------------------------------
// curFormat
//---------------------------------------------------------
CharFormat* Text::curFormat(EditData& ed)
{
TextCursor* _cursor = cursor(ed);
return _cursor->format();
}
//---------------------------------------------------------
// draw
//---------------------------------------------------------
void Text::draw(QPainter* p) const
{
if (hasFrame()) {
if (frameWidth().val() != 0.0) {
QColor fColor = frameColor();
QPen pen(fColor, frameWidth().val() * spatium(), Qt::SolidLine,
Qt::SquareCap, Qt::MiterJoin);
p->setPen(pen);
}
else
p->setPen(Qt::NoPen);
QColor bg(bgColor());
p->setBrush(bg.alpha() ? QBrush(bg) : Qt::NoBrush);
if (circle())
p->drawEllipse(frame);
else {
int r2 = frameRound();
if (r2 > 99)
r2 = 99;
p->drawRoundedRect(frame, frameRound(), r2);
}
}
p->setBrush(Qt::NoBrush);
p->setPen(textColor());
for (const TextBlock& t : _layout)
t.draw(p, this);
}
//---------------------------------------------------------
// drawEditMode
// draw edit mode decorations
//---------------------------------------------------------
void Text::drawEditMode(QPainter* p, EditData& ed)
{
QPointF pos(canvasPos());
p->translate(pos);
TextEditData* ted = static_cast<TextEditData*>(ed.getData(this));
if (!ted) {
qDebug("ted not found");
return;
}
TextCursor* _cursor = ted->cursor;
if (_cursor->hasSelection()) {
p->setBrush(Qt::NoBrush);
p->setPen(textColor());
int r1 = _cursor->selectLine();
int r2 = _cursor->line();
int c1 = _cursor->selectColumn();
int c2 = _cursor->column();
if (r1 > r2) {
qSwap(r1, r2);
qSwap(c1, c2);
}
else if (r1 == r2) {
if (c1 > c2)
qSwap(c1, c2);
}
int row = 0;
for (const TextBlock& t : _layout) {
t.draw(p, this);
if (row >= r1 && row <= r2) {
QRectF br;
if (row == r1 && r1 == r2)
br = t.boundingRect(c1, c2, this);
else if (row == r1)
br = t.boundingRect(c1, t.columns(), this);
else if (row == r2)
br = t.boundingRect(0, c2, this);
else
br = t.boundingRect();
br.translate(0.0, t.y());
drawSelection(p, br);
}
++row;
}
}
p->setBrush(curColor());
QPen pen(curColor());
pen.setJoinStyle(Qt::MiterJoin);
p->setPen(pen);
p->drawRect(_cursor->cursorRect());
QMatrix matrix = p->matrix();
p->translate(-pos);
p->setPen(QPen(QBrush(Qt::lightGray), 4.0 / matrix.m11())); // 4 pixel pen size
p->setBrush(Qt::NoBrush);
qreal m = spatium();
QRectF r = pageBoundingRect().adjusted(-m, -m, m, m);
p->drawRect(r);
pen = QPen(MScore::defaultColor, 0.0);
}
//---------------------------------------------------------
// deleteChar
//---------------------------------------------------------
bool TextCursor::deleteChar() const
{
TextBlock& l1 = curLine();
if (_column == l1.columns()) {
if (_line + 1 < _text->_layout.size()) {
const TextBlock& l2 = _text->_layout[_line + 1];
for (const TextFragment& f : l2.fragments())
l1.fragments().append(f);
_text->_layout.removeAt(_line + 1);
if (_text->_layout.last() == l1)
l1.setEol(false);
}
else
return false;
}
else {
QString s = l1.remove(_column);
_text->score()->undoStack()->push1(new RemoveText(this, s));
}
//TODO clearSelection();
_text->triggerLayout();
return true;
}
//---------------------------------------------------------
// edit
//---------------------------------------------------------
bool Text::edit(EditData& ed)
{
TextEditData* ted = static_cast<TextEditData*>(ed.getData(this));
TextCursor* _cursor = ted->cursor;
// do nothing on Shift, it messes up IME on Windows. See #64046
if (ed.key == Qt::Key_Shift)
return false;
QString s = ed.s;
bool ctrlPressed = ed.modifiers & Qt::ControlModifier;
bool shiftPressed = ed.modifiers & Qt::ShiftModifier;
QTextCursor::MoveMode mm = shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor;
bool wasHex = false;
if (hexState >= 0) {
if (ed.modifiers == (Qt::ControlModifier | Qt::ShiftModifier | Qt::KeypadModifier)) {
switch (ed.key) {
case Qt::Key_0 ... Qt::Key_9:
s = QChar::fromLatin1(ed.key);
++hexState;
wasHex = true;
break;
default:
break;
}
}
else if (ed.modifiers == (Qt::ControlModifier | Qt::ShiftModifier)) {
switch (ed.key) {
case Qt::Key_A ... Qt::Key_F:
s = QChar::fromLatin1(ed.key);
++hexState;
wasHex = true;
break;
default:
break;
}
}
}
if (!wasHex) {
// printf("======%x\n", s.isEmpty() ? -1 : s[0].unicode());
switch (ed.key) {
case Qt::Key_Enter:
case Qt::Key_Return:
deleteSelectedText(ed);
score()->undo(new SplitText(_cursor), &ed);
return true;
case Qt::Key_Delete:
if (!deleteSelectedText(ed))
score()->undo(new RemoveText(_cursor, QString(_cursor->currentCharacter())), &ed);
return true;
case Qt::Key_Backspace:
if (!deleteSelectedText(ed)) {
if (!_cursor->movePosition(QTextCursor::Left))
return false;
score()->undo(new RemoveText(_cursor, QString(_cursor->currentCharacter())));
}
return true;
case Qt::Key_Left:
if (!_cursor->movePosition(ctrlPressed ? QTextCursor::WordLeft : QTextCursor::Left, mm) && type() == ElementType::LYRICS)
return false;
s.clear();
break;
case Qt::Key_Right:
if (!_cursor->movePosition(ctrlPressed ? QTextCursor::NextWord : QTextCursor::Right, mm) && type() == ElementType::LYRICS)
return false;
s.clear();
break;
case Qt::Key_Up:
_cursor->movePosition(QTextCursor::Up, mm);
s.clear();
break;
case Qt::Key_Down:
_cursor->movePosition(QTextCursor::Down, mm);
s.clear();
break;
case Qt::Key_Home:
_cursor->movePosition(QTextCursor::Start, mm);
s.clear();
break;
case Qt::Key_End:
_cursor->movePosition(QTextCursor::End, mm);
s.clear();
break;
case Qt::Key_Tab:
case Qt::Key_Space:
s = " ";
ed.modifiers = 0;
break;
case Qt::Key_Minus:
if (ed.modifiers == 0)
s = "-";
break;
case Qt::Key_Underscore:
if (ed.modifiers == 0)
s = "_";
break;
case Qt::Key_A:
if (ctrlPressed) {
selectAll(_cursor);
s.clear();
}
break;
default:
break;
}
if (ctrlPressed && shiftPressed) {
switch (ed.key) {
case Qt::Key_U:
if (hexState == -1) {
hexState = 0;
s = "u";
}
break;
case Qt::Key_B:
insertSym(ed, SymId::accidentalFlat);
return true;
case Qt::Key_NumberSign:
insertSym(ed, SymId::accidentalSharp);
return true;
case Qt::Key_H:
insertSym(ed, SymId::accidentalNatural);
return true;
case Qt::Key_Space:
insertSym(ed, SymId::space);
return true;
case Qt::Key_F:
insertSym(ed, SymId::dynamicForte);
return true;
case Qt::Key_M:
insertSym(ed, SymId::dynamicMezzo);
return true;
case Qt::Key_N:
insertSym(ed, SymId::dynamicNiente);
return true;
case Qt::Key_P:
insertSym(ed, SymId::dynamicPiano);
return true;
case Qt::Key_S:
insertSym(ed, SymId::dynamicSforzando);
return true;
case Qt::Key_R:
insertSym(ed, SymId::dynamicRinforzando);
return true;
case Qt::Key_Z:
// Ctrl+Z is normally "undo"
// but this code gets hit even if you are also holding Shift
// so Shift+Ctrl+Z works
insertSym(ed, SymId::dynamicZ);
return true;
}
}
}
if (!s.isEmpty()) {
deleteSelectedText(ed);
score()->undo(new InsertText(_cursor, s), &ed);
}
return true;
}
}