a780a36dea
Resolves: https://musescore.org/en/node/301259 See also https://github.com/musescore/MuseScore/pull/5728/ The problem occurs in several different overrides to getPropertyStyle() for various different text classes. The function is supposed to determine which Sid to use for Pid::OFFSET, based on placement above/below. But that calculation should only be relevant if the element is using the default text style for its type. Otherwise it should use the offset in the current text style. This code removes the overrides for getPropertyStyle() in each class, instead modifying TextBase::getPropertyStyle() to check if the element is using its default text style or not, and then only if so does it use the placement to select a Sid. Otherwise it uses the offset of the current text style.
656 lines
22 KiB
C++
656 lines
22 KiB
C++
//=============================================================================
|
|
// MuseScore
|
|
// Music Composition & Notation
|
|
//
|
|
// Copyright (C) 2002-2011 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 "lyrics.h"
|
|
|
|
#include "chord.h"
|
|
#include "score.h"
|
|
#include "sym.h"
|
|
#include "system.h"
|
|
#include "xml.h"
|
|
#include "staff.h"
|
|
#include "segment.h"
|
|
#include "undo.h"
|
|
#include "textedit.h"
|
|
#include "measure.h"
|
|
|
|
namespace Ms {
|
|
|
|
//---------------------------------------------------------
|
|
// lyricsElementStyle
|
|
//---------------------------------------------------------
|
|
|
|
static const ElementStyle lyricsElementStyle {
|
|
{ Sid::lyricsPlacement, Pid::PLACEMENT },
|
|
};
|
|
|
|
//---------------------------------------------------------
|
|
// Lyrics
|
|
//---------------------------------------------------------
|
|
|
|
Lyrics::Lyrics(Score* s)
|
|
: TextBase(s, Tid::LYRICS_ODD)
|
|
{
|
|
_even = false;
|
|
initElementStyle(&lyricsElementStyle);
|
|
_no = 0;
|
|
_ticks = Fraction(0,1);
|
|
_syllabic = Syllabic::SINGLE;
|
|
_separator = 0;
|
|
}
|
|
|
|
Lyrics::Lyrics(const Lyrics& l)
|
|
: TextBase(l)
|
|
{
|
|
_even = l._even;
|
|
_no = l._no;
|
|
_ticks = l._ticks;
|
|
_syllabic = l._syllabic;
|
|
_separator = 0;
|
|
}
|
|
|
|
Lyrics::~Lyrics()
|
|
{
|
|
if (_separator)
|
|
remove(_separator);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// scanElements
|
|
//---------------------------------------------------------
|
|
|
|
void Lyrics::scanElements(void* data, void (*func)(void*, Element*), bool /*all*/)
|
|
{
|
|
func(data, this);
|
|
/* DO NOT ADD EITHER THE LYRICSLINE OR THE SEGMENTS: segments are added through the system each belongs to;
|
|
LyricsLine is not needed, as it is internally manged.
|
|
if (_separator)
|
|
_separator->scanElements(data, func, all); */
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// write
|
|
//---------------------------------------------------------
|
|
|
|
void Lyrics::write(XmlWriter& xml) const
|
|
{
|
|
if (!xml.canWrite(this))
|
|
return;
|
|
xml.stag(this);
|
|
writeProperty(xml, Pid::VERSE);
|
|
if (_syllabic != Syllabic::SINGLE) {
|
|
static const char* sl[] = {
|
|
"single", "begin", "end", "middle"
|
|
};
|
|
xml.tag("syllabic", sl[int(_syllabic)]);
|
|
}
|
|
xml.tag("ticks", _ticks.ticks(), 0); // pre-3.1 compatibility: write integer ticks under <ticks> tag
|
|
writeProperty(xml, Pid::LYRIC_TICKS);
|
|
|
|
TextBase::writeProperties(xml);
|
|
xml.etag();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// read
|
|
//---------------------------------------------------------
|
|
|
|
void Lyrics::read(XmlReader& e)
|
|
{
|
|
while (e.readNextStartElement()) {
|
|
if (!readProperties(e))
|
|
e.unknown();
|
|
}
|
|
if (!isStyled(Pid::OFFSET) && !e.pasteMode()) {
|
|
// fix offset for pre-3.1 scores
|
|
// 3.0: y offset was meaningless if autoplace is set
|
|
QString version = masterScore()->mscoreVersion();
|
|
if (autoplace() && !version.isEmpty() && version < "3.1") {
|
|
QPointF off = propertyDefault(Pid::OFFSET).toPointF();
|
|
ryoffset() = off.y();
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// readProperties
|
|
//---------------------------------------------------------
|
|
|
|
bool Lyrics::readProperties(XmlReader& e)
|
|
{
|
|
const QStringRef& tag(e.name());
|
|
|
|
if (tag == "no")
|
|
_no = e.readInt();
|
|
else if (tag == "syllabic") {
|
|
QString val(e.readElementText());
|
|
if (val == "single")
|
|
_syllabic = Syllabic::SINGLE;
|
|
else if (val == "begin")
|
|
_syllabic = Syllabic::BEGIN;
|
|
else if (val == "end")
|
|
_syllabic = Syllabic::END;
|
|
else if (val == "middle")
|
|
_syllabic = Syllabic::MIDDLE;
|
|
else
|
|
qDebug("bad syllabic property");
|
|
}
|
|
else if (tag == "ticks") // obsolete
|
|
_ticks = e.readFraction(); // will fall back to reading integer ticks on older scores
|
|
else if (tag == "ticks_f")
|
|
_ticks = e.readFraction();
|
|
else if (readProperty(tag, e, Pid::PLACEMENT))
|
|
;
|
|
else if (!TextBase::readProperties(e))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// add
|
|
//---------------------------------------------------------
|
|
|
|
void Lyrics::add(Element* el)
|
|
{
|
|
// el->setParent(this);
|
|
// if (el->type() == ElementType::LINE)
|
|
// _separator.append((Line*)el); // ignore! Internally managed
|
|
// ;
|
|
// else
|
|
qDebug("Lyrics::add: unknown element %s", el->name());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// remove
|
|
//---------------------------------------------------------
|
|
|
|
void Lyrics::remove(Element* el)
|
|
{
|
|
if (el->isLyricsLine()) {
|
|
// only if separator still exists and is the right one
|
|
if (_separator && el == _separator) {
|
|
// Lyrics::remove() and LyricsLine::removeUnmanaged() call each other;
|
|
// be sure each finds a clean context
|
|
LyricsLine* separ = _separator;
|
|
_separator = 0;
|
|
separ->setParent(0);
|
|
separ->removeUnmanaged();
|
|
//done in undo/redo? delete separ;
|
|
}
|
|
}
|
|
else
|
|
qDebug("Lyrics::remove: unknown element %s", el->name());
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// isMelisma
|
|
//---------------------------------------------------------
|
|
|
|
bool Lyrics::isMelisma() const
|
|
{
|
|
// entered as melisma using underscore?
|
|
if (_ticks > Fraction(0,1))
|
|
return true;
|
|
|
|
// hyphenated?
|
|
// if so, it is a melisma only if there is no lyric in same verse on next CR
|
|
if (_syllabic == Syllabic::BEGIN || _syllabic == Syllabic::MIDDLE) {
|
|
// find next CR on same track and check for existence of lyric in same verse
|
|
ChordRest* cr = chordRest();
|
|
if (cr) {
|
|
Segment* s = cr->segment()->next1();
|
|
ChordRest* ncr = s ? s->nextChordRest(cr->track()) : 0;
|
|
if (ncr && !ncr->lyrics(_no, placement()))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// default - not a melisma
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// layout
|
|
// - does not touch vertical position
|
|
//---------------------------------------------------------
|
|
|
|
void Lyrics::layout()
|
|
{
|
|
if (!parent()) { // palette & clone trick
|
|
setPos(QPointF());
|
|
TextBase::layout1();
|
|
return;
|
|
}
|
|
|
|
//
|
|
// parse leading verse number and/or punctuation, so we can factor it into layout separately
|
|
//
|
|
bool hasNumber = false; // _verseNumber;
|
|
|
|
// find:
|
|
// 1) string of numbers and non-word characters at start of syllable
|
|
// 2) at least one other character (indicating start of actual lyric)
|
|
// 3) string of non-word characters at end of syllable
|
|
//QRegularExpression leadingPattern("(^[\\d\\W]+)([^\\d\\W]+)");
|
|
|
|
const QString text = plainText();
|
|
QString leading;
|
|
QString trailing;
|
|
|
|
if (score()->styleB(Sid::lyricsAlignVerseNumber)) {
|
|
QRegularExpression punctuationPattern("(^[\\d\\W]*)([^\\d\\W].*?)([\\d\\W]*$)", QRegularExpression::UseUnicodePropertiesOption);
|
|
QRegularExpressionMatch punctuationMatch = punctuationPattern.match(text);
|
|
if (punctuationMatch.hasMatch()) {
|
|
// leading and trailing punctuation
|
|
leading = punctuationMatch.captured(1);
|
|
trailing = punctuationMatch.captured(3);
|
|
//QString actualLyric = punctuationMatch.captured(2);
|
|
if (!leading.isEmpty() && leading[0].isDigit())
|
|
hasNumber = true;
|
|
}
|
|
}
|
|
|
|
bool styleDidChange = false;
|
|
if ((_no & 1) && !_even) {
|
|
initTid(Tid::LYRICS_EVEN, /* preserveDifferent */ true);
|
|
_even = true;
|
|
styleDidChange = true;
|
|
}
|
|
if (!(_no & 1) && _even) {
|
|
initTid(Tid::LYRICS_ODD, /* preserveDifferent */ true);
|
|
_even = false;
|
|
styleDidChange = true;
|
|
}
|
|
|
|
if (styleDidChange)
|
|
styleChanged();
|
|
|
|
if (isMelisma() || hasNumber)
|
|
if (isStyled(Pid::ALIGN)) {
|
|
setAlign(score()->styleV(Sid::lyricsMelismaAlign).value<Align>());
|
|
}
|
|
QPointF o(propertyDefault(Pid::OFFSET).toPointF());
|
|
rxpos() = o.x();
|
|
qreal x = pos().x();
|
|
TextBase::layout1();
|
|
|
|
qreal centerAdjust = 0.0;
|
|
qreal leftAdjust = 0.0;
|
|
|
|
if (score()->styleB(Sid::lyricsAlignVerseNumber)) {
|
|
// Calculate leading and trailing parts widths. Lyrics
|
|
// should have text layout to be able to do it correctly.
|
|
Q_ASSERT(rows() != 0);
|
|
if (!leading.isEmpty() || !trailing.isEmpty()) {
|
|
// qDebug("create leading, trailing <%s> -- <%s><%s>", qPrintable(text), qPrintable(leading), qPrintable(trailing));
|
|
const TextBlock& tb = textBlock(0);
|
|
|
|
const qreal leadingWidth = tb.xpos(leading.length(), this) - tb.boundingRect().x();
|
|
const int trailingPos = text.length() - trailing.length();
|
|
const qreal trailingWidth = tb.boundingRect().right() - tb.xpos(trailingPos, this);
|
|
|
|
leftAdjust = leadingWidth;
|
|
centerAdjust = leadingWidth - trailingWidth;
|
|
}
|
|
}
|
|
|
|
ChordRest* cr = chordRest();
|
|
|
|
if (align() & Align::HCENTER) {
|
|
//
|
|
// center under notehead, not origin
|
|
// however, lyrics that are melismas or have verse numbers will be forced to left alignment
|
|
//
|
|
// center under note head
|
|
qreal nominalWidth = symWidth(SymId::noteheadBlack);
|
|
x += nominalWidth * .5 - cr->x() - centerAdjust * 0.5;
|
|
}
|
|
else if (!(align() & Align::RIGHT)) {
|
|
// even for left aligned syllables, ignore leading verse numbers and/or punctuation
|
|
x -= leftAdjust;
|
|
}
|
|
|
|
rxpos() = x;
|
|
|
|
if (_ticks > Fraction(0,1) || _syllabic == Syllabic::BEGIN || _syllabic == Syllabic::MIDDLE) {
|
|
if (!_separator) {
|
|
_separator = new LyricsLine(score());
|
|
_separator->setTick(cr->tick());
|
|
score()->addUnmanagedSpanner(_separator);
|
|
}
|
|
_separator->setParent(this);
|
|
_separator->setTick(cr->tick());
|
|
// HACK separator should have non-zero length to get its layout
|
|
// always triggered. A proper ticks length will be set later on the
|
|
// separator layout.
|
|
_separator->setTicks(Fraction::fromTicks(1));
|
|
_separator->setTrack(track());
|
|
_separator->setTrack2(track());
|
|
_separator->setVisible(visible());
|
|
// bbox().setWidth(bbox().width()); // ??
|
|
}
|
|
else {
|
|
if (_separator) {
|
|
_separator->removeUnmanaged();
|
|
delete _separator;
|
|
_separator = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// layout2
|
|
// compute vertical position
|
|
//---------------------------------------------------------
|
|
|
|
void Lyrics::layout2(int nAbove)
|
|
{
|
|
qreal lh = lineSpacing() * score()->styleD(Sid::lyricsLineHeight);
|
|
|
|
if (placeBelow()) {
|
|
qreal yo = segment()->measure()->system()->staff(staffIdx())->bbox().height();
|
|
rypos() = lh * (_no - nAbove) + yo - chordRest()->y();
|
|
rpos() += styleValue(Pid::OFFSET, Sid::lyricsPosBelow).toPointF();
|
|
}
|
|
else {
|
|
rypos() = -lh * (nAbove - _no - 1) - chordRest()->y();
|
|
rpos() += styleValue(Pid::OFFSET, Sid::lyricsPosAbove).toPointF();
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// paste
|
|
//---------------------------------------------------------
|
|
|
|
void Lyrics::paste(EditData& ed)
|
|
{
|
|
MuseScoreView* scoreview = ed.view;
|
|
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
|
QClipboard::Mode mode = QClipboard::Clipboard;
|
|
#else
|
|
QClipboard::Mode mode = QClipboard::Selection;
|
|
#endif
|
|
QString txt = QApplication::clipboard()->text(mode);
|
|
QString regex = QString("[^\\S") + QChar(0xa0) + QChar(0x202F) + "]+";
|
|
QStringList sl = txt.split(QRegExp(regex), QString::SkipEmptyParts);
|
|
if (sl.empty())
|
|
return;
|
|
|
|
QStringList hyph = sl[0].split("-");
|
|
bool minus = false;
|
|
bool underscore = false;
|
|
score()->startCmd();
|
|
|
|
if(hyph.length() > 1) {
|
|
score()->undo(new InsertText(cursor(ed), hyph[0]), &ed);
|
|
hyph.removeFirst();
|
|
sl[0] = hyph.join("-");
|
|
minus = true;
|
|
}
|
|
else if (sl.length() > 1 && sl[1] == "-") {
|
|
score()->undo(new InsertText(cursor(ed), sl[0]), &ed);
|
|
sl.removeFirst();
|
|
sl.removeFirst();
|
|
minus = true;
|
|
}
|
|
else if (sl[0].startsWith("_")) {
|
|
sl[0].remove(0, 1);
|
|
if (sl[0].isEmpty())
|
|
sl.removeFirst();
|
|
underscore = true;
|
|
}
|
|
else if (sl[0].contains("_")) {
|
|
int p = sl[0].indexOf("_");
|
|
score()->undo(new InsertText(cursor(ed), sl[0]), &ed);
|
|
sl[0] = sl[0].mid(p + 1);
|
|
if (sl[0].isEmpty())
|
|
sl.removeFirst();
|
|
underscore = true;
|
|
}
|
|
else if (sl.length() > 1 && sl[1] == "_") {
|
|
score()->undo(new InsertText(cursor(ed), sl[0]), &ed);
|
|
sl.removeFirst();
|
|
sl.removeFirst();
|
|
underscore = true;
|
|
}
|
|
else {
|
|
score()->undo(new InsertText(cursor(ed), sl[0]), &ed);
|
|
sl.removeFirst();
|
|
}
|
|
|
|
score()->endCmd();
|
|
txt = sl.join(" ");
|
|
|
|
QApplication::clipboard()->setText(txt, mode);
|
|
if (minus)
|
|
scoreview->lyricsMinus();
|
|
else if (underscore)
|
|
scoreview->lyricsUnderscore();
|
|
else
|
|
scoreview->lyricsTab(false, false, true);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// endTick
|
|
//---------------------------------------------------------
|
|
|
|
Fraction Lyrics::endTick() const
|
|
{
|
|
return segment()->tick() + ticks();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// acceptDrop
|
|
//---------------------------------------------------------
|
|
|
|
bool Lyrics::acceptDrop(EditData& data) const
|
|
{
|
|
return data.dropElement->isText() || TextBase::acceptDrop(data);
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// drop
|
|
//---------------------------------------------------------
|
|
|
|
Element* Lyrics::drop(EditData& data)
|
|
{
|
|
ElementType type = data.dropElement->type();
|
|
if (type == ElementType::SYMBOL || type == ElementType::FSYMBOL) {
|
|
TextBase::drop(data);
|
|
return 0;
|
|
}
|
|
if (!data.dropElement->isText()) {
|
|
delete data.dropElement;
|
|
data.dropElement = 0;
|
|
return 0;
|
|
}
|
|
Text* e = toText(data.dropElement);
|
|
e->setParent(this);
|
|
score()->undoAddElement(e);
|
|
return e;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// endEdit
|
|
//---------------------------------------------------------
|
|
|
|
void Lyrics::endEdit(EditData& ed)
|
|
{
|
|
TextBase::endEdit(ed);
|
|
triggerLayoutAll();
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// removeFromScore
|
|
//---------------------------------------------------------
|
|
|
|
void Lyrics::removeFromScore()
|
|
{
|
|
if (_separator) {
|
|
_separator->removeUnmanaged();
|
|
delete _separator;
|
|
_separator = 0;
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// getProperty
|
|
//---------------------------------------------------------
|
|
|
|
QVariant Lyrics::getProperty(Pid propertyId) const
|
|
{
|
|
switch (propertyId) {
|
|
case Pid::SYLLABIC:
|
|
return int(_syllabic);
|
|
case Pid::LYRIC_TICKS:
|
|
return _ticks;
|
|
case Pid::VERSE:
|
|
return _no;
|
|
default:
|
|
return TextBase::getProperty(propertyId);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// setProperty
|
|
//---------------------------------------------------------
|
|
|
|
bool Lyrics::setProperty(Pid propertyId, const QVariant& v)
|
|
{
|
|
switch (propertyId) {
|
|
case Pid::PLACEMENT:
|
|
setPlacement(Placement(v.toInt()));
|
|
break;
|
|
case Pid::SYLLABIC:
|
|
_syllabic = Syllabic(v.toInt());
|
|
break;
|
|
case Pid::LYRIC_TICKS:
|
|
_ticks = v.value<Fraction>();
|
|
break;
|
|
case Pid::VERSE:
|
|
_no = v.toInt();
|
|
break;
|
|
default:
|
|
if (!TextBase::setProperty(propertyId, v))
|
|
return false;
|
|
break;
|
|
}
|
|
triggerLayout();
|
|
return true;
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// propertyDefault
|
|
//---------------------------------------------------------
|
|
|
|
QVariant Lyrics::propertyDefault(Pid id) const
|
|
{
|
|
switch (id) {
|
|
case Pid::SUB_STYLE:
|
|
return int((_no & 1) ? Tid::LYRICS_EVEN : Tid::LYRICS_ODD);
|
|
case Pid::PLACEMENT:
|
|
return score()->styleV(Sid::lyricsPlacement);
|
|
case Pid::SYLLABIC:
|
|
return int(Syllabic::SINGLE);
|
|
case Pid::LYRIC_TICKS:
|
|
return Fraction(0,1);
|
|
case Pid::VERSE:
|
|
return 0;
|
|
case Pid::ALIGN:
|
|
if (isMelisma())
|
|
return score()->styleV(Sid::lyricsMelismaAlign);
|
|
// fall through
|
|
default:
|
|
return TextBase::propertyDefault(id);
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// forAllLyrics
|
|
//---------------------------------------------------------
|
|
|
|
void Score::forAllLyrics(std::function<void(Lyrics*)> f)
|
|
{
|
|
for (Segment* s = firstSegment(SegmentType::ChordRest); s; s = s->next1(SegmentType::ChordRest)) {
|
|
for (Element* e : s->elist()) {
|
|
if (e) {
|
|
for (Lyrics* l : toChordRest(e)->lyrics()) {
|
|
f(l);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------
|
|
// undoChangeProperty
|
|
//---------------------------------------------------------
|
|
|
|
void Lyrics::undoChangeProperty(Pid id, const QVariant& v, PropertyFlags ps)
|
|
{
|
|
if (id == Pid::VERSE && no() != v.toInt()) {
|
|
for (Lyrics* l : chordRest()->lyrics()) {
|
|
if (l->no() == v.toInt()) {
|
|
// verse already exists, swap
|
|
l->TextBase::undoChangeProperty(id, no(), ps);
|
|
Placement p = l->placement();
|
|
l->TextBase::undoChangeProperty(Pid::PLACEMENT, int(placement()), ps);
|
|
TextBase::undoChangeProperty(Pid::PLACEMENT, int(p), ps);
|
|
break;
|
|
}
|
|
}
|
|
TextBase::undoChangeProperty(id, v, ps);
|
|
return;
|
|
}
|
|
else if (id == Pid::AUTOPLACE && v.toBool() != autoplace()) {
|
|
if (v.toBool()) {
|
|
// setting autoplace
|
|
// reset offset
|
|
undoResetProperty(Pid::OFFSET);
|
|
}
|
|
else {
|
|
// unsetting autoplace
|
|
// rebase offset
|
|
QPointF off = offset();
|
|
qreal y = pos().y() - propertyDefault(Pid::OFFSET).toPointF().y();
|
|
off.ry() = placeAbove() ? y : y - staff()->height();
|
|
undoChangeProperty(Pid::OFFSET, off, PropertyFlags::UNSTYLED);
|
|
}
|
|
TextBase::undoChangeProperty(id, v, ps);
|
|
return;
|
|
}
|
|
#if 0
|
|
// TODO: create new command to do this
|
|
if (id == Pid::PLACEMENT) {
|
|
if (Placement(v.toInt()) == Placement::ABOVE) {
|
|
// change placment of all verse for the same voice upto this one to ABOVE
|
|
score()->forAllLyrics([this,id,v,ps](Lyrics* l) {
|
|
if (l->no() <= no() && l->voice() == voice())
|
|
l->TextBase::undoChangeProperty(id, v, ps);
|
|
});
|
|
}
|
|
else {
|
|
// change placment of all verse for the same voce starting from this one to BELOW
|
|
score()->forAllLyrics([this,id,v,ps](Lyrics* l) {
|
|
if (l->no() >= no() && l->voice() == voice())
|
|
l->TextBase::undoChangeProperty(id, v, ps);
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
TextBase::undoChangeProperty(id, v, ps);
|
|
}
|
|
|
|
}
|
|
|