bool PasteCommand::processXmlData(Element *element, KoXmlDocument *data) { const QRect pasteArea = element->rect(); Sheet *const sheet = element->sheet(); Q_ASSERT(sheet == m_sheet); Map *const map = sheet->map(); const KoXmlElement root = data->documentElement(); // "spreadsheet-snippet" if (root.hasAttribute("cut")) { const Region cutRegion(root.attribute("cut"), map, sheet); if (cutRegion.isValid()) { const Cell destination(sheet, pasteArea.topLeft()); map->dependencyManager()->regionMoved(cutRegion, destination); } } const int sourceHeight = root.attribute("rows").toInt(); const int sourceWidth = root.attribute("columns").toInt(); // Find size of rectangle that we want to paste to (either clipboard size or current selection) const bool noRowsInClipboard = root.namedItem("rows").toElement().isNull(); const bool noColumnsInClipboard = root.namedItem("columns").toElement().isNull(); const bool noRowsSelected = !Region::Range(pasteArea).isRow(); const bool noColumnsSelected = !Region::Range(pasteArea).isColumn(); const bool biggerSelectedWidth = pasteArea.width() >= sourceWidth; const bool biggerSelectedHeight = pasteArea.height() >= sourceHeight; const int pasteWidth = biggerSelectedWidth && noRowsSelected && noRowsInClipboard ? pasteArea.width() : sourceWidth; const int pasteHeight = biggerSelectedHeight && noColumnsSelected && noColumnsInClipboard ? pasteArea.height() : sourceHeight; const int xOffset = noRowsInClipboard ? pasteArea.left() - 1 : 0; const int yOffset = noColumnsInClipboard ? pasteArea.top() - 1 : 0; kDebug(36005) << "selected size (col x row):" << pasteArea.width() << 'x' << pasteArea.height(); kDebug(36005) << "source size (col x row):" << sourceWidth << 'x' << sourceHeight; kDebug(36005) << "paste area size (col x row):" << pasteWidth << 'x' << pasteHeight; kDebug(36005) << "xOffset:" << xOffset << "yOffset:" << yOffset; // Determine the shift direction, if needed. if (m_insertMode == ShiftCells) { if (!noColumnsInClipboard && !noRowsInClipboard) { // There are columns and rows in the source data. m_insertMode = ShiftCellsRight; // faster than down } else if (!noColumnsInClipboard) { // There are columns in the source data. m_insertMode = ShiftCellsRight; } else if (!noRowsInClipboard) { // There are rows in the source data. m_insertMode = ShiftCellsDown; } else { // Should not happen. // ShiftCells should only be set, if the data contains columns/rows. Q_ASSERT(false); m_insertMode = ShiftCellsRight; // faster than down } } const bool noColumns = noColumnsInClipboard && noColumnsSelected; const bool noRows = noRowsInClipboard && noRowsSelected; // Shift cells down. if (m_insertMode == ShiftCellsDown) { // Cases: // 1. Columns AND rows are contained in either source or selection // 1.a Columns in source and rows in selection // I.e. yOffset=0 // Clear everything. // Taking the column data/style and fill all columns. // 1.b Columns and rows in source, but not in selection // I.e. xOffset=0,yOffset=0 // Leave everything as is? No, data from different sheet is possible! // Clear everything. // Fill with the source column/row data/style, // i.e. the sheet data becomes equal to the source data. // Same procedure as in 1.e // 1.c Columns and rows in selection, but not in source // Clear everything. // Fill with the source data. Tiling -> HUGE task! // 1.d Rows in source and columns in selection // I.e. xOffset=0 // Clear everything. // Taking the row data/style and fill all rows. // 1.e Columns AND rows in both // I.e. xOffset=0,yOffset=0 // Leave everything as is? No, data from different sheet is possible! // Clear everything. // Fill with the source column/row data/style, // i.e. the sheet data becomes equal to the source data. // Same procedure as in 1.b // 2. Columns are present in either source or selection, but no rows // 2a Columns in source // I.e. yOffset=0 // Clear the appropriate columns in the paste area. // Fill them with the source data. // 2b Columns in selection // Clear the selected columns. // Fill them with the source data. Tiling -> HUGE task! // 2c Columns in both // I.e. yOffset=0 // Clear the selected columns. // Fill them with the source column data/style. // 3. Rows are present in either source or selection, but no columns // 3a Rows in source // I.e. xOffset=0 // Insert rows. // Fill in data. // 3b Rows in selection // Insert rows. // Fill in data. Tiling -> HUGE task! // 3c Rows in both // I.e. xOffset=0 // Insert rows. // Fill in data/style from source rows. // 4. Neither column, nor rows are present // Shift the cell down. // Fill in data. if ((!noColumns && !noRows) || (!noColumns && noRows)) { // Everything or only columns present. DeleteCommand *const command = new DeleteCommand(this); command->setSheet(m_sheet); command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet)); command->setMode(DeleteCommand::OnlyCells); } else if (noColumns && !noRows) { // Rows present. InsertDeleteRowManipulator *const command = new InsertDeleteRowManipulator(this); command->setSheet(sheet); command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet)); } else { // Neither columns, nor rows present. ShiftManipulator *const command = new ShiftManipulator(this); command->setSheet(sheet); command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet)); command->setDirection(ShiftManipulator::ShiftBottom); } } // Shift cells right. if (m_insertMode == ShiftCellsRight) { // Cases: // Same as for ShiftCellsDown, // except that clearing and inserting are exchanged for cases 2 and 3. // Shifting a column to the right is the same as column insertion. // Shifting a row to the right is the same as clearing the row. if ((!noColumns && !noRows) || (noColumns && !noRows)) { // Everything or only rows present. DeleteCommand *const command = new DeleteCommand(this); command->setSheet(m_sheet); command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet)); command->setMode(DeleteCommand::OnlyCells); } else if (!noColumns && noRows) { // Columns present. InsertDeleteColumnManipulator *const command = new InsertDeleteColumnManipulator(this); command->setSheet(sheet); command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet)); } else { // Neither columns, nor rows present. ShiftManipulator *const command = new ShiftManipulator(this); command->setSheet(sheet); command->add(Region(pasteArea.x(), pasteArea.y(), pasteWidth, pasteHeight, sheet)); command->setDirection(ShiftManipulator::ShiftRight); } } // This command will collect as many cell loads as possible in the iteration. PasteCellCommand *pasteCellCommand = 0; KoXmlElement e = root.firstChild().toElement(); // "columns", "rows" or "cell" for (; !e.isNull(); e = e.nextSibling().toElement()) { // If the element is not a cell, unset the pasteCellCommand pointer. // If existing, it is attached as child commnand, so no leaking here. if (e.tagName() != "cell") { pasteCellCommand = 0; } // entire columns given if (e.tagName() == "columns" && !sheet->isProtected()) { const int number = e.attribute("count").toInt(); if (m_insertMode == NoInsertion) { // Clear the existing content; not the column style. DeleteCommand *const command = new DeleteCommand(this); command->setSheet(m_sheet); const int col = e.attribute("column").toInt(); const int cols = qMax(pasteArea.width(), number); const Region region(col + xOffset, 1, cols, KS_rowMax, m_sheet); command->add(region); command->setMode(DeleteCommand::OnlyCells); } // Set the column style. ColumnFormat columnFormat; columnFormat.setSheet(sheet); KoXmlElement c = e.firstChild().toElement(); for (; !c.isNull(); c = c.nextSibling().toElement()) { if (c.tagName() != "column") { continue; } if (columnFormat.load(c, xOffset, m_pasteMode)) { const int col = columnFormat.column(); const int cols = qMax(pasteArea.width(), number); for (int coff = 0; col - xOffset + coff <= cols; coff += number) { ColumnStyleCommand *const command = new ColumnStyleCommand(this); command->setSheet(m_sheet); command->add(Region(col + coff, 1, 1, 1, m_sheet)); command->setTemplate(columnFormat); } } } } // entire rows given if (e.tagName() == "rows" && !sheet->isProtected()) { const int number = e.attribute("count").toInt(); if (m_insertMode == NoInsertion) { // Clear the existing content; not the row style. DeleteCommand *const command = new DeleteCommand(this); command->setSheet(m_sheet); const int row = e.attribute("row").toInt(); const int rows = qMax(pasteArea.height(), number); const Region region(1, row + yOffset, KS_colMax, rows, m_sheet); command->add(region); command->setMode(DeleteCommand::OnlyCells); } // Set the row style. RowFormat rowFormat; rowFormat.setSheet(sheet); KoXmlElement c = e.firstChild().toElement(); for (; !c.isNull(); c = c.nextSibling().toElement()) { if (c.tagName() != "row") { continue; } if (rowFormat.load(c, yOffset, m_pasteMode)) { const int row = rowFormat.row(); const int rows = qMax(pasteArea.height(), number); for (int roff = 0; row - yOffset + roff <= rows; roff += number) { RowStyleCommand *const command = new RowStyleCommand(this); command->setSheet(m_sheet); command->add(Region(1, rowFormat.row(), 1, 1, m_sheet)); command->setTemplate(rowFormat); } } } } if (e.tagName() == "cell") { // Create a new PasteCellCommand, if necessary. if (!pasteCellCommand) { pasteCellCommand = new PasteCellCommand(this); pasteCellCommand->setSheet(m_sheet); pasteCellCommand->m_pasteMode = m_pasteMode; pasteCellCommand->m_pasteOperation = m_operation; pasteCellCommand->m_pasteFC = m_pasteFC; } // Source cell location: const int row = e.attribute("row").toInt(); const int col = e.attribute("column").toInt(); // tile the selection with the clipboard contents for (int roff = 0; row + roff <= pasteHeight; roff += sourceHeight) { for (int coff = 0; col + coff <= pasteWidth; coff += sourceWidth) { kDebug(36005) << "cell at" << (col + xOffset + coff) << ',' << (row + yOffset + roff) << " with roff,coff=" << roff << ',' << coff << ", xOffset:" << xOffset << ", yOffset:" << yOffset << endl; // Destination cell: const Cell cell(sheet, col + xOffset + coff, row + yOffset + roff); // Do nothing, if the sheet and the cell are protected. if (sheet->isProtected() && !cell.style().notProtected()) { continue; } // Add the destination cell and the XML element itself. pasteCellCommand->addXmlElement(cell, e); } } } } return true; }
void Validity::loadOdfValidation(Cell* const cell, const QString& validationName, OdfLoadingContext& tableContext) { KoXmlElement element = tableContext.validities.value(validationName); Validity validity; if (element.hasAttributeNS(KoXmlNS::table, "condition")) { QString valExpression = element.attributeNS(KoXmlNS::table, "condition", QString()); kDebug(36003) << " element.attribute( table:condition )" << valExpression; //Condition ::= ExtendedTrueCondition | TrueFunction 'and' TrueCondition //TrueFunction ::= cell-content-is-whole-number() | cell-content-is-decimal-number() | cell-content-is-date() | cell-content-is-time() //ExtendedTrueCondition ::= ExtendedGetFunction | cell-content-text-length() Operator Value //TrueCondition ::= GetFunction | cell-content() Operator Value //GetFunction ::= cell-content-is-between(Value, Value) | cell-content-is-not-between(Value, Value) //ExtendedGetFunction ::= cell-content-text-length-is-between(Value, Value) | cell-content-text-length-is-not-between(Value, Value) //Operator ::= '<' | '>' | '<=' | '>=' | '=' | '!=' //Value ::= NumberValue | String | Formula //A Formula is a formula without an equals (=) sign at the beginning. See section 8.1.3 for more information. //A String comprises one or more characters surrounded by quotation marks. //A NumberValue is a whole or decimal number. It must not contain comma separators for numbers of 1000 or greater. //ExtendedTrueCondition if (valExpression.contains("cell-content-text-length()")) { //"cell-content-text-length()>45" valExpression = valExpression.remove("oooc:cell-content-text-length()"); kDebug(36003) << " valExpression = :" << valExpression; setRestriction(Validity::TextLength); loadOdfValidationCondition(valExpression, cell->sheet()->map()->parser()); } else if (valExpression.contains("cell-content-is-text()")) { setRestriction(Validity::Text); } //cell-content-text-length-is-between(Value, Value) | cell-content-text-length-is-not-between(Value, Value) | cell-content-is-in-list( StringList ) else if (valExpression.contains("cell-content-text-length-is-between")) { setRestriction(Validity::TextLength); setCondition(Conditional::Between); valExpression = valExpression.remove("oooc:cell-content-text-length-is-between("); kDebug(36003) << " valExpression :" << valExpression; valExpression = valExpression.remove(')'); QStringList listVal = valExpression.split(',', QString::SkipEmptyParts); loadOdfValidationValue(listVal, cell->sheet()->map()->parser()); } else if (valExpression.contains("cell-content-text-length-is-not-between")) { setRestriction(Validity::TextLength); setCondition(Conditional::Different); valExpression = valExpression.remove("oooc:cell-content-text-length-is-not-between("); kDebug(36003) << " valExpression :" << valExpression; valExpression = valExpression.remove(')'); kDebug(36003) << " valExpression :" << valExpression; QStringList listVal = valExpression.split(',', QString::SkipEmptyParts); loadOdfValidationValue(listVal, cell->sheet()->map()->parser()); } else if (valExpression.contains("cell-content-is-in-list(")) { setRestriction(Validity::List); valExpression = valExpression.remove("oooc:cell-content-is-in-list("); kDebug(36003) << " valExpression :" << valExpression; valExpression = valExpression.remove(')'); setValidityList(valExpression.split(';', QString::SkipEmptyParts)); } //TrueFunction ::= cell-content-is-whole-number() | cell-content-is-decimal-number() | cell-content-is-date() | cell-content-is-time() else { if (valExpression.contains("cell-content-is-whole-number()")) { setRestriction(Validity::Number); valExpression = valExpression.remove("oooc:cell-content-is-whole-number() and "); } else if (valExpression.contains("cell-content-is-decimal-number()")) { setRestriction(Validity::Integer); valExpression = valExpression.remove("oooc:cell-content-is-decimal-number() and "); } else if (valExpression.contains("cell-content-is-date()")) { setRestriction(Validity::Date); valExpression = valExpression.remove("oooc:cell-content-is-date() and "); } else if (valExpression.contains("cell-content-is-time()")) { setRestriction(Validity::Time); valExpression = valExpression.remove("oooc:cell-content-is-time() and "); } kDebug(36003) << "valExpression :" << valExpression; if (valExpression.contains("cell-content()")) { valExpression = valExpression.remove("cell-content()"); loadOdfValidationCondition(valExpression, cell->sheet()->map()->parser()); } //GetFunction ::= cell-content-is-between(Value, Value) | cell-content-is-not-between(Value, Value) //for the moment we support just int/double value, not text/date/time :( if (valExpression.contains("cell-content-is-between(")) { valExpression = valExpression.remove("cell-content-is-between("); valExpression = valExpression.remove(')'); QStringList listVal = valExpression.split(',', QString::SkipEmptyParts); loadOdfValidationValue(listVal, cell->sheet()->map()->parser()); setCondition(Conditional::Between); } if (valExpression.contains("cell-content-is-not-between(")) { valExpression = valExpression.remove("cell-content-is-not-between("); valExpression = valExpression.remove(')'); QStringList listVal = valExpression.split(',', QString::SkipEmptyParts); loadOdfValidationValue(listVal, cell->sheet()->map()->parser()); setCondition(Conditional::Different); } } } if (element.hasAttributeNS(KoXmlNS::table, "allow-empty-cell")) { kDebug(36003) << " element.hasAttribute( table:allow-empty-cell ) :" << element.hasAttributeNS(KoXmlNS::table, "allow-empty-cell"); setAllowEmptyCell(((element.attributeNS(KoXmlNS::table, "allow-empty-cell", QString()) == "true") ? true : false)); } if (element.hasAttributeNS(KoXmlNS::table, "base-cell-address")) { //todo what is it ? } KoXmlElement help = KoXml::namedItemNS(element, KoXmlNS::table, "help-message"); if (!help.isNull()) { if (help.hasAttributeNS(KoXmlNS::table, "title")) { kDebug(36003) << "help.attribute( table:title ) :" << help.attributeNS(KoXmlNS::table, "title", QString()); setTitleInfo(help.attributeNS(KoXmlNS::table, "title", QString())); } if (help.hasAttributeNS(KoXmlNS::table, "display")) { kDebug(36003) << "help.attribute( table:display ) :" << help.attributeNS(KoXmlNS::table, "display", QString()); setDisplayValidationInformation(((help.attributeNS(KoXmlNS::table, "display", QString()) == "true") ? true : false)); } KoXmlElement attrText = KoXml::namedItemNS(help, KoXmlNS::text, "p"); if (!attrText.isNull()) { kDebug(36003) << "help text :" << attrText.text(); setMessageInfo(attrText.text()); } } KoXmlElement error = KoXml::namedItemNS(element, KoXmlNS::table, "error-message"); if (!error.isNull()) { if (error.hasAttributeNS(KoXmlNS::table, "title")) setTitle(error.attributeNS(KoXmlNS::table, "title", QString())); if (error.hasAttributeNS(KoXmlNS::table, "message-type")) { QString str = error.attributeNS(KoXmlNS::table, "message-type", QString()); if (str == "warning") setAction(Validity::Warning); else if (str == "information") setAction(Validity::Information); else if (str == "stop") setAction(Validity::Stop); else kDebug(36003) << "validation : message type unknown :" << str; } if (error.hasAttributeNS(KoXmlNS::table, "display")) { kDebug(36003) << " display message :" << error.attributeNS(KoXmlNS::table, "display", QString()); setDisplayMessage((error.attributeNS(KoXmlNS::table, "display", QString()) == "true")); } KoXmlElement attrText = KoXml::namedItemNS(error, KoXmlNS::text, "p"); if (!attrText.isNull()) setMessage(attrText.text()); } cell->setValidity(validity); }
bool Validity::loadXML(Cell* const cell, const KoXmlElement& validityElement) { ValueParser *const parser = cell->sheet()->map()->parser(); bool ok = false; KoXmlElement param = validityElement.namedItem("param").toElement(); if (!param.isNull()) { if (param.hasAttribute("cond")) { d->cond = (Conditional::Type) param.attribute("cond").toInt(&ok); if (!ok) return false; } if (param.hasAttribute("action")) { d->action = (Action) param.attribute("action").toInt(&ok); if (!ok) return false; } if (param.hasAttribute("allow")) { d->restriction = (Restriction) param.attribute("allow").toInt(&ok); if (!ok) return false; } if (param.hasAttribute("valmin")) { d->minValue = parser->tryParseNumber(param.attribute("valmin"), &ok); if (!ok) return false; } if (param.hasAttribute("valmax")) { d->maxValue = parser->tryParseNumber(param.attribute("valmax"), &ok); if (!ok) return false; } if (param.hasAttribute("displaymessage")) { d->displayMessage = (bool)param.attribute("displaymessage").toInt(); } if (param.hasAttribute("displayvalidationinformation")) { d->displayValidationInformation = (bool)param.attribute("displayvalidationinformation").toInt(); } if (param.hasAttribute("allowemptycell")) { d->allowEmptyCell = (bool)param.attribute("allowemptycell").toInt(); } if (param.hasAttribute("listvalidity")) { d->listValidity = param.attribute("listvalidity").split(';', QString::SkipEmptyParts); } } KoXmlElement inputTitle = validityElement.namedItem("inputtitle").toElement(); if (!inputTitle.isNull()) { d->titleInfo = inputTitle.text(); } KoXmlElement inputMessage = validityElement.namedItem("inputmessage").toElement(); if (!inputMessage.isNull()) { d->messageInfo = inputMessage.text(); } KoXmlElement titleElement = validityElement.namedItem("title").toElement(); if (!titleElement.isNull()) { d->title = titleElement.text(); } KoXmlElement messageElement = validityElement.namedItem("message").toElement(); if (!messageElement.isNull()) { d->message = messageElement.text(); } KoXmlElement timeMinElement = validityElement.namedItem("timemin").toElement(); if (!timeMinElement.isNull()) { d->minValue = parser->tryParseTime(timeMinElement.text()); } KoXmlElement timeMaxElement = validityElement.namedItem("timemax").toElement(); if (!timeMaxElement.isNull()) { d->maxValue = parser->tryParseTime(timeMaxElement.text()); } KoXmlElement dateMinElement = validityElement.namedItem("datemin").toElement(); if (!dateMinElement.isNull()) { d->minValue = parser->tryParseTime(dateMinElement.text()); } KoXmlElement dateMaxElement = validityElement.namedItem("datemax").toElement(); if (!dateMaxElement.isNull()) { d->maxValue = parser->tryParseTime(dateMaxElement.text()); } return true; }
bool SvgParser::parseGradient(const KoXmlElement &e, const KoXmlElement &referencedBy) { // IMPROVEMENTS: // - Store the parsed colorstops in some sort of a cache so they don't need to be parsed again. // - A gradient inherits attributes it does not have from the referencing gradient. // - Gradients with no color stops have no fill or stroke. // - Gradients with one color stop have a solid color. SvgGraphicsContext *gc = m_context.currentGC(); if (!gc) return false; SvgGradientHelper gradhelper; if (e.hasAttribute("xlink:href")) { QString href = e.attribute("xlink:href").mid(1); if (! href.isEmpty()) { // copy the referenced gradient if found SvgGradientHelper *pGrad = findGradient(href); if (pGrad) gradhelper = *pGrad; } else { //gc->fillType = SvgGraphicsContext::None; // <--- TODO Fill OR Stroke are none return false; } } // Use the gradient that is referencing, or if there isn't one, the original gradient. KoXmlElement b; if (!referencedBy.isNull()) b = referencedBy; else b = e; QString gradientId = b.attribute("id"); if (! gradientId.isEmpty()) { // check if we have this gradient already parsed // copy existing gradient if it exists if (m_gradients.find(gradientId) != m_gradients.end()) gradhelper.copyGradient(m_gradients[ gradientId ].gradient()); } if (b.attribute("gradientUnits") == "userSpaceOnUse") gradhelper.setGradientUnits(SvgGradientHelper::UserSpaceOnUse); // parse color prop QColor c = gc->currentColor; if (!b.attribute("color").isEmpty()) { m_context.styleParser().parseColor(c, b.attribute("color")); } else { // try style attr QString style = b.attribute("style").simplified(); const QStringList substyles = style.split(';', QString::SkipEmptyParts); for (QStringList::ConstIterator it = substyles.begin(); it != substyles.end(); ++it) { QStringList substyle = it->split(':'); QString command = substyle[0].trimmed(); QString params = substyle[1].trimmed(); if (command == "color") m_context.styleParser().parseColor(c, params); } } gc->currentColor = c; if (b.tagName() == "linearGradient") { QLinearGradient *g = new QLinearGradient(); if (gradhelper.gradientUnits() == SvgGradientHelper::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setStart(QPointF(SvgUtil::fromPercentage(b.attribute("x1", "0%")), SvgUtil::fromPercentage(b.attribute("y1", "0%")))); g->setFinalStop(QPointF(SvgUtil::fromPercentage(b.attribute("x2", "100%")), SvgUtil::fromPercentage(b.attribute("y2", "0%")))); } else { g->setStart(QPointF(SvgUtil::fromUserSpace(b.attribute("x1").toDouble()), SvgUtil::fromUserSpace(b.attribute("y1").toDouble()))); g->setFinalStop(QPointF(SvgUtil::fromUserSpace(b.attribute("x2").toDouble()), SvgUtil::fromUserSpace(b.attribute("y2").toDouble()))); } // preserve color stops if (gradhelper.gradient()) g->setStops(gradhelper.gradient()->stops()); gradhelper.setGradient(g); } else if (b.tagName() == "radialGradient") { QRadialGradient *g = new QRadialGradient(); if (gradhelper.gradientUnits() == SvgGradientHelper::ObjectBoundingBox) { g->setCoordinateMode(QGradient::ObjectBoundingMode); g->setCenter(QPointF(SvgUtil::fromPercentage(b.attribute("cx", "50%")), SvgUtil::fromPercentage(b.attribute("cy", "50%")))); g->setRadius(SvgUtil::fromPercentage(b.attribute("r", "50%"))); g->setFocalPoint(QPointF(SvgUtil::fromPercentage(b.attribute("fx", "50%")), SvgUtil::fromPercentage(b.attribute("fy", "50%")))); } else { g->setCenter(QPointF(SvgUtil::fromUserSpace(b.attribute("cx").toDouble()), SvgUtil::fromUserSpace(b.attribute("cy").toDouble()))); g->setFocalPoint(QPointF(SvgUtil::fromUserSpace(b.attribute("fx").toDouble()), SvgUtil::fromUserSpace(b.attribute("fy").toDouble()))); g->setRadius(SvgUtil::fromUserSpace(b.attribute("r").toDouble())); } // preserve color stops if (gradhelper.gradient()) g->setStops(gradhelper.gradient()->stops()); gradhelper.setGradient(g); } else { return false; } // handle spread method QString spreadMethod = b.attribute("spreadMethod"); if (!spreadMethod.isEmpty()) { if (spreadMethod == "reflect") gradhelper.gradient()->setSpread(QGradient::ReflectSpread); else if (spreadMethod == "repeat") gradhelper.gradient()->setSpread(QGradient::RepeatSpread); else gradhelper.gradient()->setSpread(QGradient::PadSpread); } else gradhelper.gradient()->setSpread(QGradient::PadSpread); // Parse the color stops. The referencing gradient does not have colorstops, // so use the stops from the gradient it references to (e in this case and not b) m_context.styleParser().parseColorStops(gradhelper.gradient(), e); gradhelper.setTransform(SvgUtil::parseTransform(b.attribute("gradientTransform"))); m_gradients.insert(gradientId, gradhelper); return true; }