diff --git a/filters/sheets/excel/sidewinder/excel.cpp b/filters/sheets/excel/sidewinder/excel.cpp index 8e4b731ca5f..19db6a1e955 100644 --- a/filters/sheets/excel/sidewinder/excel.cpp +++ b/filters/sheets/excel/sidewinder/excel.cpp @@ -1,2749 +1,2749 @@ /* Swinder - Portable library for spreadsheet Copyright (C) 2003-2005 Ariya Hidayat Copyright (C) 2006 Marijn Kruisselbrink Copyright (C) 2009,2010 Sebastian Sauer This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA */ #include "excel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "swinder.h" #include "utils.h" #include "globalssubstreamhandler.h" #include "worksheetsubstreamhandler.h" #include "chartsubstreamhandler.h" #include "XlsRecordOutputStream.h" //#define SWINDER_XLS2RAW using namespace Swinder; //============================================= // EString //============================================= class EString::Private { public: bool unicode; bool richText; QString str; unsigned size; std::map formatRuns; }; EString::EString() { d = new EString::Private(); d->unicode = false; d->richText = false; d->size = 0; } EString::EString(const EString& es) { d = new EString::Private(); operator=(es); } EString& EString::operator=(const EString & es) { d->unicode = es.d->unicode; d->richText = es.d->richText; d->size = es.d->size; d->str = es.d->str; return *this; } EString::~EString() { delete d; } bool EString::unicode() const { return d->unicode; } void EString::setUnicode(bool u) { d->unicode = u; } bool EString::richText() const { return d->richText; } void EString::setRichText(bool r) { d->richText = r; } QString EString::str() const { return d->str; } void EString::setStr(const QString& str) { d->str = str; } std::map EString::formatRuns() const { return d->formatRuns; } void EString::setFormatRuns(const std::map& formatRuns) { d->formatRuns = formatRuns; } unsigned EString::size() const { return d->size; } void EString::setSize(unsigned s) { d->size = s; } // FIXME use maxsize for sanity check EString EString::fromUnicodeString(const void* p, bool longString, unsigned /* maxsize */, const unsigned* continuePositions, unsigned continuePositionsOffset) { const unsigned char* data = (const unsigned char*) p; QString str; unsigned offset = longString ? 2 : 1; unsigned len = longString ? readU16(data) : data[0]; unsigned char flag = data[ offset ]; offset++; // for flag (1 byte) bool unicode = flag & 0x01; bool asianPhonetics = flag & 0x04; bool richText = flag & 0x08; unsigned formatRuns = 0; unsigned asianPhoneticsSize = 0; if (richText) { formatRuns = readU16(data + offset); offset += 2; } if (asianPhonetics) { asianPhoneticsSize = readU32(data + offset); offset += 4; } // find out total bytes used in this string unsigned size = offset; if (richText) size += (formatRuns * 4); if (asianPhonetics) size += asianPhoneticsSize; str.clear(); for (unsigned k = 0; k < len; ++k) { unsigned uchar; if (unicode) { uchar = readU16(data + offset); offset += 2; size += 2; } else { uchar = data[offset++]; size++; } str.append(QString(QChar(uchar))); if (continuePositions && offset == *continuePositions - continuePositionsOffset && k < len - 1) { unicode = data[offset] & 1; size++; offset++; continuePositions++; } } // read format runs std::map formatRunsMap; for (unsigned k = 0; k < formatRuns; ++k) { unsigned index = readU16(data + offset); unsigned font = readU16(data + offset + 2); if (index < len) formatRunsMap[index] = font; offset += 4; } EString result; result.setUnicode(unicode); result.setRichText(richText); result.setSize(size); result.setStr(str); result.setFormatRuns(formatRunsMap); return result; } // FIXME use maxsize for sanity check EString EString::fromByteString(const void* p, bool longString, unsigned /* maxsize */) { const unsigned char* data = (const unsigned char*) p; QString str; unsigned offset = longString ? 2 : 1; unsigned len = longString ? readU16(data) : data[0]; char* buffer = new char[ len+1 ]; memcpy(buffer, data + offset, len); buffer[ len ] = 0; str = QString(buffer); delete[] buffer; unsigned size = offset + len; EString result; result.setUnicode(false); result.setRichText(false); result.setSize(size); result.setStr(str); return result; } // why different ? see BoundSheetRecord EString EString::fromSheetName(const void* p, unsigned datasize) { const unsigned char* data = (const unsigned char*) p; QString str; bool richText = false; // unsigned formatRuns = 0; unsigned len = data[0]; unsigned flag = data[1]; bool unicode = flag & 1; if (len > datasize - 2) len = datasize - 2; if (len == 0) return EString(); unsigned offset = 2; if (!unicode) { char* buffer = new char[ len+1 ]; memcpy(buffer, data + offset, len); buffer[ len ] = 0; str = QString(buffer); delete[] buffer; } else { for (unsigned k = 0; k < len; ++k) { unsigned uchar = readU16(data + offset + k * 2); str.append(QString(QChar(uchar))); } } EString result; result.setUnicode(unicode); result.setRichText(richText); result.setSize(datasize); result.setStr(str); return result; } //============================================= // CellInfo //============================================= class CellInfo::Private { public: unsigned row; unsigned column; unsigned xfIndex; }; CellInfo::CellInfo() { info = new CellInfo::Private(); info->row = 0; info->column = 0; info->xfIndex = 0; } CellInfo::~CellInfo() { delete info; } unsigned CellInfo::row() const { return info->row; } void CellInfo::setRow(unsigned r) { info->row = r; } unsigned CellInfo::column() const { return info->column; } void CellInfo::setColumn(unsigned c) { info->column = c; } unsigned CellInfo::xfIndex() const { return info->xfIndex; } void CellInfo::setXfIndex(unsigned i) { info->xfIndex = i; } //============================================= // ColumnSpanInfo //============================================= class ColumnSpanInfo::Private { public: unsigned firstColumn; unsigned lastColumn; }; ColumnSpanInfo::ColumnSpanInfo() { spaninfo = new ColumnSpanInfo::Private(); spaninfo->firstColumn = 0; spaninfo->lastColumn = 0; } ColumnSpanInfo::~ColumnSpanInfo() { delete spaninfo; } unsigned ColumnSpanInfo::firstColumn() const { return spaninfo->firstColumn; } void ColumnSpanInfo::setFirstColumn(unsigned c) { spaninfo->firstColumn = c; } unsigned ColumnSpanInfo::lastColumn() const { return spaninfo->lastColumn; } void ColumnSpanInfo::setLastColumn(unsigned c) { spaninfo->lastColumn = c; } // ========== EXTERNBOOK ========== const unsigned int ExternBookRecord::id = 0x01ae; class ExternBookRecord::Private { public: unsigned sheetCount; QString name; }; ExternBookRecord::ExternBookRecord(Workbook *book) : Record(book), d(new Private) { d->sheetCount = 0; } ExternBookRecord::~ExternBookRecord() { delete d; } QString ExternBookRecord::bookName() const { return d->name; } void ExternBookRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 4) return; d->sheetCount = readU16(data); if (data[2] == 0x01 && data[3] == 0x04) { // self-referencing supporting link d->name = QString("\004"); } else if (data[2] == 0x01 && data[3] == ':') { // add-in referencing type of supporting link d->name = QString(":"); } else { d->name = EString::fromUnicodeString(data + 2, true, size - 2).str(); if (d->name.length() > 2 && d->name[0] == 0x0001) { if (d->name[1] == 0x0001) { // 'unc-volume' d->name = "unc://" + d->name.remove(0, 3).replace(0x0003, '/'); } else if (d->name[1] == 0x0002) { // relative to drive volume d->name.remove(0, 2).replace(0x0003, '/'); } else if (d->name[1] == 0x0005) { // full url d->name.remove(0, 3); } else { // TODO other options d->name.remove(0, 2).replace(0x0003, '/'); } } } } void ExternBookRecord::dump(std::ostream& out) const { out << "EXTERNBOOK" << std::endl; out << " Sheet count : " << d->sheetCount << std::endl; out << " Name : " << d->name << std::endl; } // ========== EXTERNNAME ========== const unsigned int ExternNameRecord::id = 0x0023; class ExternNameRecord::Private { public: unsigned optionFlags; unsigned sheetIndex; // one-based, not zero-based QString externName; }; ExternNameRecord::ExternNameRecord(Workbook *book) : Record(book), d(new Private) { d->optionFlags = 0; d->sheetIndex = 0; } ExternNameRecord::~ExternNameRecord() { delete d; } void ExternNameRecord::setSheetIndex(unsigned sheetIndex) { d->sheetIndex = sheetIndex; } unsigned ExternNameRecord::sheetIndex() const { return d->sheetIndex; } void ExternNameRecord::setExternName(const QString& name) { d->externName = name; } QString ExternNameRecord::externName() const { return d->externName; } void ExternNameRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 6) return; if (version() == Excel97) { d->optionFlags = readU16(data); d->sheetIndex = readU16(data + 2); d->externName = EString::fromUnicodeString(data + 6, false, size).str(); } if (version() == Excel95) { d->optionFlags = 0; d->sheetIndex = 0; d->externName = EString::fromByteString(data + 6, false, size).str(); } } void ExternNameRecord::dump(std::ostream& /*out*/) const { } // ========== FORMULA ========== const unsigned int FormulaRecord::id = 0x0006; class FormulaRecord::Private { public: Value result; FormulaTokens tokens; bool shared; }; FormulaRecord::FormulaRecord(Workbook *book): Record(book) { d = new FormulaRecord::Private(); d->shared = false; } FormulaRecord::~FormulaRecord() { delete d; } Value FormulaRecord::result() const { return d->result; } void FormulaRecord::setResult(const Value& r) { d->result = r; } FormulaTokens FormulaRecord::tokens() const { return d->tokens; } void FormulaRecord::addToken(const FormulaToken &token) { d->tokens.push_back(token); } bool FormulaRecord::isShared() const { return d->shared; } void FormulaRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 20) return; // cell setRow(readU16(data)); setColumn(readU16(data + 2)); setXfIndex(readU16(data + 4)); // val if (readU16(data + 12) != 0xffff) { // Floating-point setResult(Value(readFloat64(data + 6))); } else { switch (data[6]) { case 0: // string, real value in subsequent string record setResult(Value(Value::String)); break; case 1: // boolean setResult(Value(data[8] ? true : false)); break; case 2: // error code setResult(errorAsValue(data[8])); break; case 3: // empty setResult(Value::empty()); break; default: // fallback setResult(Value::empty()); break; }; } unsigned opts = readU16(data + 14); //const bool fAlwaysCalc = opts & 0x01; //const bool reserved1 = opts & 0x02; //const bool fFill = opts & 0x04; d->shared = opts & 0x08; //const bool reserved2 = opts & 0x10; //const bool fClearErrors = opts & 0x20; // 4 bytes chn... FormulaDecoder decoder; d->tokens = decoder.decodeFormula(size, 20, data, version()); } void FormulaRecord::writeData(XlsRecordOutputStream &o) const { o.writeUnsigned(16, row()); o.writeUnsigned(16, column()); o.writeUnsigned(16, xfIndex()); if (d->result.isNumber()) { o.writeFloat(64, d->result.asFloat()); } else if (d->result.isString()) { o.writeUnsigned(8, 0); // type o.writeUnsigned(24, 0); // reserved o.writeUnsigned(16, 0); // reserved o.writeUnsigned(16, 0xFFFF); } else if (d->result.isBoolean()) { o.writeUnsigned(8, 1); // type o.writeUnsigned(8, 0); // reserved o.writeUnsigned(8, d->result.asBoolean() ? 1 : 0); o.writeUnsigned(24, 0); // reserved o.writeUnsigned(16, 0xFFFF); } else if (d->result.isError()) { o.writeUnsigned(8, 2); // type o.writeUnsigned(8, 0); // reserved Value v = d->result; if (v == Value::errorNULL()) { o.writeUnsigned(8, 0x00); } else if (v == Value::errorDIV0()) { o.writeUnsigned(8, 0x07); } else if (v == Value::errorVALUE()) { o.writeUnsigned(8, 0x0F); } else if (v == Value::errorREF()) { o.writeUnsigned(8, 0x17); } else if (v == Value::errorNAME()) { o.writeUnsigned(8, 0x1D); } else if (v == Value::errorNUM()) { o.writeUnsigned(8, 0x24); } else if (v == Value::errorNA()) { o.writeUnsigned(8, 0x2A); } else { o.writeUnsigned(8, 0x2A); } o.writeUnsigned(24, 0); // reserved o.writeUnsigned(16, 0xFFFF); } else { o.writeUnsigned(8, 3); // type o.writeUnsigned(24, 0); // reserved o.writeUnsigned(16, 0); // reserved o.writeUnsigned(16, 0xFFFF); } o.writeUnsigned(1, 1); // fAlwaysRecalc o.writeUnsigned(1, 0); // reserved o.writeUnsigned(1, 0); // fFill o.writeUnsigned(1, d->shared ? 1 : 0); o.writeUnsigned(1, 0); // reserved o.writeUnsigned(1, 0); // fClearErrors o.writeUnsigned(10, 0); // reserved o.writeUnsigned(32, 0); // chn // actual formula unsigned totalSize = 0; for (unsigned i = 0; i < d->tokens.size(); ++i) { totalSize += d->tokens[i].size() + 1; } o.writeUnsigned(16, totalSize); for (unsigned i = 0; i < d->tokens.size(); ++i) { o.writeUnsigned(8, d->tokens[i].id()); // ptg std::vector data = d->tokens[i].data(); o.writeBlob(QByteArray::fromRawData(reinterpret_cast(&data[0]), data.size())); } } void FormulaRecord::dump(std::ostream& out) const { out << "FORMULA" << std::endl; out << " Row : " << row() << std::endl; out << " Column : " << column() << std::endl; out << " XF Index : " << xfIndex() << std::endl; out << " Result : " << result() << std::endl; FormulaTokens ts = tokens(); out << " Tokens : " << ts.size() << std::endl; for (unsigned i = 0; i < ts.size(); ++i) out << " " << ts[i] << std::endl; } // SHAREDFMLA const unsigned int SharedFormulaRecord::id = 0x04BC; class SharedFormulaRecord::Private { public: // range int numCells; FormulaTokens tokens; }; SharedFormulaRecord::SharedFormulaRecord(Workbook *book): Record(book) { d = new SharedFormulaRecord::Private(); } SharedFormulaRecord::~SharedFormulaRecord() { delete d; } FormulaTokens SharedFormulaRecord::tokens() const { return d->tokens; } void SharedFormulaRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 8) return; // maybe read range d->numCells = data[7]; unsigned formula_len = readU16(data + 8); // reconstruct all tokens d->tokens.clear(); for (unsigned j = 10; j < size;) { unsigned ptg = data[j++]; ptg = ((ptg & 0x40) ? (ptg | 0x20) : ptg) & 0x3F; FormulaToken token(ptg); token.setVersion(version()); if (token.id() == FormulaToken::String) { // find bytes taken to represent the string EString estr = (version() == Excel97) ? EString::fromUnicodeString(data + j, false, formula_len) : EString::fromByteString(data + j, false, formula_len); token.setData(estr.size(), data + j); j += estr.size(); } else { // normal, fixed-size token if (token.size() > 1) { token.setData(token.size(), data + j); j += token.size(); } } d->tokens.push_back(token); } } void SharedFormulaRecord::dump(std::ostream& out) const { out << "SHAREDFMLA" << std::endl; // range out << " Num cells : " << d->numCells << std::endl; FormulaTokens ts = tokens(); out << " Tokens : " << ts.size() << std::endl; for (unsigned i = 0; i < ts.size(); ++i) out << " " << ts[i] << std::endl; } // ========== MULRK ========== const unsigned int MulRKRecord::id = 0x00bd; class MulRKRecord::Private { public: std::vector xfIndexes; std::vector isIntegers; std::vector intValues; std::vector floatValues; std::vector rkValues; }; MulRKRecord::MulRKRecord(Workbook *book): Record(book), CellInfo(), ColumnSpanInfo() { d = new MulRKRecord::Private(); } MulRKRecord::~MulRKRecord() { delete d; } unsigned MulRKRecord::xfIndex(unsigned i) const { if (i >= d->xfIndexes.size()) return 0; return d->xfIndexes[ i ]; } bool MulRKRecord::isInteger(unsigned i) const { if (i >= d->isIntegers.size()) return true; return d->isIntegers[ i ]; } int MulRKRecord::asInteger(unsigned i) const { if (i >= d->intValues.size()) return 0; return d->intValues[ i ]; } double MulRKRecord::asFloat(unsigned i) const { if (i >= d->floatValues.size()) return 0.0; return d->floatValues[ i ]; } unsigned MulRKRecord::encodedRK(unsigned i) const { if (i >= d->rkValues.size()) return 0; return d->rkValues[ i ]; } void MulRKRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 6) return; setRow(readU16(data)); setFirstColumn(readU16(data + 2)); setLastColumn(readU16(data + size - 2)); d->xfIndexes.clear(); d->isIntegers.clear(); d->intValues.clear(); d->floatValues.clear(); for (unsigned i = 4; i < size - 2; i += 6) { d->xfIndexes.push_back(readU16(data + i)); unsigned rk = readU32(data + i + 2); d->rkValues.push_back(rk); bool isInteger = true; int iv = 0; double fv = 0.0; decodeRK(rk, isInteger, iv, fv); d->isIntegers.push_back(isInteger); d->intValues.push_back(isInteger ? iv : (int)fv); d->floatValues.push_back(!isInteger ? fv : (double)iv); } // FIXME sentinel ! } void MulRKRecord::dump(std::ostream& out) const { out << "MULRK" << std::endl; out << " Row : " << row() << std::endl; out << " First Column : " << firstColumn() << std::endl; out << " Last Column : " << lastColumn() << std::endl; for (unsigned c = firstColumn(); c <= lastColumn(); ++c) { out << " Column " << c << " : " << asFloat(c - firstColumn()); out << " Encoded: " << std::hex << encodedRK(c - firstColumn()); out << " Xf: " << std::dec << xfIndex(c - firstColumn()); out << std::endl; } } // ========== NAME ========== const unsigned int NameRecord::id = 0x0018; // Lbl record class NameRecord::Private { public: unsigned optionFlags; QString definedName; int sheetIndex; // 0 for global bool builtin; }; NameRecord::NameRecord(Workbook *book) : Record(book) { d = new Private; d->optionFlags = 0; } NameRecord::~NameRecord() { delete d; } void NameRecord::setDefinedName(const QString& name) { d->definedName = name; } QString NameRecord::definedName() const { return d->definedName; } unsigned NameRecord::sheetIndex() const { return d->sheetIndex; } bool NameRecord::isBuiltin() const { return d->builtin; } void NameRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 14) { setIsValid(false); return; } d->optionFlags = readU16(data); //const bool fHidden = d->optionFlags & 0x01; //const bool fFunc = d->optionFlags & 0x02; //const bool fOB = d->optionFlags & 0x04; //const bool fProc = d->optionFlags & 0x08; //const bool fCalcExp = d->optionFlags & 0x10; d->builtin = d->optionFlags & 0x20; // 6 bits fGrp //const bool reserved1 = d->optionFlags & 0x1800; //const bool fPublished = d->optionFlags & 0x3000; //const bool fWorkbookParam = d->optionFlags & 0x6000; //const bool reserved2 = d->optionFlags & 0xC000; const unsigned len = readU8(data + 3); // cch const unsigned cce = readU16(data + 4); // len of rgce // 2 bytes reserved d->sheetIndex = readU16(data + 8); // if !=0 then its a local name // 4 bytes reserved if (version() == Excel95) { char* buffer = new char[ len+1 ]; memcpy(buffer, data + 14, len); buffer[ len ] = 0; d->definedName = QString(buffer); delete[] buffer; } else if (version() == Excel97) { if (d->builtin) { // field is for a build-in name const unsigned opts = readU8(data + 14); const bool fHighByte = opts & 0x01; const unsigned id = fHighByte ? readU16(data + 15) : readU8(data + 15) + 0x0*256; switch(id) { case 0x00: d->definedName = "Consolidate_Area"; break; case 0x01: d->definedName = "Auto_Open"; break; case 0x02: d->definedName = "Auto_Close"; break; case 0x03: d->definedName = "Extract"; break; case 0x04: d->definedName = "Database"; break; case 0x05: d->definedName = "Criteria"; break; case 0x06: d->definedName = "Print_Area"; break; case 0x07: d->definedName = "Print_Titles"; break; case 0x08: d->definedName = "Recorder"; break; case 0x09: d->definedName = "Data_Form"; break; case 0x0A: d->definedName = "Auto_Activate"; break; case 0x0B: d->definedName = "Auto_Deactivate"; break; case 0x0C: d->definedName = "Sheet_Title"; break; case 0x0D: d->definedName = "_FilterDatabase"; break; default: break; } } else { // must satisfy same restrictions then name field on XLNameUnicodeString const unsigned opts = readU8(data + 14); const bool fHighByte = opts & 0x01; // XLUnicodeStringNoCch QString str; if (fHighByte) { for (unsigned k = 0; k < len*2; ++k) { unsigned zc = readU16(data + 15 + k * 2); str.append(QString(zc)); } } else { for (unsigned k = 0; k < len; ++k) { unsigned char uc = readU8(data + 15 + k) + 0x0 * 256; str.append(QString(uc)); } } // This is rather illogical and seems there is nothing in the specs about this, // but the string "_xlfn." may in front of the string we are looking for. So, // remove that one and ignore whatever it means... if (str.startsWith("_xlfn.")) str.remove(0, 6); d->definedName = str; } } else { setIsValid(false); } // rgce, NamedParsedFormula if(cce >= 1) { /* FormulaDecoder decoder; m_formula = decoder.decodeNamedFormula(cce, data + size - cce, version()); std::cout << ">>" << m_formula.ascii() << std::endl; */ const unsigned char* startNamedParsedFormula = data + size - cce; unsigned ptg = readU8(startNamedParsedFormula); ptg = ((ptg & 0x40) ? (ptg | 0x20) : ptg) & 0x3F; FormulaToken t(ptg); t.setVersion(version()); t.setData(cce - 1, startNamedParsedFormula + 1); m_formula = t; } std::cout << "NameRecord name=" << d->definedName << " iTab=" << d->sheetIndex << " fBuiltin=" << d->builtin << " formula=" << m_formula.id() << " (" << m_formula.idAsString() << ")" << std::endl; } void NameRecord::dump(std::ostream& /*out*/) const { } // ========== RK ========== const unsigned int RKRecord::id = 0x027e; class RKRecord::Private { public: bool integer; unsigned rk; int i; double f; }; RKRecord::RKRecord(Workbook *book): Record(book), CellInfo() { d = new RKRecord::Private(); d->integer = true; d->rk = 0; d->i = 0; d->f = 0.0; } RKRecord::~RKRecord() { delete d; } bool RKRecord::isInteger() const { return d->integer; } bool RKRecord::isFloat() const { return !d->integer; } int RKRecord::asInteger() const { if (d->integer) return d->i; else return (int)d->f; } double RKRecord::asFloat() const { if (!d->integer) return d->f; else return (double)d->i; } void RKRecord::setInteger(int i) { d->integer = true; d->i = i; d->f = (double)i; } void RKRecord::setFloat(double f) { d->integer = false; d->i = (int)f; d->f = f; } unsigned RKRecord::encodedRK() const { return d->rk; } // FIXME check sizeof(int) is 32 // big vs little endian problem void RKRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 10) return; setRow(readU16(data)); setColumn(readU16(data + 2)); setXfIndex(readU16(data + 4)); int i = 0; double f = 0.0; d->rk = readU32(data + 6); decodeRK(d->rk, d->integer, i, f); if (d->integer) setInteger(i); else setFloat(f); } void RKRecord::dump(std::ostream& out) const { out << "RK" << std::endl; out << " Row : " << row() << std::endl; out << " Column : " << column() << std::endl; out << " XF Index : " << xfIndex() << std::endl; out << " Value : " << asFloat() << std::endl; out << " Encoded RK : 0x" << std::hex << encodedRK() << std::endl; out << std::dec; } // ========== RSTRING ========== const unsigned int RStringRecord::id = 0x00d6; class RStringRecord::Private { public: QString label; }; RStringRecord::RStringRecord(Workbook *book): Record(book), CellInfo() { d = new RStringRecord::Private(); } RStringRecord::~RStringRecord() { delete d; } QString RStringRecord::label() const { return d->label; } void RStringRecord::setLabel(const QString& l) { d->label = l; } // FIXME formatting runs ? in EString perhaps ? void RStringRecord::setData(unsigned size, const unsigned char* data, const unsigned int*) { if (size < 6) return; setRow(readU16(data)); setColumn(readU16(data + 2)); setXfIndex(readU16(data + 4)); // FIXME check Excel97 QString label = (version() >= Excel97) ? EString::fromUnicodeString(data + 6, true, size - 6).str() : EString::fromByteString(data + 6, true, size - 6).str(); setLabel(label); } void RStringRecord::dump(std::ostream& out) const { out << "RSTRING" << std::endl; out << " Row : " << row() << std::endl; out << " Column : " << column() << std::endl; out << " XF Index : " << xfIndex() << std::endl; out << " Label : " << label() << std::endl; } // ========== SST ========== const unsigned int SSTRecord::id = 0x00fc; class SSTRecord::Private { public: unsigned total; std::vector strings; std::vector > formatRuns; ExtSSTRecord* esst; }; SSTRecord::SSTRecord(Workbook *book): Record(book) { d = new SSTRecord::Private(); d->total = 0; d->esst = 0; } SSTRecord::~SSTRecord() { delete d; } QString sstrecord_get_plain_string(const unsigned char* data, unsigned length) { char* buffer = new char[ length+1 ]; memcpy(buffer, data, length); buffer[ length ] = 0; QString str = QString(buffer); delete[] buffer; return str; } void SSTRecord::setData(unsigned size, const unsigned char* data, const unsigned int* continuePositions) { if (size < 8) return; d->total = readU32(data); unsigned count = readU32(data + 4); unsigned offset = 8; unsigned int nextContinuePosIdx = 0; unsigned int nextContinuePos = continuePositions[0]; d->strings.clear(); for (unsigned i = 0; i < count; ++i) { // check against size if (offset >= size) { std::cerr << "Warning: reached end of SST record, but not all strings have been read!" << std::endl; break; } EString es = EString::fromUnicodeString(data + offset, true, size - offset, continuePositions + nextContinuePosIdx, offset); d->strings.push_back(es.str()); d->formatRuns.push_back(es.formatRuns()); offset += es.size(); while (nextContinuePos < offset) nextContinuePos = continuePositions[++nextContinuePosIdx]; } // sanity check, adjust to safer condition if (count > d->strings.size()) { std::cerr << "Warning: mismatch number of string in SST record, expected " << count << ", got " << d->strings.size() << "!" << std::endl; } } void SSTRecord::writeData(XlsRecordOutputStream &out) const { unsigned dsst = qMax(8, (count() / 128)+1); if (d->esst) { d->esst->setDsst(dsst); d->esst->setGroupCount((count() + dsst-1) / dsst); } out.writeUnsigned(32, d->total); out.writeUnsigned(32, count()); for (unsigned i = 0; i < count(); ++i) { if (i % dsst == 0 && d->esst) { d->esst->setIb(i/dsst, out.pos()); d->esst->setCbOffset(i/dsst, out.recordPos() + 4); } out.writeUnicodeStringWithFlagsAndLength(stringAt(i)); } } unsigned SSTRecord::count() const { return d->strings.size(); } unsigned SSTRecord::useCount() const { return d->total; } void SSTRecord::setUseCount(unsigned count) { d->total = count; } void SSTRecord::setExtSSTRecord(ExtSSTRecord *esst) { d->esst = esst; } // why not just string() ? to avoid easy confusion with std::string QString SSTRecord::stringAt(unsigned index) const { if (index >= count()) return QString(); return d->strings[ index ]; } std::map SSTRecord::formatRunsAt(unsigned index) const { if (index >= count()) return std::map(); return d->formatRuns[ index ]; } unsigned SSTRecord::addString(const QString &string) { d->strings.push_back(string); return d->strings.size()-1; } void SSTRecord::dump(std::ostream& out) const { out << "SST" << std::endl; out << " Occurrences : " << d->total << std::endl; out << " Count : " << count() << std::endl; for (unsigned i = 0; i < count(); ++i) out << " String #" << std::setw(2) << i << " : " << stringAt(i) << std::endl; } // ========== Obj ========== const unsigned ObjRecord::id = 0x5D; ObjRecord::ObjRecord(Workbook *book) : Record(book), m_object(0) {} ObjRecord::~ObjRecord() { delete m_object; } void ObjRecord::dump(std::ostream& out) const { out << "Obj" << std::endl; if (m_object) { out << " id: " << m_object->id() << std::endl; out << " type: " << m_object->type() << std::endl; } } void ObjRecord::setData(unsigned size, const unsigned char* data, const unsigned* /* continuePositions */) { if (size < 4) { setIsValid(false); return; } // FtCmo struct const unsigned char* startFtCmo = data; const unsigned long ftcmo = readU16(startFtCmo); const unsigned long cbcmo = readU16(startFtCmo + 2); if (ftcmo != 0x15 || cbcmo != 0x12) { std::cerr << "ObjRecord::setData: invalid ObjRecord" << std::endl; setIsValid(false); return; } // cmo struct const unsigned long ot = readU16(startFtCmo + 4); const unsigned long id = readU16(startFtCmo + 6); //const unsigned long opts = readU16(startFtCmo + 8); //const bool fLocked = opts & 0x01; //const bool reserved = opts & 0x02; //const bool fDefaultSize = opts & 0x04; //const bool fPublished = opts & 0x08; //const bool fPrint = opts & 0x10; //const bool unused1 = opts & 0x20; //const bool unused2 = opts & 0x60; //const bool fDisabled = opts & 0xC0; //const bool fUIObj = opts & 0x180; //const bool fRecalcObj = opts & 0x300; //const bool unused3 = opts & 0x600; //const bool unused4 = opts & 0xC00; //const bool fRecalcObjAlways = opts & 0x1800; //const bool unused5 = opts & 0x3000; //const bool unused6 = opts & 0x6000; //const bool unused7 = opts & 0xC000; //const unsigned long unused8 = readU32(startFtCmo + 10); //const unsigned long unused9 = readU32(startFtCmo + 14); //const unsigned long unused10 = readU32(startFtCmo + 18); bool fDde = false; // dynamic data exchange reference? bool fCtl = false; // ActiveX control? bool fPrstm = false; // false=embedded store or true=control stream const unsigned char* startPict = data + 22; switch (ot) { case Object::Group: // gmo printf("ObjRecord::setData group\n"); startPict += 6; break; case Object::Picture: { // pictFormat and pictFlags m_object = new Object(Object::Picture, id); //const unsigned long ft = readU16(startPict); //const unsigned long cb = readU16(startPict + 2); // cf specifies Windows Clipboard format -- so far unused const unsigned long cf = readU16(startPict + 4); switch (cf) { case 0x0002: // enhanced metafile break; case 0x0009: // bitmap break; case 0xFFFF: // unspecified format, neither enhanced metafile nor a bitmap break; default: std::cerr << "ObjRecord::setData: invalid ObjRecord Picture" << std::endl; setIsValid(false); delete m_object; m_object = 0; return; } const unsigned long ft2 = readU16(startPict + 6); Q_ASSERT(ft2 == 0x0008); Q_UNUSED(ft2); const unsigned long cb2 = readU16(startPict + 8); Q_ASSERT(cb2 == 0x0002); Q_UNUSED(cb2); const unsigned long opts2 = readU16(startPict + 10); //const bool fAutoPict = opts2 & 0x01; fDde = opts2 & 0x02; // dynamic data exchange reference? //const bool dPrintCalc = opts2 & 0x04; //const bool fIcon = opts2 & 0x08; fCtl = opts2 & 0x10; // ActiveX control? Q_ASSERT(!(fCtl && fDde)); fPrstm = opts2 & 0x20; //const bool unused1 = opts2 & 0x60; //const bool fCamera = opts2 & 0xC0; //const bool fDefaultSize = opts2 & 0x180; //const bool fAutoload = opts2 & 0x300; //const bool unused2 = opts2 & 0x600; //const bool unused3 = opts2 & 0xC00; //const bool unused4 = opts2 & 0x1800; //const bool unused5 = opts2 & 0x3000; //const bool unused6 = opts2 & 0x6000; //const bool unused7 = opts2 & 0xC000; std::cout << "ObjRecord::setData picture id=" << id << " fDde=" << fDde << " FCtl=" << fCtl << " fPrstm=" << fPrstm << std::endl; startPict += 12; } break; case Object::Checkbox: // cbls printf("ObjRecord::setData checkbox\n"); startPict += 16; break; case Object::RadioButton: // cbls and rbo printf("ObjRecord::setData RadioButton\n"); startPict += 26; break; case Object::SpinControl: // sbs printf("ObjRecord::setData SpinControl\n"); startPict += 24; break; case Object::Scrollbar: // sbs printf("ObjRecord::setData Scrollbar\n"); startPict += 24; break; case Object::List: // sbs printf("ObjRecord::setData List\n"); startPict += 24; break; case Object::DropdownList: // sbs printf("ObjRecord::setData DropdownList\n"); startPict += 24; break; case Object::Note: { // nts std::cout << "ObjRecord::setData note id=" << id << std::endl; m_object = new NoteObject(id); const unsigned long ft = readU16(startPict); const unsigned long cb = readU16(startPict + 2); startPict += 20; // skip guid if (ft != 0x000D || cb != 0x0016) { std::cerr << "ObjRecord::setData: invalid ObjRecord note with id=" << id << std::endl; setIsValid(false); delete m_object; m_object = 0; return; } //const unsigned long isShared = readU16(startPict); // 0x0000 = Not shared, 0x0001 = Shared. //Q_ASSERT( isShared == 0x0000 || isShared == 0x0001 ); startPict += 6; // includes 4 unused bytes //the TxO record that contains the text comes after this record... //static_cast(m_object)->setNote( ); } break; case Object::Chart: std::cout << "ObjRecord::setData chart id=" << id << std::endl; m_object = new ChartObject(id); break; case Object::Rectangle: printf("ObjRecord::setData Rectangle\n"); break; case Object::Line: printf("ObjRecord::setData Line\n"); break; case Object::Oval: printf("ObjRecord::setData Oval\n"); break; case Object::Arc: printf("ObjRecord::setData Arc\n"); break; case Object::Text: printf("ObjRecord::setData Text\n"); break; case Object::Button: printf("ObjRecord::setData Button\n"); break; case Object::Polygon: printf("ObjRecord::setData Polygon\n"); break; case Object::EditBox: printf("ObjRecord::setData EditBox\n"); break; case Object::Label: printf("ObjRecord::setData Label\n"); break; case Object::DialogBox: printf("ObjRecord::setData DialogBox\n"); break; case Object::GroupBox: printf("ObjRecord::setData GroupBox\n"); break; case Object::OfficeArt: printf("ObjRecord::setData OfficeArt\n"); break; default: std::cerr << "ObjRecord::setData: Unexpected objecttype " << ot << " in ObjRecord" << std::endl; setIsValid(false); delete m_object; m_object = 0; return; } { // FtMacro. Specs say it's optional by not when it's used. So, we need to check it by assuming // a valid FtMacro starts with 0x0004... The following code is untested. The only thing we are // interested in here is seeking to the structs after this one anyway. const unsigned long ft = readU16(startPict); if (ft == 0x0004) { const unsigned long cmFmla = readU16(startPict + 2); startPict += 4; int sizeFmla = 0; if (cmFmla > 0x0000) { // ObjectParseFormula const unsigned long cce = readU16(startPict) >> 1; // 15 bits cce + 1 bit reserved // 4 bytes unused sizeFmla = 2 + 4 + cce; //startPict += sizeFmla; } // skip embedInfo cause we are not a FtPictFmla startPict += cmFmla - sizeFmla - 0; // padding } } // pictFmla if (ot == Object::Picture && readU16(startPict) == 0x0009 /* checks ft */) { //const unsigned long cb = readU16(startPict + 2); startPict += 4; /* from the specs; fmla (variable): An ObjFmla that specifies the location of the data for the object associated with the Obj record that contains this FtPictFmla. If the pictFlags.fDde field of the Obj record that contains this FtPictFmla is 1, fmla MUST refer to a name which is defined in an ExternName record whose fOle field is 1. If the pictFlags.fCamera field of the Obj record that contains this FtPictFmla is 1, fmla MUST refer to a range. Otherwise, the fmla.cce field of this fmla MUST be 0x5 and the fmla.rgce field of this fmla MUST contain a PtgTbl followed by four bytes that are undefined and MUST be ignored. */ // fmla variable, an ObjFmla struct FormulaToken token; const unsigned long cbFmla = readU16(startPict); int cbFmlaSize = 0; int embedInfoSize = 0; if (cbFmla > 0x0000) { // fmla variable, optional ObjectParsedFormula struct const unsigned long cce = readU16(startPict + cbFmlaSize + 2) >> 1; // 15 bits cce + 1 bit reserved cbFmlaSize += 2 + 2 + 4; // 4 bytes unused // rgce unsigned ptg = readU8(startPict + cbFmlaSize); cbFmlaSize += 1; ptg = ((ptg & 0x40) ? (ptg | 0x20) : ptg) & 0x3F; token = FormulaToken(ptg); token.setVersion(version()); std::cout << "ObjRecord::setData: Picture is of type id=" << token.id() << " name=" << token.idAsString() << std::endl; if (token.size() > 0) { token.setData(token.size(), startPict + cbFmlaSize); cbFmlaSize += token.size(); } if (cce == 0x5 && token.id() == FormulaToken::Table) { cbFmlaSize += 4; } // embededInfo variable, an optional PictFmlaEmbedInfo if (token.id() == FormulaToken::Table) { const unsigned ttb = readU8(startPict + cbFmlaSize); if (ttb == 0x03) { const unsigned cbClass = readU8(startPict + cbFmlaSize + embedInfoSize + 1); //const unsigned reserved = readU8(startPict + cbFmlaSize + embedInfoSize + 2); embedInfoSize += 3; if (cbClass > 0x0000) { // strClass specifies the class name of the embedded control unsigned size = 0; QString className = readUnicodeString(startPict + cbFmlaSize + embedInfoSize, cbClass, -1, 0, &size); embedInfoSize += size; //TODO std::cout << "ObjRecord::setData: className=" << qPrintable(className) << std::endl; } } } } startPict += cbFmla + 2; // IPosInCtlStm variable if (token.id() == FormulaToken::Table) { const unsigned int iposInCtlStm = readU32(startPict); if (fPrstm) { // iposInCtlStm specifies the zero-based offset of this object's data within the control stream. const unsigned int cbBufInCtlStm = readU32(startPict + 4); startPict += 8; Q_UNUSED(iposInCtlStm); // it was used in PictureObject as offset, but nobody used it Q_UNUSED(cbBufInCtlStm); // it was used in PictureObject as size, but nobody used it } else { // The object‘s data MUST reside in an embedding storage. std::stringstream out; out << std::setw(8) << std::setfill('0') << std::uppercase << std::hex << iposInCtlStm; // out.str() was used as embedding storage, but nobody used it } } // key variable, PictFmlaKey struct if (fCtl) { std::string key; const unsigned int cbKey = readU32(startPict); startPict += 4; for (uint i = 0; i < cbKey; ++i) { if (key.size() > 0) key += "."; key = readU32(startPict); startPict += 4; } //fmlaLinkedCell //fmlaListFillRange std::cout << "ObjRecord::setData: Runtime license key is: " << key.c_str() << std::endl; } } // linkFmla // checkBox // radionButton // edit // list // gbo } // ========== TxO ========== class TxORecord::Private { public: QString text; QSharedPointer richText; // NULL if plainText else it defines the richText TxORecord::HorizontalAlignment hAlign; TxORecord::VerticalAlignment vAlign; }; const unsigned TxORecord::id = 0x1B6; TxORecord::TxORecord(Workbook *book) : Record(book) { d = new TxORecord::Private(); } TxORecord::TxORecord(const TxORecord& other) : Record(other) { d = new TxORecord::Private(); operator=(other); } TxORecord::~TxORecord() { delete d; } TxORecord& TxORecord::operator=(const TxORecord &other) { d->text = other.d->text; d->richText = other.d->richText; d->hAlign = other.d->hAlign; d->vAlign = other.d->vAlign; return *this; } void TxORecord::dump(std::ostream& out) const { out << "TxO" << std::endl; out << " " << d->text << " " << d->hAlign << " " << d->vAlign; } void TxORecord::setData(unsigned size, const unsigned char* data, const unsigned* continuePositions) { const unsigned long opts1 = readU16(data); //const bool reserved1 = opts1 & 0x01; d->hAlign = static_cast((opts1 & 0x000e) >> 1); // 3 bits d->vAlign = static_cast((opts1 & 0x0070) >> 4); // 3 bits //const unsigned long rot = readU16(data + 2); // 4 bytes reserved // controlInfo (6 bytes): An optional ControlInfo structure that specifies the properties for some // form controls. The field MUST exist if and only if the value of cmo.ot in the preceding Obj // record is 0, 5, 7, 11, 12, or 14. const unsigned long cchText = readU16(data + 14); const unsigned char* startPict = data + 16; const unsigned char* endPict = data + size; if(cchText > 0) { //const unsigned long cbRuns = readU16(startPict); const unsigned long cbFmla = readU16(startPict + 2); // fmla, ObjFmla structure startPict += 4 + cbFmla; } else { //const unsigned long ifntEmpty = readU16(startPict); // FontIndex startPict += 2; const unsigned *endOffset = continuePositions; while (data + *endOffset <= startPict && *endOffset < size) endOffset++; endPict = data + *endOffset; } const unsigned opts = readU8(startPict); const bool fHighByte = opts & 0x01; // this seems to assert with some documents... //Q_ASSERT((opts << 1) == 0x0); // XLUnicodeStringNoCch d->text.clear(); unsigned k = 1; if(fHighByte) { for (; startPict + k + 1 < endPict; k += 2) { unsigned zc = readU16(startPict + k); if (!zc) break; if (!QChar(zc).isPrint() && zc != 10) { d->text.clear(); break; } d->text.append(QChar(zc)); } } else { for (; startPict + k < endPict; k += 1) { unsigned char uc = readU8(startPict + k) + 0x0*256; if (!uc) break; if (!QChar(uc).isPrint() && uc != 10) { d->text.clear(); break; } d->text.append(QChar(uc)); } } d->richText.clear(); // Now look for TxORun structures that specify the formatting run information for the TxO record. int ToXRunsPositionIndex = 0; do { unsigned pos = continuePositions[ToXRunsPositionIndex]; if (pos + 8 > size) { ToXRunsPositionIndex = 0; break; // not found } if (pos >= k) { break; // we have it } ++ToXRunsPositionIndex; } while(true); if (ToXRunsPositionIndex > 0) { d->richText = QSharedPointer(new QTextDocument()); // also add a textrangemanager, as KoTextWriter assumes one - KoTextDocument(d->richText).setTextRangeManager(new KoTextRangeManager); + KoTextDocument(d->richText.data()).setTextRangeManager(new KoTextRangeManager); d->richText->setPlainText(d->text); QTextCursor cursor(d->richText.data()); //cursor.setVisualNavigation(true); QTextCharFormat format; unsigned pos = continuePositions[ToXRunsPositionIndex]; for(;pos + 8 <= size; pos += 8) { // now walk through the array of Run records const unsigned ich = readU16(data + pos); const unsigned ifnt = readU16(data + pos + 2); if (format.isValid()) { cursor.setPosition(ich, QTextCursor::KeepAnchor); cursor.setCharFormat(format); cursor.setPosition(ich, QTextCursor::MoveAnchor); } if (ich >= unsigned(d->text.length())) { break; } FormatFont font = m_workbook->font(ifnt); Q_ASSERT(!font.isNull()); format.setFontFamily(font.fontFamily()); format.setFontPointSize(font.fontSize()); format.setForeground(QBrush(font.color())); format.setFontWeight(font.bold() ? QFont::Bold : QFont::Normal); format.setFontItalic(font.italic()); format.setFontUnderline(font.underline()); format.setFontStrikeOut(font.strikeout()); //TODO font.subscript() //TODO font.superscript() } } std::cout << "TxORecord::setData size=" << size << " text=" << qPrintable(d->text) << std::endl; } const QString& TxORecord::text() const { return d->text; } TxORecord::HorizontalAlignment TxORecord::hAlign() const { return d->hAlign; } TxORecord::VerticalAlignment TxORecord::vAlign() const { return d->vAlign; } const QTextDocument* TxORecord::richText() const { return d->richText.data(); } // ========== MsoDrawing ========== const unsigned MsoDrawingRecord::id = 0xEC; class MsoDrawingRecord::Private { public: MSO::OfficeArtDgContainer container; }; MsoDrawingRecord::MsoDrawingRecord(Workbook *book) : Record(book) { d = new MsoDrawingRecord::Private(); } MsoDrawingRecord::~MsoDrawingRecord() { delete d; } const MSO::OfficeArtDgContainer& MsoDrawingRecord::dgContainer() const { return d->container; } void MsoDrawingRecord::dump(std::ostream& out) const { out << "MsoDrawingRecord" << std::endl; } void MsoDrawingRecord::setData(unsigned size, const unsigned char* data, const unsigned* continuePositions) { Q_UNUSED(continuePositions); QByteArray byteArr = QByteArray::fromRawData(reinterpret_cast(data), size); QBuffer buff(&byteArr); buff.open(QIODevice::ReadOnly); LEInputStream in(&buff); MSO::OfficeArtDgContainer container; // First try to parse a OfficeArtDgContainer and if that fails try to parse a single OfficeArtSpgrContainerFileBlock. Note // that the xls-specs say that the rgChildRec of a MsoDrawing-record always is a OfficeArtDgContainer but that's just wrong // since at some documents it's direct the OfficeArtSpContainer we are interested in. LEInputStream::Mark _m = in.setMark(); try { MSO::parseOfficeArtDgContainer(in, container); } catch(const IOException&) { in.rewind(_m); container.groupShape = QSharedPointer(new MSO::OfficeArtSpgrContainer(&container)); container.groupShape->rgfb.append(MSO::OfficeArtSpgrContainerFileBlock(&container)); try { parseOfficeArtSpgrContainerFileBlock(in, container.groupShape->rgfb.last()); } catch(const IOException& e) { std::cerr << "Invalid MsoDrawingRecord record: " << qPrintable(e.msg) << std::endl; setIsValid(false); return; } catch(...) { std::cerr << "Invalid MsoDrawingRecord record: Unexpected error" << std::endl; setIsValid(false); return; } } // Be sure we got at least something useful we can work with. if(!container.groupShape) { std::cerr << "Invalid MsoDrawingRecord record: Expected groupShape missing in the container." << std::endl; setIsValid(false); return; } // Finally remember the container to be able to extract later content out of it. d->container = container; } // ========== MsoDrawingGroup ========== const unsigned MsoDrawingGroupRecord::id = 0xEB; class MsoDrawingGroupRecord::Private { public: MSO::OfficeArtDggContainer container; QMap pictureNames; }; MsoDrawingGroupRecord::MsoDrawingGroupRecord(Workbook *book) : Record(book) { d = new Private(); } MsoDrawingGroupRecord::~MsoDrawingGroupRecord() { delete d; } const MSO::OfficeArtDggContainer& MsoDrawingGroupRecord::dggContainer() const { return d->container; } const QMap< QByteArray, QString > MsoDrawingGroupRecord::pictureNames() const { return d->pictureNames; } void MsoDrawingGroupRecord::dump(std::ostream& out) const { out << "MsoDrawingGroupRecord" << std::endl; } void MsoDrawingGroupRecord::setData(unsigned size, const unsigned char* data, const unsigned* continuePositions) { printf("MsoDrawingGroupRecord::setData size=%i data=%i continuePositions=%i\n",size,*data,*continuePositions); if(size < 32) { setIsValid(false); return; } QByteArray byteArr = QByteArray::fromRawData(reinterpret_cast(data), size); QBuffer buff(&byteArr); buff.open(QIODevice::ReadOnly); LEInputStream lei(&buff); try { MSO::parseOfficeArtDggContainer(lei, d->container); } catch (const IOException& e) { std::cerr << "Invalid MsoDrawingGroup record:" << qPrintable(e.msg) << std::endl; setIsValid(false); return; } if(d->container.blipStore.data() && m_workbook->store()) { m_workbook->store()->enterDirectory("Pictures"); d->pictureNames = createPictures(m_workbook->store(), 0, &d->container.blipStore->rgfb); m_workbook->store()->leaveDirectory(); } } // ========== BkHimRecord ========== const unsigned BkHimRecord::id = 0x00e9; class BkHimRecord::Private { public: Format format; QString imagePath; }; BkHimRecord::BkHimRecord(Workbook *book) : Record(book), d(new Private) { } BkHimRecord::~BkHimRecord() { delete d; } BkHimRecord::BkHimRecord( const BkHimRecord& record ) : Record(record), d(new Private) { *this = record; } BkHimRecord& BkHimRecord::operator=( const BkHimRecord& record ) { *d = *record.d; return *this; } QString BkHimRecord::formatToString(Format format) { switch (format) { case WindowsBitMap: return QString("WindowsBitMap"); case NativeFormat: return QString("NativeFormat"); default: return QString("Unknown: %1").arg(format); } } BkHimRecord::Format BkHimRecord::format() const { return d->format; } void BkHimRecord::setFormat(Format format ) { d->format = format; } QString BkHimRecord::imagePath() const { return d->imagePath; } void BkHimRecord::setImagePath(const QString &imagePath ) { d->imagePath = imagePath; } void BkHimRecord::setData( unsigned size, const unsigned char* data, const unsigned int* ) { unsigned curOffset = 0; if (size < 8) { setIsValid(false); return; } setFormat(static_cast(readU16(data + curOffset))); curOffset += 2; //16 reserved bits curOffset += 2; quint32 imageSize = readU32(data + curOffset); curOffset += 4; static int counter = 1; //we need unique file names QString filename = QString("Pictures/sheetBackground%1").arg(counter++); if(format() == WindowsBitMap) { filename.append(QString(".bmp")); } setImagePath(filename); KoStore *store = m_workbook->store(); Q_ASSERT(store); if(store->open(filename)) { //Excel doesn't include the file header, only the pixmap header, //we need to convert it to a standard BMP header //see http://www.fileformat.info/format/bmp/egff.htm for details //quint32 headerSize = readU32(data + curOffset); // this header size is always 12 curOffset += 4; const quint16 width = readU16(data + curOffset); //in px curOffset += 2; const qint16 height = readU16(data + curOffset); // in px curOffset += 2; //const qint16 planes = data + curOffset; //must be 1 curOffset += 2; qint16 bitsPerPixel = readU16(data + curOffset); //usually 24 curOffset += 2; //For the standard header see wikipedia or //http://www.fastgraph.com/help/bmp_header_format.html QByteArray newHeader; newHeader.fill(0x0, 54); int currentHeaderOffset = 0; //signature newHeader[0] = 0x42; newHeader[1] = 0x4d; currentHeaderOffset += 2; char* newHeaderChar = newHeader.data(); //size imageSize -= 12; //remove the header size const qint32 fileSize = qToLittleEndian(imageSize + 54); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&fileSize), 4); currentHeaderOffset += 4; //4 reserved bytes currentHeaderOffset += 4; //offset to the start of the image, the size of this new header: always 54 bytes const qint32 startImageData = qToLittleEndian(qint32(54)); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&startImageData), 4); currentHeaderOffset += 4; const quint32 sizeOfBitmapInfoHeader = qToLittleEndian(qint32(40)); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&sizeOfBitmapInfoHeader), 4); currentHeaderOffset += 4; const quint32 imageWidth = qToLittleEndian(qint32(width)); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&imageWidth), 4); currentHeaderOffset += 4; const quint32 imageHeight = qToLittleEndian(qint32(height)); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&imageHeight), 4); currentHeaderOffset += 4; const quint32 planes = qToLittleEndian(quint16(1)); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&planes), 2); currentHeaderOffset += 2; bitsPerPixel = qToLittleEndian(bitsPerPixel); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&bitsPerPixel), 2); currentHeaderOffset += 2; //compresion type, for this case always 0 currentHeaderOffset += 4; //size of image bits const quint32 litEndimageSize = qToLittleEndian(imageSize); memcpy(newHeaderChar + currentHeaderOffset, reinterpret_cast(&litEndimageSize), 4); currentHeaderOffset += 4; //we leave the remaining bits to zero store->write(newHeaderChar, 54); store->write((const char*)(data + curOffset), imageSize); store->close(); } else { std::cerr << "BkHimRecord: Failed to open file=" << filename << std::endl; } } void BkHimRecord::dump( std::ostream& out ) const { out << "BkHim" << std::endl; out << " Format : " << formatToString(format()) << std::endl; out << " ImagePath : " << imagePath() << std::endl; } static Record* createBkHimRecord(Workbook *book) { return new BkHimRecord(book); } //============================================= // ExcelReader //============================================= class ExcelReader::Private { public: // the workbook Workbook* workbook; GlobalsSubStreamHandler* globals; std::vector handlerStack; // active sheet, all cell records will be stored here Sheet* activeSheet; }; ExcelReader::ExcelReader() { d = new ExcelReader::Private(); d->workbook = 0; d->activeSheet = 0; d->globals = 0; } ExcelReader::~ExcelReader() { delete d; } static Record* createBOFRecord(Workbook *book) { return new BOFRecord(book); } static Record* createExternBookRecord(Workbook *book) { return new ExternBookRecord(book); } static Record* createExternNameRecord(Workbook *book) { return new ExternNameRecord(book); } static Record* createFormulaRecord(Workbook *book) { return new FormulaRecord(book); } static Record* createSharedFormulaRecord(Workbook *book) { return new SharedFormulaRecord(book); } static Record* createMulRKRecord(Workbook *book) { return new MulRKRecord(book); } static Record* createNameRecord(Workbook *book) { return new NameRecord(book); } static Record* createRKRecord(Workbook *book) { return new RKRecord(book); } static Record* createRStringRecord(Workbook *book) { return new RStringRecord(book); } static Record* createSSTRecord(Workbook *book) { return new SSTRecord(book); } static Record* createObjRecord(Workbook *book) { return new ObjRecord(book); } static Record* createTxORecord(Workbook *book) { return new TxORecord(book); } static Record* createRecordMsoDrawingRecord(Workbook *book) { return new MsoDrawingRecord(book); } static Record* createMsoDrawingGroupRecord(Workbook *book) { return new MsoDrawingGroupRecord(book); } static void registerAllRecordClasses() { registerRecordClasses(); RecordRegistry::registerRecordClass(BOFRecord::id, createBOFRecord); RecordRegistry::registerRecordClass(ExternBookRecord::id, createExternBookRecord); RecordRegistry::registerRecordClass(ExternNameRecord::id, createExternNameRecord); RecordRegistry::registerRecordClass(FormulaRecord::id, createFormulaRecord); RecordRegistry::registerRecordClass(SharedFormulaRecord::id, createSharedFormulaRecord); RecordRegistry::registerRecordClass(MulRKRecord::id, createMulRKRecord); RecordRegistry::registerRecordClass(NameRecord::id, createNameRecord); RecordRegistry::registerRecordClass(RKRecord::id, createRKRecord); RecordRegistry::registerRecordClass(RStringRecord::id, createRStringRecord); RecordRegistry::registerRecordClass(SSTRecord::id, createSSTRecord); RecordRegistry::registerRecordClass(ObjRecord::id, createObjRecord); RecordRegistry::registerRecordClass(TxORecord::id, createTxORecord); RecordRegistry::registerRecordClass(MsoDrawingRecord::id, createRecordMsoDrawingRecord); RecordRegistry::registerRecordClass(MsoDrawingGroupRecord::id, createMsoDrawingGroupRecord); RecordRegistry::registerRecordClass(BkHimRecord::id, createBkHimRecord); } void printEntries(POLE::Storage &storage, const std::string path = "/", int level = 0) { std::cout << std::setw(level) << "PATH=" << path << std::endl; std::list entries = storage.entries(path); for (std::list::iterator it = entries.begin(); it != entries.end(); ++it) { std::cout << std::setw(level + 1) << "ENTRY=" << *it << std::endl; std::string p = path == "/" ? "/" + *it + "/" : path + "/" + *it + "/"; if (storage.isDirectory(p)) { printEntries(storage, p, level + 1); } } } bool ExcelReader::load(Workbook* workbook, const char* filename) { registerAllRecordClasses(); POLE::Storage storage(filename); if (!storage.open()) { std::cerr << "Cannot open " << filename << std::endl; return false; } #ifdef SWINDER_XLS2RAW std::cout << "Streams:" << std::endl; printEntries(storage); #endif unsigned streamVersion = Swinder::Excel97; POLE::Stream* stream; stream = new POLE::Stream(&storage, "/Workbook"); if (stream->fail()) { delete stream; stream = new POLE::Stream(&storage, "/Book"); streamVersion = Swinder::Excel95; } if (stream->fail()) { std::cerr << filename << " is not Excel workbook" << std::endl; delete stream; return false; } unsigned int buffer_size = 65536; // current size of the buffer unsigned char *buffer = (unsigned char *) malloc(buffer_size); unsigned char small_buffer[128]; // small, fixed size buffer { // read document meta information POLE::Stream *summarystream = new POLE::Stream(&storage, "/SummaryInformation"); const unsigned long streamStartPosition = summarystream->tell(); unsigned bytes_read = summarystream->read(buffer, 8); //const unsigned long byteorder = readU16( buffer ); // must be 0xFFFE //const unsigned long version = readU16( buffer + 2 ); // must be 0x0000 or 0x0001 //const unsigned long systemId = readU32( buffer + 4 ); QTextCodec* codec = QTextCodec::codecForLocale(); summarystream->seek(summarystream->tell() + 16); // skip CLSID bytes_read = summarystream->read(buffer, 4); const unsigned long numPropertySets = bytes_read == 4 ? readU32(buffer) : 0; // must be 0x00000001 or 0x00000002 for (uint i = 0; i < numPropertySets; ++ i) { summarystream->seek(summarystream->tell() + 16); // skip FMTIDO bytes_read = summarystream->read(buffer, 4); if (bytes_read != 4) break; const unsigned long firstPropertyOffset = readU32(buffer); const unsigned long p = summarystream->tell(); const unsigned long propertysetStartPosition = streamStartPosition + firstPropertyOffset; summarystream->seek(propertysetStartPosition); bytes_read = summarystream->read(buffer, 8); if (bytes_read != 8) break; //unsigned long size = readU32( buffer ); unsigned long propertyCount = readU32(buffer + 4); for (uint i = 0; i < propertyCount; ++i) { bytes_read = summarystream->read(buffer, 8); if (bytes_read != 8) break; Workbook::PropertyType propertyId = Workbook::PropertyType(readU32(buffer)); // Offset (4 bytes): An unsigned integer representing the offset in bytes from the beginning of // the PropertySet packet to the beginning of the Property field for the property represented. // MUST be a multiple of 4 bytes. unsigned long propertyOffset = readU32(buffer + 4); unsigned long p2 = summarystream->tell(); summarystream->seek(propertysetStartPosition + propertyOffset); bytes_read = summarystream->read(buffer, 4); if (bytes_read != 4) break; unsigned long type = readU16(buffer); //unsigned long padding = readU16( buffer + 2 ); switch (propertyId) { case Workbook::PIDSI_TITLE: case Workbook::PIDSI_SUBJECT: case Workbook::PIDSI_AUTHOR: case Workbook::PIDSI_KEYWORDS: case Workbook::PIDSI_COMMENTS: case Workbook::PIDSI_TEMPLATE: case Workbook::PIDSI_LASTAUTHOR: case Workbook::PIDSI_REVNUMBER: case Workbook::PIDSI_EDITTIME: case Workbook::PIDSI_LASTPRINTED_DTM: case Workbook::PIDSI_CREATE_DTM: case Workbook::PIDSI_LASTSAVED_DTM: case Workbook::PIDSI_APPNAME: switch (type) { case 0x001E: { //VT_LPSTR bytes_read = summarystream->read(buffer, 4); if (bytes_read != 4) break; const unsigned long length = readU32(buffer); bytes_read = summarystream->read(buffer, length); if (bytes_read != length) break; QString s = codec->toUnicode(reinterpret_cast(buffer), static_cast(length)); workbook->setProperty(propertyId, s); } break; case 0x0040: { //VT_FILETIME bytes_read = summarystream->read(buffer, 8); if (bytes_read != 8) break; const unsigned long dwLowDateTime = readU32(buffer); const unsigned long dwHighDateTime = readU32(buffer + 4); long long int time = dwHighDateTime; time <<= 32; time += (unsigned long) dwLowDateTime; time -= 116444736000000000LL; QString s(QDateTime::fromTime_t(time / 10000000.0).toString(Qt::ISODate)); workbook->setProperty(propertyId, s); } break; default: std::cout << "Ignoring property with known id=" << propertyId << " and unknown type=" << type; break; } break; case Workbook::PIDSI_CODEPAGE: { if (type != 0x0002) break; // type should always be 2 bytes_read = summarystream->read(buffer, 4); unsigned int codepage = readU32(buffer); QTextCodec* newCodec = QTextCodec::codecForName("CP" + QByteArray::number(codepage)); if (newCodec) { codec = newCodec; } std::cout << "Codepage:" << codepage << std::endl; } break; default: if (propertyId != 0x0013 /* GKPIDDSI_SHAREDDOC */) { std::cout << "Ignoring property with unknown id=" << propertyId << " and type=" << type << std::endl; } break; } summarystream->seek(p2); } summarystream->seek(p); } delete summarystream; } { // read CompObj stream POLE::Stream *combObjStream = new POLE::Stream(&storage, "/CompObj"); // header unsigned bytes_read = combObjStream->read(buffer, 28); unsigned long length = 0; bool hasCombObjStream = bytes_read == 28; if(hasCombObjStream) { //const unsigned long version = readU32( buffer + 5 ); //printf(">>>> combObjStream->fullName=%s\n",combObjStream->fullName().c_str()); //printEntries(storage,"CompObj"); // AnsiUserType bytes_read = combObjStream->read( buffer, 4 ); length = readU32( buffer ); bytes_read = combObjStream->read( buffer, length ); if(bytes_read != length) hasCombObjStream = false; } if(hasCombObjStream) { QString ansiUserType = readByteString(buffer, length); printf( "length=%lu ansiUserType=%s\n",length, qPrintable(ansiUserType) ); // AnsiClipboardFormat bytes_read = combObjStream->read( buffer, 4 ); const unsigned long markerOrLength = readU32( buffer ); switch (markerOrLength) { case 0x00000000: break; // Must not be present, do nothing... case 0xFFFFFFFF: // fall through case 0xFFFFFFFE: { // must be 4 bytes and contains a clipboard identifier bytes_read = combObjStream->read( buffer, 4 ); //const unsigned long standardFormat = readU32( buffer ); // switch(standardFormat) { // case 0x00000002: standardFormat=CF_BITMAP; // case 0x00000003: standardFormat=CF_METAFILEPICT // case 0x00000008: standardFormat=CF_DIB // case 0x0000000E: standardFormat=CF_ENHMETAFILE // } //TODO... } break; default: if (markerOrLength > 65535) { printf("invalid length reading compobj stream: %lu\n", markerOrLength); } else { bytes_read = combObjStream->read( buffer, markerOrLength ); QString ansiString = readByteString(buffer, markerOrLength); //TODO... //printf( "markerOrLength=%i ansiString=%s\n",markerOrLength,ansiString.ascii() ); } } //TODO Reserved1, UnicodeMarker, UnicodeUserType, UnicodeClipboardFormat, Reserved2 } delete combObjStream; } const unsigned long stream_size = stream->size(); unsigned int continuePositionsSize = 128; // size of array for continue positions unsigned int *continuePositions = (unsigned int *) malloc(continuePositionsSize * sizeof(int)); workbook->clear(); d->workbook = workbook; d->globals = new GlobalsSubStreamHandler(workbook, streamVersion); d->handlerStack.clear(); bool useMsoDrawingRecordWorkaround = false; while (stream->tell() < stream_size) { const int percent = int(stream->tell() / double(stream_size) * 100.0 + 0.5); workbook->emitProgress(percent); // this is set by FILEPASS record // subsequent records will need to be decrypted // since we do not support it yet, we have to bail out if (d->globals->passwordProtected() && !d->globals->encryptionTypeSupported()) { d->workbook->setPasswordProtected(true); break; } // get record type and data size unsigned long pos = stream->tell(); unsigned bytes_read = stream->read(buffer, 4); if (bytes_read != 4) break; unsigned long type = readU16(buffer); // Work around a known bug in Excel. See below for a more detailed description of the problem. if (useMsoDrawingRecordWorkaround) { useMsoDrawingRecordWorkaround = false; type = MsoDrawingRecord::id; } unsigned long size = readU16(buffer + 2); d->globals->decryptionSkipBytes(4); unsigned int continuePositionsCount = 0; // verify buffer is large enough to hold the record data if (size > buffer_size) { buffer = (unsigned char *) realloc(buffer, size); buffer_size = size; } // load actual record data bytes_read = stream->read(buffer, size); if (bytes_read != size) break; d->globals->decryptRecord(type, size, buffer); // save current position in stream, to be able to restore the position later on unsigned long saved_pos; // repeatedly check if the next record is type 0x3C, a continuation record unsigned long next_type; // the type of the next record do { saved_pos = stream->tell(); bytes_read = stream->read(small_buffer, 4); if (bytes_read != 4) break; next_type = readU16(small_buffer); unsigned long next_size = readU16(small_buffer + 2); if(next_type == MsoDrawingGroupRecord::id) { if (type != MsoDrawingGroupRecord::id) break; } else if(next_type == 0x3C) { // 0x3C are continues records which are always just merged... // if the previous record is an Obj record, than the Continue record // was supposed to be a MsoDrawingRecord (known bug in MS Excel...) // a similar problem exists with TxO/Continue records, but there it is // harder to find out which Continue records are part of the TxO and which // are part of the MsoDrawing record if (type == ObjRecord::id || type == TxORecord::id) { unsigned long saved_pos_2 = stream->tell(); bool isMsoDrawingRecord = false; bytes_read = stream->read(small_buffer, 8); if (bytes_read == 8) { QByteArray byteArr = QByteArray::fromRawData(reinterpret_cast(small_buffer), 8); QBuffer buff(&byteArr); buff.open(QIODevice::ReadOnly); LEInputStream in(&buff); MSO::OfficeArtRecordHeader rh; parseOfficeArtRecordHeader(in, rh); isMsoDrawingRecord = (rh.recVer == 0xF && rh.recInstance == 0x0 && rh.recType == 0xF002) || // OfficeArtDgContainer (rh.recVer == 0xF && rh.recInstance == 0x0 && rh.recType == 0x0F004) || // OfficeArtSpContainer (rh.recVer == 0xF && rh.recInstance == 0x0 && rh.recType == 0x0F003) || // OfficeArtSpgrContainer (rh.recVer == 0x2 && rh.recInstance <= 202 && rh.recType == 0x0F00A && rh.recLen == 8) || // OfficeArtFSP (rh.recVer == 0x1 && rh.recInstance == 0x0 && rh.recType == 0x0F009 && rh.recLen == 0x10) || // OfficeArtFSPGR (rh.recVer == 0x0 && rh.recInstance == 0x0 && rh.recType == 0xF010 && (rh.recLen == 0x8 || rh.recLen == 0x12)) || // XlsOfficeArtClientAnchor (rh.recVer == 0x0 && rh.recInstance == 0x0 && rh.recType == 0xF011 && rh.recLen == 0); // XlsOfficeArtClientData } stream->seek(saved_pos_2); if (isMsoDrawingRecord) { useMsoDrawingRecordWorkaround = true; break; } } } else { break; // and abort merging of records } // compress multiple records into one. continuePositions[continuePositionsCount++] = size; if (continuePositionsCount >= continuePositionsSize) { continuePositionsSize *= 2; continuePositions = (unsigned int *) realloc(continuePositions, continuePositionsSize * sizeof(int)); } // first verify the buffer is large enough to hold all the data if ((size + next_size) > buffer_size) { buffer = (unsigned char *) realloc(buffer, size + next_size); buffer_size = size + next_size; } // next read the data of the record bytes_read = stream->read(buffer + size, next_size); if (bytes_read != next_size) { std::cout << "ERROR!" << std::endl; break; } d->globals->decryptionSkipBytes(4); d->globals->decryptRecord(next_type, next_size, buffer+size); // and finally update size size += next_size; } while (true); // append total size as last continue position continuePositions[continuePositionsCount] = size; // restore position in stream to the beginning of the next record stream->seek(saved_pos); // skip record type 0, this is just for filler if (type == 0) continue; // create the record using the factory Record* record = Record::create(type, workbook); if (!record) { //#ifdef SWINDER_XLS2RAW std::cout << "Unhandled Record 0x"; std::cout << std::setfill('0') << std::setw(4) << std::hex << type; std::cout << std::dec; std::cout << " (" << type << ")"; std::cout << std::endl; //#endif } else { // setup the record and invoke handler record->setVersion(d->globals->version()); record->setData(size, buffer, continuePositions); record->setPosition(pos); #ifdef SWINDER_XLS2RAW std::cout << std::setfill('0') << std::setw(8) << std::dec << record->position() << " "; if (!record->isValid()) std::cout << "Invalid "; std::cout << "Record 0x"; std::cout << std::setfill('0') << std::setw(4) << std::hex << record->rtti(); std::cout << " ("; std::cout << std::dec; std::cout << record->rtti() << ") "; record->dump(std::cout); std::cout << std::endl; #endif if (record->isValid()) { if (record->rtti() == BOFRecord::id) handleRecord(record); if (!d->handlerStack.empty() && d->handlerStack.back()) d->handlerStack.back()->handleRecord(record); if (record->rtti() == EOFRecord::id) handleRecord(record); } delete record; } } free(buffer); free(continuePositions); delete d->globals; delete stream; storage.close(); return true; } void ExcelReader::handleRecord(Record* record) { if (!record) return; unsigned type = record->rtti(); if (type == BOFRecord::id) handleBOF(static_cast(record)); else if (type == EOFRecord::id) handleEOF(static_cast(record)); } void ExcelReader::handleBOF(BOFRecord* record) { if (!record) return; if (record->type() == BOFRecord::Workbook) { d->handlerStack.push_back(d->globals); qDebug() << "figuring out version" << record->version() << record->rawVersion(); if (record->version() == Swinder::Excel95) { d->workbook->setVersion(Workbook::Excel95); } else if (record->version() == Swinder::Excel97) { if (record->recordSize() >= 8) { switch (record->verLastXLSaved()) { case BOFRecord::LExcel97: d->workbook->setVersion(Workbook::Excel97); break; case BOFRecord::LExcel2000: d->workbook->setVersion(Workbook::Excel2000); break; case BOFRecord::LExcel2002: d->workbook->setVersion(Workbook::Excel2002); break; case BOFRecord::LExcel2003: d->workbook->setVersion(Workbook::Excel2003); break; case BOFRecord::LExcel2007: d->workbook->setVersion(Workbook::Excel2007); break; case BOFRecord::LExcel2010: d->workbook->setVersion(Workbook::Excel2010); break; default: // pretend that anything newer than 2010 is 2010 d->workbook->setVersion(Workbook::Excel2010); break; } } else { d->workbook->setVersion(Workbook::Excel97); } } else { d->workbook->setVersion(Workbook::Unknown); } } else if (record->type() == BOFRecord::Worksheet) { // find the sheet and make it active // which sheet ? look from from previous BoundSheet Sheet* sheet = d->globals->sheetFromPosition(record->position()); if (sheet) d->activeSheet = sheet; d->handlerStack.push_back(new WorksheetSubStreamHandler(sheet, d->globals)); } else if (record->type() == BOFRecord::Chart) { SubStreamHandler* parentHandler = d->handlerStack.empty() ? 0 : d->handlerStack.back(); d->handlerStack.push_back(new Swinder::ChartSubStreamHandler(d->globals, parentHandler)); } else { std::cout << "ExcelReader::handleBOF Unhandled type=" << record->type() << std::endl; } } void ExcelReader::handleEOF(EOFRecord* record) { if (!record) return; if (d->handlerStack.empty()) return; SubStreamHandler* handler = d->handlerStack.back(); d->handlerStack.pop_back(); if (handler != d->globals) delete handler; } #ifdef SWINDER_XLS2RAW #include int main(int argc, char ** argv) { QApplication app(argc, argv); if (argc < 2) { std::cout << "Usage: sidewinder filename" << std::endl; return 0; } char* filename = argv[1]; std::cout << "Checking " << filename << std::endl; Workbook* workbook = new Workbook(); ExcelReader* reader = new ExcelReader(); reader->load(workbook, filename); workbook->dumpStats(); delete reader; delete workbook; return 0; } #endif // XLS2RAW diff --git a/libs/main/KoFilterManager_p.h b/libs/main/KoFilterManager_p.h index f4005c4a7b9..37e46ee4693 100644 --- a/libs/main/KoFilterManager_p.h +++ b/libs/main/KoFilterManager_p.h @@ -1,66 +1,66 @@ /* This file is part of the KDE project Copyright (C) 2003 Clarence Dang Copyright (C) 2009 Thomas Zander This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __koFilterManager_p_h__ #define __koFilterManager_p_h__ #include "KoFilterManager.h" #include #include #include #include #include -#include +#include class QListWidget; class Q_DECL_HIDDEN KoFilterManager::Private { public: bool batch; QByteArray importMimeType; - QWeakPointer progressUpdater; + QPointer progressUpdater; Private(KoProgressUpdater *progressUpdater_ = 0) : progressUpdater(progressUpdater_) { } }; class KoFilterChooser : public KoDialog { Q_OBJECT public: KoFilterChooser(QWidget *parent, const QStringList &mimeTypes, const QString &nativeFormat = QString(), const QUrl &url = QUrl()); ~KoFilterChooser(); QString filterSelected(); private: QStringList m_mimeTypes; QListWidget *m_filterList; }; #endif diff --git a/libs/text/KoList.cpp b/libs/text/KoList.cpp index e5dc1fcb512..f50dbcda23e 100644 --- a/libs/text/KoList.cpp +++ b/libs/text/KoList.cpp @@ -1,249 +1,249 @@ /* This file is part of the KDE project * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2010 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoList.h" #include "KoList_p.h" #include "KoTextDocument.h" #include "styles/KoListLevelProperties.h" #include "styles/KoParagraphStyle.h" #include "styles/KoStyleManager.h" #include "TextDebug.h" #include KoList::KoList(const QTextDocument *document, KoListStyle *style, KoList::Type type) : QObject(const_cast(document)), d(new KoListPrivate(this, document)) { Q_ASSERT(document); d->type = type; setStyle(style); KoTextDocument(document).addList(this); } KoList::~KoList() { KoTextDocument(d->document).removeList(this); delete d; } -QVector > KoList::textLists() const +QVector > KoList::textLists() const { return d->textLists; } QVector KoList::textListIds() const { return d->textListIds; } KoList *KoList::applyStyle(const QTextBlock &block, KoListStyle *style, int level) { Q_ASSERT(style); KoTextDocument document(block.document()); KoList *list = document.list(block); if (list && *list->style() == *style) { list->add(block, level); return list; } //the block was already another list but with a different style - remove block from list if (list) list->remove(block); // Ok, so we are now ready to add the block to another list, but which other list? // For headers we always want to continue from any previous header // For normal lists we either want to continue an adjacent list or create a new one if (block.blockFormat().hasProperty(KoParagraphStyle::OutlineLevel)) { for (QTextBlock b = block.previous();b.isValid(); b = b.previous()) { list = document.list(b); if (list && *list->style() == *style) { break; } } if (!list || *list->style() != *style) { list = new KoList(block.document(), style); } } else { list = document.list(block.previous()); if (!list || *list->style() != *style) { list = document.list(block.next()); if (!list || *list->style() != *style) { list = new KoList(block.document(), style); } } } list->add(block, level); return list; } void KoList::add(const QTextBlock &block, int level) { if (!block.isValid()) return; Q_ASSERT(level >= 0 && level <= 10); if (level == 0) { // fetch the first proper level we have level = 1; // if nothing works... for (int i = 1; i <= 10; i++) { if (d->style->hasLevelProperties(i)) { level = i; break; } } } remove(block); QTextList *textList = d->textLists.value(level-1).data(); if (!textList) { QTextCursor cursor(block); QTextListFormat format = d->style->listFormat(level); textList = cursor.createList(format); format.setProperty(KoListStyle::ListId, (KoListStyle::ListIdType)(textList)); textList->setFormat(format); d->textLists[level-1] = textList; d->textListIds[level-1] = (KoListStyle::ListIdType)textList; } else { textList->add(block); } QTextCursor cursor(block); QTextBlockFormat blockFormat = cursor.blockFormat(); if (d->style->styleId()) { blockFormat.setProperty(KoParagraphStyle::ListStyleId, d->style->styleId()); } else { blockFormat.clearProperty(KoParagraphStyle::ListStyleId); } if (d->type == KoList::TextList) { blockFormat.clearProperty(KoParagraphStyle::ListLevel); } else { blockFormat.setProperty(KoParagraphStyle::ListLevel, level); } cursor.setBlockFormat(blockFormat); d->invalidate(block); } void KoList::remove(const QTextBlock &block) { //QTextLists are created with a blockIndent of 1. When a block is removed from a QTextList, it's blockIndent is set to (block.indent + list.indent). //Since we do not use Qt's indentation for lists, we need to clear the block's blockIndent, otherwise the block's style will appear as modified. bool clearIndent = !block.blockFormat().hasProperty(4160); if (QTextList *textList = block.textList()) { // invalidate the list before we remove the item // (since the list might disappear if the block is the only item) KoListPrivate::invalidateList(block); textList->remove(block); } KoListPrivate::invalidate(block); if (clearIndent) { QTextBlockFormat format = block.blockFormat(); format.clearProperty(4160); QTextCursor cursor(block); cursor.setBlockFormat(format); } } void KoList::setStyle(KoListStyle *style) { if (style == 0) { KoStyleManager *styleManager = KoTextDocument(d->document).styleManager(); Q_ASSERT(styleManager); style = styleManager->defaultListStyle(); } if (style != d->style) { if (d->style) disconnect(d->style, 0, this, 0); d->style = style->clone(this); connect(d->style, SIGNAL(styleChanged(int)), this, SLOT(styleChanged(int))); } for (int i = 0; i < d->textLists.count(); i++) { QTextList *textList = d->textLists.value(i).data(); if (!textList) continue; KoListLevelProperties properties = d->style->levelProperties(i+1); if (properties.listId()) d->textListIds[i] = properties.listId(); QTextListFormat format; properties.applyStyle(format); textList->setFormat(format); d->invalidate(textList->item(0)); } //if this list is a heading list then update the style manager with the list properties if (KoTextDocument(d->document).headingList() == this) { if (KoStyleManager *styleManager = KoTextDocument(d->document).styleManager()) { if (styleManager->outlineStyle()) { styleManager->outlineStyle()->copyProperties(style); } } } } KoListStyle *KoList::style() const { return d->style; } void KoList::updateStoredList(const QTextBlock &block) { if (block.textList()) { int level = block.textList()->format().property(KoListStyle::Level).toInt(); QTextList *textList = block.textList(); textList->format().setProperty(KoListStyle::ListId, (KoListStyle::ListIdType)(textList)); d->textLists[level-1] = textList; d->textListIds[level-1] = (KoListStyle::ListIdType)textList; } } bool KoList::contains(QTextList *list) const { return list && d->textLists.contains(list); } int KoList::level(const QTextBlock &block) { if (!block.textList()) return 0; int l = block.blockFormat().intProperty(KoParagraphStyle::ListLevel); if (!l) { // not a numbered-paragraph QTextListFormat format = block.textList()->format(); l = format.intProperty(KoListStyle::Level); } return l; } KoList *KoList::listContinuedFrom() const { return d->listToBeContinuedFrom; } void KoList::setListContinuedFrom(KoList *list) { d->listToBeContinuedFrom = list; } //have to include this because of Q_PRIVATE_SLOT #include "moc_KoList.cpp" diff --git a/libs/text/KoList.h b/libs/text/KoList.h index 9811e26b8fe..9950062a100 100644 --- a/libs/text/KoList.h +++ b/libs/text/KoList.h @@ -1,99 +1,99 @@ /* This file is part of the KDE project * Copyright (C) 2008 Girish Ramakrishnan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOLIST_H #define KOLIST_H #include "styles/KoListStyle.h" #include -#include +#include #include #include class KoListPrivate; /** * This class represents an ODF list. An ODF list may have upto 10 levels * Each of the levels is represented as a QTextList (QTextList does not support * embedded lists). There is always an associated KoListStyle that holds the * styling information for various level of the ODF list. */ class KOTEXT_EXPORT KoList : public QObject { Q_OBJECT public: enum Type { TextList, NumberedParagraph }; /// Constructor KoList(const QTextDocument *document, KoListStyle *style, Type type = TextList); /// Destructor ~KoList(); /// Adds \a block to \a level of this list void add(const QTextBlock &block, int level); /// Removes \a block from any KoList the block is a part of static void remove(const QTextBlock &block); /** * Adds \a block to a list that follows \a style at \a level. If the block is * already a part of a list, it is removed from that list. If the block before * or after this block is part of a list that follows \a style, this block is * added to that list. If required a new KoList is created. * Returns the KoList that this block was added to. */ static KoList *applyStyle(const QTextBlock &block, KoListStyle *style, int level); /// Sets the style of this list void setStyle(KoListStyle *style); /// Returns the style of this list KoListStyle *style() const; /// Return true if this list contains \a textlist bool contains(QTextList *textList) const; /// Returns the QTextLists that form this list - QVector > textLists() const; + QVector > textLists() const; QVector textListIds() const; static int level(const QTextBlock &block); /// Update the stored QTextList pointer for the given block void updateStoredList(const QTextBlock &block); KoList *listContinuedFrom() const; void setListContinuedFrom(KoList *list); private: KoListPrivate * const d; Q_PRIVATE_SLOT(d, void styleChanged(int)) }; Q_DECLARE_METATYPE(KoList*) Q_DECLARE_METATYPE(QList) #endif // KOLIST_H diff --git a/libs/text/KoList_p.h b/libs/text/KoList_p.h index 2a15cc754e7..e91376bc115 100644 --- a/libs/text/KoList_p.h +++ b/libs/text/KoList_p.h @@ -1,80 +1,81 @@ /* This file is part of the KDE project * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2008 Pierre Stirnweiss * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOLIST_P_H #define KOLIST_P_H #include "KoList.h" #include "styles/KoListStyle.h" #include "KoTextBlockData.h" #include #include #include #include +#include #include class KoListPrivate { public: KoListPrivate(KoList *q, const QTextDocument *document) : q(q), type(KoList::TextList), style(0), textLists(10), textListIds(10), document(document), listToBeContinuedFrom(0) { } ~KoListPrivate() { } static void invalidate(const QTextBlock &block) { QTextBlock currentBlock = block; KoTextBlockData data(currentBlock); data.setCounterWidth(-1.0); } static void invalidateList(const QTextBlock &block) { for (int i = 0; i < block.textList()->count(); i++) { if (block.textList()->item(i) != block) { invalidate(block.textList()->item(i)); break; } } } void styleChanged(int level) { Q_UNUSED(level); q->setStyle(style); } KoList *q; KoList::Type type; KoListStyle *style; - QVector > textLists; + QVector > textLists; QVector textListIds; const QTextDocument *document; QMap properties; KoList *listToBeContinuedFrom; }; #endif // KOLIST_P_H diff --git a/libs/text/KoTextDocument.cpp b/libs/text/KoTextDocument.cpp index 272eb8305ca..6bf4ddae564 100644 --- a/libs/text/KoTextDocument.cpp +++ b/libs/text/KoTextDocument.cpp @@ -1,416 +1,416 @@ /* This file is part of the KDE project * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2009 Thomas Zander * Copyright (C) 2008 Pierre Stirnweiss \pierre.stirnweiss_calligra@gadz.org> * Copyright (C) 2011-2012 C. Boemann * Copyright (C) 2014-2015 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTextDocument.h" #include #include #include #include "TextDebug.h" #include #include #include "KoText.h" #include "KoTextEditor.h" #include "styles/KoStyleManager.h" #include "OdfTextTrackStyles.h" #include "KoTextRangeManager.h" #include "KoInlineTextObjectManager.h" #include "KoList.h" #include "KoOdfLineNumberingConfiguration.h" #include "changetracker/KoChangeTracker.h" #include #include Q_DECLARE_METATYPE(QAbstractTextDocumentLayout::Selection) Q_DECLARE_METATYPE(QTextFrame*) Q_DECLARE_METATYPE(QTextCharFormat) Q_DECLARE_METATYPE(QTextBlockFormat) const QUrl KoTextDocument::StyleManagerURL = QUrl("kotext://stylemanager"); const QUrl KoTextDocument::ListsURL = QUrl("kotext://lists"); const QUrl KoTextDocument::InlineObjectTextManagerURL = QUrl("kotext://inlineObjectTextManager"); const QUrl KoTextDocument::TextRangeManagerURL = QUrl("kotext://textRangeManager"); const QUrl KoTextDocument::UndoStackURL = QUrl("kotext://undoStack"); const QUrl KoTextDocument::ChangeTrackerURL = QUrl("kotext://changetracker"); const QUrl KoTextDocument::TextEditorURL = QUrl("kotext://textEditor"); const QUrl KoTextDocument::LineNumberingConfigurationURL = QUrl("kotext://linenumberingconfiguration"); const QUrl KoTextDocument::RelativeTabsURL = QUrl("kotext://relativetabs"); const QUrl KoTextDocument::HeadingListURL = QUrl("kotext://headingList"); const QUrl KoTextDocument::SelectionsURL = QUrl("kotext://selections"); const QUrl KoTextDocument::LayoutTextPageUrl = QUrl("kotext://layoutTextPage"); const QUrl KoTextDocument::ParaTableSpacingAtStartUrl = QUrl("kotext://spacingAtStart"); const QUrl KoTextDocument::IndexGeneratorManagerUrl = QUrl("kotext://indexGeneratorManager"); const QUrl KoTextDocument::FrameCharFormatUrl = QUrl("kotext://frameCharFormat"); const QUrl KoTextDocument::FrameBlockFormatUrl = QUrl("kotext://frameBlockFormat"); const QUrl KoTextDocument::ShapeControllerUrl = QUrl("kotext://shapeController"); const QUrl KoTextDocument::SectionModelUrl = QUrl("ktext://sectionModel"); KoTextDocument::KoTextDocument(QTextDocument *document) : m_document(document) { Q_ASSERT(m_document); } KoTextDocument::KoTextDocument(const QTextDocument *document) : m_document(const_cast(document)) { Q_ASSERT(m_document); } -KoTextDocument::KoTextDocument(QWeakPointer document) +KoTextDocument::KoTextDocument(QPointer document) : m_document(document.data()) { Q_ASSERT(m_document); } KoTextDocument::~KoTextDocument() { } QTextDocument *KoTextDocument::document() const { return m_document; } void KoTextDocument::setTextEditor (KoTextEditor* textEditor) { Q_ASSERT(textEditor->document() == m_document); QVariant v; v.setValue(textEditor); m_document->addResource(KoTextDocument::TextEditor, TextEditorURL, v); } KoTextEditor* KoTextDocument::textEditor() const { QVariant resource = m_document->resource(KoTextDocument::TextEditor, TextEditorURL); return resource.value(); } void KoTextDocument::setStyleManager(KoStyleManager *sm) { QVariant v; v.setValue(sm); m_document->addResource(KoTextDocument::StyleManager, StyleManagerURL, v); if (sm) { OdfTextTrackStyles *cf = OdfTextTrackStyles::instance(sm); cf->registerDocument(m_document); } } void KoTextDocument::setInlineTextObjectManager(KoInlineTextObjectManager *manager) { QVariant v; v.setValue(manager); m_document->addResource(KoTextDocument::InlineTextManager, InlineObjectTextManagerURL, v); } void KoTextDocument::setTextRangeManager(KoTextRangeManager *manager) { QVariant v; v.setValue(manager); m_document->addResource(KoTextDocument::TextRangeManager, TextRangeManagerURL, v); } KoStyleManager *KoTextDocument::styleManager() const { QVariant resource = m_document->resource(KoTextDocument::StyleManager, StyleManagerURL); return resource.value(); } void KoTextDocument::setChangeTracker(KoChangeTracker *changeTracker) { QVariant v; v.setValue(changeTracker); m_document->addResource(KoTextDocument::ChangeTrackerResource, ChangeTrackerURL, v); } KoChangeTracker *KoTextDocument::changeTracker() const { QVariant resource = m_document->resource(KoTextDocument::ChangeTrackerResource, ChangeTrackerURL); if (resource.isValid()) { return resource.value(); } else { return 0; } } void KoTextDocument::setShapeController(KoShapeController *controller) { QVariant v; v.setValue(controller); m_document->addResource(KoTextDocument::ShapeController, ShapeControllerUrl, v); } KoShapeController *KoTextDocument::shapeController() const { QVariant resource = m_document->resource(KoTextDocument::ShapeController, ShapeControllerUrl); if (resource.isValid()) { return resource.value(); } else { return 0; } } void KoTextDocument::setLineNumberingConfiguration(KoOdfLineNumberingConfiguration *lineNumberingConfiguration) { lineNumberingConfiguration->setParent(m_document); QVariant v; v.setValue(lineNumberingConfiguration); m_document->addResource(KoTextDocument::LineNumberingConfiguration, LineNumberingConfigurationURL, v); } KoOdfLineNumberingConfiguration *KoTextDocument::lineNumberingConfiguration() const { return m_document->resource(KoTextDocument::LineNumberingConfiguration, LineNumberingConfigurationURL) .value(); } void KoTextDocument::setHeadingList(KoList *headingList) { QVariant v; v.setValue(headingList); m_document->addResource(KoTextDocument::HeadingList, HeadingListURL, v); } KoList *KoTextDocument::headingList() const { QVariant resource = m_document->resource(KoTextDocument::HeadingList, HeadingListURL); return resource.value(); } void KoTextDocument::setUndoStack(KUndo2Stack *undoStack) { QVariant v; v.setValue(undoStack); m_document->addResource(KoTextDocument::UndoStack, UndoStackURL, v); } KUndo2Stack *KoTextDocument::undoStack() const { QVariant resource = m_document->resource(KoTextDocument::UndoStack, UndoStackURL); return static_cast(resource.value()); } void KoTextDocument::setLists(const QList &lists) { QVariant v; v.setValue(lists); m_document->addResource(KoTextDocument::Lists, ListsURL, v); } QList KoTextDocument::lists() const { QVariant resource = m_document->resource(KoTextDocument::Lists, ListsURL); return resource.value >(); } void KoTextDocument::addList(KoList *list) { Q_ASSERT(list); list->setParent(m_document); QList l = lists(); if (l.contains(list)) return; l.append(list); setLists(l); } void KoTextDocument::removeList(KoList *list) { QList l = lists(); if (l.contains(list)) { l.removeAll(list); setLists(l); } } KoList *KoTextDocument::list(const QTextBlock &block) const { QTextList *textList = block.textList(); if (!textList) return 0; return list(textList); } KoList *KoTextDocument::list(QTextList *textList) const { if (!textList) { return 0; } // FIXME: this is horrible. foreach(KoList *l, lists()) { if (l->textLists().contains(textList)) return l; } return 0; } KoList *KoTextDocument::list(KoListStyle::ListIdType listId) const { foreach(KoList *l, lists()) { if (l->textListIds().contains(listId)) return l; } return 0; } void KoTextDocument::clearText() { QTextCursor cursor(m_document); cursor.select(QTextCursor::Document); cursor.removeSelectedText(); } QVector< QAbstractTextDocumentLayout::Selection > KoTextDocument::selections() const { QVariant resource = m_document->resource(KoTextDocument::Selections, SelectionsURL); QVariantList variants = resource.toList(); QVector selections; foreach(const QVariant &variant, variants) { selections.append(variant.value()); } return selections; } void KoTextDocument::setSelections(const QVector< QAbstractTextDocumentLayout::Selection >& selections) { QVariantList variants; foreach(const QAbstractTextDocumentLayout::Selection &selection, selections) { variants.append(QVariant::fromValue(selection)); } m_document->addResource(KoTextDocument::Selections, SelectionsURL, variants); } KoInlineTextObjectManager *KoTextDocument::inlineTextObjectManager() const { QVariant resource = m_document->resource(KoTextDocument::InlineTextManager, InlineObjectTextManagerURL); return resource.value(); } KoTextRangeManager *KoTextDocument::textRangeManager() const { QVariant resource = m_document->resource(KoTextDocument::TextRangeManager, TextRangeManagerURL); return resource.value(); } QTextFrame *KoTextDocument::auxillaryFrame() { QTextCursor cursor(m_document->rootFrame()->lastCursorPosition()); cursor.movePosition(QTextCursor::PreviousCharacter); QTextFrame *frame = cursor.currentFrame(); if (frame->format().intProperty(KoText::SubFrameType) != KoText::AuxillaryFrameType) { cursor = m_document->rootFrame()->lastCursorPosition(); QTextFrameFormat format; format.setProperty(KoText::SubFrameType, KoText::AuxillaryFrameType); frame = cursor.insertFrame(format); } return frame; } void KoTextDocument::setRelativeTabs(bool relative) { QVariant v(relative); m_document->addResource(KoTextDocument::RelativeTabs, RelativeTabsURL, v); } bool KoTextDocument::relativeTabs() const { QVariant resource = m_document->resource(KoTextDocument::RelativeTabs, RelativeTabsURL); if (resource.isValid()) return resource.toBool(); else return true; } void KoTextDocument::setParaTableSpacingAtStart(bool spacingAtStart) { QVariant v(spacingAtStart); m_document->addResource(KoTextDocument::ParaTableSpacingAtStart, ParaTableSpacingAtStartUrl, v); } bool KoTextDocument::paraTableSpacingAtStart() const { QVariant resource = m_document->resource(KoTextDocument::ParaTableSpacingAtStart, ParaTableSpacingAtStartUrl); if (resource.isValid()) return resource.toBool(); else return false; } QTextCharFormat KoTextDocument::frameCharFormat() const { QVariant resource = m_document->resource(KoTextDocument::FrameCharFormat, FrameCharFormatUrl); if (resource.isValid()) return resource.value(); else return QTextCharFormat(); } void KoTextDocument::setFrameCharFormat(const QTextCharFormat &format) { m_document->addResource(KoTextDocument::FrameCharFormat, FrameCharFormatUrl, QVariant::fromValue(format)); } QTextBlockFormat KoTextDocument::frameBlockFormat() const { QVariant resource = m_document->resource(KoTextDocument::FrameBlockFormat, FrameBlockFormatUrl); if (resource.isValid()) return resource.value(); else return QTextBlockFormat(); } void KoTextDocument::setFrameBlockFormat(const QTextBlockFormat &format) { m_document->addResource(KoTextDocument::FrameBlockFormat, FrameBlockFormatUrl, QVariant::fromValue(format)); } KoSectionModel* KoTextDocument::sectionModel() { QVariant resource = m_document->resource(KoTextDocument::SectionModel, SectionModelUrl); if (!resource.isValid()) { setSectionModel(new KoSectionModel(document())); // Using create on demand strategy } return m_document->resource(KoTextDocument::SectionModel, SectionModelUrl).value(); } void KoTextDocument::setSectionModel(KoSectionModel *model) { QVariant v; v.setValue(model); m_document->addResource(KoTextDocument::SectionModel, SectionModelUrl, v); } diff --git a/libs/text/KoTextDocument.h b/libs/text/KoTextDocument.h index 09e54df1c38..b311422f6ab 100644 --- a/libs/text/KoTextDocument.h +++ b/libs/text/KoTextDocument.h @@ -1,267 +1,267 @@ /* This file is part of the KDE project * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2009 Thomas Zander * Copyright (C) 2008 Pierre Stirnweiss * Copyright (C) 2014-2015 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOTEXTDOCUMENT_H #define KOTEXTDOCUMENT_H #include -#include +#include #include #include #include "KoListStyle.h" class KoList; class KoStyleManager; class KoInlineTextObjectManager; class KoTextRangeManager; class KUndo2Stack; class KoTextEditor; class KoOdfLineNumberingConfiguration; class KoChangeTracker; class KoShapeController; class KoSectionModel; class QTextCharFormat; /** * KoTextDocument provides an easy mechanism to set and access the * editing members of a QTextDocument. The meta data are stored as resources * in the QTextDocument using QTextDocument::addResource() and fetched * using QTextDocument::resource(). * */ class KOTEXT_EXPORT KoTextDocument { public: /// Constructor KoTextDocument(QTextDocument *document); // krazy:exclude=explicit /// Constructor KoTextDocument(const QTextDocument *document); // krazy:exclude=explicit /// Constructor - KoTextDocument(QWeakPointer document); // krazy:exclude=explicit + KoTextDocument(QPointer document); // krazy:exclude=explicit /// Destructor ~KoTextDocument(); /// Returns the document that was passed in the constructor QTextDocument *document() const; ///Returns the text editor for that document KoTextEditor *textEditor() const; ///Sets the text editor for the document void setTextEditor(KoTextEditor *textEditor); /// Sets the style manager that defines the named styles in the document void setStyleManager(KoStyleManager *styleManager); /// Returns the style manager KoStyleManager *styleManager() const; /// Sets the change tracker of the document void setChangeTracker(KoChangeTracker *changeTracker); ///Returns the change tracker of the document KoChangeTracker *changeTracker() const; void setLineNumberingConfiguration(KoOdfLineNumberingConfiguration *lineNumberingConfiguration); /// @return the notes configuration KoOdfLineNumberingConfiguration *lineNumberingConfiguration() const; ///Sets the global undo stack void setUndoStack(KUndo2Stack *undoStack); ///Returns the global undo stack KUndo2Stack *undoStack() const; ///Sets the global heading list void setHeadingList(KoList *list); ///Returns the global heading list KoList *headingList() const; /// Sets the lists of the document void setLists(const QList &lists); /// Returns the lists in the document QList lists() const; /// Adds a list to the document void addList(KoList *list); /// Removes a list from the document void removeList(KoList *list); /// Returns the KoList that holds \a block; 0 if block is not part of any list KoList *list(const QTextBlock &block) const; /// Returns the KoList that holds \a list KoList *list(QTextList *textList) const; /// Return the KoList that holds \a listId KoList *list(KoListStyle::ListIdType listId) const; /// Return the selections used during painting. QVector selections() const; /** * Set the selections to use for painting. * * The selections are used to apply temporary styling to * parts of a document. * * \param selections The new selections to use. */ void setSelections(const QVector &selections); /// Returns the KoInlineTextObjectManager KoInlineTextObjectManager *inlineTextObjectManager() const; /// Set the KoInlineTextObjectManager void setInlineTextObjectManager(KoInlineTextObjectManager *manager); /// @return section model for the document KoSectionModel *sectionModel(); /// Sets the section model for the document void setSectionModel(KoSectionModel *model); /// Returns the KoTextRangeManager KoTextRangeManager *textRangeManager() const; /// Set the KoTextRangeManager void setTextRangeManager(KoTextRangeManager *manager); /// Set the KoDocument's shapeController. This controller exists as long as KoDocument exists. It should only be used for deleting shapes. void setShapeController(KoShapeController *controller); /// Returns the shapeController KoShapeController *shapeController() const; QTextFrame* auxillaryFrame(); /** * Specifies if tabs are relative to paragraph indent. * * By default it's false. */ void setRelativeTabs(bool relative); /** * Returns if tabs are placed relative to paragraph indent. * * By default, this is false. * * @see setRelativeTabs */ bool relativeTabs() const; void setParaTableSpacingAtStart(bool spacingAtStart); bool paraTableSpacingAtStart() const; /** * Returns the character format for the frame of this document. * * @return the character format for the frame of this document. * @see setFrameCharFormat */ QTextCharFormat frameCharFormat() const; /** * Sets the character format for the frame of this document. * * @param format the character format for the frame of this document. * @see frameCharFormat */ void setFrameCharFormat(const QTextCharFormat &format); /** * Returns the block format for the frame of this document. * * @return the block format for the frame of this document. * @see setFrameBlockFormat */ QTextBlockFormat frameBlockFormat() const; /** * Sets the block format for the frame of this document. * * @param format the block format for the frame of this document. * @see frameBlockFormat */ void setFrameBlockFormat(const QTextBlockFormat &format); /** * Clears the text in the document. Unlike QTextDocument::clear(), this * function does not clear the resources of the QTextDocument. */ void clearText(); /// Enum (type) used to add resources using QTextDocument::addResource() enum ResourceType { StyleManager = QTextDocument::UserResource, Lists, TextRangeManager, InlineTextManager, ChangeTrackerResource, UndoStack, TextEditor, LineNumberingConfiguration, RelativeTabs, HeadingList, Selections, LayoutTextPage, /// this is used for setting the correct page variable on the first resize and should not be used for other purposes ParaTableSpacingAtStart, /// this is used during layouting to specify if at the first paragraph margin-top should be applied. IndexGeneratorManager, FrameCharFormat, FrameBlockFormat, ShapeController, SectionModel }; static const QUrl StyleManagerURL; static const QUrl ListsURL; static const QUrl TextRangeManagerURL; static const QUrl InlineObjectTextManagerURL; static const QUrl ChangeTrackerURL; static const QUrl UndoStackURL; static const QUrl TextEditorURL; static const QUrl LineNumberingConfigurationURL; static const QUrl BibliographyConfigurationURL; static const QUrl RelativeTabsURL; static const QUrl HeadingListURL; static const QUrl SelectionsURL; static const QUrl LayoutTextPageUrl; static const QUrl ParaTableSpacingAtStartUrl; static const QUrl IndexGeneratorManagerUrl; static const QUrl FrameCharFormatUrl; static const QUrl FrameBlockFormatUrl; static const QUrl ShapeControllerUrl; static const QUrl SectionModelUrl; private: QTextDocument *m_document; }; #endif // KOTEXTDOCUMENT_H diff --git a/libs/text/KoTextEditor_undo.cpp b/libs/text/KoTextEditor_undo.cpp index 2c8690bebe5..7db965ebda1 100644 --- a/libs/text/KoTextEditor_undo.cpp +++ b/libs/text/KoTextEditor_undo.cpp @@ -1,329 +1,329 @@ /* This file is part of the KDE project * Copyright (C) 2009-2012 Pierre Stirnweiss * Copyright (C) 2006-2010 Thomas Zander * Copyright (c) 2011 Boudewijn Rempt * Copyright (C) 2011-2012 C. Boemann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTextEditor.h" #include "KoTextEditor_p.h" #include "KoTextDocument.h" #include #include #include -#include +#include #include "TextDebug.h" /** Calligra's undo/redo framework. The @class KoTextEditor undo/redo framework sits between the @class QTextDocument and the apllication's undo/redo stack. When the @class QTextDocument is changed by an editing action, it internally creates an undo/redo command. When doing so a signal (undoCommandAdded()) is emitted by the @class QTextDocument in order for applications to update their undo/redo stack accordingly. Each @class QTextDocument used in Calligra is handled by a specific @class KoTextEditor. It is responsible for on the one hand edit the @class QTextDocument, and on the other hand to listen for the QTextDocument's signal. Calligra uses a @class KUndo2Stack as its application undo/redo stack. This stack is populated by @class KUndo2Command or sub-classes of it. In order to limit the number of command sub-classes, KoTextEditor provides a framework which uses a generic command. The framework is based on a sort of state machine. The KoTextEditor can be in several different states (see @enum KoTextEditor::Private::State). These are: NoOp: this states indicates that the KoTextEditor is not editing the QTextDocument. KeyPress: this state indicates that the user is typing text. All text typed in succession should correspond to one undo command. To be used when entering text outside of an insertTextCommand. Delete: this state indicates that the user is deleting characters. All deletions done in succession should correspond to one undo command. To be used for deleting outside a deleteCommand. Currently not in use, our deletion is done through a command because of inline objects. Format: this state indicates that we are formatting text. To be used when formatting outside of a command. Custom: this state indicates that the QTextDocument is changed through a KUndo2Command. KoTextEditor reacts differently when receiving the QTextDocument's signal, depending on its state. In addition the framework allows to encapsulate modifications in a on-the-fly created custom command (\sa beginEditBlock() endEditBlock()). Furthermore the framework allows to push complete KUndo2Commands. See the documentation file for how to use this framework. */ /* Important members: commandStack: This stack holds the headCommands. These parent the generated UndoTextCommands. When undo or redo is called, they will in turn call UndoTextCommand::undo/redo. editorState: Holds the state of the KoTextEditor. see above commandTitle: Holds the title which is to be used when creating a headCommand. addNewCommand: bool used to tell the framework to create a new headCommand and push it on the commandStack, when receiving an undoCommandAdded signal from QTextDocument. customCommandCount: counter used to keep track of nested KUndo2Commands that are pushed on the KoTextEditor. */ // This slot is called when the KoTextEditor receives the signal undoCommandAdded() from QTextDocument. A generic UndoTextCommand is used to match the QTextDocument's internal undo command. This command calls QTextDocument::undo() or QTextDocument::redo() respectively, which triggers the undo redo actions on the document. //In order to allow nesting commands, we maintain a stack of commands. The top command will be the parent of our auto generated UndoTextCommands. //Depending on the case, we might create a command which will serve as head command. This is pushed to our commandStack and eventually to the application's stack. void KoTextEditor::Private::documentCommandAdded() { class UndoTextCommand : public KUndo2Command { public: UndoTextCommand(QTextDocument *document, KoTextEditor::Private *p, KUndo2Command *parent = 0) : KUndo2Command(kundo2_i18n("Text"), parent), m_document(document) , m_p(p) {} void undo() { QTextDocument *doc = m_document.data(); if (doc == 0) return; doc->undo(KoTextDocument(doc).textEditor()->cursor()); m_p->emitTextFormatChanged(); } void redo() { QTextDocument *doc = m_document.data(); if (doc == 0) return; doc->redo(KoTextDocument(doc).textEditor()->cursor()); m_p->emitTextFormatChanged(); } - QWeakPointer m_document; + QPointer m_document; KoTextEditor::Private *m_p; }; debugText << "received a QTextDocument undoCommand signal"; debugText << "commandStack count: " << commandStack.count(); debugText << "addCommand: " << addNewCommand; debugText << "editorState: " << editorState; if (commandStack.isEmpty()) { //We have an empty stack. We need a head command which is to be pushed onto our commandStack and on the application stack if there is one. //This command will serve as a parent for the auto-generated UndoTextCommands. debugText << "empty stack, will push a new headCommand on both commandStack and application's stack. title: " << commandTitle; commandStack.push(new KUndo2Command(commandTitle)); if (KoTextDocument(document).undoStack()) { KoTextDocument(document).undoStack()->push(commandStack.top()); } addNewCommand = false; debugText << "commandStack is now: " << commandStack.count(); } else if (addNewCommand) { //We have already a headCommand on the commandStack. However we want a new child headCommand (nested commands) on the commandStack for parenting further UndoTextCommands. This shouldn't be pushed on the application's stack because it is a child of the current commandStack's top. debugText << "we have a headCommand on the commandStack but need a new child command. we will push it only on the commandStack: " << commandTitle; commandStack.push(new KUndo2Command(commandTitle, commandStack.top())); addNewCommand = false; debugText << "commandStack count is now: " << commandStack.count(); } else if ((editorState == KeyPress || editorState == Delete) && !commandStack.isEmpty() && commandStack.top()->childCount()) { //QTextDocument emits a signal on the first key press (or delete) and "merges" the subsequent ones, until an incompatible one is done. In which case it re-emit a signal. //Here we are in KeyPress (or Delete) state. The fact that the commandStack isn't empty and its top command has children means that we just received such a signal. We therefore need to pop the previous headCommand (which were either key press or delete) and create a new one to parent the UndoTextCommands. This command also need to be pushed on the application's stack. debugText << "we are in subsequent keyPress/delete state and still received a signal. we need to create a new headCommand: " << commandTitle; debugText << "so we pop the current one and push the new one on both the commandStack and the application's stack"; commandStack.pop(); commandStack.push(new KUndo2Command(commandTitle, !commandStack.isEmpty()?commandStack.top():0)); if (KoTextDocument(document).undoStack()) { KoTextDocument(document).undoStack()->push(commandStack.top()); } debugText << "commandStack count: " << commandStack.count(); } //Now we can create our UndoTextCommand which is parented to the commandStack't top headCommand. new UndoTextCommand(document, this, commandStack.top()); debugText << "done creating the dummy UndoTextCommand"; } //This method is used to update the KoTextEditor state, which will condition how the QTextDocument::undoCommandAdded signal will get handled. void KoTextEditor::Private::updateState(KoTextEditor::Private::State newState, const KUndo2MagicString &title) { debugText << "updateState from: " << editorState << " to: " << newState << " with: " << title; debugText << "commandStack count: " << commandStack.count(); if (editorState == Custom && newState != NoOp) { //We already are in a custom state (meaning that either a KUndo2Command was pushed on us, an on-the-fly macro command was started or we are executing a complex editing from within the KoTextEditor. //In that state any update of the state different from NoOp is part of that "macro". However, updating the state means that we are now wanting to have a new command for parenting the UndoTextCommand generated after the signal //from QTextDocument. This is to ensure that undo/redo actions are done in the proper order. Setting addNewCommand will ensure that we create such a child headCommand on the commandStack. This command will not be pushed on the application's stack. debugText << "we are already in a custom state. a new state, which is not NoOp is part of the macro we are doing. we need however to create a new command on the commandStack to parent a signal induced UndoTextCommand"; addNewCommand = true; if (!title.isEmpty()) commandTitle = title; else commandTitle = kundo2_i18n("Text"); debugText << "returning now. commandStack is not modified at this stage"; return; } if (newState == NoOp && !commandStack.isEmpty()) { //Calling updateState to NoOp when the commandStack isn't empty means that the current headCommand on the commandStack is finished. Further UndoTextCommands do not belong to it. So we pop it. //If after poping the headCommand we still have some commands on the commandStack means we have not finished with the highest "macro". In that case we need to stay in the "Custom" state. //On the contrary, an empty commandStack means we have finished with the "macro". In that case, we set the editor to NoOp state. A signal from the QTextDocument should also generate a new headCommand. debugText << "we are in a macro and update the state to NoOp. this means that the command on top of the commandStack is finished. we should pop it"; debugText << "commandStack count before: " << commandStack.count(); commandStack.pop(); debugText << "commandStack count after: " << commandStack.count(); if (commandStack.isEmpty()) { debugText << "we have no more commands on the commandStack. the macro is complete. next signal induced command will need to be parented to a new headCommand. Also the editor should go to NoOp"; addNewCommand = true; editorState = NoOp; } debugText << "returning now. commandStack count: " << commandStack.count(); return; } if (editorState != newState || commandTitle != title) { //We are not in "Custom" state but either are moving to a new state (from editing to format,...) or the command type is the same, but not the command itself (like format:bold, format:italic). The later case is caracterised by a different command title. //When we change command, we need to pop the current commandStack's top and ask for a new headCommand to be created. debugText << "we are not in a custom state but change the command"; debugText << "commandStack count: " << commandStack.count(); if (!commandStack.isEmpty()) { debugText << "the commandStack is not empty. however the command on it is not a macro. so we pop it and ask to recreate a new one: " << title; commandStack.pop(); addNewCommand = true; } } editorState = newState; if (!title.isEmpty()) commandTitle = title; else commandTitle = kundo2_i18n("Text"); debugText << "returning now. commandStack count: " << commandStack.count(); } /// This method is used to push a complete KUndo2Command on the KoTextEditor. This command will be pushed on the application's stack if needed. The logic allows to push several commands which are then going to be nested, provided these children are pushed from within the redo method of their parent. void KoTextEditor::addCommand(KUndo2Command *command) { debugText << "we receive a command to add on the stack."; debugText << "commandStack count: " << d->commandStack.count(); debugText << "customCommandCount counter: " << d->customCommandCount << " will increase"; //We increase the customCommandCount counter to inform the framework that we are having a further KUndo2Command and update the KoTextEditor's state to Custom. //However, this update will request a new headCommand to be pushed on the commandStack. This is what we want for internal complex editions but not in this case. Indeed, it must be the KUndo2Command which will parent the UndoTextCommands. Therefore we set the addNewCommand back to false. //If the commandStack is empty, we are the highest "macro" command and we should therefore push the KUndo2Command on the application's stack. //On the contrary, if the commandStack is not empty, or the pushed command has a parent, it means that we are adding a nested KUndo2Command. In which case we just want to put it on the commandStack to parent UndoTextCommands. We need to call the redo method manually though. ++d->customCommandCount; debugText << "we will now go to custom state"; d->updateState(KoTextEditor::Private::Custom, (!command->text().isEmpty())?command->text():kundo2_i18n("Text")); debugText << "but will set the addCommand to false. we don't want a new headCommand"; d->addNewCommand = false; debugText << "commandStack count is: " << d->commandStack.count(); if (d->commandStack.isEmpty()) { debugText << "the commandStack is empty. this means we are the top most command"; d->commandStack.push(command); debugText << "command pushed on the commandStack. count: " << d->commandStack.count(); KUndo2QStack *stack = KoTextDocument(d->document).undoStack(); if (stack && !command->hasParent()) { debugText << "we have an application stack and the command is not a sub command of a non text command (which have been pushed outside kotext"; stack->push(command); debugText << "so we pushed it on the application's' stack"; } else { debugText << "we either have no application's stack, or our command is actually the child of a non kotext command"; command->redo(); debugText << "still called redo on it"; } } else { debugText << "the commandStack is not empty, our command is actually nested in another kotext command. we don't push on the application stack but only on the commandStack"; d->commandStack.push(command); debugText << "commandStack count after push: " << d->commandStack.count(); command->redo(); debugText << "called redo still"; } //When we reach that point, the command has been executed. We first need to clean up all the automatically generated headCommand on our commandStack, which could potentially have been created during the editing. When we reach our pushed command, the commandStack is clean. We can then call a state update to NoOp and decrease the customCommandCount counter. debugText << "the command has been executed. we need to clean up the commandStack of the auto generated headCommands"; debugText << "before cleaning. commandStack count: " << d->commandStack.count(); while (d->commandStack.top() != command) { d->commandStack.pop(); } debugText << "after cleaning. commandStack count: " << d->commandStack.count() << " will set NoOp"; d->updateState(KoTextEditor::Private::NoOp); debugText << "after NoOp set. inCustomCounter: " << d->customCommandCount << " will decrease and return"; --d->customCommandCount; } /// DO NOT USE THIS. It stays here for compiling reasons. But it will severely break everything. Again: DO NOT USE THIS. void KoTextEditor::instantlyExecuteCommand(KUndo2Command *command) { d->updateState(KoTextEditor::Private::Custom, (!command->text().isEmpty())?command->text():kundo2_i18n("Text")); command->redo(); // instant replay done let's not keep it dangling if (!command->hasParent()) { d->updateState(KoTextEditor::Private::NoOp); } } /// This method is used to start an on-the-fly macro command. Use KoTextEditor::endEditBlock to stop it. /// *** /// Important note: /// *** /// The framework does not allow to push a complete KUndo2Command (through KoTextEditor::addCommand) from within an EditBlock. Doing so will lead in the best case to several undo/redo commands on the application's stack instead of one, in the worst case to an out of sync application's stack. /// *** KUndo2Command *KoTextEditor::beginEditBlock(const KUndo2MagicString &title) { debugText << "beginEditBlock"; debugText << "commandStack count: " << d->commandStack.count(); debugText << "customCommandCount counter: " << d->customCommandCount; if (!d->customCommandCount) { // We are not in a custom macro command. So we first need to update the KoTextEditor's state to Custom. Additionally, if the commandStack is empty, we need to create a master headCommand for our macro and push it on the stack. debugText << "we are not in a custom command. will update state to custom"; d->updateState(KoTextEditor::Private::Custom, title); debugText << "commandStack count: " << d->commandStack.count(); if (d->commandStack.isEmpty()) { debugText << "the commandStack is empty. we need a dummy headCommand both on the commandStack and on the application's stack"; KUndo2Command *command = new KUndo2Command(title); d->commandStack.push(command); ++d->customCommandCount; d->dummyMacroAdded = true; //This bool is used to tell endEditBlock that we have created a master headCommand. KUndo2QStack *stack = KoTextDocument(d->document).undoStack(); if (stack) { stack->push(command); } else { command->redo(); } debugText << "done adding the headCommand. commandStack count: " << d->commandStack.count() << " inCommand counter: " << d->customCommandCount; } } //QTextDocument sends the undoCommandAdded signal at the end of the QTextCursor edit block. Since we want our master headCommand to parent the signal induced UndoTextCommands, we should not call QTextCursor::beginEditBlock for the headCommand. if (!(d->dummyMacroAdded && d->customCommandCount == 1)) { debugText << "we did not add a dummy command, or we are further down nesting. call beginEditBlock on the caret to nest the QTextDoc changes"; //we don't call beginEditBlock for the first headCommand because we want the signals to be sent before we finished our command. d->caret.beginEditBlock(); } debugText << "will return top od commandStack"; return (d->commandStack.isEmpty())?0:d->commandStack.top(); } void KoTextEditor::endEditBlock() { debugText << "endEditBlock"; //Only the self created master headCommand (see beginEditBlock) is left on the commandStack, we need to decrease the customCommandCount counter that we increased on creation. //If we are not yet at this master headCommand, we can call QTextCursor::endEditBlock if (d->dummyMacroAdded && d->customCommandCount == 1) { debugText << "only the created dummy headCommand from beginEditBlock is left. we need to decrease further the nesting counter"; //we don't call caret.endEditBlock because we did not begin a block for the first headCommand --d->customCommandCount; d->dummyMacroAdded = false; } else { debugText << "we are not at our top dummy headCommand. call caret.endEditBlock"; d->caret.endEditBlock(); } if (!d->customCommandCount) { //We have now finished completely the macro, set the editor state to NoOp then. debugText << "we have finished completely the macro, set the state to NoOp now. commandStack count: " << d->commandStack.count(); d->updateState(KoTextEditor::Private::NoOp); debugText << "done setting the state. editorState: " << d->editorState << " commandStack count: " << d->commandStack.count(); } } diff --git a/libs/text/KoTextInlineRdf.cpp b/libs/text/KoTextInlineRdf.cpp index a453feaf859..5ed6edfaf38 100644 --- a/libs/text/KoTextInlineRdf.cpp +++ b/libs/text/KoTextInlineRdf.cpp @@ -1,365 +1,365 @@ /* This file is part of the KDE project Copyright (C) 2010 KO GmbH Copyright (C) 2012 C. Boemann This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "KoTextInlineRdf.h" // lib #include #include #include #include #include #include #include // komain #include #include #include #include // TODO: the "errorText" define in TextDebug.h results in a build error, if used as second include here // find out why and perhaps change the rather generic "errorText" & Co defines to something less conflict-prone #include "TextDebug.h" // Qt #include #include #include #ifdef SHOULD_BUILD_RDF #include enum Type { EmptyNode = Soprano::Node::EmptyNode, ResourceNode = Soprano::Node::ResourceNode, LiteralNode = Soprano::Node::LiteralNode, BlankNode = Soprano::Node::BlankNode }; #else enum Type { EmptyNode, ResourceNode, LiteralNode, BlankNode }; #endif class Q_DECL_HIDDEN KoTextInlineRdf::Private { public: Private(const QTextDocument *doc, const QTextBlock &b) : block(b) , document(doc) { isObjectAttributeUsed = false; sopranoObjectType = LiteralNode; } Private(const QTextDocument *doc, KoBookmark *b) : document(doc) , bookmark(b) { isObjectAttributeUsed = false; sopranoObjectType = LiteralNode; } Private(const QTextDocument *doc, KoAnnotation *b) : document(doc) , annotation(b) { isObjectAttributeUsed = false; sopranoObjectType = LiteralNode; } Private(const QTextDocument *doc, KoTextMeta *b) : document(doc) , kotextmeta(b) { isObjectAttributeUsed = false; sopranoObjectType = LiteralNode; } Private(const QTextDocument *doc, const QTextTableCell &c) : document(doc) , cell(c) { isObjectAttributeUsed = false; sopranoObjectType = LiteralNode; } Private(const QTextDocument *doc, KoSection *s) : document(doc) , section(s) { isObjectAttributeUsed = false; sopranoObjectType = LiteralNode; } QString id; // original xml:id //FIXME: design like this seems inapropriate, maybe // making Interface from KoTextInlineRdf will be better. // Just my thoughts. // where we might get the object value from QTextBlock block; // or document and one of bookmark, annotation, kotextmeta, ... - QWeakPointer document; - QWeakPointer bookmark; - QWeakPointer annotation; - QWeakPointer kotextmeta; + QPointer document; + QPointer bookmark; + QPointer annotation; + QPointer kotextmeta; KoSection *section; QTextTableCell cell; QString subject; QString predicate; int sopranoObjectType; QString dt; // if the content="" attribute was used, // then isObjectAttributeUsed=1 and object=content attribute value. QString object; bool isObjectAttributeUsed; }; KoTextInlineRdf::KoTextInlineRdf(const QTextDocument *doc, const QTextBlock &b) : QObject(const_cast(doc)) , d(new Private(doc, b)) { } KoTextInlineRdf::KoTextInlineRdf(const QTextDocument *doc, KoBookmark *b) : QObject(const_cast(doc)) , d(new Private(doc, b)) { } KoTextInlineRdf::KoTextInlineRdf(const QTextDocument *doc, KoAnnotation *b) : QObject(const_cast(doc)) , d(new Private(doc, b)) { } KoTextInlineRdf::KoTextInlineRdf(const QTextDocument *doc, KoTextMeta *b) : QObject(const_cast(doc)) , d(new Private(doc, b)) { } KoTextInlineRdf::KoTextInlineRdf(const QTextDocument *doc, const QTextTableCell &b) : QObject(const_cast(doc)) , d(new Private(doc, b)) { } KoTextInlineRdf::KoTextInlineRdf(const QTextDocument *doc, KoSection *s) : QObject(const_cast(doc)) , d(new Private(doc, s)) { } KoTextInlineRdf::~KoTextInlineRdf() { debugText << " this:" << (void*)this; delete d; } bool KoTextInlineRdf::loadOdf(const KoXmlElement &e) { d->id = e.attribute("id", QString()); d->subject = e.attributeNS(KoXmlNS::xhtml, "about"); d->predicate = e.attributeNS(KoXmlNS::xhtml, "property"); d->dt = e.attributeNS(KoXmlNS::xhtml, "datatype"); QString content = e.attributeNS(KoXmlNS::xhtml, "content"); // // Content / triple object explicitly set through an attribute // if (e.hasAttributeNS(KoXmlNS::xhtml, "content")) { d->isObjectAttributeUsed = true; d->object = content; } return true; } bool KoTextInlineRdf::saveOdf(KoShapeSavingContext &context, KoXmlWriter *writer, KoElementReference id) const { debugText << " this:" << (void*)this << " xmlid:" << d->id << "passed id" << id.toString(); QString oldID = d->id; if (!id.isValid()) { id = KoElementReference(); } QString newID = id.toString(); if (KoTextSharedSavingData *sharedData = dynamic_cast(context.sharedData(KOTEXT_SHARED_SAVING_ID))) { sharedData->addRdfIdMapping(oldID, newID); } debugText << "oldID:" << oldID << " newID:" << newID; writer->addAttribute("xml:id", newID); if (!d->subject.isEmpty()) { writer->addAttribute("xhtml:about", d->subject); } if (!d->predicate.isEmpty()) { writer->addAttribute("xhtml:property", d->predicate); } if (!d->dt.isEmpty()) { writer->addAttribute("xhtml:datatype", d->dt); } if (d->isObjectAttributeUsed) { writer->addAttribute("xhtml:content", d->object); } debugText << "done.."; return true; } QString KoTextInlineRdf::createXmlId() { KoElementReference ref; return ref.toString(); } QString KoTextInlineRdf::subject() const { return d->subject; } QString KoTextInlineRdf::predicate() const { return d->predicate; } QPair KoTextInlineRdf::findExtent() const { if (d->bookmark && d->document) { return QPair(d->bookmark.data()->rangeStart(), d->bookmark.data()->rangeEnd()); } if (d->annotation && d->document) { return QPair(d->annotation.data()->rangeStart(), d->annotation.data()->rangeEnd()); } // FIXME: We probably have to do something with endAnnotation() // too, but I don't know exactly what... if (d->kotextmeta && d->document) { KoTextMeta *e = d->kotextmeta.data()->endBookmark(); if (!e) { return QPair(0, 0); } // debugText << "(Semantic)meta... start:" << d->kotextmeta.data()->position() << " end:" << e->position(); return QPair(d->kotextmeta.data()->position(), e->position()); } if (d->cell.isValid() && d->document) { QTextCursor b = d->cell.firstCursorPosition(); QTextCursor e = d->cell.lastCursorPosition(); return QPair(b.position(), e.position()); } if (d->section) { return d->section->bounds(); } return QPair(0, 0); } QString KoTextInlineRdf::object() const { if (d->isObjectAttributeUsed) { return d->object; } KoTextDocument textDocument(d->document.data()); if (d->bookmark && d->document) { QString ret = d->bookmark.data()->text(); return ret.remove(QChar::ObjectReplacementCharacter); } else if (d->kotextmeta && d->document) { // FIXME: Need to do something with endAnnotation? KoTextMeta *e = d->kotextmeta.data()->endBookmark(); if (!e) { debugText << "Broken KoTextMeta, no end tag found!"; return QString(); } else { KoTextEditor *editor = textDocument.textEditor(); editor->setPosition(d->kotextmeta.data()->position(), QTextCursor::MoveAnchor); editor->setPosition(e->position(), QTextCursor::KeepAnchor); QString ret = editor->selectedText(); return ret.remove(QChar::ObjectReplacementCharacter); } } else if (d->cell.isValid() && d->document) { QTextCursor b = d->cell.firstCursorPosition(); b.setPosition(d->cell.lastCursorPosition().position(), QTextCursor::KeepAnchor); QString ret = b.selectedText(); return ret.remove(QChar::ObjectReplacementCharacter); } return d->block.text(); } int KoTextInlineRdf::sopranoObjectType() const { return d->sopranoObjectType; } QString KoTextInlineRdf::xmlId() const { return d->id; } void KoTextInlineRdf::setXmlId(const QString &id) { d->id = id; } KoTextInlineRdf *KoTextInlineRdf::tryToGetInlineRdf(const QTextFormat &tf) { if (!tf.hasProperty(KoCharacterStyle::InlineRdf)) { return 0; } QVariant v = tf.property(KoCharacterStyle::InlineRdf); return v.value(); } KoTextInlineRdf *KoTextInlineRdf::tryToGetInlineRdf(QTextCursor &cursor) { QTextCharFormat cf = cursor.charFormat(); if (!cf.hasProperty(KoCharacterStyle::InlineRdf)) { return 0; } QVariant v = cf.property(KoCharacterStyle::InlineRdf); return v.value(); } KoTextInlineRdf *KoTextInlineRdf::tryToGetInlineRdf(KoTextEditor *handler) { QTextCharFormat cf = handler->charFormat(); if (!cf.hasProperty(KoCharacterStyle::InlineRdf)) { return 0; } QVariant v = cf.property(KoCharacterStyle::InlineRdf); return v.value(); } void KoTextInlineRdf::attach(KoTextInlineRdf *inlineRdf, QTextCursor &cursor) { QTextCharFormat format = cursor.charFormat(); QVariant v = QVariant::fromValue(inlineRdf); format.setProperty(KoCharacterStyle::InlineRdf, v); cursor.mergeCharFormat(format); } diff --git a/libs/text/KoTextMeta.cpp b/libs/text/KoTextMeta.cpp index 284902674ec..c96a925befa 100644 --- a/libs/text/KoTextMeta.cpp +++ b/libs/text/KoTextMeta.cpp @@ -1,139 +1,139 @@ /* This file is part of the KDE project * Copyright (C) 2010 KO GmbH * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "KoTextMeta.h" #include #include #include #include #include #include -#include +#include #include "TextDebug.h" // Include Q_UNSUSED classes, for building on Windows #include class Q_DECL_HIDDEN KoTextMeta::Private { public: Private(const QTextDocument *doc) : document(doc), posInDocument(0) { } const QTextDocument *document; int posInDocument; - QWeakPointer endBookmark; + QPointer endBookmark; BookmarkType type; }; KoTextMeta::KoTextMeta(const QTextDocument *document) : KoInlineObject(false), d(new Private(document)) { d->endBookmark.clear(); } KoTextMeta::~KoTextMeta() { delete d; } void KoTextMeta::saveOdf(KoShapeSavingContext &context) { KoXmlWriter &writer = context.xmlWriter(); debugText << "kom.save() this:" << (void*)this << " d->type:" << d->type; if (inlineRdf()) { debugText << "kom.save() have inline Rdf"; } if (d->type == StartBookmark) { writer.startElement("text:meta", false); writer.addAttribute("text:name", "foo"); if (inlineRdf()) { inlineRdf()->saveOdf(context, &writer); } } else { debugText << "adding endelement."; writer.endElement(); } debugText << "kom.save() done this:" << (void*)this << " d->type:" << d->type; } bool KoTextMeta::loadOdf(const KoXmlElement &element, KoShapeLoadingContext &context) { Q_UNUSED(element); Q_UNUSED(context); debugText << "kom.load()"; return true; } void KoTextMeta::updatePosition(const QTextDocument *document, int posInDocument, const QTextCharFormat &format) { Q_UNUSED(format); d->document = document; d->posInDocument = posInDocument; } void KoTextMeta::resize(const QTextDocument *document, QTextInlineObject &object, int posInDocument, const QTextCharFormat &format, QPaintDevice *pd) { Q_UNUSED(document); Q_UNUSED(posInDocument); Q_UNUSED(format); Q_UNUSED(pd); object.setWidth(0); object.setAscent(0); object.setDescent(0); } void KoTextMeta::paint(QPainter &, QPaintDevice *, const QTextDocument *, const QRectF &, const QTextInlineObject &, int , const QTextCharFormat &) { // nothing to paint. } void KoTextMeta::setType(BookmarkType type) { d->type = type; } KoTextMeta::BookmarkType KoTextMeta::type() const { return d->type; } void KoTextMeta::setEndBookmark(KoTextMeta *bookmark) { d->type = StartBookmark; bookmark->d->type = EndBookmark; d->endBookmark = bookmark; } KoTextMeta *KoTextMeta::endBookmark() const { return d->endBookmark.data(); } int KoTextMeta::position() const { return d->posInDocument; } diff --git a/libs/text/OdfTextTrackStyles.h b/libs/text/OdfTextTrackStyles.h index 052b5ee4e23..78bd4bb253d 100644 --- a/libs/text/OdfTextTrackStyles.h +++ b/libs/text/OdfTextTrackStyles.h @@ -1,80 +1,80 @@ /* This file is part of the KDE project * Copyright (C) 2006 Thomas Zander * Copyright (C) 2012 C. Boemann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef CHANGEFOLLOWER_H #define CHANGEFOLLOWER_H #include "commands/ChangeStylesMacroCommand.h" #include #include -#include +#include #include #include /** * OdfTextTrackStyles is used to update a list of qtextdocument with * any changes made in the style manager. * * It also creates undo commands and adds them to the undo stack * * Style changes affect a lot of qtextdocuments and we store the changes and apply * the changes to every qtextdocument, so every KoTextDocument has to * register their QTextDocument to us. * * We use the QObject principle of children getting deleted when the * parent gets deleted. Thus we die when the KoStyleManager dies. */ class OdfTextTrackStyles : public QObject { Q_OBJECT public: static OdfTextTrackStyles *instance(KoStyleManager *manager); private: static QHash instances; explicit OdfTextTrackStyles(KoStyleManager *manager); /// Destructor, called when the parent is deleted. ~OdfTextTrackStyles(); private Q_SLOTS: void beginEdit(); void endEdit(); void recordStyleChange(int id, const KoParagraphStyle *origStyle, const KoParagraphStyle *newStyle); void recordStyleChange(int id, const KoCharacterStyle *origStyle, const KoCharacterStyle *newStyle); void styleManagerDied(QObject *manager); void documentDied(QObject *manager); public: void registerDocument(QTextDocument *qDoc); void unregisterDocument(QTextDocument *qDoc); private: QList m_documents; - QWeakPointer m_styleManager; + QPointer m_styleManager; ChangeStylesMacroCommand *m_changeCommand; }; #endif diff --git a/libs/text/commands/ChangeTrackedDeleteCommand.cpp b/libs/text/commands/ChangeTrackedDeleteCommand.cpp index 50330fed12c..31127f88344 100644 --- a/libs/text/commands/ChangeTrackedDeleteCommand.cpp +++ b/libs/text/commands/ChangeTrackedDeleteCommand.cpp @@ -1,440 +1,440 @@ /* This file is part of the KDE project * Copyright (C) 2009 Ganesh Paramasivam * Copyright (C) 2009 Pierre Stirnweiss * Copyright (C) 2010 Thomas Zander * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA.*/ #include "ChangeTrackedDeleteCommand.h" #include "TextPasteCommand.h" #include "ListItemNumberingCommand.h" #include "ChangeListCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "TextDebug.h" //A convenience function to get a ListIdType from a format static KoListStyle::ListIdType ListId(const QTextListFormat &format) { KoListStyle::ListIdType listId; if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) listId = format.property(KoListStyle::ListId).toUInt(); else listId = format.property(KoListStyle::ListId).toULongLong(); return listId; } using namespace std; ChangeTrackedDeleteCommand::ChangeTrackedDeleteCommand(DeleteMode mode, QTextDocument *document, KoShapeController *shapeController, KUndo2Command *parent) : KoTextCommandBase (parent), m_document(document), m_rdf(0), m_shapeController(shapeController), m_first(true), m_undone(false), m_canMerge(true), m_mode(mode), m_removedElements() { setText(kundo2_i18n("Delete")); m_rdf = qobject_cast(shapeController->resourceManager()->resource(KoText::DocumentRdf).value()); } void ChangeTrackedDeleteCommand::undo() { if (m_document.isNull()) return; KoTextCommandBase::undo(); UndoRedoFinalizer finalizer(this); KoTextDocument textDocument(m_document.data()); textDocument.changeTracker()->elementById(m_addedChangeElement)->setValid(false); foreach (int changeId, m_removedElements) { textDocument.changeTracker()->elementById(changeId)->setValid(true); } updateListChanges(); m_undone = true; } void ChangeTrackedDeleteCommand::redo() { if (!m_document.isNull()) return; m_undone = false; KoTextDocument textDocument(m_document.data()); if (!m_first) { KoTextCommandBase::redo(); UndoRedoFinalizer finalizer(this); textDocument.changeTracker()->elementById(m_addedChangeElement)->setValid(true); foreach (int changeId, m_removedElements) { textDocument.changeTracker()->elementById(changeId)->setValid(false); } } else { m_first = false; textDocument.textEditor()->beginEditBlock(); if(m_mode == PreviousChar) deletePreviousChar(); else deleteChar(); textDocument.textEditor()->endEditBlock(); } } void ChangeTrackedDeleteCommand::deleteChar() { if (m_document.isNull()) return; KoTextEditor *editor = KoTextDocument(m_document).textEditor(); if (editor->atEnd() && !editor->hasSelection()) return; if (!editor->hasSelection()) editor->movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); deleteSelection(editor); } void ChangeTrackedDeleteCommand::deletePreviousChar() { if (m_document.isNull()) return; KoTextEditor *editor = KoTextDocument(m_document).textEditor(); if (editor->atStart() && !editor->hasSelection()) return; if (!editor->hasSelection() && editor->block().textList() && (editor->position() == editor->block().position())) { handleListItemDelete(editor); return; } if (!editor->hasSelection()) { editor->movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); } deleteSelection(editor); } void ChangeTrackedDeleteCommand::handleListItemDelete(KoTextEditor *editor) { if (m_document.isNull()) return; m_canMerge = false; bool numberedListItem = false; if (!editor->blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) numberedListItem = true; // Mark the complete list-item QTextBlock block = m_document.data()->findBlock(editor->position()); editor->movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, (block.length() - 1)); // Copy the marked item int from = editor->anchor(); int to = editor->position(); KoTextOdfSaveHelper saveHelper(m_document.data(), from, to); KoTextDrag drag; #ifdef SHOULD_BUILD_RDF if (m_rdf) { saveHelper.setRdfModel(m_rdf->model()); } #endif drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper); QTextDocumentFragment fragment = editor->selection(); drag.setData("text/html", fragment.toHtml("utf-8").toUtf8()); drag.setData("text/plain", fragment.toPlainText().toUtf8()); // Delete the marked section editor->setPosition(editor->anchor() -1); editor->movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, block.length()); deleteSelection(editor); // Mark it as inserted content QTextCharFormat format = editor->charFormat(); editor->registerTrackedChange(*editor->cursor(), KoGenChange::InsertChange, i18n("Key Press"), format, format, false); //Paste the selected text from the clipboard... (XXX: is this really correct here?) TextPasteCommand *pasteCommand = new TextPasteCommand(drag.mimeData(), m_document.data(), m_shapeController, this); pasteCommand->redo(); // Convert it into a un-numbered list-item or a paragraph if (numberedListItem) { ListItemNumberingCommand *changeNumberingCommand = new ListItemNumberingCommand(editor->block(), false, this); changeNumberingCommand->redo(); } else { KoListLevelProperties llp; llp.setStyle(KoListStyle::None); llp.setLevel(0); ChangeListCommand *changeListCommand = new ChangeListCommand(*editor->cursor(), llp, KoTextEditor::ModifyExistingList | KoTextEditor::MergeWithAdjacentList, this); changeListCommand->redo(); } editor->setPosition(editor->block().position()); } void ChangeTrackedDeleteCommand::deleteSelection(KoTextEditor *editor) { if (m_document.isNull()) return; // XXX: don't allow anyone to steal our cursor! QTextCursor *selection = editor->cursor(); QTextCursor checker = QTextCursor(*editor->cursor()); bool backwards = (checker.anchor() > checker.position()); int selectionBegin = qMin(checker.anchor(), checker.position()); int selectionEnd = qMax(checker.anchor(), checker.position()); int changeId; QList shapesInSelection; checker.setPosition(selectionBegin); KoTextDocument textDocument(m_document.data()); KoInlineTextObjectManager *inlineTextObjectManager = textDocument.inlineTextObjectManager(); while ((checker.position() < selectionEnd) && (!checker.atEnd())) { QChar charAtPos = m_document.data()->characterAt(checker.position()); checker.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); if (inlineTextObjectManager->inlineTextObject(checker) && charAtPos == QChar::ObjectReplacementCharacter) { /* This has changed but since this entire command is going away - let's not bother KoTextAnchor *anchor = dynamic_cast(inlineTextObjectManager->inlineTextObject(checker)); if (anchor) shapesInSelection.push_back(anchor->shape()); */ } checker.setPosition(checker.position()); } checker.setPosition(selectionBegin); if (!KoTextDocument(m_document).changeTracker()->displayChanges()) { QChar charAtPos = m_document.data()->characterAt(checker.position() - 1); } if (KoTextDocument(m_document).changeTracker()->containsInlineChanges(checker.charFormat())) { int changeId = checker.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt(); if (KoTextDocument(m_document).changeTracker()->elementById(changeId)->getChangeType() == KoGenChange::DeleteChange) { QTextDocumentFragment prefix = KoTextDocument(m_document).changeTracker()->elementById(changeId)->getDeleteData(); selectionBegin -= (KoChangeTracker::fragmentLength(prefix) + 1 ); KoTextDocument(m_document).changeTracker()->elementById(changeId)->setValid(false); m_removedElements.push_back(changeId); } } checker.setPosition(selectionEnd); if (!checker.atEnd()) { QChar charAtPos = m_document.data()->characterAt(checker.position()); checker.movePosition(QTextCursor::NextCharacter); } selection->setPosition(selectionBegin); selection->setPosition(selectionEnd, QTextCursor::KeepAnchor); QTextDocumentFragment deletedFragment; changeId = KoTextDocument(m_document).changeTracker()->getDeleteChangeId(i18n("Delete"), deletedFragment, 0); KoChangeTrackerElement *element = KoTextDocument(m_document).changeTracker()->elementById(changeId); QTextCharFormat charFormat; charFormat.setProperty(KoCharacterStyle::ChangeTrackerId, changeId); selection->mergeCharFormat(charFormat); deletedFragment = KoChangeTracker::generateDeleteFragment(*selection); element->setDeleteData(deletedFragment); //Store the position and length. Will be used in updateListChanges() m_position = (selection->anchor() < selection->position()) ? selection->anchor():selection->position(); m_length = qAbs(selection->anchor() - selection->position()); updateListIds(*editor->cursor()); m_addedChangeElement = changeId; //Insert the deleted data again after the marker with the charformat set to the change-id if (KoTextDocument(m_document).changeTracker()->displayChanges()) { int startPosition = selection->position(); KoChangeTracker::insertDeleteFragment(*selection); QTextCursor tempCursor(*selection); tempCursor.setPosition(startPosition); tempCursor.setPosition(selection->position(), QTextCursor::KeepAnchor); // XXX: why was this commented out? //tempCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, fragmentLength(deletedFragment)); updateListIds(tempCursor); if (backwards) { selection->movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor, KoChangeTracker::fragmentLength(deletedFragment) + 1); } } else { if (backwards) { selection->movePosition(QTextCursor::PreviousCharacter, QTextCursor::MoveAnchor,1); } foreach (KoShape *shape, shapesInSelection) { KUndo2Command *shapeDeleteCommand = m_shapeController->removeShape(shape, this); shapeDeleteCommand->redo(); m_canMerge = false; } } } int ChangeTrackedDeleteCommand::id() const { return 98765; } bool ChangeTrackedDeleteCommand::mergeWith( const KUndo2Command *command) { class UndoTextCommand : public KUndo2Command { public: UndoTextCommand(QTextDocument *document, KUndo2Command *parent = 0) : KUndo2Command(kundo2_i18n("Text"), parent), m_document(document) {} void undo() { QTextDocument *doc = const_cast(m_document.data()); if (doc) doc->undo(KoTextDocument(doc).textEditor()->cursor()); } void redo() { QTextDocument *doc = const_cast(m_document.data()); if (doc) doc->redo(KoTextDocument(doc).textEditor()->cursor()); } - QWeakPointer m_document; + QPointer m_document; }; if (command->id() != id()) return false; ChangeTrackedDeleteCommand *other = const_cast(static_cast(command)); if (other->m_canMerge == false) return false; if (other->m_removedElements.contains(m_addedChangeElement)) { removeChangeElement(m_addedChangeElement); other->m_removedElements.removeAll(m_addedChangeElement); m_addedChangeElement = other->m_addedChangeElement; m_removedElements += other->m_removedElements; other->m_removedElements.clear(); m_newListIds = other->m_newListIds; m_position = other->m_position; m_length = other->m_length; for(int i=0; i < command->childCount(); i++) { new UndoTextCommand(m_document.data(), this); } return true; } return false; } void ChangeTrackedDeleteCommand::updateListIds(QTextCursor &cursor) { if (m_document.isNull()) return; m_newListIds.clear(); QTextCursor tempCursor(m_document.data()); QTextBlock startBlock = m_document.data()->findBlock(cursor.anchor()); QTextBlock endBlock = m_document.data()->findBlock(cursor.position()); QTextList *currentList; for (QTextBlock currentBlock = startBlock; currentBlock != endBlock.next(); currentBlock = currentBlock.next()) { tempCursor.setPosition(currentBlock.position()); currentList = tempCursor.currentList(); if (currentList) { KoListStyle::ListIdType listId = ListId(currentList->format()); m_newListIds.push_back(listId); } } } void ChangeTrackedDeleteCommand::updateListChanges() { if (m_document.isNull()) return; QTextCursor tempCursor(m_document.data()); QTextBlock startBlock = m_document.data()->findBlock(m_position); QTextBlock endBlock = m_document.data()->findBlock(m_position + m_length); QTextList *currentList; int newListIdsCounter = 0; for (QTextBlock currentBlock = startBlock; currentBlock != endBlock.next(); currentBlock = currentBlock.next()) { tempCursor.setPosition(currentBlock.position()); currentList = tempCursor.currentList(); if (currentList) { KoListStyle::ListIdType listId = m_newListIds[newListIdsCounter]; if (!KoTextDocument(m_document).list(currentBlock)) { KoList *list = KoTextDocument(m_document).list(listId); if (list) list->updateStoredList(currentBlock); } newListIdsCounter++; } } } ChangeTrackedDeleteCommand::~ChangeTrackedDeleteCommand() { if (m_undone) { removeChangeElement(m_addedChangeElement); } else { foreach (int changeId, m_removedElements) { removeChangeElement(changeId); } } } void ChangeTrackedDeleteCommand::removeChangeElement(int changeId) { KoTextDocument textDocument(m_document); KoChangeTrackerElement *element = textDocument.changeTracker()->elementById(changeId); KoTextDocument(m_document).changeTracker()->removeById(changeId); } diff --git a/libs/text/commands/ChangeTrackedDeleteCommand.h b/libs/text/commands/ChangeTrackedDeleteCommand.h index 6b64e37ce0f..26d3de7de2a 100644 --- a/libs/text/commands/ChangeTrackedDeleteCommand.h +++ b/libs/text/commands/ChangeTrackedDeleteCommand.h @@ -1,76 +1,76 @@ /* This file is part of the KDE project * Copyright (C) 2009 Ganesh Paramasivam * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA.*/ #ifndef CHANGETRACKEDDELETECOMMAND_H #define CHANGETRACKEDDELETECOMMAND_H #include "KoTextCommandBase.h" #include #include -#include +#include class QTextDocument; class QTextCursor; class KoShapeController; class KoDocumentRdfBase; class KoTextEditor; class ChangeTrackedDeleteCommand : public KoTextCommandBase { public: enum DeleteMode { PreviousChar, NextChar }; ChangeTrackedDeleteCommand(DeleteMode mode, QTextDocument *document, KoShapeController *shapeController, KUndo2Command* parent = 0); virtual ~ChangeTrackedDeleteCommand(); virtual void undo(); virtual void redo(); virtual int id() const; virtual bool mergeWith ( const KUndo2Command *command); private: - QWeakPointer m_document; + QPointer m_document; KoDocumentRdfBase *m_rdf; KoShapeController *m_shapeController; bool m_first; bool m_undone; bool m_canMerge; DeleteMode m_mode; QList m_removedElements; QList m_newListIds; int m_position, m_length; int m_addedChangeElement; virtual void deleteChar(); virtual void deletePreviousChar(); virtual void deleteSelection(KoTextEditor *editor); virtual void removeChangeElement(int changeId); virtual void updateListIds(QTextCursor &cursor); virtual void updateListChanges(); virtual void handleListItemDelete(KoTextEditor *editor); }; #endif // CHANGETRACKEDDELETECOMMAND_H diff --git a/libs/text/commands/DeleteCommand.cpp b/libs/text/commands/DeleteCommand.cpp index 30d11f98b34..10ea50469f2 100644 --- a/libs/text/commands/DeleteCommand.cpp +++ b/libs/text/commands/DeleteCommand.cpp @@ -1,613 +1,613 @@ /* This file is part of the KDE project * Copyright (C) 2009 Ganesh Paramasivam * Copyright (C) 2009 Pierre Stirnweiss * Copyright (C) 2010 Thomas Zander * Copyright (C) 2012 C. Boemann * Copyright (C) 2014-2015 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA.*/ #include "DeleteCommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include bool DeleteCommand::SectionDeleteInfo::operator<(const DeleteCommand::SectionDeleteInfo &other) const { // At first we remove sections that lays deeper in tree // On one level we delete sections by descending order of their childIdx // That is needed on undo, cuz we want it to be simply done by inserting // sections back in reverse order of their deletion. // Without childIdx compare it is possible that we will want to insert // section on position 2 while the number of children is less than 2. if (section->level() != other.section->level()) { return section->level() > other.section->level(); } return childIdx > other.childIdx; } DeleteCommand::DeleteCommand(DeleteMode mode, QTextDocument *document, KoShapeController *shapeController, KUndo2Command *parent) : KoTextCommandBase (parent) , m_document(document) , m_shapeController(shapeController) , m_first(true) , m_mode(mode) , m_mergePossible(true) { setText(kundo2_i18n("Delete")); } void DeleteCommand::undo() { KoTextCommandBase::undo(); UndoRedoFinalizer finalizer(this); // Look at KoTextCommandBase documentation // KoList updateListChanges(); // KoTextRange KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); foreach (KoTextRange *range, m_rangesToRemove) { rangeManager->insert(range); } // KoInlineObject foreach (KoInlineObject *object, m_invalidInlineObjects) { object->manager()->addInlineObject(object); } // KoSectionModel insertSectionsToModel(); } void DeleteCommand::redo() { if (!m_first) { KoTextCommandBase::redo(); UndoRedoFinalizer finalizer(this); // Look at KoTextCommandBase documentation // KoTextRange KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); foreach (KoTextRange *range, m_rangesToRemove) { rangeManager->remove(range); } // KoSectionModel deleteSectionsFromModel(); // TODO: there is nothing for InlineObjects and Lists. Is it OK? } else { m_first = false; if (m_document) { KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); if (textEditor) { textEditor->beginEditBlock(); doDelete(); textEditor->endEditBlock(); } } } } // Section handling algorithm: // At first, we go though the all section starts and ends // that are in selection, and delete all pairs, because // they will be deleted. // Then we have multiple cases: selection start split some block // or don't split any block. // In the first case all formatting info will be stored in the // split block(it has startBlockNum number). // In the second case it will be stored in the block pointed by the // selection end(it has endBlockNum number). // Also there is a trivial case, when whole selection is inside // one block, in this case hasEntirelyInsideBlock will be false // and we will do nothing. class DeleteVisitor : public KoTextVisitor { public: DeleteVisitor(KoTextEditor *editor, DeleteCommand *command) : KoTextVisitor(editor) , m_first(true) , m_command(command) , m_startBlockNum(-1) , m_endBlockNum(-1) , m_hasEntirelyInsideBlock(false) { } virtual void visitBlock(QTextBlock &block, const QTextCursor &caret) { for (QTextBlock::iterator it = block.begin(); it != block.end(); ++it) { QTextCursor fragmentSelection(caret); fragmentSelection.setPosition(qMax(caret.selectionStart(), it.fragment().position())); fragmentSelection.setPosition( qMin(caret.selectionEnd(), it.fragment().position() + it.fragment().length()), QTextCursor::KeepAnchor ); if (fragmentSelection.anchor() >= fragmentSelection.position()) { continue; } visitFragmentSelection(fragmentSelection); } // Section handling below bool doesBeginInside = false; bool doesEndInside = false; if (block.position() >= caret.selectionStart()) { // Begin of the block is inside selection. doesBeginInside = true; QList openList = KoSectionUtils::sectionStartings(block.blockFormat()); foreach (KoSection *sec, openList) { m_curSectionDelimiters.push_back(SectionHandle(sec->name(), sec)); } } if (block.position() + block.length() <= caret.selectionEnd()) { // End of the block is inside selection. doesEndInside = true; QList closeList = KoSectionUtils::sectionEndings(block.blockFormat()); foreach (KoSectionEnd *se, closeList) { if (!m_curSectionDelimiters.empty() && m_curSectionDelimiters.last().name == se->name()) { KoSection *section = se->correspondingSection(); int childIdx = KoTextDocument(m_command->m_document).sectionModel() ->findRowOfChild(section); m_command->m_sectionsToRemove.push_back( DeleteCommand::SectionDeleteInfo( section, childIdx ) ); m_curSectionDelimiters.pop_back(); // This section will die } else { m_curSectionDelimiters.push_back(SectionHandle(se->name(), se)); } } } if (!doesBeginInside && doesEndInside) { m_startBlockNum = block.blockNumber(); } else if (doesBeginInside && !doesEndInside) { m_endBlockNum = block.blockNumber(); } else if (doesBeginInside && doesEndInside) { m_hasEntirelyInsideBlock = true; } } virtual void visitFragmentSelection(QTextCursor &fragmentSelection) { if (m_first) { m_firstFormat = fragmentSelection.charFormat(); m_first = false; } if (m_command->m_mergePossible && fragmentSelection.charFormat() != m_firstFormat) { m_command->m_mergePossible = false; } // Handling InlineObjects below KoTextDocument textDocument(fragmentSelection.document()); KoInlineTextObjectManager *manager = textDocument.inlineTextObjectManager(); QString selected = fragmentSelection.selectedText(); fragmentSelection.setPosition(fragmentSelection.selectionStart() + 1); int position = fragmentSelection.position(); const QChar *data = selected.constData(); for (int i = 0; i < selected.length(); i++) { if (data->unicode() == QChar::ObjectReplacementCharacter) { fragmentSelection.setPosition(position + i); KoInlineObject *object = manager->inlineTextObject(fragmentSelection); m_command->m_invalidInlineObjects.insert(object); } data++; } } enum SectionHandleAction { SectionClose, ///< Denotes close of the section. SectionOpen ///< Denotes start or beginning of the section. }; /// Helper struct for handling sections. struct SectionHandle { QString name; ///< Name of the section. SectionHandleAction type; ///< Action of a SectionHandle. KoSection *dataSec; ///< Pointer to KoSection. KoSectionEnd *dataSecEnd; ///< Pointer to KoSectionEnd. SectionHandle(const QString &_name, KoSection *_data) : name(_name) , type(SectionOpen) , dataSec(_data) , dataSecEnd(0) { } SectionHandle(const QString &_name, KoSectionEnd *_data) : name(_name) , type(SectionClose) , dataSec(0) , dataSecEnd(_data) { } }; bool m_first; DeleteCommand *m_command; QTextCharFormat m_firstFormat; int m_startBlockNum; int m_endBlockNum; bool m_hasEntirelyInsideBlock; QList m_curSectionDelimiters; }; void DeleteCommand::finalizeSectionHandling(QTextCursor *cur, DeleteVisitor &v) { // Lets handle pointers from block formats first // It means that selection isn't within one block. if (v.m_hasEntirelyInsideBlock || v.m_startBlockNum != -1 || v.m_endBlockNum != -1) { QList openList; QList closeList; foreach (const DeleteVisitor::SectionHandle &handle, v.m_curSectionDelimiters) { if (handle.type == v.SectionOpen) { // Start of the section. openList << handle.dataSec; } else { // End of the section. closeList << handle.dataSecEnd; } } // We're expanding ends in affected blocks to the end of the start block, // delete all sections, that are entirely in affected blocks, // and move ends, we have, to the begin of the next after the end block. if (v.m_startBlockNum != -1) { QTextBlockFormat fmt = cur->document()->findBlockByNumber(v.m_startBlockNum).blockFormat(); QTextBlockFormat fmt2 = cur->document()->findBlockByNumber(v.m_endBlockNum + 1).blockFormat(); fmt.clearProperty(KoParagraphStyle::SectionEndings); // m_endBlockNum != -1 in this case. QList closeListEndBlock = KoSectionUtils::sectionEndings( cur->document()->findBlockByNumber(v.m_endBlockNum).blockFormat()); while (!openList.empty() && !closeListEndBlock.empty() && openList.last()->name() == closeListEndBlock.first()->name()) { int childIdx = KoTextDocument(m_document) .sectionModel()->findRowOfChild(openList.back()); m_sectionsToRemove.push_back( DeleteCommand::SectionDeleteInfo( openList.back(), childIdx ) ); openList.pop_back(); closeListEndBlock.pop_front(); } openList << KoSectionUtils::sectionStartings(fmt2); closeList << closeListEndBlock; // We leave open section of start block untouched. KoSectionUtils::setSectionStartings(fmt2, openList); KoSectionUtils::setSectionEndings(fmt, closeList); QTextCursor changer = *cur; changer.setPosition(cur->document()->findBlockByNumber(v.m_startBlockNum).position()); changer.setBlockFormat(fmt); if (v.m_endBlockNum + 1 < cur->document()->blockCount()) { changer.setPosition(cur->document()->findBlockByNumber(v.m_endBlockNum + 1).position()); changer.setBlockFormat(fmt2); } } else { // v.m_startBlockNum == -1 // v.m_endBlockNum != -1 in this case. // We're pushing all new section info to the end block. QTextBlockFormat fmt = cur->document()->findBlockByNumber(v.m_endBlockNum).blockFormat(); QList allStartings = KoSectionUtils::sectionStartings(fmt); fmt.clearProperty(KoParagraphStyle::SectionStartings); QList pairedEndings; QList unpairedEndings; foreach (KoSectionEnd *se, KoSectionUtils::sectionEndings(fmt)) { KoSection *sec = se->correspondingSection(); if (allStartings.contains(sec)) { pairedEndings << se; } else { unpairedEndings << se; } } if (cur->selectionStart()) { QTextCursor changer = *cur; changer.setPosition(cur->selectionStart() - 1); QTextBlockFormat prevFmt = changer.blockFormat(); QList prevEndings = KoSectionUtils::sectionEndings(prevFmt); prevEndings = prevEndings + closeList; KoSectionUtils::setSectionEndings(prevFmt, prevEndings); changer.setBlockFormat(prevFmt); } KoSectionUtils::setSectionStartings(fmt, openList); KoSectionUtils::setSectionEndings(fmt, pairedEndings + unpairedEndings); QTextCursor changer = *cur; changer.setPosition(cur->document()->findBlockByNumber(v.m_endBlockNum).position()); changer.setBlockFormat(fmt); } } // Now lets deal with KoSectionModel qSort(m_sectionsToRemove.begin(), m_sectionsToRemove.end()); deleteSectionsFromModel(); } void DeleteCommand::deleteSectionsFromModel() { KoSectionModel *model = KoTextDocument(m_document).sectionModel(); foreach (const SectionDeleteInfo &info, m_sectionsToRemove) { model->deleteFromModel(info.section); } } void DeleteCommand::insertSectionsToModel() { KoSectionModel *model = KoTextDocument(m_document).sectionModel(); QList::ConstIterator it = m_sectionsToRemove.constEnd(); while (it != m_sectionsToRemove.constBegin()) { --it; model->insertToModel(it->section, it->childIdx); } } void DeleteCommand::doDelete() { KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); Q_ASSERT(textEditor); QTextCursor *caret = textEditor->cursor(); QTextCharFormat charFormat = caret->charFormat(); bool caretAtBeginOfBlock = (caret->position() == caret->block().position()); if (!textEditor->hasSelection()) { if (m_mode == PreviousChar) { caret->movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor); } else { caret->movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); } } DeleteVisitor visitor(textEditor, this); textEditor->recursivelyVisitSelection(m_document.data()->rootFrame()->begin(), visitor); // Sections Model finalizeSectionHandling(caret, visitor); // Finalize section handling routine. // InlineObjects foreach (KoInlineObject *object, m_invalidInlineObjects) { deleteInlineObject(object); } // Ranges KoTextRangeManager *rangeManager = KoTextDocument(m_document).textRangeManager(); m_rangesToRemove = rangeManager->textRangesChangingWithin( textEditor->document(), textEditor->selectionStart(), textEditor->selectionEnd(), textEditor->selectionStart(), textEditor->selectionEnd() ); foreach (KoTextRange *range, m_rangesToRemove) { KoAnchorTextRange *anchorRange = dynamic_cast(range); KoAnnotation *annotation = dynamic_cast(range); if (anchorRange) { // we should only delete the anchor if the selection is covering it... not if the selection is // just adjacent to the anchor. This is more in line with what other wordprocessors do if (anchorRange->position() != textEditor->selectionStart() && anchorRange->position() != textEditor->selectionEnd()) { KoShape *shape = anchorRange->anchor()->shape(); if (m_shapeController) { KUndo2Command *shapeDeleteCommand = m_shapeController->removeShape(shape, this); shapeDeleteCommand->redo(); } // via m_shapeController->removeShape a DeleteAnchorsCommand should be created that // also calls rangeManager->remove(range), so we shouldn't do that here aswell } } else if (annotation) { KoShape *shape = annotation->annotationShape(); if (m_shapeController) { KUndo2Command *shapeDeleteCommand = m_shapeController->removeShape(shape, this); shapeDeleteCommand->redo(); } // via m_shapeController->removeShape a DeleteAnnotationsCommand should be created that // also calls rangeManager->remove(range), so we shouldn't do that here aswell } else { rangeManager->remove(range); } } // Check: is merge possible? if (textEditor->hasComplexSelection()) { m_mergePossible = false; } //FIXME: lets forbid merging of "section affecting" deletions by now if (!m_sectionsToRemove.empty()) { m_mergePossible = false; } if (m_mergePossible) { // Store various info needed for checkMerge m_format = textEditor->charFormat(); m_position = textEditor->selectionStart(); m_length = textEditor->selectionEnd() - textEditor->selectionStart(); } // Actual deletion of text caret->deleteChar(); if (m_mode != PreviousChar || !caretAtBeginOfBlock) { caret->setCharFormat(charFormat); } } void DeleteCommand::deleteInlineObject(KoInlineObject *object) { if (object) { KoAnchorInlineObject *anchorObject = dynamic_cast(object); if (anchorObject) { KoShape *shape = anchorObject->anchor()->shape(); KUndo2Command *shapeDeleteCommand = m_shapeController->removeShape(shape, this); shapeDeleteCommand->redo(); } else { object->manager()->removeInlineObject(object); } } } int DeleteCommand::id() const { // Should be an enum declared somewhere. KoTextCommandBase.h ??? return 56789; } bool DeleteCommand::mergeWith(const KUndo2Command *command) { class UndoTextCommand : public KUndo2Command { public: UndoTextCommand(QTextDocument *document, KUndo2Command *parent = 0) : KUndo2Command(kundo2_i18n("Text"), parent), - m_document(document) + m_document(document) {} void undo() { QTextDocument *doc = m_document.data(); if (doc) doc->undo(KoTextDocument(doc).textEditor()->cursor()); } void redo() { QTextDocument *doc = m_document.data(); if (doc) doc->redo(KoTextDocument(doc).textEditor()->cursor()); } - QWeakPointer m_document; + QPointer m_document; }; KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); if (textEditor == 0) return false; if (command->id() != id()) return false; if (!checkMerge(command)) return false; DeleteCommand *other = const_cast(static_cast(command)); m_invalidInlineObjects += other->m_invalidInlineObjects; other->m_invalidInlineObjects.clear(); for (int i=0; i < command->childCount(); i++) new UndoTextCommand(const_cast(textEditor->document()), this); return true; } bool DeleteCommand::checkMerge(const KUndo2Command *command) { DeleteCommand *other = const_cast(static_cast(command)); if (!(m_mergePossible && other->m_mergePossible)) return false; if (m_position == other->m_position && m_format == other->m_format) { m_length += other->m_length; return true; } if ((other->m_position + other->m_length == m_position) && (m_format == other->m_format)) { m_position = other->m_position; m_length += other->m_length; return true; } return false; } void DeleteCommand::updateListChanges() { KoTextEditor *textEditor = KoTextDocument(m_document).textEditor(); if (textEditor == 0) return; QTextDocument *document = const_cast(textEditor->document()); QTextCursor tempCursor(document); QTextBlock startBlock = document->findBlock(m_position); QTextBlock endBlock = document->findBlock(m_position + m_length); if (endBlock != document->end()) endBlock = endBlock.next(); QTextList *currentList; for (QTextBlock currentBlock = startBlock; currentBlock != endBlock; currentBlock = currentBlock.next()) { tempCursor.setPosition(currentBlock.position()); currentList = tempCursor.currentList(); if (currentList) { KoListStyle::ListIdType listId; if (sizeof(KoListStyle::ListIdType) == sizeof(uint)) listId = currentList->format().property(KoListStyle::ListId).toUInt(); else listId = currentList->format().property(KoListStyle::ListId).toULongLong(); if (!KoTextDocument(document).list(currentBlock)) { KoList *list = KoTextDocument(document).list(listId); if (list) { list->updateStoredList(currentBlock); } } } } } DeleteCommand::~DeleteCommand() { } diff --git a/libs/text/commands/DeleteCommand.h b/libs/text/commands/DeleteCommand.h index 5fa6d0620c0..17c37231db7 100644 --- a/libs/text/commands/DeleteCommand.h +++ b/libs/text/commands/DeleteCommand.h @@ -1,98 +1,98 @@ /* This file is part of the KDE project * Copyright (C) 2009 Ganesh Paramasivam * Copyright (C) 2012 C. Boemann * Copyright (C) 2014-2015 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA.*/ #ifndef DELETECOMMAND_H #define DELETECOMMAND_H #include "KoTextCommandBase.h" #include #include #include -#include +#include class QTextDocument; class KoShapeController; class KoInlineObject; class KoTextRange; class KoSection; class DeleteVisitor; class DeleteCommand : public KoTextCommandBase { public: enum DeleteMode { PreviousChar, NextChar }; DeleteCommand(DeleteMode mode, QTextDocument *document, KoShapeController *shapeController, KUndo2Command* parent = 0); virtual ~DeleteCommand(); virtual void undo(); virtual void redo(); virtual int id() const; virtual bool mergeWith(const KUndo2Command *command); private: friend class DeleteVisitor; struct SectionDeleteInfo { SectionDeleteInfo(KoSection *_section, int _childIdx) : section(_section) , childIdx(_childIdx) { } bool operator<(const SectionDeleteInfo &other) const; KoSection *section; ///< Section to remove int childIdx; ///< Position of section in parent's children() list }; - QWeakPointer m_document; + QPointer m_document; KoShapeController *m_shapeController; QSet m_invalidInlineObjects; QList m_cursorsToWholeDeleteBlocks; QHash m_rangesToRemove; QList m_sectionsToRemove; bool m_first; DeleteMode m_mode; int m_position; int m_length; QTextCharFormat m_format; bool m_mergePossible; void doDelete(); void deleteInlineObject(KoInlineObject *object); bool checkMerge(const KUndo2Command *command); void updateListChanges(); void finalizeSectionHandling(QTextCursor *caret, DeleteVisitor &visitor); void deleteSectionsFromModel(); void insertSectionsToModel(); }; #endif // DELETECOMMAND_H diff --git a/libs/text/commands/InsertDeleteChangesCommand.h b/libs/text/commands/InsertDeleteChangesCommand.h index dec6aac3b46..620b096b41c 100644 --- a/libs/text/commands/InsertDeleteChangesCommand.h +++ b/libs/text/commands/InsertDeleteChangesCommand.h @@ -1,40 +1,40 @@ /* * Copyright (c) 2011 C. Boemann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef INSERTDELETECHANGESCOMMAND_H #define INSERTDELETECHANGESCOMMAND_H #include -#include +#include class QTextDocument; class InsertDeleteChangesCommand : public KUndo2Command { public: explicit InsertDeleteChangesCommand(QTextDocument *document, KUndo2Command *parent = 0); void redo(); private: - QWeakPointer m_document; + QPointer m_document; void insertDeleteChanges(); }; #endif // INSERTDELETECHANGESCOMMAND_H diff --git a/libs/text/commands/InsertNoteCommand.h b/libs/text/commands/InsertNoteCommand.h index a6262de777a..e2902ffe968 100644 --- a/libs/text/commands/InsertNoteCommand.h +++ b/libs/text/commands/InsertNoteCommand.h @@ -1,49 +1,49 @@ /* This file is part of the KDE project * Copyright (C) 2009 Ganesh Paramasivam * Copyright (C) 2012 C. Boemann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA.*/ #ifndef INSERTNOTECOMMAND_H #define INSERTNOTECOMMAND_H #include "KoInlineNote.h" #include -#include +#include class QTextDocument; class InsertNoteCommand : public KUndo2Command { public: InsertNoteCommand(KoInlineNote::Type type, QTextDocument *document); virtual ~InsertNoteCommand(); virtual void undo(); virtual void redo(); KoInlineNote *m_inlineNote; private: - QWeakPointer m_document; + QPointer m_document; bool m_first; int m_framePosition; // a cursor position inside the frame at the time of creation }; #endif // INSERTNODECOMMAND_H diff --git a/libs/text/commands/RemoveDeleteChangesCommand.h b/libs/text/commands/RemoveDeleteChangesCommand.h index 5c4752a7d82..c6607680725 100644 --- a/libs/text/commands/RemoveDeleteChangesCommand.h +++ b/libs/text/commands/RemoveDeleteChangesCommand.h @@ -1,39 +1,39 @@ /* * Copyright (c) 2011 C. Boemann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef REMOVEDELETECHANGESCOMMAND_H #define REMOVEDELETECHANGESCOMMAND_H #include -#include +#include class QTextDocument; class RemoveDeleteChangesCommand : public KUndo2Command { public: explicit RemoveDeleteChangesCommand(QTextDocument *document, KUndo2Command *parent = 0); void redo(); private: - QWeakPointer m_document; + QPointer m_document; void removeDeleteChanges(); }; #endif // REMOVEDELETECHANGESCOMMAND_H diff --git a/libs/text/commands/TextPasteCommand.h b/libs/text/commands/TextPasteCommand.h index 8bd673a2ba0..1cfd4a2d397 100644 --- a/libs/text/commands/TextPasteCommand.h +++ b/libs/text/commands/TextPasteCommand.h @@ -1,57 +1,57 @@ /* This file is part of the KDE project * Copyright (C) 2009 Pierre Stirnweiss * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA.*/ #ifndef TEXTPASTECOMMAND_H #define TEXTPASTECOMMAND_H #include -#include +#include #include class QTextDocument; class KoDocumentRdfBase; class KoShapeController; class QMimeData; class KoCanvasBase; class TextPasteCommand : public KUndo2Command { public: TextPasteCommand(const QMimeData *mimeData, QTextDocument *document, KoShapeController *shapeController, KoCanvasBase *canvas, KUndo2Command *parent = 0, bool pasteAsText = false); virtual void undo(); virtual void redo(); private: const QMimeData *m_mimeData; - QWeakPointer m_document; + QPointer m_document; KoDocumentRdfBase *m_rdf; KoShapeController *m_shapeController; KoCanvasBase *m_canvas; bool m_pasteAsText; bool m_first; }; #endif // TEXTPASTECOMMAND_H diff --git a/plugins/textshape/TextTool.cpp b/plugins/textshape/TextTool.cpp index 259ff10c70e..6f52978a776 100644 --- a/plugins/textshape/TextTool.cpp +++ b/plugins/textshape/TextTool.cpp @@ -1,3132 +1,3132 @@ /* This file is part of the KDE project * Copyright (C) 2006-2010 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2008 Girish Ramakrishnan * Copyright (C) 2008, 2012 Pierre Stirnweiss * Copyright (C) 2009 KO GmbH * Copyright (C) 2011 Mojtaba Shahi Senobari * Copyright (C) 2014 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include "TextTool.h" #include "TextEditingPluginContainer.h" #include "dialogs/SimpleCharacterWidget.h" #include "dialogs/SimpleParagraphWidget.h" #include "dialogs/SimpleTableWidget.h" #include "dialogs/SimpleInsertWidget.h" #include "dialogs/ParagraphSettingsDialog.h" #include "dialogs/StyleManagerDialog.h" #include "dialogs/InsertCharacter.h" #include "dialogs/FontDia.h" #include "dialogs/TableDialog.h" #include "dialogs/SectionFormatDialog.h" #include "dialogs/SectionsSplitDialog.h" #include "commands/AutoResizeCommand.h" #include "commands/ChangeListLevelCommand.h" #include "FontSizeAction.h" #include "FontFamilyAction.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AnnotationTextShape.h" #define AnnotationShape_SHAPEID "AnnotationTextShapeID" #include "KoShapeBasedDocumentBase.h" #include #include #include #include class TextToolSelection : public KoToolSelection { public: - TextToolSelection(QWeakPointer editor) + TextToolSelection(QPointer editor) : KoToolSelection(0) , m_editor(editor) { } bool hasSelection() { if (!m_editor.isNull()) { return m_editor.data()->hasSelection(); } return false; } - QWeakPointer m_editor; + QPointer m_editor; }; static bool hit(const QKeySequence &input, KStandardShortcut::StandardShortcut shortcut) { foreach (const QKeySequence & ks, KStandardShortcut::shortcut(shortcut)) { if (input == ks) return true; } return false; } TextTool::TextTool(KoCanvasBase *canvas) : KoToolBase(canvas) , m_textShape(0) , m_textShapeData(0) , m_changeTracker(0) , m_allowActions(true) , m_allowAddUndoCommand(true) , m_allowResourceManagerUpdates(true) , m_prevCursorPosition(-1) , m_caretTimer(this) , m_caretTimerState(true) , m_currentCommand(0) , m_currentCommandHasChildren(false) , m_specialCharacterDocker(0) , m_textTyping(false) , m_textDeleting(false) , m_editTipTimer(this) , m_delayedEnsureVisible(false) , m_toolSelection(0) , m_tableDraggedOnce(false) , m_tablePenMode(false) , m_lastImMicroFocus(QRectF(0,0,0,0)) , m_drag(0) { setTextMode(true); createActions(); m_unit = canvas->resourceManager()->unitResource(KoCanvasResourceManager::Unit); foreach (KoTextEditingPlugin* plugin, textEditingPluginContainer()->values()) { connect(plugin, SIGNAL(startMacro(QString)), this, SLOT(startMacro(QString))); connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro())); const QHash actions = plugin->actions(); QHash::ConstIterator i = actions.begin(); while (i != actions.end()) { addAction(i.key(), i.value()); ++i; } } // setup the context list. QSignalMapper *signalMapper = new QSignalMapper(this); connect(signalMapper, SIGNAL(mapped(QString)), this, SLOT(startTextEditingPlugin(QString))); QList list; list.append(this->action("format_font")); foreach (const QString &key, KoTextEditingRegistry::instance()->keys()) { KoTextEditingFactory *factory = KoTextEditingRegistry::instance()->value(key); if (factory->showInMenu()) { QAction *a = new QAction(factory->title(), this); connect(a, SIGNAL(triggered()), signalMapper, SLOT(map())); signalMapper->setMapping(a, factory->id()); list.append(a); addAction(QString("apply_%1").arg(factory->id()), a); } } setPopupActionList(list); connect(canvas->shapeManager()->selection(), SIGNAL(selectionChanged()), this, SLOT(shapeAddedToCanvas())); m_caretTimer.setInterval(500); connect(&m_caretTimer, SIGNAL(timeout()), this, SLOT(blinkCaret())); m_editTipTimer.setInterval(500); m_editTipTimer.setSingleShot(true); connect(&m_editTipTimer, SIGNAL(timeout()), this, SLOT(showEditTip())); } void TextTool::createActions() { bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); m_actionConfigureSection = new QAction(koIconNeededWithSubs("", "configure-text-section", "configure"), i18n("Configure current section"), this); addAction("configure_section", m_actionConfigureSection); connect(m_actionConfigureSection, SIGNAL(triggered(bool)), this, SLOT(configureSection())); m_actionInsertSection = new QAction(koIconNeededWithSubs("", "insert-text-section", "insert-text"), i18n("Insert new section"), this); addAction("insert_section", m_actionInsertSection); connect(m_actionInsertSection, SIGNAL(triggered(bool)), this, SLOT(insertNewSection())); m_actionSplitSections = new QAction(koIconNeededWithSubs("", "text-section-split", "split"), i18n("Insert paragraph between sections"), this); addAction("split_sections", m_actionSplitSections); connect(m_actionSplitSections, SIGNAL(triggered(bool)), this, SLOT(splitSections())); m_actionPasteAsText = new QAction(koIcon("edit-paste"), i18n("Paste As Text"), this); addAction("edit_paste_text", m_actionPasteAsText); m_actionPasteAsText->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_V); connect(m_actionPasteAsText, SIGNAL(triggered(bool)), this, SLOT(pasteAsText())); m_actionFormatBold = new QAction(koIcon("format-text-bold"), i18n("Bold"), this); addAction("format_bold", m_actionFormatBold); m_actionFormatBold->setShortcut(Qt::CTRL + Qt::Key_B); m_actionFormatBold->setCheckable(true); connect(m_actionFormatBold, SIGNAL(triggered(bool)), this, SLOT(bold(bool))); m_actionFormatItalic = new QAction(koIcon("format-text-italic"), i18n("Italic"), this); addAction("format_italic", m_actionFormatItalic); m_actionFormatItalic->setShortcut(Qt::CTRL + Qt::Key_I); m_actionFormatItalic->setCheckable(true); connect(m_actionFormatItalic, SIGNAL(triggered(bool)), this, SLOT(italic(bool))); m_actionFormatUnderline = new QAction(koIcon("format-text-underline"), i18nc("Text formatting", "Underline"), this); addAction("format_underline", m_actionFormatUnderline); m_actionFormatUnderline->setShortcut(Qt::CTRL + Qt::Key_U); m_actionFormatUnderline->setCheckable(true); connect(m_actionFormatUnderline, SIGNAL(triggered(bool)), this, SLOT(underline(bool))); m_actionFormatStrikeOut = new QAction(koIcon("format-text-strikethrough"), i18n("Strikethrough"), this); addAction("format_strike", m_actionFormatStrikeOut); m_actionFormatStrikeOut->setCheckable(true); connect(m_actionFormatStrikeOut, SIGNAL(triggered(bool)), this, SLOT(strikeOut(bool))); QActionGroup *alignmentGroup = new QActionGroup(this); m_actionAlignLeft = new QAction(koIcon("format-justify-left"), i18n("Align Left"), this); addAction("format_alignleft", m_actionAlignLeft); m_actionAlignLeft->setShortcut(Qt::CTRL + Qt::Key_L); m_actionAlignLeft->setCheckable(true); alignmentGroup->addAction(m_actionAlignLeft); connect(m_actionAlignLeft, SIGNAL(triggered(bool)), this, SLOT(alignLeft())); m_actionAlignRight = new QAction(koIcon("format-justify-right"), i18n("Align Right"), this); addAction("format_alignright", m_actionAlignRight); m_actionAlignRight->setShortcut(Qt::CTRL + Qt::Key_R); m_actionAlignRight->setCheckable(true); alignmentGroup->addAction(m_actionAlignRight); connect(m_actionAlignRight, SIGNAL(triggered(bool)), this, SLOT(alignRight())); m_actionAlignCenter = new QAction(koIcon("format-justify-center"), i18n("Align Center"), this); addAction("format_aligncenter", m_actionAlignCenter); m_actionAlignCenter->setShortcut(Qt::CTRL + Qt::Key_E); m_actionAlignCenter->setCheckable(true); alignmentGroup->addAction(m_actionAlignCenter); connect(m_actionAlignCenter, SIGNAL(triggered(bool)), this, SLOT(alignCenter())); m_actionAlignBlock = new QAction(koIcon("format-justify-fill"), i18n("Align Block"), this); addAction("format_alignblock", m_actionAlignBlock); m_actionAlignBlock->setShortcut(Qt::CTRL + Qt::Key_J); m_actionAlignBlock->setCheckable(true); alignmentGroup->addAction(m_actionAlignBlock); connect(m_actionAlignBlock, SIGNAL(triggered(bool)), this, SLOT(alignBlock())); m_actionChangeDirection = new QAction(koIcon("format-text-direction-rtl"), i18n("Change text direction"), this); addAction("change_text_direction", m_actionChangeDirection); m_actionChangeDirection->setToolTip(i18n("Change writing direction")); m_actionChangeDirection->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_D); m_actionChangeDirection->setCheckable(true); connect(m_actionChangeDirection, SIGNAL(triggered()), this, SLOT(textDirectionChanged())); m_actionFormatSuper = new QAction(koIcon("format-text-superscript"), i18n("Superscript"), this); m_actionFormatSuper->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_P); addAction("format_super", m_actionFormatSuper); m_actionFormatSuper->setCheckable(true); connect(m_actionFormatSuper, SIGNAL(triggered(bool)), this, SLOT(superScript(bool))); m_actionFormatSub = new QAction(koIcon("format-text-subscript"), i18n("Subscript"), this); m_actionFormatSub->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_B); addAction("format_sub", m_actionFormatSub); m_actionFormatSub->setCheckable(true); connect(m_actionFormatSub, SIGNAL(triggered(bool)), this, SLOT(subScript(bool))); const char* const increaseIndentActionIconName = QApplication::isRightToLeft() ? koIconNameCStr("format-indent-less") : koIconNameCStr("format-indent-more"); m_actionFormatIncreaseIndent = new QAction( QIcon::fromTheme(QLatin1String(increaseIndentActionIconName)), i18n("Increase Indent"), this); addAction("format_increaseindent", m_actionFormatIncreaseIndent); connect(m_actionFormatIncreaseIndent, SIGNAL(triggered()), this, SLOT(increaseIndent())); const char* const decreaseIndentActionIconName = QApplication::isRightToLeft() ? koIconNameCStr("format-indent-more") : koIconNameCStr("format-indent-less"); m_actionFormatDecreaseIndent = new QAction(QIcon::fromTheme(QLatin1String(decreaseIndentActionIconName)), i18n("Decrease Indent"), this); addAction("format_decreaseindent", m_actionFormatDecreaseIndent); connect(m_actionFormatDecreaseIndent, SIGNAL(triggered()), this, SLOT(decreaseIndent())); QAction *action = new QAction(koIcon("format-list-unordered"), i18n("Toggle List or List Level Formatting"), this); action->setToolTip(i18n("Toggle list on/off, or change format of current level")); addAction("format_list", action); action = new QAction(i18n("Increase Font Size"), this); action->setShortcut(Qt::CTRL + Qt::Key_Greater); addAction("fontsizeup", action); connect(action, SIGNAL(triggered()), this, SLOT(increaseFontSize())); action = new QAction(i18n("Decrease Font Size"), this); action->setShortcut(Qt::CTRL + Qt::Key_Less); addAction("fontsizedown", action); connect(action, SIGNAL(triggered()), this, SLOT(decreaseFontSize())); m_actionFormatFontFamily = new KoFontFamilyAction(this); m_actionFormatFontFamily->setText(i18n("Font Family")); addAction("format_fontfamily", m_actionFormatFontFamily); connect(m_actionFormatFontFamily, SIGNAL(triggered(QString)), this, SLOT(setFontFamily(QString))); m_variableMenu = new KActionMenu(i18n("Variable"), this); addAction("insert_variable", m_variableMenu); // ------------------- Actions with a key binding and no GUI item action = new QAction(i18n("Insert Non-Breaking Space"), this); addAction("nonbreaking_space", action); action->setShortcut(Qt::CTRL + Qt::Key_Space); connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingSpace())); action = new QAction(i18n("Insert Non-Breaking Hyphen"), this); addAction("nonbreaking_hyphen", action); action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_Minus); connect(action, SIGNAL(triggered()), this, SLOT(nonbreakingHyphen())); action = new QAction(i18n("Insert Index"), this); action->setShortcut(Qt::CTRL + Qt::Key_T); addAction("insert_index", action); connect(action, SIGNAL(triggered()), this, SLOT(insertIndexMarker())); action = new QAction(i18n("Insert Soft Hyphen"), this); addAction("soft_hyphen", action); //action->setShortcut(Qt::CTRL + Qt::Key_Minus); // TODO this one is also used for the kde-global zoom-out :( connect(action, SIGNAL(triggered()), this, SLOT(softHyphen())); if (useAdvancedText) { action = new QAction(i18n("Line Break"), this); addAction("line_break", action); action->setShortcut(Qt::SHIFT + Qt::Key_Return); connect(action, SIGNAL(triggered()), this, SLOT(lineBreak())); action = new QAction(koIcon("insert-page-break"), i18n("Page Break"), this); addAction("insert_framebreak", action); action->setShortcut(Qt::CTRL + Qt::Key_Return); connect(action, SIGNAL(triggered()), this, SLOT(insertFrameBreak())); action->setToolTip(i18n("Insert a page break")); action->setWhatsThis(i18n("All text after this point will be moved into the next page.")); } action = new QAction(i18n("Font..."), this); addAction("format_font", action); action->setShortcut(Qt::ALT + Qt::CTRL + Qt::Key_F); action->setToolTip(i18n("Change character size, font, boldface, italics etc.")); action->setWhatsThis(i18n("Change the attributes of the currently selected characters.")); connect(action, SIGNAL(triggered()), this, SLOT(selectFont())); m_actionFormatFontSize = new FontSizeAction(i18n("Font Size"), this); addAction("format_fontsize", m_actionFormatFontSize); connect(m_actionFormatFontSize, SIGNAL(fontSizeChanged(qreal)), this, SLOT(setFontSize(qreal))); m_actionFormatTextColor = new KoColorPopupAction(this); m_actionFormatTextColor->setIcon(koIcon("format-text-color")); m_actionFormatTextColor->setToolTip(i18n("Text Color...")); m_actionFormatTextColor->setText(i18n("Text Color")); addAction("format_textcolor", m_actionFormatTextColor); connect(m_actionFormatTextColor, SIGNAL(colorChanged(KoColor)), this, SLOT(setTextColor(KoColor))); m_actionFormatBackgroundColor = new KoColorPopupAction(this); m_actionFormatBackgroundColor->setIcon(koIcon("format-fill-color")); m_actionFormatBackgroundColor->setToolTip(i18n("Background Color...")); m_actionFormatBackgroundColor->setText(i18n("Background")); addAction("format_backgroundcolor", m_actionFormatBackgroundColor); connect(m_actionFormatBackgroundColor, SIGNAL(colorChanged(KoColor)), this, SLOT(setBackgroundColor(KoColor))); m_autoResizeAction = new QAction(koIcon("zoom-fit-best"), i18n("Auto Resize To Content"), this); addAction("auto_resize", m_autoResizeAction); m_autoResizeAction->setCheckable(true); connect(m_autoResizeAction, SIGNAL(triggered(bool)), this, SLOT(setAutoResize(bool))); m_growWidthAction = new QAction(koIcon("zoom-fit-best"), i18n("Grow To Fit Width"), this); addAction("grow_to_fit_width", m_growWidthAction); m_growWidthAction->setCheckable(true); connect(m_growWidthAction, SIGNAL(triggered(bool)), this, SLOT(setGrowWidthToFit(bool))); m_growHeightAction = new QAction(koIcon("zoom-fit-best"), i18n("Grow To Fit Height"), this); addAction("grow_to_fit_height", m_growHeightAction); m_growHeightAction->setCheckable(true); connect(m_growHeightAction, SIGNAL(triggered(bool)), this, SLOT(setGrowHeightToFit(bool))); m_shrinkToFitAction = new QAction(koIcon("zoom-fit-best"), i18n("Shrink To Fit"), this); addAction("shrink_to_fit", m_shrinkToFitAction); m_shrinkToFitAction->setCheckable(true); connect(m_shrinkToFitAction, SIGNAL(triggered(bool)), this, SLOT(setShrinkToFit(bool))); if (useAdvancedText) { action = new QAction(koIcon("insert-table"), i18n("Insert Custom..."), this); addAction("insert_table", action); action->setToolTip(i18n("Insert a table into the document.")); connect(action, SIGNAL(triggered()), this, SLOT(insertTable())); action = new QAction(koIcon("edit-table-insert-row-above"), i18n("Row Above"), this); action->setToolTip(i18n("Insert Row Above")); addAction("insert_tablerow_above", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowAbove())); action = new QAction(koIcon("edit-table-insert-row-below"), i18n("Row Below"), this); action->setToolTip(i18n("Insert Row Below")); addAction("insert_tablerow_below", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableRowBelow())); action = new QAction(koIcon("edit-table-insert-column-left"), i18n("Column Left"), this); action->setToolTip(i18n("Insert Column Left")); addAction("insert_tablecolumn_left", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnLeft())); action = new QAction(koIcon("edit-table-insert-column-right"), i18n("Column Right"), this); action->setToolTip(i18n("Insert Column Right")); addAction("insert_tablecolumn_right", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(insertTableColumnRight())); action = new QAction(koIcon("edit-table-delete-column"), i18n("Column"), this); action->setToolTip(i18n("Delete Column")); addAction("delete_tablecolumn", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableColumn())); action = new QAction(koIcon("edit-table-delete-row"), i18n("Row"), this); action->setToolTip(i18n("Delete Row")); addAction("delete_tablerow", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(deleteTableRow())); action = new QAction(koIcon("edit-table-cell-merge"), i18n("Merge Cells"), this); addAction("merge_tablecells", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(mergeTableCells())); action = new QAction(koIcon("edit-table-cell-split"), i18n("Split Cells"), this); addAction("split_tablecells", action); connect(action, SIGNAL(triggered(bool)), this, SLOT(splitTableCells())); action = new QAction(koIcon("borderpainter"), "", this); action->setToolTip(i18n("Select a border style and paint that style onto a table")); addAction("activate_borderpainter", action); } action = new QAction(i18n("Paragraph..."), this); addAction("format_paragraph", action); action->setShortcut(Qt::ALT + Qt::CTRL + Qt::Key_P); action->setToolTip(i18n("Change paragraph margins, text flow, borders, bullets, numbering etc.")); action->setWhatsThis(i18n("

Change paragraph margins, text flow, borders, bullets, numbering etc.

Select text in multiple paragraphs to change the formatting of all selected paragraphs.

If no text is selected, the paragraph where the cursor is located will be changed.

")); connect(action, SIGNAL(triggered()), this, SLOT(formatParagraph())); action = new QAction(i18n("Style Manager..."), this); action->setShortcut(Qt::ALT + Qt::CTRL + Qt::Key_S); action->setToolTip(i18n("Change attributes of styles")); action->setWhatsThis(i18n("

Change font and paragraph attributes of styles.

Multiple styles can be changed using the dialog box.

")); addAction("format_stylist", action); connect(action, SIGNAL(triggered()), this, SLOT(showStyleManager())); action = KStandardAction::selectAll(this, SLOT(selectAll()), this); addAction("edit_select_all", action); action = new QAction(i18n("Special Character..."), this); action->setIcon(koIcon("character-set")); action->setShortcut(Qt::ALT + Qt::SHIFT + Qt::Key_C); addAction("insert_specialchar", action); action->setToolTip(i18n("Insert one or more symbols or characters not found on the keyboard")); action->setWhatsThis(i18n("Insert one or more symbols or characters not found on the keyboard.")); connect(action, SIGNAL(triggered()), this, SLOT(insertSpecialCharacter())); action = new QAction(i18n("Repaint"), this); action->setIcon(koIcon("view-refresh")); addAction("repaint", action); connect(action, SIGNAL(triggered()), this, SLOT(relayoutContent())); action = new QAction(i18n("Insert Comment"), this); addAction("insert_annotation", action); action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C); connect(action, SIGNAL(triggered()), this, SLOT(insertAnnotation())); #ifndef NDEBUG action = new QAction("Paragraph Debug", this); // do NOT add i18n! action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::ALT + Qt::Key_P); addAction("detailed_debug_paragraphs", action); connect(action, SIGNAL(triggered()), this, SLOT(debugTextDocument())); action = new QAction("Styles Debug", this); // do NOT add i18n! action->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::ALT + Qt::Key_S); addAction("detailed_debug_styles", action); connect(action, SIGNAL(triggered()), this, SLOT(debugTextStyles())); #endif } #ifndef NDEBUG #include "tests/MockShapes.h" #include #include #include TextTool::TextTool(MockCanvas *canvas) // constructor for our unit tests; : KoToolBase(canvas), m_textShape(0), m_textShapeData(0), m_changeTracker(0), m_allowActions(true), m_allowAddUndoCommand(true), m_allowResourceManagerUpdates(true), m_prevCursorPosition(-1), m_caretTimer(this), m_caretTimerState(true), m_currentCommand(0), m_currentCommandHasChildren(false), m_specialCharacterDocker(0), m_textEditingPlugins(0) , m_editTipTimer(this) , m_delayedEnsureVisible(false) , m_tableDraggedOnce(false) , m_tablePenMode(false) { // we could init some vars here, but we probably don't have to QLocale::setDefault(QLocale("en")); QTextDocument *document = new QTextDocument(); // this document is leaked KoInlineTextObjectManager *inlineManager = new KoInlineTextObjectManager(); KoTextDocument(document).setInlineTextObjectManager(inlineManager); KoTextRangeManager *locationManager = new KoTextRangeManager(); KoTextDocument(document).setTextRangeManager(locationManager); m_textEditor = new KoTextEditor(document); KoTextDocument(document).setTextEditor(m_textEditor.data()); m_toolSelection = new TextToolSelection(m_textEditor); m_changeTracker = new KoChangeTracker(); KoTextDocument(document).setChangeTracker(m_changeTracker); KoTextDocument(document).setUndoStack(new KUndo2Stack()); } #endif TextTool::~TextTool() { delete m_toolSelection; } void TextTool::showEditTip() { if (!m_textShapeData || m_editTipPointedAt.position == -1) return; QTextCursor c(m_textShapeData->document()); c.setPosition(m_editTipPointedAt.position); QString text = "

"; int toolTipWidth = 0; if (m_changeTracker && m_changeTracker->containsInlineChanges(c.charFormat()) && m_changeTracker->displayChanges()) { KoChangeTrackerElement *element = m_changeTracker->elementById(c.charFormat().property(KoCharacterStyle::ChangeTrackerId).toInt()); if (element->isEnabled()) { QString changeType; if (element->getChangeType() == KoGenChange::InsertChange) changeType = i18n("Insertion"); else if (element->getChangeType() == KoGenChange::DeleteChange) changeType = i18n("Deletion"); else changeType = i18n("Formatting"); text += "" + changeType + "
"; QString date = element->getDate(); //Remove the T which separates the Data and Time. date[10] = QLatin1Char(' '); date = element->getCreator() + QLatin1Char(' ') + date; text += date + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(date).width(); } } if (m_editTipPointedAt.bookmark || !m_editTipPointedAt.externalHRef.isEmpty()) { QString help = i18n("Ctrl+click to go to link "); help += m_editTipPointedAt.externalHRef; text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } if (m_editTipPointedAt.note) { QString help = i18n("Ctrl+click to go to the note "); text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } if (m_editTipPointedAt.noteReference>0) { QString help = i18n("Ctrl+click to go to the note reference"); text += help + "

"; toolTipWidth = QFontMetrics(QToolTip::font()).boundingRect(help).width(); } QToolTip::hideText(); if (toolTipWidth) { QRect keepRect(m_editTipPos - QPoint(3,3), QSize(6,6)); QToolTip::showText(m_editTipPos - QPoint(toolTipWidth/2, 0), text, canvas()->canvasWidget(), keepRect); } } void TextTool::blinkCaret() { if (!(canvas()->canvasWidget() ? canvas()->canvasWidget()->hasFocus() : canvas()->canvasItem()->hasFocus())) { m_caretTimer.stop(); m_caretTimerState = false; // not visible. } else { m_caretTimerState = !m_caretTimerState; } repaintCaret(); } void TextTool::relayoutContent() { KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); foreach (KoTextLayoutRootArea *rootArea, lay->rootAreas()) { rootArea->setDirty(); } lay->emitLayoutIsDirty(); } void TextTool::paint(QPainter &painter, const KoViewConverter &converter) { if (m_textEditor.isNull()) return; if (canvas() && (( canvas()->canvasWidget() && canvas()->canvasWidget()->hasFocus()) || (canvas()->canvasItem() && canvas()->canvasItem()->hasFocus()) ) && !m_caretTimer.isActive()) { // make sure we blink m_caretTimer.start(); m_caretTimerState = true; } if (!m_caretTimerState) m_caretTimer.setInterval(500); // we set it lower during typing, so set it back to normal if (!m_textShapeData) return; if (m_textShapeData->isDirty()) return; qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); painter.save(); QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter); shapeMatrix.scale(zoomX, zoomY); shapeMatrix.translate(0, -m_textShapeData->documentOffset()); // Possibly draw table dragging visual cues const qreal boxHeight = 20; if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) { QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(m_dx, 0.0); if (m_tableDragInfo.tableColumnDivider > 0) { //let's draw left qreal w = m_tableDragInfo.tableLeadSize - m_dx; QRectF rect(anchorPos - QPointF(w, 0.0), QSizeF(w, 0.0)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setHeight(boxHeight); drawRect.moveTop(drawRect.top() - 1.5 * boxHeight); QString label = m_unit.toUserStringValue(w); int labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.setPen(QPen(QColor(0, 0, 0, 196), 0)); if (labelWidth + 10 < drawRect.width()) { QPointF centerLeft(drawRect.left(), drawRect.center().y()); QPointF centerRight(drawRect.right(), drawRect.center().y()); painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth/2+5, 0.0)); painter.drawLine(centerLeft, centerLeft + QPointF(7, -5)); painter.drawLine(centerLeft, centerLeft + QPointF(7, 5)); painter.drawLine(drawRect.center() + QPointF(labelWidth/2+5, 0.0), centerRight); painter.drawLine(centerRight, centerRight + QPointF(-7, -5)); painter.drawLine(centerRight, centerRight + QPointF(-7, 5)); painter.drawText(drawRect, Qt::AlignCenter, label); } } if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) { //let's draw right qreal w = m_tableDragInfo.tableTrailSize + m_dx; QRectF rect(anchorPos, QSizeF(w, 0.0)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setHeight(boxHeight); drawRect.moveTop(drawRect.top() - 1.5 * boxHeight); QString label; int labelWidth; if (m_tableDragWithShift) { label = i18n("follows along"); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); drawRect.setWidth(2 * labelWidth); QLinearGradient g(drawRect.topLeft(), drawRect.topRight()); g.setColorAt(0.6, QColor(255, 64, 64, 196)); g.setColorAt(1.0, QColor(255, 64, 64, 0)); QBrush brush(g); painter.fillRect(drawRect, brush); } else { label = m_unit.toUserStringValue(w); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); drawRect.setHeight(boxHeight); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); } painter.setPen(QPen(QColor(0, 0, 0, 196), 0)); if (labelWidth + 10 < drawRect.width()) { QPointF centerLeft(drawRect.left(), drawRect.center().y()); QPointF centerRight(drawRect.right(), drawRect.center().y()); painter.drawLine(centerLeft, drawRect.center() - QPointF(labelWidth/2+5, 0.0)); painter.drawLine(centerLeft, centerLeft + QPointF(7, -5)); painter.drawLine(centerLeft, centerLeft + QPointF(7, 5)); if (!m_tableDragWithShift) { painter.drawLine(drawRect.center() + QPointF(labelWidth/2+5, 0.0), centerRight); painter.drawLine(centerRight, centerRight + QPointF(-7, -5)); painter.drawLine(centerRight, centerRight + QPointF(-7, 5)); } painter.drawText(drawRect, Qt::AlignCenter, label); } if (!m_tableDragWithShift) { // let's draw a helper text too label = i18n("Press shift to not resize this"); labelWidth = QFontMetrics(QToolTip::font()).boundingRect(label).width(); labelWidth += 10; //if (labelWidth < drawRect.width()) { drawRect.moveTop(drawRect.top() + boxHeight); drawRect.moveLeft(drawRect.left() + (drawRect.width() - labelWidth)/2); drawRect.setWidth(labelWidth); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.drawText(drawRect, Qt::AlignCenter, label); } } } } // Possibly draw table dragging visual cues if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) { QPointF anchorPos = m_tableDragInfo.tableDividerPos - QPointF(0.0, m_dy); if (m_tableDragInfo.tableRowDivider > 0) { qreal h = m_tableDragInfo.tableLeadSize - m_dy; QRectF rect(anchorPos - QPointF(0.0, h), QSizeF(0.0, h)); QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomRight())); drawRect.setWidth(boxHeight); drawRect.moveLeft(drawRect.left() - 1.5 * boxHeight); QString label = m_unit.toUserStringValue(h); QRectF labelRect = QFontMetrics(QToolTip::font()).boundingRect(label); labelRect.setHeight(boxHeight); labelRect.setWidth(labelRect.width() + 10); labelRect.moveTopLeft(drawRect.center() - QPointF(labelRect.width(), labelRect.height())/2); painter.fillRect(drawRect, QColor(64, 255, 64, 196)); painter.fillRect(labelRect, QColor(64, 255, 64, 196)); painter.setPen(QPen(QColor(0, 0, 0, 196), 0)); if (labelRect.height() + 10 < drawRect.height()) { QPointF centerTop(drawRect.center().x(), drawRect.top()); QPointF centerBottom(drawRect.center().x(), drawRect.bottom()); painter.drawLine(centerTop, drawRect.center() - QPointF(0.0, labelRect.height()/2+5)); painter.drawLine(centerTop, centerTop + QPointF(-5, 7)); painter.drawLine(centerTop, centerTop + QPointF(5, 7)); painter.drawLine(drawRect.center() + QPointF(0.0, labelRect.height()/2+5), centerBottom); painter.drawLine(centerBottom, centerBottom + QPointF(-5, -7)); painter.drawLine(centerBottom, centerBottom + QPointF(5, -7)); } painter.drawText(labelRect, Qt::AlignCenter, label); } } if (m_caretTimerState) { // Lets draw the caret ourselves, as the Qt method doesn't take cursor // charFormat into consideration. QTextBlock block = m_textEditor.data()->block(); if (block.isValid()) { int posInParag = m_textEditor.data()->position() - block.position(); if (posInParag <= block.layout()->preeditAreaPosition()) posInParag += block.layout()->preeditAreaText().length(); QTextLine tl = block.layout()->lineForTextPosition(m_textEditor.data()->position() - block.position()); if (tl.isValid()) { painter.setRenderHint(QPainter::Antialiasing, false); QRectF rect = caretRect(m_textEditor.data()->cursor()); QPointF baselinePoint; if (tl.ascent() > 0) { QFontMetricsF fm(m_textEditor.data()->charFormat().font(), painter.device()); rect.setY(rect.y() + tl.ascent() - qMin(tl.ascent(), fm.ascent())); rect.setHeight(qMin(tl.ascent(), fm.ascent()) + qMin(tl.descent(), fm.descent())); baselinePoint = QPoint(rect.x(), rect.y() + tl.ascent()); } else { //line only filled with characters-without-size (eg anchors) // layout will make sure line has height of block font QFontMetricsF fm(block.charFormat().font(), painter.device()); rect.setHeight(fm.ascent() + fm.descent()); baselinePoint = QPoint(rect.x(), rect.y() + fm.ascent()); } QRectF drawRect(shapeMatrix.map(rect.topLeft()), shapeMatrix.map(rect.bottomLeft())); drawRect.setWidth(2); painter.setCompositionMode(QPainter::RasterOp_SourceXorDestination); if (m_textEditor.data()->isEditProtected(true)) { QRectF circleRect(shapeMatrix.map(baselinePoint),QSizeF(14, 14)); circleRect.translate(-6.5, -6.5); QPen pen(QColor(16, 255, 255)); pen.setWidthF(2.0); painter.setPen(pen); painter.setRenderHint(QPainter::Antialiasing, true); painter.drawEllipse(circleRect); painter.drawLine(circleRect.topLeft() + QPointF(4.5,4.5), circleRect.bottomRight() - QPointF(4.5,4.5)); } else { painter.fillRect(drawRect, QColor(128, 255, 128)); } } } } painter.restore(); } void TextTool::updateSelectedShape(const QPointF &point, bool noDocumentChange) { QRectF area(point, QSizeF(1, 1)); if (m_textEditor.data()->hasSelection()) repaintSelection(); else repaintCaret(); QList sortedShapes = canvas()->shapeManager()->shapesAt(area, true); qSort(sortedShapes.begin(), sortedShapes.end(), KoShape::compareShapeZIndex); for (int count = sortedShapes.count() - 1; count >= 0; count--) { KoShape *shape = sortedShapes.at(count); if (shape->isContentProtected()) continue; TextShape *textShape = dynamic_cast(shape); if (textShape) { if (textShape != m_textShape) { if (static_cast(textShape->userData())->document() != m_textShapeData->document()) { //we should only change to another document if allowed if (noDocumentChange) { return; } // if we change to another textdocument we need to remove selection in old document // or it would continue to be painted etc m_textEditor.data()->setPosition(m_textEditor.data()->position()); } m_textShape = textShape; setShapeData(static_cast(m_textShape->userData())); // This is how we inform the rulers of the active range // For now we will not consider table cells, but just give the shape dimensions QVariant v; QRectF rect(QPoint(), m_textShape->size()); rect = m_textShape->absoluteTransformation(0).mapRect(rect); v.setValue(rect); canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, v); } return; } } } void TextTool::mousePressEvent(KoPointerEvent *event) { if (m_textEditor.isNull()) return; // request the software keyboard, if any if (event->button() == Qt::LeftButton && qApp->autoSipEnabled()) { QStyle::RequestSoftwareInputPanel behavior = QStyle::RequestSoftwareInputPanel(qApp->style()->styleHint(QStyle::SH_RequestSoftwareInputPanel)); // the two following bools just make it all a lot easier to read in the following if() // basically, we require a widget for this to work (passing nullptr to QApplication::sendEvent // crashes) and there are three tests any one of which can be true to trigger the event const bool hasWidget = canvas()->canvasWidget(); const bool hasItem = canvas()->canvasItem(); if ((behavior == QStyle::RSIP_OnMouseClick && (hasWidget || hasItem)) || (hasWidget && canvas()->canvasWidget()->hasFocus()) || (hasItem && canvas()->canvasItem()->hasFocus())) { QEvent event(QEvent::RequestSoftwareInputPanel); if (hasWidget) { QApplication::sendEvent(canvas()->canvasWidget(), &event); } else { QApplication::sendEvent(canvas()->canvasItem(), &event); } } } bool shiftPressed = event->modifiers() & Qt::ShiftModifier; updateSelectedShape(event->point, shiftPressed); KoSelection *selection = canvas()->shapeManager()->selection(); if (m_textShape && !selection->isSelected(m_textShape) && m_textShape->isSelectable()) { selection->deselectAll(); selection->select(m_textShape); } KoPointedAt pointedAt = hitTest(event->point); m_tableDraggedOnce = false; m_clickWithinSelection = false; if (pointedAt.position != -1) { m_tablePenMode = false; if ((event->button() == Qt::LeftButton) && !shiftPressed && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position)) { m_clickWithinSelection = true; m_draggingOrigin = event->pos(); //we store the pixel pos } else if (! (event->button() == Qt::RightButton && m_textEditor.data()->hasSelection() && m_textEditor.data()->isWithinSelection(pointedAt.position))) { m_textEditor.data()->setPosition(pointedAt.position, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); useCursor(Qt::IBeamCursor); } m_tableDragInfo.tableHit = KoPointedAt::None; if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } } else { if (event->button() == Qt::RightButton) { m_tablePenMode = false; KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck(); if (plugin) plugin->setCurrentCursorPosition(m_textShapeData->document(), -1); event->ignore(); } else if (m_tablePenMode) { m_textEditor.data()->beginEditBlock(kundo2_i18n("Change Border Formatting")); if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { if (pointedAt.tableColumnDivider < pointedAt.table->columns()) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider, KoBorder::LeftBorder, m_tablePenBorderData); } if (pointedAt.tableColumnDivider > 0) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider - 1, KoBorder::RightBorder, m_tablePenBorderData); } } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (pointedAt.tableRowDivider < pointedAt.table->rows()) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider, pointedAt.tableColumnDivider, KoBorder::TopBorder, m_tablePenBorderData); } if (pointedAt.tableRowDivider > 0) { m_textEditor.data()->setTableBorderData(pointedAt.table, pointedAt.tableRowDivider-1, pointedAt.tableColumnDivider, KoBorder::BottomBorder, m_tablePenBorderData); } } m_textEditor.data()->endEditBlock(); } else { m_tableDragInfo = pointedAt; m_tablePenMode = false; } return; } if (shiftPressed) // altered selection. repaintSelection(); else repaintCaret(); updateSelectionHandler(); updateStyleManager(); updateActions(); //activate context-menu for spelling-suggestions if (event->button() == Qt::RightButton) { KoTextEditingPlugin *plugin = textEditingPluginContainer()->spellcheck(); if (plugin) plugin->setCurrentCursorPosition(m_textShapeData->document(), m_textEditor.data()->position()); event->ignore(); } if (event->button() == Qt::MidButton) { // Paste const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Selection); // on windows we do not have data if we try to paste this selection if (data) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data, canvas()->resourceManager()); editingPluginEvents(); } } } void TextTool::setShapeData(KoTextShapeData *data) { bool docChanged = !data || !m_textShapeData || m_textShapeData->document() != data->document(); if (m_textShapeData) { disconnect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } m_textShapeData = data; if (!m_textShapeData) return; connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); if (docChanged) { if (!m_textEditor.isNull()) { disconnect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions())); } m_textEditor = KoTextDocument(m_textShapeData->document()).textEditor(); Q_ASSERT(m_textEditor.data()); if (!m_toolSelection) { m_toolSelection = new TextToolSelection(m_textEditor.data()); } else { m_toolSelection->m_editor = m_textEditor.data(); } m_variableMenu->menu()->clear(); KoTextDocument document(m_textShapeData->document()); foreach (QAction *action, document.inlineTextObjectManager()->createInsertVariableActions(canvas())) { m_variableMenu->addAction(action); connect(action, SIGNAL(triggered()), this, SLOT(returnFocusToCanvas())); } connect(m_textEditor.data(), SIGNAL(textFormatChanged()), this, SLOT(updateActions())); updateActions(); } } void TextTool::updateSelectionHandler() { if (m_textEditor) { emit selectionChanged(m_textEditor.data()->hasSelection()); if (m_textEditor.data()->hasSelection()) { QClipboard *clipboard = QApplication::clipboard(); if (clipboard->supportsSelection()) clipboard->setText(m_textEditor.data()->selectedText(), QClipboard::Selection); } } KoCanvasResourceManager *p = canvas()->resourceManager(); m_allowResourceManagerUpdates = false; if (m_textEditor && m_textShapeData) { p->setResource(KoText::CurrentTextPosition, m_textEditor.data()->position()); p->setResource(KoText::CurrentTextAnchor, m_textEditor.data()->anchor()); QVariant variant; variant.setValue(m_textShapeData->document()); p->setResource(KoText::CurrentTextDocument, variant); } else { p->clearResource(KoText::CurrentTextPosition); p->clearResource(KoText::CurrentTextAnchor); p->clearResource(KoText::CurrentTextDocument); } m_allowResourceManagerUpdates = true; } QMimeData *TextTool::generateMimeData() const { if (!m_textShapeData || m_textEditor.isNull() || !m_textEditor.data()->hasSelection()) return 0; int from = m_textEditor.data()->position(); int to = m_textEditor.data()->anchor(); KoTextOdfSaveHelper saveHelper(m_textShapeData->document(), from, to); KoTextDrag drag; #ifdef SHOULD_BUILD_RDF KoDocumentResourceManager *rm = 0; if (canvas()->shapeController()) { rm = canvas()->shapeController()->resourceManager(); } if (rm && rm->hasResource(KoText::DocumentRdf)) { KoDocumentRdfBase *rdf = qobject_cast(rm->resource(KoText::DocumentRdf).value()); if (rdf) { saveHelper.setRdfModel(rdf->model()); } } #endif drag.setOdf(KoOdf::mimeType(KoOdf::Text), saveHelper); QTextDocumentFragment fragment = m_textEditor.data()->selection(); drag.setData("text/plain", fragment.toPlainText().toUtf8()); return drag.takeMimeData(); } TextEditingPluginContainer *TextTool::textEditingPluginContainer() { m_textEditingPlugins = canvas()->resourceManager()-> resource(TextEditingPluginContainer::ResourceId).value(); if (m_textEditingPlugins == 0) { m_textEditingPlugins = new TextEditingPluginContainer(canvas()->resourceManager()); QVariant variant; variant.setValue(m_textEditingPlugins.data()); canvas()->resourceManager()->setResource(TextEditingPluginContainer::ResourceId, variant); foreach (KoTextEditingPlugin* plugin, m_textEditingPlugins->values()) { connect(plugin, SIGNAL(startMacro(QString)), this, SLOT(startMacro(QString))); connect(plugin, SIGNAL(stopMacro()), this, SLOT(stopMacro())); const QHash actions = plugin->actions(); QHash::ConstIterator i = actions.begin(); while (i != actions.end()) { addAction(i.key(), i.value()); ++i; } } } return m_textEditingPlugins; } void TextTool::copy() const { QMimeData *mimeData = generateMimeData(); if (mimeData) { QApplication::clipboard()->setMimeData(mimeData); } } void TextTool::deleteSelection() { m_textEditor.data()->deleteChar(); editingPluginEvents(); } bool TextTool::paste() { const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard); // on windows we do not have data if we try to paste the selection if (!data) return false; // since this is not paste-as-text we will not paste in urls, but instead let KoToolProxy solve it if (data->hasUrls()) return false; if (data->hasFormat(KoOdf::mimeType(KoOdf::Text)) || data->hasText()) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data); editingPluginEvents(); return true; } return false; } void TextTool::cut() { if (m_textEditor.data()->hasSelection()) { copy(); KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Cut")); m_textEditor.data()->deleteChar(false, topCmd); m_textEditor.data()->endEditBlock(); } } QStringList TextTool::supportedPasteMimeTypes() const { QStringList list; list << "text/plain" << "application/vnd.oasis.opendocument.text"; return list; } void TextTool::dragMoveEvent(QDragMoveEvent *event, const QPointF &point) { if (event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::Text)) || event->mimeData()->hasFormat(KoOdf::mimeType(KoOdf::OpenOfficeClipboard)) || event->mimeData()->hasText()) { if (m_drag) { event->setDropAction(Qt::MoveAction); event->accept(); } else if (event->proposedAction() == Qt::CopyAction) { event->acceptProposedAction(); } else { event->ignore(); return; } KoPointedAt pointedAt = hitTest(point); if (pointedAt.position == -1) { event->ignore(); } if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } if (m_preDragSelection.cursor.isNull()) { repaintSelection(); m_preDragSelection.cursor = QTextCursor(*m_textEditor.data()->cursor()); if (m_drag) { // Make a selection that looks like the current cursor selection // so we can move the real caret around freely QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); m_preDragSelection.format = QTextCharFormat(); m_preDragSelection.format.setBackground(qApp->palette().brush(QPalette::Highlight)); m_preDragSelection.format.setForeground(qApp->palette().brush(QPalette::HighlightedText)); sels.append(m_preDragSelection); KoTextDocument(m_textShapeData->document()).setSelections(sels); } // else we want the selection to disappear } repaintCaret(); // will erase caret m_textEditor.data()->setPosition(pointedAt.position); repaintCaret(); // will paint caret in new spot // Selection has visually not appeared at a new spot so no need to repaint it } } void TextTool::dragLeaveEvent(QDragLeaveEvent *event) { if (m_drag) { // restore the old selections QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); sels.pop_back(); KoTextDocument(m_textShapeData->document()).setSelections(sels); } repaintCaret(); // will erase caret in old spot m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor); repaintCaret(); // will paint caret in new spot if (!m_drag) { repaintSelection(); // will paint selection again } // mark that we now are back to normal selection m_preDragSelection.cursor = QTextCursor(); event->accept(); } void TextTool::dropEvent(QDropEvent *event, const QPointF &) { if (m_drag) { // restore the old selections QVector< QAbstractTextDocumentLayout::Selection > sels = KoTextDocument(m_textShapeData->document()).selections(); sels.pop_back(); KoTextDocument(m_textShapeData->document()).setSelections(sels); } QTextCursor insertCursor(*m_textEditor.data()->cursor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.anchor()); m_textEditor.data()->setPosition(m_preDragSelection.cursor.position(), QTextCursor::KeepAnchor); repaintSelection(); // will erase the selection in new spot if (m_drag) { m_textEditor.data()->deleteChar(); } m_prevCursorPosition = insertCursor.position(); m_textEditor.data()->setPosition(m_prevCursorPosition); m_textEditor.data()->paste(canvas(), event->mimeData()); m_textEditor.data()->setPosition(m_prevCursorPosition); //since the paste made insertCursor we can now use that for the end position m_textEditor.data()->setPosition(insertCursor.position(), QTextCursor::KeepAnchor); // mark that we no are back to normal selection m_preDragSelection.cursor = QTextCursor(); event->accept(); } KoPointedAt TextTool::hitTest(const QPointF & point) const { if (!m_textShape || !m_textShapeData) { return KoPointedAt(); } QPointF p = m_textShape->convertScreenPos(point); KoTextLayoutRootArea *rootArea = m_textShapeData->rootArea(); return rootArea ? rootArea->hitTest(p, Qt::FuzzyHit) : KoPointedAt(); } void TextTool::mouseDoubleClickEvent(KoPointerEvent *event) { if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) { event->ignore(); // allow the event to be used by another return; } if (event->modifiers() & Qt::ShiftModifier) { // When whift is pressed we behave as a single press return mousePressEvent(event); } m_textEditor.data()->select(QTextCursor::WordUnderCursor); m_clickWithinSelection = false; repaintSelection(); updateSelectionHandler(); } void TextTool::mouseTripleClickEvent(KoPointerEvent *event) { if (canvas()->shapeManager()->shapeAt(event->point) != m_textShape) { event->ignore(); // allow the event to be used by another return; } if (event->modifiers() & Qt::ShiftModifier) { // When whift is pressed we behave as a single press return mousePressEvent(event); } m_textEditor.data()->clearSelection(); m_textEditor.data()->movePosition(QTextCursor::StartOfBlock); m_textEditor.data()->movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); m_clickWithinSelection = false; repaintSelection(); updateSelectionHandler(); } void TextTool::mouseMoveEvent(KoPointerEvent *event) { m_editTipPos = event->globalPos(); if (event->buttons()) { updateSelectedShape(event->point, true); } m_editTipTimer.stop(); if (QToolTip::isVisible()) QToolTip::hideText(); KoPointedAt pointedAt = hitTest(event->point); if (event->buttons() == Qt::NoButton) { if (m_tablePenMode) { if (pointedAt.tableHit == KoPointedAt::ColumnDivider || pointedAt.tableHit == KoPointedAt::RowDivider) { useTableBorderCursor(); } else { useCursor(Qt::IBeamCursor); } // do nothing else return; } if (!m_textShapeData || pointedAt.position < 0) { if (pointedAt.tableHit == KoPointedAt::ColumnDivider) { useCursor(Qt::SplitHCursor); m_draggingOrigin = event->point; } else if (pointedAt.tableHit == KoPointedAt::RowDivider) { if (pointedAt.tableRowDivider > 0) { useCursor(Qt::SplitVCursor); m_draggingOrigin = event->point; } else useCursor(Qt::IBeamCursor); } else { useCursor(Qt::IBeamCursor); } return; } QTextCursor mouseOver(m_textShapeData->document()); mouseOver.setPosition(pointedAt.position); if (m_changeTracker && m_changeTracker->containsInlineChanges(mouseOver.charFormat())) { m_editTipPointedAt = pointedAt; if (QToolTip::isVisible()) { QTimer::singleShot(0, this, SLOT(showEditTip())); }else { m_editTipTimer.start(); } } if ((pointedAt.bookmark || !pointedAt.externalHRef.isEmpty()) || pointedAt.note || (pointedAt.noteReference>0)) { if (event->modifiers() & Qt::ControlModifier) { useCursor(Qt::PointingHandCursor); } m_editTipPointedAt = pointedAt; if (QToolTip::isVisible()) { QTimer::singleShot(0, this, SLOT(showEditTip())); }else { m_editTipTimer.start(); } return; } // check if mouse pointer is over shape with hyperlink KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point); if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) { useCursor(Qt::PointingHandCursor); return; } useCursor(Qt::IBeamCursor); // Set Arrow Cursor when mouse is on top of annotation shape. if (selectedShape) { if (selectedShape->shapeId() == "AnnotationTextShapeID") { QPointF point(event->point); if (point.y() <= (selectedShape->position().y() + 25)) useCursor(Qt::ArrowCursor); } } return; } else { if (m_tableDragInfo.tableHit == KoPointedAt::ColumnDivider) { m_tableDragWithShift = event->modifiers() & Qt::ShiftModifier; if(m_tableDraggedOnce) { canvas()->shapeController()->resourceManager()->undoStack()->undo(); } KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Column Width")); m_dx = m_draggingOrigin.x() - event->point.x(); if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns() && m_tableDragInfo.tableTrailSize + m_dx < 0) { m_dx = -m_tableDragInfo.tableTrailSize; } if (m_tableDragInfo.tableColumnDivider > 0) { if (m_tableDragInfo.tableLeadSize - m_dx < 0) { m_dx = m_tableDragInfo.tableLeadSize; } m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table, m_tableDragInfo.tableColumnDivider - 1, m_tableDragInfo.tableLeadSize - m_dx, topCmd); } else { m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, -m_dx, 0.0); } if (m_tableDragInfo.tableColumnDivider < m_tableDragInfo.table->columns()) { if (!m_tableDragWithShift) { m_textEditor.data()->adjustTableColumnWidth(m_tableDragInfo.table, m_tableDragInfo.tableColumnDivider, m_tableDragInfo.tableTrailSize + m_dx, topCmd); } } else { m_tableDragWithShift = true; // act like shift pressed } if (m_tableDragWithShift) { m_textEditor.data()->adjustTableWidth(m_tableDragInfo.table, 0.0, m_dx); } m_textEditor.data()->endEditBlock(); m_tableDragInfo.tableDividerPos.setY(m_textShape->convertScreenPos(event->point).y()); if (m_tableDraggedOnce) { //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) canvas()->canvasWidget()->update(); if (canvas()->canvasItem()) canvas()->canvasItem()->update(); } m_tableDraggedOnce = true; } else if (m_tableDragInfo.tableHit == KoPointedAt::RowDivider) { if(m_tableDraggedOnce) { canvas()->shapeController()->resourceManager()->undoStack()->undo(); } if (m_tableDragInfo.tableRowDivider > 0) { KUndo2Command *topCmd = m_textEditor.data()->beginEditBlock(kundo2_i18n("Adjust Row Height")); m_dy = m_draggingOrigin.y() - event->point.y(); if (m_tableDragInfo.tableLeadSize - m_dy < 0) { m_dy = m_tableDragInfo.tableLeadSize; } m_textEditor.data()->adjustTableRowHeight(m_tableDragInfo.table, m_tableDragInfo.tableRowDivider - 1, m_tableDragInfo.tableLeadSize - m_dy, topCmd); m_textEditor.data()->endEditBlock(); m_tableDragInfo.tableDividerPos.setX(m_textShape->convertScreenPos(event->point).x()); if (m_tableDraggedOnce) { //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) canvas()->canvasWidget()->update(); if (canvas()->canvasItem()) canvas()->canvasItem()->update(); } m_tableDraggedOnce = true; } } else if (m_tablePenMode) { // do nothing } else if (m_clickWithinSelection) { if (!m_drag && (event->pos() - m_draggingOrigin).manhattanLength() >= QApplication::startDragDistance()) { QMimeData *mimeData = generateMimeData(); if (mimeData) { m_drag = new QDrag(canvas()->canvasWidget()); m_drag->setMimeData(mimeData); m_drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::CopyAction); m_drag = 0; } } } else { useCursor(Qt::IBeamCursor); if (pointedAt.position == m_textEditor.data()->position()) return; if (pointedAt.position >= 0) { if (m_textEditor.data()->hasSelection()) repaintSelection(); // will erase selection else repaintCaret(); m_textEditor.data()->setPosition(pointedAt.position, QTextCursor::KeepAnchor); if (m_textEditor.data()->hasSelection()) repaintSelection(); else repaintCaret(); } } updateSelectionHandler(); } } void TextTool::mouseReleaseEvent(KoPointerEvent *event) { event->ignore(); editingPluginEvents(); m_tableDragInfo.tableHit = KoPointedAt::None; if (m_tableDraggedOnce) { m_tableDraggedOnce = false; //we need to redraw like this so we update outside the textshape too if (canvas()->canvasWidget()) canvas()->canvasWidget()->update(); if (canvas()->canvasItem()) canvas()->canvasItem()->update(); } if (!m_textShapeData) return; // check if mouse pointer is not over some shape with hyperlink KoShape *selectedShape = canvas()->shapeManager()->shapeAt(event->point); if (selectedShape != 0 && selectedShape != m_textShape && selectedShape->hyperLink().size() != 0) { QString url = selectedShape->hyperLink(); runUrl(event, url); return; } KoPointedAt pointedAt = hitTest(event->point); if (m_clickWithinSelection && !m_drag) { if (m_caretTimer.isActive()) { // make the caret not blink, (blinks again after first draw) m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret instantly on on click } repaintCaret(); // will erase caret repaintSelection(); // will erase selection m_textEditor.data()->setPosition(pointedAt.position); repaintCaret(); // will paint caret in new spot } // Is there an anchor here ? if ((event->modifiers() & Qt::ControlModifier) && !m_textEditor.data()->hasSelection()) { if (pointedAt.bookmark) { m_textEditor.data()->setPosition(pointedAt.bookmark->rangeStart()); ensureCursorVisible(); event->accept(); return; } if (pointedAt.note) { m_textEditor.data()->setPosition(pointedAt.note->textFrame()->firstPosition()); ensureCursorVisible(); event->accept(); return; } if (pointedAt.noteReference>0) { m_textEditor.data()->setPosition(pointedAt.noteReference); ensureCursorVisible(); event->accept(); return; } if (!pointedAt.externalHRef.isEmpty()) { runUrl(event, pointedAt.externalHRef); } } } void TextTool::shortcutOverrideEvent(QKeyEvent *event) { QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers())); if (hit(item, KStandardShortcut::Begin) || hit(item, KStandardShortcut::End)) { event->accept(); } } void TextTool::keyPressEvent(QKeyEvent *event) { int destinationPosition = -1; // for those cases where the moveOperation is not relevant; QTextCursor::MoveOperation moveOperation = QTextCursor::NoMove; KoTextEditor *textEditor = m_textEditor.data(); m_tablePenMode = false; // keypress always stops the table (border) pen mode Q_ASSERT(textEditor); if (event->key() == Qt::Key_Backspace) { if (!textEditor->hasSelection() && textEditor->block().textList() && (textEditor->position() == textEditor->block().position()) && !(m_changeTracker && m_changeTracker->recordChanges())) { if (!textEditor->blockFormat().boolProperty(KoParagraphStyle::UnnumberedListItem)) { // backspace at beginning of numbered list item, makes it unnumbered textEditor->toggleListNumbering(false); } else { KoListLevelProperties llp; llp.setLabelType(KoListStyle::None); llp.setLevel(0); // backspace on numbered, empty parag, removes numbering. textEditor->setListProperties(llp); } } else if (textEditor->position() > 0 || textEditor->hasSelection()) { if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) { // delete prev word. textEditor->movePosition(QTextCursor::PreviousWord, QTextCursor::KeepAnchor); } textEditor->deletePreviousChar(); editingPluginEvents(); } } else if ((event->key() == Qt::Key_Tab) && ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1); textEditor->addCommand(cll); editingPluginEvents(); } else if ((event->key() == Qt::Key_Backtab) && ((!textEditor->hasSelection() && (textEditor->position() == textEditor->block().position())) || (textEditor->block().document()->findBlock(textEditor->anchor()) != textEditor->block().document()->findBlock(textEditor->position()))) && textEditor->block().textList() && !(m_changeTracker && m_changeTracker->recordChanges())) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, 1); textEditor->addCommand(cll); editingPluginEvents(); } else if (event->key() == Qt::Key_Delete) { if (!textEditor->hasSelection() && event->modifiers() & Qt::ControlModifier) {// delete next word. textEditor->movePosition(QTextCursor::NextWord, QTextCursor::KeepAnchor); } // the event only gets through when the Del is not used in the app // if the app forwards Del then deleteSelection is used textEditor->deleteChar(); editingPluginEvents(); } else if ((event->key() == Qt::Key_Left) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Left; } else if ((event->key() == Qt::Key_Right) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Right; } else if ((event->key() == Qt::Key_Up) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Up; } else if ((event->key() == Qt::Key_Down) && (event->modifiers() & Qt::ControlModifier) == 0) { moveOperation = QTextCursor::Down; } else { // check for shortcuts. QKeySequence item(event->key() | ((Qt::ControlModifier | Qt::AltModifier) & event->modifiers())); if (hit(item, KStandardShortcut::Begin)) { // Goto beginning of the document. Default: Ctrl-Home destinationPosition = 0; } else if (hit(item, KStandardShortcut::End)) { // Goto end of the document. Default: Ctrl-End if (m_textShapeData) { QTextBlock last = m_textShapeData->document()->lastBlock(); destinationPosition = last.position() + last.length() - 1; } } else if (hit(item, KStandardShortcut::Prior)) { // page up // Scroll up one page. Default: Prior event->ignore(); // let app level actions handle it return; } else if (hit(item, KStandardShortcut::Next)) { // Scroll down one page. Default: Next event->ignore(); // let app level actions handle it return; } else if (hit(item, KStandardShortcut::BeginningOfLine)) // Goto beginning of current line. Default: Home moveOperation = QTextCursor::StartOfLine; else if (hit(item, KStandardShortcut::EndOfLine)) // Goto end of current line. Default: End moveOperation = QTextCursor::EndOfLine; else if (hit(item, KStandardShortcut::BackwardWord)) moveOperation = QTextCursor::WordLeft; else if (hit(item, KStandardShortcut::ForwardWord)) moveOperation = QTextCursor::WordRight; #ifdef Q_WS_MAC // Don't reject "alt" key, it may be used for typing text on Mac OS else if ((event->modifiers() & Qt::ControlModifier) #else else if ((event->modifiers() & (Qt::ControlModifier | Qt::AltModifier)) #endif || event->text().length() == 0 || event->key() == Qt::Key_Escape) { event->ignore(); return; } else if ((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) { m_prevCursorPosition = textEditor->position(); textEditor->newLine(); updateActions(); editingPluginEvents(); } else if ((event->key() == Qt::Key_Tab || !(event->text().length() == 1 && !event->text().at(0).isPrint()))) { // insert the text m_prevCursorPosition = textEditor->position(); startingSimpleEdit(); //signal editing plugins that this is a simple edit textEditor->insertText(event->text()); editingPluginEvents(); } } if (moveOperation != QTextCursor::NoMove || destinationPosition != -1) { useCursor(Qt::BlankCursor); bool shiftPressed = event->modifiers() & Qt::ShiftModifier; if (textEditor->hasSelection()) repaintSelection(); // will erase selection else repaintCaret(); QTextBlockFormat format = textEditor->blockFormat(); KoText::Direction dir = static_cast(format.intProperty(KoParagraphStyle::TextProgressionDirection)); bool isRtl; if (dir == KoText::AutoDirection) isRtl = textEditor->block().text().isRightToLeft(); else isRtl = dir == KoText::RightLeftTopBottom; if (isRtl) { // if RTL toggle direction of cursor movement. switch (moveOperation) { case QTextCursor::Left: moveOperation = QTextCursor::Right; break; case QTextCursor::Right: moveOperation = QTextCursor::Left; break; case QTextCursor::WordRight: moveOperation = QTextCursor::WordLeft; break; case QTextCursor::WordLeft: moveOperation = QTextCursor::WordRight; break; default: break; } } int prevPosition = textEditor->position(); if (moveOperation != QTextCursor::NoMove) textEditor->movePosition(moveOperation, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); else textEditor->setPosition(destinationPosition, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); if (moveOperation == QTextCursor::Down && prevPosition == textEditor->position()) { // change behavior a little big from Qt; at the bottom of the doc we go to the end of the doc textEditor->movePosition(QTextCursor::End, shiftPressed ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor); } if (shiftPressed) // altered selection. repaintSelection(); else repaintCaret(); updateActions(); editingPluginEvents(); } if (m_caretTimer.isActive()) { // make the caret not blink but decide on the action if its visible or not. m_caretTimer.stop(); m_caretTimer.setInterval(50); m_caretTimer.start(); m_caretTimerState = true; // turn caret on while typing } if (moveOperation != QTextCursor::NoMove) // this difference in handling is need to prevent leaving a trail of old cursors onscreen ensureCursorVisible(); else m_delayedEnsureVisible = true; updateActions(); updateSelectionHandler(); } QVariant TextTool::inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) return QVariant(); switch (query) { case Qt::ImMicroFocus: { // The rectangle covering the area of the input cursor in widget coordinates. QRectF rect = caretRect(textEditor->cursor()); rect.moveTop(rect.top() - m_textShapeData->documentOffset()); QTransform shapeMatrix = m_textShape->absoluteTransformation(&converter); qreal zoomX, zoomY; converter.zoom(&zoomX, &zoomY); shapeMatrix.scale(zoomX, zoomY); rect = shapeMatrix.mapRect(rect); return rect.toRect(); } case Qt::ImFont: // The currently used font for text input. return textEditor->charFormat().font(); case Qt::ImCursorPosition: // The logical position of the cursor within the text surrounding the input area (see ImSurroundingText). return textEditor->position() - textEditor->block().position(); case Qt::ImSurroundingText: // The plain text around the input area, for example the current paragraph. return textEditor->block().text(); case Qt::ImCurrentSelection: // The currently selected text. return textEditor->selectedText(); default: ; // Qt 4.6 adds ImMaximumTextLength and ImAnchorPosition } return QVariant(); } void TextTool::inputMethodEvent(QInputMethodEvent *event) { KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) return; if (event->replacementLength() > 0) { textEditor->setPosition(textEditor->position() + event->replacementStart()); for (int i = event->replacementLength(); i > 0; --i) { textEditor->deleteChar(); } } if (!event->commitString().isEmpty()) { QKeyEvent ke(QEvent::KeyPress, -1, 0, event->commitString()); keyPressEvent(&ke); // The cursor may reside in a different block before vs. after keyPressEvent. QTextBlock block = textEditor->block(); QTextLayout *layout = block.layout(); Q_ASSERT(layout); layout->setPreeditArea(-1, QString()); } else { QTextBlock block = textEditor->block(); QTextLayout *layout = block.layout(); Q_ASSERT(layout); layout->setPreeditArea(textEditor->position() - block.position(), event->preeditString()); const_cast(textEditor->document())->markContentsDirty(textEditor->position(), event->preeditString().length()); } event->accept(); } void TextTool::ensureCursorVisible(bool moveView) { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) return; bool upToDate; QRectF cRect = caretRect(textEditor->cursor(), &upToDate); KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); KoTextLayoutRootArea *rootArea = lay->rootAreaForPoint(cRect.center()); if (rootArea && rootArea->associatedShape() && m_textShapeData->rootArea() != rootArea) { // If we have changed root area we need to update m_textShape and m_textShapeData m_textShape = static_cast(rootArea->associatedShape()); Q_ASSERT(m_textShape); disconnect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); m_textShapeData = static_cast(m_textShape->userData()); Q_ASSERT(m_textShapeData); connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } if (!moveView) { return; } if (! upToDate) { // paragraph is not yet layouted. // The number one usecase for this is when the user pressed enter. // try to do it on next caret blink m_delayedEnsureVisible = true; return; // we shouldn't move to an obsolete position } cRect.moveTop(cRect.top() - m_textShapeData->documentOffset()); canvas()->ensureVisible(m_textShape->absoluteTransformation(0).mapRect(cRect)); } void TextTool::keyReleaseEvent(QKeyEvent *event) { event->accept(); } void TextTool::updateActions() { bool notInAnnotation = !dynamic_cast(m_textShape); KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) { return; } m_allowActions = false; //Update the characterStyle related GUI elements QTextCharFormat cf = textEditor->charFormat(); m_actionFormatBold->setChecked(cf.fontWeight() > QFont::Normal); m_actionFormatItalic->setChecked(cf.fontItalic()); m_actionFormatUnderline->setChecked(cf.intProperty(KoCharacterStyle::UnderlineType) != KoCharacterStyle::NoLineType); m_actionFormatStrikeOut->setChecked(cf.intProperty(KoCharacterStyle::StrikeOutType) != KoCharacterStyle::NoLineType); bool super = false, sub = false; switch (cf.verticalAlignment()) { case QTextCharFormat::AlignSuperScript: super = true; break; case QTextCharFormat::AlignSubScript: sub = true; break; default:; } m_actionFormatSuper->setChecked(super); m_actionFormatSub->setChecked(sub); m_actionFormatFontSize->setFontSize(cf.font().pointSizeF()); m_actionFormatFontFamily->setFont(cf.font().family()); KoTextShapeData::ResizeMethod resizemethod = KoTextShapeData::AutoResize; if(m_textShapeData) { resizemethod = m_textShapeData->resizeMethod(); } m_shrinkToFitAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_shrinkToFitAction->setChecked(resizemethod == KoTextShapeData::ShrinkToFitResize); m_growWidthAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_growWidthAction->setChecked(resizemethod == KoTextShapeData::AutoGrowWidth || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight); m_growHeightAction->setEnabled(resizemethod != KoTextShapeData::AutoResize && notInAnnotation); m_growHeightAction->setChecked(resizemethod == KoTextShapeData::AutoGrowHeight || resizemethod == KoTextShapeData::AutoGrowWidthAndHeight); //update paragraphStyle GUI element QTextBlockFormat bf = textEditor->blockFormat(); if (bf.hasProperty(KoParagraphStyle::TextProgressionDirection)) { switch(bf.intProperty(KoParagraphStyle::TextProgressionDirection)) { case KoText::RightLeftTopBottom: m_actionChangeDirection->setChecked(true); break; case KoText::LeftRightTopBottom: default: m_actionChangeDirection->setChecked(false); break; } } else { m_actionChangeDirection->setChecked(textEditor->block().text().isRightToLeft()); } if (bf.alignment() == Qt::AlignLeading || bf.alignment() == Qt::AlignTrailing) { bool revert = (textEditor->block().layout()->textOption().textDirection() == Qt::RightToLeft); if ((bf.alignment() == Qt::AlignLeading) ^ revert) m_actionAlignLeft->setChecked(true); else m_actionAlignRight->setChecked(true); } else if (bf.alignment() == Qt::AlignHCenter) m_actionAlignCenter->setChecked(true); if (bf.alignment() == Qt::AlignJustify) m_actionAlignBlock->setChecked(true); else if (bf.alignment() == (Qt::AlignLeft | Qt::AlignAbsolute)) m_actionAlignLeft->setChecked(true); else if (bf.alignment() == (Qt::AlignRight | Qt::AlignAbsolute)) m_actionAlignRight->setChecked(true); if (textEditor->block().textList()) { QTextListFormat listFormat = textEditor->block().textList()->format(); if(listFormat.intProperty(KoListStyle::Level) > 1) { m_actionFormatDecreaseIndent->setEnabled(true); } else { m_actionFormatDecreaseIndent->setEnabled(false); } if (listFormat.intProperty(KoListStyle::Level) < 10) { m_actionFormatIncreaseIndent->setEnabled(true); } else { m_actionFormatIncreaseIndent->setEnabled(false); } } else { m_actionFormatDecreaseIndent->setEnabled(textEditor->blockFormat().leftMargin() > 0.); } m_allowActions = true; bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); if (useAdvancedText) { action("insert_table")->setEnabled(notInAnnotation); bool hasTable = textEditor->currentTable(); action("insert_tablerow_above")->setEnabled(hasTable && notInAnnotation); action("insert_tablerow_below")->setEnabled(hasTable && notInAnnotation); action("insert_tablecolumn_left")->setEnabled(hasTable && notInAnnotation); action("insert_tablecolumn_right")->setEnabled(hasTable && notInAnnotation); action("delete_tablerow")->setEnabled(hasTable && notInAnnotation); action("delete_tablecolumn")->setEnabled(hasTable && notInAnnotation); action("merge_tablecells")->setEnabled(hasTable && notInAnnotation); action("split_tablecells")->setEnabled(hasTable && notInAnnotation); action("activate_borderpainter")->setEnabled(hasTable && notInAnnotation); } action("insert_annotation")->setEnabled(notInAnnotation); ///TODO if selection contains several different format emit blockChanged(textEditor->block()); emit charFormatChanged(cf, textEditor->blockCharFormat()); emit blockFormatChanged(bf); } void TextTool::updateStyleManager() { if (!m_textShapeData) return; KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager(); emit styleManagerChanged(styleManager); //TODO move this to its own method m_changeTracker = KoTextDocument(m_textShapeData->document()).changeTracker(); } void TextTool::activate(ToolActivation toolActivation, const QSet &shapes) { Q_UNUSED(toolActivation); m_caretTimer.start(); m_caretTimerState = true; foreach (KoShape *shape, shapes) { m_textShape = dynamic_cast(shape); if (m_textShape) break; } if (!m_textShape) { // none found emit done(); // This is how we inform the rulers of the active range // No shape means no active range canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, QVariant(QRectF())); return; } // This is how we inform the rulers of the active range // For now we will not consider table cells, but just give the shape dimensions QVariant v; QRectF rect(QPoint(), m_textShape->size()); rect = m_textShape->absoluteTransformation(0).mapRect(rect); v.setValue(rect); canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, v); if ((!m_oldTextEditor.isNull()) && m_oldTextEditor.data()->document() != static_cast(m_textShape->userData())->document()) { m_oldTextEditor.data()->setPosition(m_oldTextEditor.data()->position()); //we need to redraw like this so we update the old textshape whereever it may be if (canvas()->canvasWidget()) canvas()->canvasWidget()->update(); } setShapeData(static_cast(m_textShape->userData())); useCursor(Qt::IBeamCursor); updateStyleManager(); repaintSelection(); updateSelectionHandler(); updateActions(); if (m_specialCharacterDocker) m_specialCharacterDocker->setEnabled(true); } void TextTool::deactivate() { m_caretTimer.stop(); m_caretTimerState = false; repaintCaret(); m_textShape = 0; // This is how we inform the rulers of the active range // No shape means no active range canvas()->resourceManager()->setResource(KoCanvasResourceManager::ActiveRange, QVariant(QRectF())); m_oldTextEditor = m_textEditor; setShapeData(0); updateSelectionHandler(); if (m_specialCharacterDocker) { m_specialCharacterDocker->setEnabled(false); m_specialCharacterDocker->setVisible(false); } } void TextTool::repaintDecorations() { if (m_textShapeData) repaintSelection(); } void TextTool::repaintCaret() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) return; KoTextDocumentLayout *lay = qobject_cast(m_textShapeData->document()->documentLayout()); Q_ASSERT(lay); Q_UNUSED(lay); // If we have changed root area we need to update m_textShape and m_textShapeData if (m_delayedEnsureVisible) { m_delayedEnsureVisible = false; ensureCursorVisible(); return; } ensureCursorVisible(false); // ensures the various vars are updated bool upToDate; QRectF repaintRect = caretRect(textEditor->cursor(), &upToDate); repaintRect.moveTop(repaintRect.top() - m_textShapeData->documentOffset()); if (repaintRect.isValid()) { repaintRect = m_textShape->absoluteTransformation(0).mapRect(repaintRect); // Make sure there is enough space to show an icon QRectF iconSize = canvas()->viewConverter()->viewToDocument(QRect(0, 0, 18, 18)); repaintRect.setX(repaintRect.x() - iconSize.width() / 2); repaintRect.setRight(repaintRect.right() + iconSize.width() / 2); repaintRect.setTop(repaintRect.y() - iconSize.height() / 2); repaintRect.setBottom(repaintRect.bottom() + iconSize.height() / 2); canvas()->updateCanvas(repaintRect); } } void TextTool::repaintSelection() { KoTextEditor *textEditor = m_textEditor.data(); if (textEditor == 0) return; QTextCursor cursor = *textEditor->cursor(); QList shapes; KoTextDocumentLayout *lay = qobject_cast(textEditor->document()->documentLayout()); Q_ASSERT(lay); foreach (KoShape* shape, lay->shapes()) { TextShape *textShape = dynamic_cast(shape); if (textShape == 0) // when the shape is being deleted its no longer a TextShape but a KoShape continue; //Q_ASSERT(!shapes.contains(textShape)); if (!shapes.contains(textShape)) { shapes.append(textShape); } } // loop over all shapes that contain the text and update per shape. QRectF repaintRect = textRect(cursor); foreach (TextShape *ts, shapes) { QRectF rect = repaintRect; rect.moveTop(rect.y() - ts->textShapeData()->documentOffset()); rect = ts->absoluteTransformation(0).mapRect(rect); QRectF r = ts->boundingRect().intersected(rect); canvas()->updateCanvas(r); } } QRectF TextTool::caretRect(QTextCursor *cursor, bool *upToDate) const { QTextCursor tmpCursor(*cursor); tmpCursor.setPosition(cursor->position()); // looses the anchor QRectF rect = textRect(tmpCursor); if (rect.size() == QSizeF(0,0)) { if (upToDate) { *upToDate = false; } rect = m_lastImMicroFocus; // prevent block changed but layout not done } else { if (upToDate) { *upToDate = true; } m_lastImMicroFocus = rect; } return rect; } QRectF TextTool::textRect(QTextCursor &cursor) const { if (!m_textShapeData) return QRectF(); KoTextEditor *textEditor = m_textEditor.data(); KoTextDocumentLayout *lay = qobject_cast(textEditor->document()->documentLayout()); return lay->selectionBoundingBox(cursor); } KoToolSelection* TextTool::selection() { return m_toolSelection; } QList > TextTool::createOptionWidgets() { QList > widgets; SimpleCharacterWidget *scw = new SimpleCharacterWidget(this, 0); SimpleParagraphWidget *spw = new SimpleParagraphWidget(this, 0); if (m_textEditor.data()) { // connect(m_textEditor.data(), SIGNAL(paragraphStyleApplied(KoParagraphStyle*)), spw, SLOT(slotParagraphStyleApplied(KoParagraphStyle*))); // connect(m_textEditor.data(), SIGNAL(characterStyleApplied(KoCharacterStyle*)), scw, SLOT(slotCharacterStyleApplied(KoCharacterStyle*))); //initialise the char- and par- widgets with the current block and formats. scw->setCurrentBlockFormat(m_textEditor.data()->blockFormat()); scw->setCurrentFormat(m_textEditor.data()->charFormat(), m_textEditor.data()-> blockCharFormat()); spw->setCurrentBlock(m_textEditor.data()->block()); spw->setCurrentFormat(m_textEditor.data()->blockFormat()); } SimpleTableWidget *stw = new SimpleTableWidget(this, 0); SimpleInsertWidget *siw = new SimpleInsertWidget(this, 0); /* We do not use these for now. Let's see if they become useful at a certain point in time. If not, we can remove the whole chain (SimpleCharWidget, SimpleParWidget, DockerStyleComboModel) if (m_textShapeData && KoTextDocument(m_textShapeData->document()).styleManager()) { scw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedCharacterStyles()); spw->setInitialUsedStyles(KoTextDocument(m_textShapeData->document()).styleManager()->usedParagraphStyles()); } */ // Connect to/with simple character widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), scw, SLOT(setStyleManager(KoStyleManager*))); connect(this, SIGNAL(charFormatChanged(QTextCharFormat,QTextCharFormat)), scw, SLOT(setCurrentFormat(QTextCharFormat,QTextCharFormat))); connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), scw, SLOT(setCurrentBlockFormat(QTextBlockFormat))); connect(scw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(scw, SIGNAL(characterStyleSelected(KoCharacterStyle*)), this, SLOT(setStyle(KoCharacterStyle*))); connect(scw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentCharFormat(QString))); connect(scw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int))); // Connect to/with simple paragraph widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), spw, SLOT(setStyleManager(KoStyleManager*))); connect(this, SIGNAL(blockChanged(QTextBlock)), spw, SLOT(setCurrentBlock(QTextBlock))); connect(this, SIGNAL(blockFormatChanged(QTextBlockFormat)), spw, SLOT(setCurrentFormat(QTextBlockFormat))); connect(spw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(spw, SIGNAL(paragraphStyleSelected(KoParagraphStyle*)), this, SLOT(setStyle(KoParagraphStyle*))); connect(spw, SIGNAL(newStyleRequested(QString)), this, SLOT(createStyleFromCurrentBlockFormat(QString))); connect(spw, SIGNAL(showStyleManager(int)), this, SLOT(showStyleManager(int))); // Connect to/with simple table widget (docker) connect(this, SIGNAL(styleManagerChanged(KoStyleManager*)), stw, SLOT(setStyleManager(KoStyleManager*))); connect(stw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(stw, SIGNAL(tableBorderDataUpdated(KoBorder::BorderData)), this, SLOT(setTableBorderData(KoBorder::BorderData))); // Connect to/with simple insert widget (docker) connect(siw, SIGNAL(doneWithFocus()), this, SLOT(returnFocusToCanvas())); connect(siw, SIGNAL(insertTableQuick(int,int)), this, SLOT(insertTableQuick(int,int))); updateStyleManager(); if (m_textShape) { updateActions(); } scw->setWindowTitle(i18n("Character")); widgets.append(scw); spw->setWindowTitle(i18n("Paragraph")); widgets.append(spw); bool useAdvancedText = !(canvas()->resourceManager()->intResource(KoCanvasResourceManager::ApplicationSpeciality) & KoCanvasResourceManager::NoAdvancedText); if (useAdvancedText) { stw->setWindowTitle(i18n("Table")); widgets.append(stw); siw->setWindowTitle(i18n("Insert")); widgets.append(siw); } return widgets; } void TextTool::returnFocusToCanvas() { canvas()->canvasWidget()->setFocus(); } void TextTool::startEditing(KUndo2Command* command) { m_currentCommand = command; m_currentCommandHasChildren = true; } void TextTool::stopEditing() { m_currentCommand = 0; m_currentCommandHasChildren = false; } void TextTool::insertNewSection() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) return; textEditor->newSection(); } void TextTool::configureSection() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) return; SectionFormatDialog *dia = new SectionFormatDialog(0, m_textEditor.data()); dia->exec(); delete dia; returnFocusToCanvas(); updateActions(); } void TextTool::splitSections() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) return; SectionsSplitDialog *dia = new SectionsSplitDialog(0, m_textEditor.data()); dia->exec(); delete dia; returnFocusToCanvas(); updateActions(); } void TextTool::pasteAsText() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor) return; const QMimeData *data = QApplication::clipboard()->mimeData(QClipboard::Clipboard); // on windows we do not have data if we try to paste this selection if (!data) return; if (data->hasFormat(KoOdf::mimeType(KoOdf::Text)) || data->hasText()) { m_prevCursorPosition = m_textEditor.data()->position(); m_textEditor.data()->paste(canvas(), data, true); editingPluginEvents(); } } void TextTool::bold(bool bold) { m_textEditor.data()->bold(bold); } void TextTool::italic(bool italic) { m_textEditor.data()->italic(italic); } void TextTool::underline(bool underline) { m_textEditor.data()->underline(underline); } void TextTool::strikeOut(bool strikeOut) { m_textEditor.data()->strikeOut(strikeOut); } void TextTool::nonbreakingSpace() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->insertText(QString(QChar(Qt::Key_nobreakspace))); } void TextTool::nonbreakingHyphen() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->insertText(QString(QChar(0x2013))); } void TextTool::softHyphen() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->insertText(QString(QChar(Qt::Key_hyphen))); } void TextTool::lineBreak() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->insertText(QString(QChar(0x2028))); } void TextTool::alignLeft() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignLeft | Qt::AlignAbsolute); } void TextTool::alignRight() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignRight | Qt::AlignAbsolute); } void TextTool::alignCenter() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignHCenter); } void TextTool::alignBlock() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setHorizontalTextAlignment(Qt::AlignJustify); } void TextTool::superScript(bool on) { if (!m_allowActions || !m_textEditor.data()) return; if (on) m_actionFormatSub->setChecked(false); m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignTop : Qt::AlignVCenter); } void TextTool::subScript(bool on) { if (!m_allowActions || !m_textEditor.data()) return; if (on) m_actionFormatSuper->setChecked(false); m_textEditor.data()->setVerticalTextAlignment(on ? Qt::AlignBottom : Qt::AlignVCenter); } void TextTool::increaseIndent() { if (!m_allowActions || !m_textEditor.data()) return; if (m_textEditor.data()->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::IncreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1); m_textEditor.data()->addCommand(cll); editingPluginEvents(); } else { m_textEditor.data()->increaseIndent(); } updateActions(); } void TextTool::decreaseIndent() { if (!m_allowActions || !m_textEditor.data()) return; if (m_textEditor.data()->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::DecreaseLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*(m_textEditor.data()->cursor()), type, 1); m_textEditor.data()->addCommand(cll); editingPluginEvents(); } else { m_textEditor.data()->decreaseIndent(); } updateActions(); } void TextTool::decreaseFontSize() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->decreaseFontSize(); } void TextTool::increaseFontSize() { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->increaseFontSize(); } void TextTool::setFontFamily(const QString &font) { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setFontFamily(font); } void TextTool::setFontSize (qreal size) { if (!m_allowActions || !m_textEditor.data()) return; m_textEditor.data()->setFontSize(size); } void TextTool::insertIndexMarker() { // TODO handle result when we figure out how to report errors from a tool. m_textEditor.data()->insertIndexMarker(); } void TextTool::insertFrameBreak() { m_textEditor.data()->insertFrameBreak(); ensureCursorVisible(); m_delayedEnsureVisible = true; } void TextTool::setStyle(KoCharacterStyle *style) { KoCharacterStyle *charStyle = style; //if the given KoCharacterStyle is null, set the KoParagraphStyle character properties if (!charStyle){ charStyle = static_cast(KoTextDocument(m_textShapeData->document()).styleManager()->paragraphStyle(m_textEditor.data()->blockFormat().intProperty(KoParagraphStyle::StyleId))); } if (charStyle) { m_textEditor.data()->setStyle(charStyle); updateActions(); } } void TextTool::setStyle(KoParagraphStyle *style) { m_textEditor.data()->setStyle(style); updateActions(); } void TextTool::insertTable() { TableDialog *dia = new TableDialog(0); if (dia->exec() == TableDialog::Accepted) m_textEditor.data()->insertTable(dia->rows(), dia->columns()); delete dia; updateActions(); } void TextTool::insertTableQuick(int rows, int columns) { m_textEditor.data()->insertTable(rows, columns); updateActions(); } void TextTool::insertTableRowAbove() { m_textEditor.data()->insertTableRowAbove(); } void TextTool::insertTableRowBelow() { m_textEditor.data()->insertTableRowBelow(); } void TextTool::insertTableColumnLeft() { m_textEditor.data()->insertTableColumnLeft(); } void TextTool::insertTableColumnRight() { m_textEditor.data()->insertTableColumnRight(); } void TextTool::deleteTableColumn() { m_textEditor.data()->deleteTableColumn(); } void TextTool::deleteTableRow() { m_textEditor.data()->deleteTableRow(); } void TextTool::mergeTableCells() { m_textEditor.data()->mergeTableCells(); } void TextTool::splitTableCells() { m_textEditor.data()->splitTableCells(); } void TextTool::useTableBorderCursor() { static const unsigned char data[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x68, 0x00, 0x00, 0x00, 0xf4, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x80, 0x7e, 0x00, 0x00, 0x40, 0x3f, 0x00, 0x00, 0xa0, 0x1f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x03, 0x00, 0x00, 0xe4, 0x01, 0x00, 0x00, 0xc2, 0x00, 0x00, 0x80, 0x41, 0x00, 0x00, 0x40, 0x32, 0x00, 0x00, 0xa0, 0x0f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xd0, 0x0f, 0x00, 0x00, 0xe8, 0x07, 0x00, 0x00, 0xf4, 0x01, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; QBitmap result(32, 32); result.fill(Qt::color0); QPainter painter(&result); painter.drawPixmap(0, 0, QBitmap::fromData(QSize(25, 23), data)); QBitmap brushMask = result.createHeuristicMask(false); useCursor(QCursor(result, brushMask, 1, 21)); } void TextTool::setTableBorderData(const KoBorder::BorderData &data) { m_tablePenMode = true; m_tablePenBorderData = data; } void TextTool::formatParagraph() { ParagraphSettingsDialog *dia = new ParagraphSettingsDialog(this, m_textEditor.data()); dia->setUnit(canvas()->unit()); dia->setImageCollection(m_textShape->imageCollection()); dia->exec(); delete dia; returnFocusToCanvas(); } void TextTool::testSlot(bool on) { qDebug() << "signal received. bool:" << on; } void TextTool::selectAll() { KoTextEditor *textEditor = m_textEditor.data(); if (!textEditor || !m_textShapeData) return; const int selectionLength = qAbs(textEditor->position() - textEditor->anchor()); textEditor->movePosition(QTextCursor::End); textEditor->setPosition(0, QTextCursor::KeepAnchor); repaintSelection(); if (selectionLength != qAbs(textEditor->position() - textEditor->anchor())) // it actually changed emit selectionChanged(true); } void TextTool::startMacro(const QString &title) { if (title != i18n("Key Press") && title !=i18n("Autocorrection")) //dirty hack while waiting for refactor of text editing m_textTyping = false; else m_textTyping = true; if (title != i18n("Delete") && title != i18n("Autocorrection")) //same dirty hack as above m_textDeleting = false; else m_textDeleting = true; if (m_currentCommand) return; class MacroCommand : public KUndo2Command { public: MacroCommand(const KUndo2MagicString &title) : KUndo2Command(title), m_first(true) {} virtual void redo() { if (! m_first) KUndo2Command::redo(); m_first = false; } virtual bool mergeWith(const KUndo2Command *) { return false; } bool m_first; }; /** * FIXME: The messages genearted by the Text Tool might not be * properly translated, since we don't control it in * type-safe way. * * The title is already translated string, we just don't * have any type control over it. */ KUndo2MagicString title_workaround = kundo2_noi18n(title); m_currentCommand = new MacroCommand(title_workaround); m_currentCommandHasChildren = false; } void TextTool::stopMacro() { if (!m_currentCommand) return; if (! m_currentCommandHasChildren) delete m_currentCommand; m_currentCommand = 0; } void TextTool::showStyleManager(int styleId) { if (!m_textShapeData) return; KoStyleManager *styleManager = KoTextDocument(m_textShapeData->document()).styleManager(); Q_ASSERT(styleManager); if (!styleManager) return; //don't crash StyleManagerDialog *dia = new StyleManagerDialog(canvas()->canvasWidget()); dia->setStyleManager(styleManager); dia->setUnit(canvas()->unit()); KoParagraphStyle *paragraphStyle = styleManager->paragraphStyle(styleId); if (paragraphStyle) { dia->setParagraphStyle(paragraphStyle); } KoCharacterStyle *characterStyle = styleManager->characterStyle(styleId); if (characterStyle) { dia->setCharacterStyle(characterStyle); } dia->show(); } void TextTool::startTextEditingPlugin(const QString &pluginId) { KoTextEditingPlugin *plugin = textEditingPluginContainer()->plugin(pluginId); if (plugin) { if (m_textEditor.data()->hasSelection()) { plugin->checkSection(m_textShapeData->document(), m_textEditor.data()->selectionStart(), m_textEditor.data()->selectionEnd()); } else plugin->finishedWord(m_textShapeData->document(), m_textEditor.data()->position()); } } void TextTool::canvasResourceChanged(int key, const QVariant &var) { if (m_textEditor.isNull()) return; if (!m_textShapeData) return; if (m_allowResourceManagerUpdates == false) return; if (key == KoText::CurrentTextPosition) { repaintSelection(); m_textEditor.data()->setPosition(var.toInt()); ensureCursorVisible(); } else if (key == KoText::CurrentTextAnchor) { repaintSelection(); int pos = m_textEditor.data()->position(); m_textEditor.data()->setPosition(var.toInt()); m_textEditor.data()->setPosition(pos, QTextCursor::KeepAnchor); } else if (key == KoCanvasResourceManager::Unit) { m_unit = var.value(); } else return; repaintSelection(); } void TextTool::insertSpecialCharacter() { if (m_specialCharacterDocker == 0) { m_specialCharacterDocker = new InsertCharacter(canvas()->canvasWidget()); connect(m_specialCharacterDocker, SIGNAL(insertCharacter(QString)), this, SLOT(insertString(QString))); } m_specialCharacterDocker->show(); } void TextTool::insertString(const QString& string) { m_textEditor.data()->insertText(string); returnFocusToCanvas(); } void TextTool::selectFont() { FontDia *fontDlg = new FontDia(m_textEditor.data()); fontDlg->exec(); delete fontDlg; returnFocusToCanvas(); } void TextTool::shapeAddedToCanvas() { qDebug(); if (m_textShape) { KoSelection *selection = canvas()->shapeManager()->selection(); KoShape *shape = selection->firstSelectedShape(); if (shape != m_textShape && canvas()->shapeManager()->shapes().contains(m_textShape)) { // this situation applies when someone, not us, changed the selection by selecting another // text shape. Possibly by adding one. // Deselect the new shape again, so we can keep editing what we were already editing selection->select(m_textShape); selection->deselect(shape); } } } void TextTool::shapeDataRemoved() { m_textShapeData = 0; m_textShape = 0; if (!m_textEditor.isNull() && !m_textEditor.data()->cursor()->isNull()) { const QTextDocument *doc = m_textEditor.data()->document(); Q_ASSERT(doc); KoTextDocumentLayout *lay = qobject_cast(doc->documentLayout()); if (!lay || lay->shapes().isEmpty()) { emit done(); return; } m_textShape = static_cast(lay->shapes().first()); m_textShapeData = static_cast(m_textShape->userData()); connect(m_textShapeData, SIGNAL(destroyed(QObject*)), this, SLOT(shapeDataRemoved())); } } void TextTool::createStyleFromCurrentBlockFormat(const QString &name) { KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoParagraphStyle *paragraphStyle = new KoParagraphStyle(m_textEditor.data()->blockFormat(), m_textEditor.data()->charFormat()); paragraphStyle->setName(name); styleManager->add(paragraphStyle); m_textEditor.data()->setStyle(paragraphStyle); emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); emit blockFormatChanged(m_textEditor.data()->blockFormat()); } void TextTool::createStyleFromCurrentCharFormat(const QString &name) { KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoCharacterStyle *originalCharStyle = styleManager->characterStyle(m_textEditor.data()->charFormat().intProperty(KoCharacterStyle::StyleId)); KoCharacterStyle *autoStyle; if (!originalCharStyle) { KoCharacterStyle blankStyle; originalCharStyle = &blankStyle; autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); autoStyle->setParentStyle(0); } else { autoStyle = originalCharStyle->autoStyle(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); } autoStyle->setName(name); styleManager->add(autoStyle); m_textEditor.data()->setStyle(autoStyle); emit charFormatChanged(m_textEditor.data()->charFormat(), m_textEditor.data()->blockCharFormat()); } // ---------- editing plugins methods. void TextTool::editingPluginEvents() { if (m_prevCursorPosition == -1 || m_prevCursorPosition == m_textEditor.data()->position()) { qDebug()<<"m_prevCursorPosition="<position()="<position(); return; } QTextBlock block = m_textEditor.data()->block(); if (! block.contains(m_prevCursorPosition)) { qDebug()<<"m_prevCursorPosition="<position(); if (from > to) qSwap(from, to); QString section = block.text().mid(from - block.position(), to - from); qDebug()<<"from="<values()) { plugin->finishedWord(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::finishedParagraph() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin* plugin, textEditingPluginContainer()->values()) { plugin->finishedParagraph(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::startingSimpleEdit() { if (m_textShapeData && textEditingPluginContainer()) { foreach (KoTextEditingPlugin* plugin, textEditingPluginContainer()->values()) { plugin->startingSimpleEdit(m_textShapeData->document(), m_prevCursorPosition); } } } void TextTool::setTextColor(const KoColor &color) { m_textEditor.data()->setTextColor(color.toQColor()); } void TextTool::setBackgroundColor(const KoColor &color) { m_textEditor.data()->setTextBackgroundColor(color.toQColor()); } void TextTool::setAutoResize(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoResize, enabled)); updateActions(); } void TextTool::setGrowWidthToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowWidth, enabled)); updateActions(); } void TextTool::setGrowHeightToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::AutoGrowHeight, enabled)); updateActions(); } void TextTool::setShrinkToFit(bool enabled) { m_textEditor.data()->addCommand(new AutoResizeCommand(m_textShapeData, KoTextShapeData::ShrinkToFitResize, enabled)); updateActions(); } void TextTool::runUrl(KoPointerEvent *event, QString &url) { QUrl _url = QUrl::fromLocalFile(url); if (_url.isLocalFile()) { QMimeDatabase db; QString type = db.mimeTypeForUrl(_url).name(); if (KRun::isExecutableFile(_url, type)) { QString question = i18n("This link points to the program or script '%1'.\n" "Malicious programs can harm your computer. " "Are you sure that you want to run this program?", url); // this will also start local programs, so adding a "don't warn again" // checkbox will probably be too dangerous int choice = KMessageBox::warningYesNo(0, question, i18n("Open Link?")); if (choice != KMessageBox::Yes) return; } } event->accept(); new KRun(_url, 0); } void TextTool::debugTextDocument() { #ifndef NDEBUG if (!m_textShapeData) return; const int CHARSPERLINE = 80; // TODO Make configurable using ENV var? const int CHARPOSITION = 278301935; KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); KoInlineTextObjectManager *inlineManager = document.inlineTextObjectManager(); QTextBlock block = m_textShapeData->document()->begin(); for (;block.isValid(); block = block.next()) { QVariant var = block.blockFormat().property(KoParagraphStyle::StyleId); if (!var.isNull()) { KoParagraphStyle *ps = styleManager->paragraphStyle(var.toInt()); qDebug() << "--- Paragraph Style:" << (ps ? ps->name() : QString()) << var.toInt(); } var = block.charFormat().property(KoCharacterStyle::StyleId); if (!var.isNull()) { KoCharacterStyle *cs = styleManager->characterStyle(var.toInt()); qDebug() << "--- Character Style:" << (cs ? cs->name() : QString()) << var.toInt(); } int lastPrintedChar = -1; QTextBlock::iterator it; QString fragmentText; QList inlineCharacters; for (it = block.begin(); !it.atEnd(); ++it) { QTextFragment fragment = it.fragment(); if (!fragment.isValid()) continue; QTextCharFormat fmt = fragment.charFormat(); qDebug() << "changeId: " << fmt.property(KoCharacterStyle::ChangeTrackerId); const int fragmentStart = fragment.position() - block.position(); for (int i = fragmentStart; i < fragmentStart + fragment.length(); i += CHARSPERLINE) { if (lastPrintedChar == fragmentStart-1) fragmentText += '|'; if (lastPrintedChar < fragmentStart || i > fragmentStart) { QString debug = block.text().mid(lastPrintedChar, CHARSPERLINE); lastPrintedChar += CHARSPERLINE; if (lastPrintedChar > block.length()) debug += "\\n"; qDebug() << debug; } var = fmt.property(KoCharacterStyle::StyleId); QString charStyleLong, charStyleShort; if (! var.isNull()) { // named style charStyleShort = QString::number(var.toInt()); KoCharacterStyle *cs = styleManager->characterStyle(var.toInt()); if (cs) charStyleLong = cs->name(); } if (inlineManager && fmt.hasProperty(KoCharacterStyle::InlineInstanceId)) { QTextCharFormat inlineFmt = fmt; inlineFmt.setProperty(CHARPOSITION, fragmentStart); inlineCharacters << inlineFmt; } if (fragment.length() > charStyleLong.length()) fragmentText += charStyleLong; else if (fragment.length() > charStyleShort.length()) fragmentText += charStyleShort; else if (fragment.length() >= 2) fragmentText += QChar(8230); // ellipsis int rest = fragmentStart - (lastPrintedChar-CHARSPERLINE) + fragment.length() - fragmentText.length(); rest = qMin(rest, CHARSPERLINE - fragmentText.length()); if (rest >= 2) fragmentText = QString("%1%2").arg(fragmentText).arg(' ', rest); if (rest >= 0) fragmentText += '|'; if (fragmentText.length() >= CHARSPERLINE) { qDebug() << fragmentText; fragmentText.clear(); } } } if (!fragmentText.isEmpty()) { qDebug() << fragmentText; } else if (block.length() == 1) { // no actual tet qDebug() << "\\n"; } foreach (const QTextCharFormat &cf, inlineCharacters) { KoInlineObject *object= inlineManager->inlineTextObject(cf); qDebug() << "At pos:" << cf.intProperty(CHARPOSITION) << object; // qDebug() << "-> id:" << cf.intProperty(577297549); } QTextList *list = block.textList(); if (list) { if (list->format().hasProperty(KoListStyle::StyleId)) { KoListStyle *ls = styleManager->listStyle(list->format().intProperty(KoListStyle::StyleId)); qDebug() << " List style applied:" << ls->styleId() << ls->name(); } else qDebug() << " +- is a list..." << list; } } #endif } void TextTool::debugTextStyles() { #ifndef NDEBUG if (!m_textShapeData) return; KoTextDocument document(m_textShapeData->document()); KoStyleManager *styleManager = document.styleManager(); QSet seenStyles; foreach (KoParagraphStyle *style, styleManager->paragraphStyles()) { qDebug() << style->styleId() << style->name() << (styleManager->defaultParagraphStyle() == style ? "[Default]" : ""); KoListStyle *ls = style->listStyle(); if (ls) { // optional ;) qDebug() << " +- ListStyle: " << ls->styleId() << ls->name() << (ls == styleManager->defaultListStyle() ? "[Default]":""); foreach (int level, ls->listLevels()) { KoListLevelProperties llp = ls->levelProperties(level); qDebug() << " | level" << llp.level() << " style (enum):" << llp.labelType(); if (llp.bulletCharacter().unicode() != 0) { qDebug() << " | bullet" << llp.bulletCharacter(); } } seenStyles << ls->styleId(); } } bool first = true; foreach (KoCharacterStyle *style, styleManager->characterStyles()) { if (seenStyles.contains(style->styleId())) continue; if (first) { qDebug() << "--- Character styles ---"; first = false; } qDebug() << style->styleId() << style->name(); qDebug() << style->font(); } first = true; foreach (KoListStyle *style, styleManager->listStyles()) { if (seenStyles.contains(style->styleId())) continue; if (first) { qDebug() << "--- List styles ---"; first = false; } qDebug() << style->styleId() << style->name() << (style == styleManager->defaultListStyle() ? "[Default]":""); } #endif } void TextTool::textDirectionChanged() { if (!m_allowActions || !m_textEditor.data()) return; QTextBlockFormat blockFormat; if (m_actionChangeDirection->isChecked()) { blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::RightLeftTopBottom); } else { blockFormat.setProperty(KoParagraphStyle::TextProgressionDirection, KoText::LeftRightTopBottom); } m_textEditor.data()->mergeBlockFormat(blockFormat); } void TextTool::setListLevel(int level) { if (level < 1 || level > 10) { return; } KoTextEditor *textEditor = m_textEditor.data(); if (textEditor->block().textList()) { ChangeListLevelCommand::CommandType type = ChangeListLevelCommand::SetLevel; ChangeListLevelCommand *cll = new ChangeListLevelCommand(*textEditor->cursor(), type, level); textEditor->addCommand(cll); editingPluginEvents(); } } void TextTool::insertAnnotation() { AnnotationTextShape *shape = (AnnotationTextShape*)KoShapeRegistry::instance()->value(AnnotationShape_SHAPEID)->createDefaultShape(canvas()->shapeController()->resourceManager()); textEditor()->addAnnotation(shape); // Set annotation creator. KConfig cfg("calligrarc"); cfg.reparseConfiguration(); KConfigGroup authorGroup(&cfg, "Author"); QStringList profiles = authorGroup.readEntry("profile-names", QStringList()); KSharedConfig::openConfig()->reparseConfiguration(); KConfigGroup appAuthorGroup( KSharedConfig::openConfig(), "Author"); QString profile = appAuthorGroup.readEntry("active-profile", ""); KConfigGroup cgs(&authorGroup, "Author-" + profile); if (profiles.contains(profile)) { KConfigGroup cgs(&authorGroup, "Author-" + profile); shape->setCreator(cgs.readEntry("creator")); } else { if (profile == "anonymous") { shape->setCreator("Anonymous"); } else { KUser user(KUser::UseRealUserID); shape->setCreator(user.property(KUser::FullName).toString()); } } // Set Annotation creation date. shape->setDate(QDate::currentDate().toString(Qt::ISODate)); } diff --git a/plugins/textshape/TextTool.h b/plugins/textshape/TextTool.h index 3a5413b5840..9989f20cac1 100644 --- a/plugins/textshape/TextTool.h +++ b/plugins/textshape/TextTool.h @@ -1,422 +1,423 @@ /* This file is part of the KDE project * Copyright (C) 2006-2009 Thomas Zander * Copyright (C) 2008 Thorsten Zachmann * Copyright (C) 2009 KO GmbH * Copyright (C) 2011 Mojtaba Shahi Senobari * Copyright (C) 2008, 2012 Pierre Stirnweiss * Copyright (C) 2014 Denis Kuplyakov * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public License * along with this library; see the file COPYING.LIB. If not, write to * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KOTEXTTOOL_H #define KOTEXTTOOL_H #include "TextShape.h" #include "KoPointedAt.h" #include #include #include #include +#include #include #include #include #include #include -#include +#include #include #include #include class InsertCharacter; class KoChangeTracker; class KoCharacterStyle; class KoColor; class KoColorPopupAction; class KoParagraphStyle; class KoStyleManager; class KoTextEditor; class UndoTextCommand; class QAction; class KActionMenu; class KoFontFamilyAction; class FontSizeAction; class KUndo2Command; class QDrag; class QMimeData; class MockCanvas; class TextToolSelection; /** * This is the tool for the text-shape (which is a flake-based plugin). */ class TextTool : public KoToolBase, public KoUndoableTool { Q_OBJECT public: explicit TextTool(KoCanvasBase *canvas); #ifndef NDEBUG explicit TextTool(MockCanvas *canvas); #endif virtual ~TextTool(); /// reimplemented from superclass virtual void paint(QPainter &painter, const KoViewConverter &converter); /// reimplemented from superclass virtual void mousePressEvent(KoPointerEvent *event); /// reimplemented from superclass virtual void mouseDoubleClickEvent(KoPointerEvent *event); /// reimplemented from superclass virtual void mouseTripleClickEvent(KoPointerEvent *event); /// reimplemented from superclass virtual void mouseMoveEvent(KoPointerEvent *event); /// reimplemented from superclass virtual void mouseReleaseEvent(KoPointerEvent *event); /// reimplemented from superclass virtual void shortcutOverrideEvent(QKeyEvent *event); /// reimplemented from superclass virtual void keyPressEvent(QKeyEvent *event); /// reimplemented from superclass virtual void keyReleaseEvent(QKeyEvent *event); /// reimplemented from superclass virtual void activate(ToolActivation toolActivation, const QSet &shapes); /// reimplemented from superclass virtual void deactivate(); /// reimplemented from superclass virtual void copy() const; /// reimplemented from KoUndoableTool virtual void setAddUndoCommandAllowed(bool allowed) { m_allowAddUndoCommand = allowed; } ///reimplemented virtual void deleteSelection(); /// reimplemented from superclass virtual void cut(); /// reimplemented from superclass virtual bool paste(); /// reimplemented from superclass virtual QStringList supportedPasteMimeTypes() const; /// reimplemented from superclass virtual void dragMoveEvent(QDragMoveEvent *event, const QPointF &point); /// reimplemented from superclass void dragLeaveEvent(QDragLeaveEvent *event); /// reimplemented from superclass virtual void dropEvent(QDropEvent *event, const QPointF &point); /// reimplemented from superclass virtual void repaintDecorations(); /// reimplemented from superclass virtual KoToolSelection* selection(); /// reimplemented from superclass virtual QList > createOptionWidgets(); /// reimplemented from superclass virtual QVariant inputMethodQuery(Qt::InputMethodQuery query, const KoViewConverter &converter) const; /// reimplemented from superclass virtual void inputMethodEvent(QInputMethodEvent * event); /// The following two methods allow an undo/redo command to tell the tool, it will modify the QTextDocument and wants to be parent of the undo/redo commands resulting from these changes. void startEditing(KUndo2Command* command); void stopEditing(); void setShapeData(KoTextShapeData *data); QRectF caretRect(QTextCursor *cursor, bool *upToDate=0) const; QRectF textRect(QTextCursor &cursor) const; protected: virtual void createActions(); TextShape *textShape() {return m_textShape;} friend class SimpleParagraphWidget; friend class ParagraphSettingsDialog; KoTextEditor *textEditor() { return m_textEditor.data(); } public Q_SLOTS: /// Insert comment to document. void insertAnnotation(); /// start the textedit-plugin. void startTextEditingPlugin(const QString &pluginId); /// reimplemented from KoToolBase virtual void canvasResourceChanged(int key, const QVariant &res); Q_SIGNALS: /// emitted every time a different styleManager is set. void styleManagerChanged(KoStyleManager *manager); /// emitted every time a caret move leads to a different character format being under the caret void charFormatChanged(const QTextCharFormat &format, const QTextCharFormat& refBlockCharFormat); /// emitted every time a caret move leads to a different paragraph format being under the caret void blockFormatChanged(const QTextBlockFormat &format); /// emitted every time a caret move leads to a different paragraph format being under the caret void blockChanged(const QTextBlock &block); private Q_SLOTS: /// inserts new paragraph and includes it into the new section void insertNewSection(); /// configures params of the current section void configureSection(); /// inserts paragraph between sections bounds void splitSections(); /// paste text from the clipboard without formatting void pasteAsText(); /// make the selected text bold or not void bold(bool); /// make the selected text italic or not void italic(bool); /// underline of the selected text void underline(bool underline); /// strikethrough of the selected text void strikeOut(bool strikeOut); /// insert a non breaking space at the caret position void nonbreakingSpace(); /// insert a non breaking hyphen at the caret position void nonbreakingHyphen(); /// insert a soft hyphen at the caret position void softHyphen(); /// insert a linebreak at the caret position void lineBreak(); /// force the remainder of the text into the next page void insertFrameBreak(); /// align all of the selected text left void alignLeft(); /// align all of the selected text right void alignRight(); /// align all of the selected text centered void alignCenter(); /// align all of the selected text block-justified void alignBlock(); /// make the selected text switch to be super-script void superScript(bool); /// make the selected text switch to be sub-script void subScript(bool); /// move the paragraph indent of the selected text to be less (left on LtR text) void decreaseIndent(); /// move the paragraph indent of the selected text to be more (right on LtR text) void increaseIndent(); /// Increase the font size. This will preserve eventual difference in font size within the selection. void increaseFontSize(); /// Decrease font size. See above. void decreaseFontSize(); /// Set font family void setFontFamily(const QString &); /// Set Font size void setFontSize(qreal size); /// see KoTextEditor::insertIndexMarker void insertIndexMarker(); /// shows a dialog to insert a table void insertTable(); /// insert a table of given dimensions void insertTableQuick(int rows, int columns); /// insert a row above void insertTableRowAbove(); /// insert a row below void insertTableRowBelow(); /// insert a column left void insertTableColumnLeft(); /// insert a column right void insertTableColumnRight(); /// delete a column void deleteTableColumn(); /// delete a row void deleteTableRow(); /// merge table cells void mergeTableCells(); /// split previous merged table cells void splitTableCells(); /// format the table border (enter table pen mode) void setTableBorderData(const KoBorder::BorderData &data); /// shows a dialog to alter the paragraph properties void formatParagraph(); /// select all text in the current document. void selectAll(); /// show the style manager void showStyleManager(int styleId = -1); /// change color of a selected text void setTextColor(const KoColor &color); /// change background color of a selected text void setBackgroundColor(const KoColor &color); /// Enable or disable auto-resize void setAutoResize(bool enabled); /// Enable or disable grow-width-to-fit-text. void setGrowWidthToFit(bool enabled); /// Enable or disable grow-height-to-fit-text. void setGrowHeightToFit(bool enabled); /// Enable or disable shrink-to-fit-text. void setShrinkToFit(bool enabled); /// set Paragraph style of current selection. Existing style will be completely overridden. void setStyle(KoParagraphStyle *syle); /// set the characterStyle of the current selection. see above. void setStyle(KoCharacterStyle *style); /// set the level of current selected list void setListLevel(int level); /// slot to call when a series of commands is started that together need to become 1 undo action. void startMacro(const QString &title); /// slot to call when a series of commands has ended that together should be 1 undo action. void stopMacro(); /// show the insert special character docker. void insertSpecialCharacter(); /// insert string void insertString(const QString &string); /// returns the focus to canvas when styles are selected in the optionDocker void returnFocusToCanvas(); void selectFont(); void shapeAddedToCanvas(); void blinkCaret(); void relayoutContent(); // called when the m_textShapeData has been deleted. void shapeDataRemoved(); //Show tooltip with editing info void showEditTip(); /// print debug about the details of the text document void debugTextDocument(); /// print debug about the details of the styles on the current text document void debugTextStyles(); void ensureCursorVisible(bool moveView = true); void createStyleFromCurrentBlockFormat(const QString &name); void createStyleFromCurrentCharFormat(const QString &name); void testSlot(bool); /// change block text direction void textDirectionChanged(); void updateActions(); private: void repaintCaret(); void repaintSelection(); KoPointedAt hitTest(const QPointF & point) const; void updateStyleManager(); void updateSelectedShape(const QPointF &point, bool noDocumentChange); void updateSelectionHandler(); void editingPluginEvents(); void finishedWord(); void finishedParagraph(); void startingSimpleEdit(); void runUrl(KoPointerEvent *event, QString &url); void useTableBorderCursor(); QMimeData *generateMimeData() const; TextEditingPluginContainer *textEditingPluginContainer(); private: friend class UndoTextCommand; friend class ChangeTracker; friend class TextCutCommand; friend class ShowChangesCommand; TextShape *m_textShape; // where caret of m_textEditor currently is KoTextShapeData *m_textShapeData; // where caret of m_textEditor currently is - QWeakPointer m_textEditor; - QWeakPointer m_oldTextEditor; + QPointer m_textEditor; + QPointer m_oldTextEditor; KoChangeTracker *m_changeTracker; KoUnit m_unit; bool m_allowActions; bool m_allowAddUndoCommand; bool m_allowResourceManagerUpdates; int m_prevCursorPosition; /// used by editingPluginEvents int m_prevMouseSelectionStart, m_prevMouseSelectionEnd; QTimer m_caretTimer; bool m_caretTimerState; QAction *m_actionPasteAsText; QAction *m_actionFormatBold; QAction *m_actionFormatItalic; QAction *m_actionFormatUnderline; QAction *m_actionFormatStrikeOut; QAction *m_actionAlignLeft; QAction *m_actionAlignRight; QAction *m_actionAlignCenter; QAction *m_actionAlignBlock; QAction *m_actionFormatSuper; QAction *m_actionFormatSub; QAction *m_actionFormatIncreaseIndent; QAction *m_actionFormatDecreaseIndent; QAction *m_autoResizeAction; QAction *m_growWidthAction; QAction *m_growHeightAction; QAction *m_shrinkToFitAction; QAction *m_actionChangeDirection; QAction *m_actionInsertSection; QAction *m_actionConfigureSection; QAction *m_actionSplitSections; KActionMenu *m_variableMenu; FontSizeAction *m_actionFormatFontSize; KoFontFamilyAction *m_actionFormatFontFamily; KoColorPopupAction *m_actionFormatTextColor; KoColorPopupAction *m_actionFormatBackgroundColor; KUndo2Command *m_currentCommand; //this command will be the direct parent of undoCommands generated as the result of QTextDocument changes bool m_currentCommandHasChildren; InsertCharacter *m_specialCharacterDocker; QPointer m_textEditingPlugins; bool m_textTyping; bool m_textDeleting; QTimer m_editTipTimer; KoPointedAt m_editTipPointedAt; QPoint m_editTipPos; bool m_delayedEnsureVisible; TextToolSelection *m_toolSelection; KoPointedAt m_tableDragInfo; bool m_tableDraggedOnce; bool m_tableDragWithShift; QPointF m_draggingOrigin; qreal m_dx; qreal m_dy; bool m_tablePenMode; KoBorder::BorderData m_tablePenBorderData; mutable QRectF m_lastImMicroFocus; bool m_clickWithinSelection; QDrag *m_drag; QAbstractTextDocumentLayout::Selection m_preDragSelection; }; #endif